Initial commit
This commit is contained in:
30
gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua
Normal file
30
gamemodes/darkrp/gamemode/modules/base/cl_drawfunctions.lua
Normal file
@@ -0,0 +1,30 @@
|
||||
-- concatenate a space to avoid the text being parsed as valve string
|
||||
local function safeText(text)
|
||||
return string.match(text, "^#([a-zA-Z_]+)$") and text .. " " or text
|
||||
end
|
||||
|
||||
DarkRP.deLocalise = safeText
|
||||
|
||||
function draw.DrawNonParsedText(text, font, x, y, color, xAlign)
|
||||
return draw.DrawText(safeText(text), font, x, y, color, xAlign)
|
||||
end
|
||||
|
||||
function draw.DrawNonParsedSimpleText(text, font, x, y, color, xAlign, yAlign)
|
||||
return draw.SimpleText(safeText(text), font, x, y, color, xAlign, yAlign)
|
||||
end
|
||||
|
||||
function draw.DrawNonParsedSimpleTextOutlined(text, font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor)
|
||||
return draw.SimpleTextOutlined(safeText(text), font, x, y, color, xAlign, yAlign, outlineWidth, outlineColor)
|
||||
end
|
||||
|
||||
function surface.DrawNonParsedText(text)
|
||||
return surface.DrawText(safeText(text))
|
||||
end
|
||||
|
||||
function chat.AddNonParsedText(...)
|
||||
local tbl = {...}
|
||||
for i = 2, #tbl, 2 do
|
||||
tbl[i] = safeText(tbl[i])
|
||||
end
|
||||
return chat.AddText(unpack(tbl))
|
||||
end
|
||||
139
gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua
Normal file
139
gamemodes/darkrp/gamemode/modules/base/cl_entityvars.lua
Normal file
@@ -0,0 +1,139 @@
|
||||
DarkRP.ClientsideDarkRPVars = DarkRP.ClientsideDarkRPVars or {}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface
|
||||
---------------------------------------------------------------------------]]
|
||||
local pmeta = FindMetaTable("Player")
|
||||
-- This function is made local to optimise getDarkRPVar, which is called often
|
||||
-- enough to warrant optimizing. See https://github.com/FPtje/DarkRP/pull/3212
|
||||
local get_user_id = pmeta.UserID
|
||||
function pmeta:getDarkRPVar(var, fallback)
|
||||
local user_id = get_user_id(self)
|
||||
|
||||
-- Special case: when in the EntityRemoved hook, UserID returns -1. In this
|
||||
-- case, hope that we still have a stored userID lying around somewhere.
|
||||
-- See https://github.com/FPtje/DarkRP/pull/3270
|
||||
if user_id == -1 then
|
||||
user_id = self._darkrp_stored_user_id_for_entity_removed_hook
|
||||
end
|
||||
|
||||
local vars = DarkRP.ClientsideDarkRPVars[user_id]
|
||||
if vars == nil then return fallback end
|
||||
|
||||
local results = vars[var]
|
||||
if results == nil then return fallback end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the information of a player var
|
||||
---------------------------------------------------------------------------]]
|
||||
local function RetrievePlayerVar(userID, var, value)
|
||||
local ply = Player(userID)
|
||||
DarkRP.ClientsideDarkRPVars[userID] = DarkRP.ClientsideDarkRPVars[userID] or {}
|
||||
|
||||
hook.Call("DarkRPVarChanged", nil, ply, var, DarkRP.ClientsideDarkRPVars[userID][var], value)
|
||||
DarkRP.ClientsideDarkRPVars[userID][var] = value
|
||||
|
||||
-- Backwards compatibility
|
||||
if IsValid(ply) then
|
||||
ply.DarkRPVars = DarkRP.ClientsideDarkRPVars[userID]
|
||||
end
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve a player var.
|
||||
Read the usermessage and attempt to set the DarkRP var
|
||||
---------------------------------------------------------------------------]]
|
||||
local function doRetrieve()
|
||||
local userID = net.ReadUInt(16)
|
||||
local var, value = DarkRP.readNetDarkRPVar()
|
||||
|
||||
RetrievePlayerVar(userID, var, value)
|
||||
end
|
||||
net.Receive("DarkRP_PlayerVar", doRetrieve)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Retrieve the message to remove a DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
local function doRetrieveRemoval()
|
||||
local userID = net.ReadUInt(16)
|
||||
local vars = DarkRP.ClientsideDarkRPVars[userID] or {}
|
||||
local var = DarkRP.readNetDarkRPVarRemoval()
|
||||
local ply = Player(userID)
|
||||
|
||||
hook.Call("DarkRPVarChanged", nil, ply, var, vars[var], nil)
|
||||
|
||||
vars[var] = nil
|
||||
end
|
||||
net.Receive("DarkRP_PlayerVarRemoval", doRetrieveRemoval)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Initialize the DarkRPVars at the start of the game
|
||||
---------------------------------------------------------------------------]]
|
||||
local function InitializeDarkRPVars(len)
|
||||
local plyCount = net.ReadUInt(8)
|
||||
|
||||
for i = 1, plyCount, 1 do
|
||||
local userID = net.ReadUInt(16)
|
||||
local varCount = net.ReadUInt(DarkRP.DARKRP_ID_BITS + 2)
|
||||
|
||||
for j = 1, varCount, 1 do
|
||||
local var, value = DarkRP.readNetDarkRPVar()
|
||||
RetrievePlayerVar(userID, var, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
net.Receive("DarkRP_InitializeVars", InitializeDarkRPVars)
|
||||
timer.Simple(0, fp{RunConsoleCommand, "_sendDarkRPvars"})
|
||||
|
||||
net.Receive("DarkRP_DarkRPVarDisconnect", function(len)
|
||||
local userID = net.ReadUInt(16)
|
||||
local ply = Player(userID)
|
||||
|
||||
-- If the player is already gone, then immediately clear the data and move on.
|
||||
if not IsValid(ply) then
|
||||
DarkRP.ClientsideDarkRPVars[userID] = nil
|
||||
return
|
||||
end
|
||||
-- Otherwise, we need to wait until the player is actually removed
|
||||
-- clientside. The net message may come in _much_ earlier than the message
|
||||
-- that the player disconnected and should therefore be removed.
|
||||
local hook_name = "darkrp_remove_darkrp_var_" .. userID
|
||||
|
||||
-- Workaround: the player's user ID is -1 in the EntityRemoved hook. This
|
||||
-- stores the user ID in a separate variable so that it is still accessible.
|
||||
-- See https://github.com/Facepunch/garrysmod-issues/issues/6117
|
||||
--
|
||||
-- This will allow getDarkRPVar to keep working
|
||||
if IsValid(ply) then
|
||||
ply._darkrp_stored_user_id_for_entity_removed_hook = userID
|
||||
end
|
||||
|
||||
hook.Add("EntityRemoved", hook_name, function(ent)
|
||||
if ent ~= ply then return end
|
||||
hook.Remove("EntityRemoved", hook_name)
|
||||
|
||||
-- Placing this in a timer allows for the rest of the hook runners to
|
||||
-- still use the DarkRPVars until the entity is _really_ gone.
|
||||
-- See https://github.com/FPtje/DarkRP/pull/3270
|
||||
timer.Simple(0, function()
|
||||
DarkRP.ClientsideDarkRPVars[userID] = nil
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Request the DarkRPVars when they haven't arrived
|
||||
---------------------------------------------------------------------------]]
|
||||
timer.Create("DarkRPCheckifitcamethrough", 15, 0, function()
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
if v:getDarkRPVar("rpname") then continue end
|
||||
|
||||
RunConsoleCommand("_sendDarkRPvars")
|
||||
return
|
||||
end
|
||||
|
||||
timer.Remove("DarkRPCheckifitcamethrough")
|
||||
end)
|
||||
158
gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua
Normal file
158
gamemodes/darkrp/gamemode/modules/base/cl_fonts.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
The fonts that DarkRP uses
|
||||
---------------------------------------------------------------------------]]
|
||||
local function loadFonts()
|
||||
surface.CreateFont("DarkRPHUD1", {
|
||||
size = 20,
|
||||
weight = 600,
|
||||
antialias = true,
|
||||
shadow = true,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("DarkRPHUD2", {
|
||||
size = 23,
|
||||
weight = 400,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Roboto20", {
|
||||
size = 20,
|
||||
weight = 600,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet18", {
|
||||
size = 18,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet20", {
|
||||
size = 20,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet24", {
|
||||
size = 24,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Trebuchet48", {
|
||||
size = 48,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Trebuchet MS",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("TabLarge", {
|
||||
size = 18,
|
||||
weight = 700,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("UiBold", {
|
||||
size = 16,
|
||||
weight = 800,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("HUDNumber5", {
|
||||
size = 30,
|
||||
weight = 800,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardHeader", {
|
||||
size = 32,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardSubtitle", {
|
||||
size = 22,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerName", {
|
||||
size = 19,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerName2", {
|
||||
size = 15,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("ScoreboardPlayerNameBig", {
|
||||
size = 22,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Roboto",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("AckBarWriting", {
|
||||
size = 20,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = false,
|
||||
font = "Akbar",
|
||||
extended = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("DarkRP_tipjar", {
|
||||
size = 100,
|
||||
weight = 500,
|
||||
antialias = true,
|
||||
shadow = true,
|
||||
font = "Verdana",
|
||||
extended = true,
|
||||
})
|
||||
end
|
||||
loadFonts()
|
||||
@@ -0,0 +1,84 @@
|
||||
local GUIToggled = false
|
||||
local mouseX, mouseY = ScrW() / 2, ScrH() / 2
|
||||
function GM:ShowSpare1()
|
||||
local jobTable = LocalPlayer():getJobTable()
|
||||
|
||||
-- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here.
|
||||
if jobTable and jobTable.ShowSpare1 then
|
||||
return jobTable.ShowSpare1(LocalPlayer())
|
||||
end
|
||||
|
||||
GUIToggled = not GUIToggled
|
||||
|
||||
if GUIToggled then
|
||||
gui.SetMousePos(mouseX, mouseY)
|
||||
else
|
||||
mouseX, mouseY = gui.MousePos()
|
||||
end
|
||||
gui.EnableScreenClicker(GUIToggled)
|
||||
end
|
||||
|
||||
function GM:ShowSpare2()
|
||||
local jobTable = LocalPlayer():getJobTable()
|
||||
|
||||
-- We need to check for the existance of jobTable here, because in very rare edge cases, the player's team isn't set, when the getJobTable-function is called here.
|
||||
if jobTable and jobTable.ShowSpare2 then
|
||||
return jobTable.ShowSpare2(LocalPlayer())
|
||||
end
|
||||
|
||||
-- DarkRP.toggleF4Menu()
|
||||
end
|
||||
|
||||
function GM:PlayerStartVoice(ply)
|
||||
if ply == LocalPlayer() then
|
||||
ply.DRPIsTalking = true
|
||||
return -- Not the original rectangle for yourself! ugh!
|
||||
end
|
||||
self.Sandbox.PlayerStartVoice(self, ply)
|
||||
end
|
||||
|
||||
function GM:PlayerEndVoice(ply)
|
||||
if ply == LocalPlayer() then
|
||||
ply.DRPIsTalking = false
|
||||
return
|
||||
end
|
||||
|
||||
self.Sandbox.PlayerEndVoice(self, ply)
|
||||
end
|
||||
|
||||
function GM:OnPlayerChat()
|
||||
end
|
||||
|
||||
local FKeyBinds = {
|
||||
["gm_showhelp"] = "ShowHelp",
|
||||
["gm_showteam"] = "ShowTeam",
|
||||
["gm_showspare1"] = "ShowSpare1",
|
||||
["gm_showspare2"] = "ShowSpare2"
|
||||
}
|
||||
|
||||
function GM:PlayerBindPress(ply, bind, pressed)
|
||||
self.Sandbox.PlayerBindPress(self, ply, bind, pressed)
|
||||
|
||||
local bnd = string.match(string.lower(bind), "gm_[a-z]+[12]?")
|
||||
if bnd and FKeyBinds[bnd] then
|
||||
hook.Call(FKeyBinds[bnd], GAMEMODE)
|
||||
end
|
||||
|
||||
if not self.Config.deadvoice and not ply:Alive() and string.find(string.lower(bind), "voicerecord") then return true end
|
||||
end
|
||||
|
||||
function GM:InitPostEntity()
|
||||
hook.Call("teamChanged", GAMEMODE, GAMEMODE.DefaultTeam, GAMEMODE.DefaultTeam)
|
||||
end
|
||||
|
||||
function GM:teamChanged(before, after)
|
||||
end
|
||||
|
||||
local function OnChangedTeam(um)
|
||||
local oldTeam, newTeam = um:ReadShort(), um:ReadShort()
|
||||
hook.Call("teamChanged", GAMEMODE, oldTeam, newTeam) -- backwards compatibility
|
||||
hook.Call("OnPlayerChangedTeam", GAMEMODE, LocalPlayer(), oldTeam, newTeam)
|
||||
end
|
||||
usermessage.Hook("OnChangedTeam", OnChangedTeam)
|
||||
|
||||
timer.Simple(0, function() GAMEMODE.ShowTeam = DarkRP.openKeysMenu end)
|
||||
131
gamemodes/darkrp/gamemode/modules/base/cl_interface.lua
Normal file
131
gamemodes/darkrp/gamemode/modules/base/cl_interface.lua
Normal file
@@ -0,0 +1,131 @@
|
||||
DarkRP.PLAYER.isInRoom = DarkRP.stub{
|
||||
name = "isInRoom",
|
||||
description = "Whether the player is in the same room as the LocalPlayer.",
|
||||
parameters = {},
|
||||
returns = {
|
||||
{
|
||||
name = "inRoom",
|
||||
description = "Whether the player is in the same room.",
|
||||
type = "boolean"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP.PLAYER
|
||||
}
|
||||
|
||||
DarkRP.deLocalise = DarkRP.stub{
|
||||
name = "deLocalise",
|
||||
description = "Makes sure the string will not be localised when drawn or printed.",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text to delocalise.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The delocalised text.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.textWrap = DarkRP.stub{
|
||||
name = "textWrap",
|
||||
description = "Wrap a text around when reaching a certain width.",
|
||||
parameters = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The text to wrap.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "font",
|
||||
description = "The font of the text.",
|
||||
type = "string",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "width",
|
||||
description = "The maximum width in pixels.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "text",
|
||||
description = "The wrapped string.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.setPreferredJobModel = DarkRP.stub{
|
||||
name = "setPreferredJobModel",
|
||||
description = "Set the model preferred by the player (if the job allows multiple models).",
|
||||
parameters = {
|
||||
{
|
||||
name = "teamNr",
|
||||
description = "The team number of the job.",
|
||||
type = "number",
|
||||
optional = false
|
||||
},
|
||||
{
|
||||
name = "model",
|
||||
description = "The preferred model for the job.",
|
||||
type = "string",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.getPreferredJobModel = DarkRP.stub{
|
||||
name = "getPreferredJobModel",
|
||||
description = "Get the model preferred by the player (if the job allows multiple models).",
|
||||
parameters = {
|
||||
{
|
||||
name = "teamNr",
|
||||
description = "The team number of the job.",
|
||||
type = "number",
|
||||
optional = false
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
{
|
||||
name = "model",
|
||||
description = "The preferred model for the job.",
|
||||
type = "string"
|
||||
}
|
||||
},
|
||||
metatable = DarkRP
|
||||
}
|
||||
|
||||
DarkRP.hookStub{
|
||||
name = "teamChanged",
|
||||
description = "When your team is changed.",
|
||||
deprecated = "Use the OnPlayerChangedTeam hook instead.",
|
||||
parameters = {
|
||||
{
|
||||
name = "before",
|
||||
description = "The team before the change.",
|
||||
type = "number"
|
||||
},
|
||||
{
|
||||
name = "after",
|
||||
description = "The team after the change.",
|
||||
type = "number"
|
||||
}
|
||||
},
|
||||
returns = {
|
||||
|
||||
}
|
||||
}
|
||||
100
gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua
Normal file
100
gamemodes/darkrp/gamemode/modules/base/cl_jobmodels.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
-- Create a table for the preferred playermodels
|
||||
--
|
||||
-- Note: in DarkRP before 2024-09, there was a different table called
|
||||
-- `darkp_playermodels` (note the misspelling of "darkp"). This table was
|
||||
-- missing the server column, meaning that preferred job models would persist
|
||||
-- across multiple servers. To make preferred job models store per server, this
|
||||
-- new table (without the spelling mistake) was created.
|
||||
--
|
||||
-- See the original issue to create the player model preference feature:
|
||||
-- https://github.com/FPtje/DarkRP/issues/979 and the subsequent refactor at
|
||||
-- https://github.com/FPtje/DarkRP/pull/3266
|
||||
sql.Query([[CREATE TABLE IF NOT EXISTS darkrp_playermodels(
|
||||
server TEXT NOT NULL,
|
||||
jobcmd TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
PRIMARY KEY (server, jobcmd)
|
||||
);]])
|
||||
|
||||
|
||||
local preferredModels = {}
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Interface functions
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.setPreferredJobModel(teamNr, model)
|
||||
local job = RPExtraTeams[teamNr]
|
||||
if not job then return end
|
||||
preferredModels[job.command] = model
|
||||
sql.Query(string.format([[REPLACE INTO darkrp_playermodels(server, jobcmd, model) VALUES(%s, %s, %s);]], sql.SQLStr(game.GetIPAddress()), sql.SQLStr(job.command), sql.SQLStr(model)))
|
||||
|
||||
net.Start("DarkRP_preferredjobmodel")
|
||||
net.WriteUInt(teamNr, 8)
|
||||
net.WriteString(model)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function DarkRP.getPreferredJobModel(teamNr)
|
||||
local job = RPExtraTeams[teamNr]
|
||||
if not job then return end
|
||||
return preferredModels[job.command]
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Load the preferred models
|
||||
---------------------------------------------------------------------------]]
|
||||
local function sendModels()
|
||||
net.Start("DarkRP_preferredjobmodels")
|
||||
for _, job in pairs(RPExtraTeams) do
|
||||
if not preferredModels[job.command] then net.WriteBit(false) continue end
|
||||
|
||||
net.WriteBit(true)
|
||||
net.WriteString(preferredModels[job.command])
|
||||
end
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local function jobHasModel(job, model)
|
||||
return istable(job.model) and table.HasValue(job.model, model) or job.model == model
|
||||
end
|
||||
|
||||
local function setPreferredModels(models)
|
||||
for _, v in ipairs(models) do
|
||||
local job = DarkRP.getJobByCommand(v.jobcmd)
|
||||
if job == nil or not jobHasModel(job, v.model) then continue end
|
||||
|
||||
preferredModels[v.jobcmd] = v.model
|
||||
end
|
||||
end
|
||||
|
||||
-- The old table, darkp_playermodels, acts as a global mapping of preferred
|
||||
-- models for jobs.
|
||||
local function setModelsFromOldTable()
|
||||
local oldTableExists = tobool(sql.QueryValue([[SELECT 1 FROM sqlite_master WHERE type='table' AND name='darkp_playermodels']]))
|
||||
if not oldTableExists then return end
|
||||
|
||||
local models = sql.Query([[SELECT jobcmd, model FROM darkp_playermodels;]])
|
||||
|
||||
if not models then return end
|
||||
setPreferredModels(models)
|
||||
end
|
||||
|
||||
-- The newer table is server specific.
|
||||
local function setModelsFromNewTable()
|
||||
local models = sql.Query(string.format([[SELECT jobcmd, model FROM darkrp_playermodels WHERE server = %s;]], sql.SQLStr(game.GetIPAddress())))
|
||||
|
||||
if not models then return end
|
||||
setPreferredModels(models)
|
||||
end
|
||||
|
||||
timer.Simple(0, function()
|
||||
-- Run after the jobs have loaded, to make sure the jobs can be looked up.
|
||||
|
||||
-- Set models from the old table, before overriding them with data from the
|
||||
-- new table. That way, server specific preferences always have precedence.
|
||||
setModelsFromOldTable()
|
||||
setModelsFromNewTable()
|
||||
|
||||
sendModels()
|
||||
end)
|
||||
107
gamemodes/darkrp/gamemode/modules/base/cl_util.lua
Normal file
107
gamemodes/darkrp/gamemode/modules/base/cl_util.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Show a black screen
|
||||
---------------------------------------------------------------------------]]
|
||||
local function blackScreen(um)
|
||||
local toggle = um:ReadBool()
|
||||
if toggle then
|
||||
local black = color_black
|
||||
local w, h = ScrW(), ScrH()
|
||||
hook.Add("HUDPaintBackground", "BlackScreen", function()
|
||||
surface.SetDrawColor(black)
|
||||
surface.DrawRect(0, 0, w, h)
|
||||
end)
|
||||
else
|
||||
hook.Remove("HUDPaintBackground", "BlackScreen")
|
||||
end
|
||||
end
|
||||
usermessage.Hook("blackScreen", blackScreen)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Wrap strings to not become wider than the given amount of pixels
|
||||
---------------------------------------------------------------------------]]
|
||||
local function charWrap(text, remainingWidth, maxWidth)
|
||||
local totalWidth = 0
|
||||
|
||||
text = text:gsub(".", function(char)
|
||||
totalWidth = totalWidth + surface.GetTextSize(char)
|
||||
|
||||
-- Wrap around when the max width is reached
|
||||
if totalWidth >= remainingWidth then
|
||||
-- totalWidth needs to include the character width because it's inserted in a new line
|
||||
totalWidth = surface.GetTextSize(char)
|
||||
remainingWidth = maxWidth
|
||||
return "\n" .. char
|
||||
end
|
||||
|
||||
return char
|
||||
end)
|
||||
|
||||
return text, totalWidth
|
||||
end
|
||||
|
||||
function DarkRP.textWrap(text, font, maxWidth)
|
||||
local totalWidth = 0
|
||||
|
||||
surface.SetFont(font)
|
||||
|
||||
local spaceWidth = surface.GetTextSize(' ')
|
||||
text = text:gsub("(%s?[%S]+)", function(word)
|
||||
local char = string.sub(word, 1, 1)
|
||||
if char == "\n" or char == "\t" then
|
||||
totalWidth = 0
|
||||
end
|
||||
|
||||
local wordlen = surface.GetTextSize(word)
|
||||
totalWidth = totalWidth + wordlen
|
||||
|
||||
-- Wrap around when the max width is reached
|
||||
if wordlen >= maxWidth then -- Split the word if the word is too big
|
||||
local splitWord, splitPoint = charWrap(word, maxWidth - (totalWidth - wordlen), maxWidth)
|
||||
totalWidth = splitPoint
|
||||
return splitWord
|
||||
elseif totalWidth < maxWidth then
|
||||
return word
|
||||
end
|
||||
|
||||
-- Split before the word
|
||||
if char == ' ' then
|
||||
totalWidth = wordlen - spaceWidth
|
||||
return '\n' .. string.sub(word, 2)
|
||||
end
|
||||
|
||||
totalWidth = wordlen
|
||||
return '\n' .. word
|
||||
end)
|
||||
|
||||
return text
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether a given player is in the same room as the local player
|
||||
note: uses a heuristic
|
||||
---------------------------------------------------------------------------]]
|
||||
function plyMeta:isInRoom()
|
||||
local tracedata = {}
|
||||
tracedata.start = LocalPlayer():GetShootPos()
|
||||
tracedata.endpos = self:GetShootPos()
|
||||
local trace = util.TraceLine(tracedata)
|
||||
|
||||
return not trace.HitWorld
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Key name to key int mapping
|
||||
---------------------------------------------------------------------------]]
|
||||
local keyNames
|
||||
function input.KeyNameToNumber(str)
|
||||
if not keyNames then
|
||||
keyNames = {}
|
||||
for i = 1, 107, 1 do
|
||||
keyNames[input.GetKeyName(i)] = i
|
||||
end
|
||||
end
|
||||
|
||||
return keyNames[str]
|
||||
end
|
||||
628
gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua
Normal file
628
gamemodes/darkrp/gamemode/modules/base/sh_checkitems.lua
Normal file
@@ -0,0 +1,628 @@
|
||||
--[[
|
||||
The base elements are shared by every custom item
|
||||
]]
|
||||
local baseSchema = tc.checkTable{
|
||||
buttonColor =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber)),
|
||||
"The buttonColor must be a Color value."
|
||||
),
|
||||
|
||||
category =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The category must be the name of an existing category!"
|
||||
),
|
||||
|
||||
customCheck =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The customCheck must be a function."
|
||||
),
|
||||
|
||||
CustomCheckFailMsg =
|
||||
tc.addHint(
|
||||
tc.optional(isstring, isfunction),
|
||||
"The CustomCheckFailMsg must be either a string or a function."
|
||||
),
|
||||
|
||||
sortOrder =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The sortOrder must be a number."
|
||||
),
|
||||
|
||||
label =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The label must be a valid string."
|
||||
),
|
||||
}
|
||||
|
||||
--[[
|
||||
Properties shared by anything buyable
|
||||
]]
|
||||
local buyableSchema = fn.FAnd{baseSchema, tc.checkTable{
|
||||
allowed =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber), isnumber),
|
||||
"The allowed field must be either an existing team or a table of existing teams.",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
),
|
||||
|
||||
getPrice =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The getPrice must be a function."
|
||||
),
|
||||
|
||||
model =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The model must be valid."
|
||||
),
|
||||
|
||||
price =
|
||||
tc.addHint(
|
||||
function(v, tbl) return isnumber(v) or isfunction(tbl.getPrice) end,
|
||||
"The price must be an existing number or (for advanced users) the getPrice field must be a function."
|
||||
),
|
||||
|
||||
spawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The spawn must be a function."
|
||||
),
|
||||
allowPurchaseWhileDead =
|
||||
tc.addHint(
|
||||
tc.default(false),
|
||||
"The allowPurchaseWhileDead must be either true or false"
|
||||
)
|
||||
}}
|
||||
|
||||
-- The command of an entity must be unique
|
||||
local uniqueEntity = function(cmd, tbl)
|
||||
for _, v in pairs(DarkRPEntities) do
|
||||
if v.cmd ~= cmd then continue end
|
||||
|
||||
return
|
||||
false,
|
||||
"This entity does not have a unique command.",
|
||||
{
|
||||
"There must be some other entity that has the same thing for 'cmd'.",
|
||||
"Fix this by changing the 'cmd' field of your entity to something else."
|
||||
}
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- The command of a job must be unique
|
||||
local uniqueJob = function(v, tbl)
|
||||
local job = DarkRP.getJobByCommand(v)
|
||||
|
||||
if not job then return true end
|
||||
|
||||
return
|
||||
false,
|
||||
"This job does not have a unique command.",
|
||||
{
|
||||
"There must be some other job that has the same command.",
|
||||
"Fix this by changing the 'command' of your job to something else."
|
||||
}
|
||||
end
|
||||
|
||||
--[[
|
||||
Validate jobs
|
||||
]]
|
||||
DarkRP.validateJob = fn.FAnd{baseSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
color =
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The color must be a Color value.",
|
||||
{"Color values look like this: Color(r, g, b, a), where r, g, b and a are numbers between 0 and 255."}
|
||||
),
|
||||
|
||||
model =
|
||||
tc.addHint(
|
||||
fn.FOr{isstring, tc.nonEmpty(tc.tableOf(isstring))},
|
||||
"The model must either be a table of correct model strings or a single correct model string.",
|
||||
{
|
||||
"This error could happens when the model does not exist on the server.",
|
||||
"Are you sure the model path is right?",
|
||||
"Is the model from an addon that is not properly installed?"
|
||||
}
|
||||
),
|
||||
|
||||
description =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The description must be a string."
|
||||
),
|
||||
|
||||
weapons =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isstring)),
|
||||
"The weapons must be a valid table of strings.",
|
||||
{"Example: weapons = {\"med_kit\", \"weapon_bugbait\"},"}
|
||||
),
|
||||
|
||||
command =
|
||||
fn.FAnd
|
||||
{
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The command must be a string."
|
||||
),
|
||||
uniqueJob
|
||||
},
|
||||
|
||||
max =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}},
|
||||
"The max must be a number greater than or equal to zero.",
|
||||
{
|
||||
"Zero means infinite.",
|
||||
"A decimal between 0 and 1 is seen as a percentage."
|
||||
}
|
||||
),
|
||||
|
||||
salary =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}},
|
||||
"The salary must be a number and it must be greater than zero."
|
||||
),
|
||||
|
||||
admin =
|
||||
tc.default(0,
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}, fp{fn.Gte, 2}},
|
||||
"The admin value must be a number and it must be greater than or equal to zero and smaller than three."
|
||||
)
|
||||
),
|
||||
|
||||
vote =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The vote must be either true or false."
|
||||
),
|
||||
|
||||
ammo =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber)),
|
||||
"The ammo must be a table containing numbers.",
|
||||
{"See example on https://darkrp.miraheze.org/wiki/DarkRP:CustomJobFields"}
|
||||
),
|
||||
|
||||
hasLicense =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The hasLicense must be either true or false."
|
||||
),
|
||||
|
||||
NeedToChangeFrom =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isnumber), isnumber),
|
||||
"The NeedToChangeFrom must be either an existing team or a table of existing teams",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
),
|
||||
|
||||
modelScale =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The modelScale must be a number."
|
||||
),
|
||||
|
||||
maxpocket =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The maxPocket must be a number."
|
||||
),
|
||||
|
||||
maps =
|
||||
tc.addHint(
|
||||
tc.optional(tc.tableOf(isstring)),
|
||||
"The maps value must be a table of valid map names."
|
||||
),
|
||||
|
||||
candemote =
|
||||
tc.default(true,
|
||||
tc.addHint(
|
||||
isbool,
|
||||
"The candemote value must be either true or false."
|
||||
)
|
||||
),
|
||||
|
||||
mayor =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The mayor value must be either true or false."
|
||||
),
|
||||
|
||||
chief =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The chief value must be either true or false."
|
||||
),
|
||||
|
||||
medic =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The medic value must be either true or false."
|
||||
),
|
||||
|
||||
cook =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The cook value must be either true or false."
|
||||
),
|
||||
|
||||
hobo =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The hobo value must be either true or false."
|
||||
),
|
||||
|
||||
playerClass =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The playerClass must be a valid string."
|
||||
),
|
||||
|
||||
CanPlayerSuicide =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The CanPlayerSuicide must be a function."
|
||||
),
|
||||
|
||||
PlayerCanPickupWeapon =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerCanPickupWeapon must be a function."
|
||||
),
|
||||
|
||||
PlayerDeath =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerDeath must be a function."
|
||||
),
|
||||
|
||||
PlayerLoadout =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerLoadout must be a function."
|
||||
),
|
||||
|
||||
PlayerSelectSpawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSelectSpawn must be a function."
|
||||
),
|
||||
|
||||
PlayerSetModel =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSetModel must be a function."
|
||||
),
|
||||
|
||||
PlayerSpawn =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSpawn must be a function."
|
||||
),
|
||||
|
||||
PlayerSpawnProp =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The PlayerSpawnProp must be a function."
|
||||
),
|
||||
|
||||
RequiresVote =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The RequiresVote must be a function."
|
||||
),
|
||||
|
||||
ShowSpare1 =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The ShowSpare1 must be a function."
|
||||
),
|
||||
|
||||
ShowSpare2 =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The ShowSpare2 must be a function."
|
||||
),
|
||||
|
||||
canStartVote =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The canStartVote must be a function."
|
||||
),
|
||||
|
||||
canStartVoteReason =
|
||||
tc.addHint(
|
||||
tc.optional(isstring, isfunction),
|
||||
"The canStartVoteReason must be either a string or a function."
|
||||
),
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate shipments
|
||||
]]
|
||||
DarkRP.validateShipment = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
entity =
|
||||
tc.addHint(
|
||||
isstring, "The entity of the shipment must be a string."
|
||||
),
|
||||
|
||||
amount =
|
||||
tc.addHint(
|
||||
fn.FAnd{isnumber, fp{fn.Lte, 0}}, "The amount must be a number and it must be greater than zero."
|
||||
),
|
||||
|
||||
separate =
|
||||
tc.addHint(
|
||||
tc.optional(isbool), "the separate field must be either true or false."
|
||||
),
|
||||
|
||||
pricesep =
|
||||
tc.addHint(
|
||||
function(v, tbl) return not tbl.separate or isnumber(v) and v >= 0 end,
|
||||
"The pricesep must be a number and it must be greater than or equal to zero."
|
||||
),
|
||||
|
||||
noship =
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The noship must be either true or false."
|
||||
),
|
||||
|
||||
shipmodel =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The shipmodel must be a valid model."
|
||||
),
|
||||
|
||||
weight =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The weight must be a number."
|
||||
),
|
||||
|
||||
spareammo =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The spareammo must be a number."
|
||||
),
|
||||
|
||||
clip1 =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The clip1 must be a number."
|
||||
),
|
||||
|
||||
clip2 =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The clip2 must be a number."
|
||||
),
|
||||
|
||||
shipmentClass =
|
||||
tc.addHint(
|
||||
tc.optional(isstring),
|
||||
"The shipmentClass must be a string."
|
||||
),
|
||||
|
||||
onBought =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The onBought must be a function."
|
||||
),
|
||||
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate vehicles
|
||||
]]
|
||||
DarkRP.validateVehicle = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name of the vehicle must be a string."
|
||||
),
|
||||
|
||||
distance =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The distance must be a number."
|
||||
),
|
||||
|
||||
angle =
|
||||
tc.addHint(
|
||||
tc.optional(isangle),
|
||||
"The distance must be a valid Angle."
|
||||
),
|
||||
}}
|
||||
|
||||
--[[
|
||||
Validate Entities
|
||||
]]
|
||||
DarkRP.validateEntity = fn.FAnd{buyableSchema, tc.checkTable{
|
||||
ent =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The ent field must be a string."
|
||||
),
|
||||
|
||||
max =
|
||||
tc.addHint(
|
||||
function(v, tbl) return isnumber(v) or isfunction(tbl.getMax) end,
|
||||
"The max must be an existing number or (for advanced users) the getMax field must be a function."
|
||||
),
|
||||
|
||||
cmd =
|
||||
fn.FAnd
|
||||
{
|
||||
tc.addHint(isstring, "The cmd must be a valid string."),
|
||||
uniqueEntity
|
||||
},
|
||||
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a valid string."
|
||||
),
|
||||
|
||||
allowTools =
|
||||
tc.default(false,
|
||||
tc.addHint(
|
||||
tc.optional(isbool),
|
||||
"The allowTools must be either true or false."
|
||||
)
|
||||
),
|
||||
|
||||
delay =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The delay must be a number."
|
||||
),
|
||||
}}
|
||||
|
||||
|
||||
-- Checks whether a team already has an agenda assigned.
|
||||
-- Jobs cannot have multiple agendas.
|
||||
|
||||
local overlappingAgendaCheck = function(t, tbl)
|
||||
local agenda = DarkRP.getAgendas()[t]
|
||||
|
||||
-- Team being -1 means the job is disabled
|
||||
if agenda == nil or t == -1 then return true end
|
||||
|
||||
local teamName = team.GetName(t)
|
||||
local err = "At least one job has multiple agendas assigned to them"
|
||||
local hints = {
|
||||
string.format([[The problem lies with the job called "%s"]], teamName),
|
||||
string.format([[It is assigned to agendas "%s" and "%s"]], agenda.Title or "unknown", tbl.Title or "unknown"),
|
||||
[[A job can only have ONE agenda. Otherwise things would become confusing, since only ONE agenda is always drawn on the screen.]]
|
||||
}
|
||||
|
||||
if agenda.Title == tbl.Title then
|
||||
table.insert(hints, "The titles of the two agendas are the same. It looks like perhaps you've made the same agenda more than once.")
|
||||
table.insert(hints, "Removing one of them should get rid of this error.")
|
||||
end
|
||||
|
||||
return false, err, hints
|
||||
end
|
||||
|
||||
--[[
|
||||
Validate Agendas
|
||||
]]
|
||||
local managerNumberCheck = tc.addHint(
|
||||
isnumber,
|
||||
"The Manager must either be a single team or a non-empty table of existing teams.",
|
||||
{"Is there a job here that doesn't exist (anymore)?"}
|
||||
)
|
||||
|
||||
DarkRP.validateAgenda = tc.checkTable{
|
||||
Title =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The title must be a string."
|
||||
),
|
||||
|
||||
-- Custom function to ensure the right error message is thrown
|
||||
Manager = function(manager, tbl)
|
||||
-- Check whether the manager is an existing team
|
||||
-- that does not already have an agenda assigned
|
||||
if isnumber(manager) then
|
||||
return fn.FAnd{overlappingAgendaCheck}(manager, tbl)
|
||||
|
||||
-- Check whether the manager is a table of existing teams
|
||||
-- and that none of the teams already have agendas assigned
|
||||
elseif istable(manager) then
|
||||
return tc.nonEmpty(
|
||||
tc.tableOf(
|
||||
fn.FAnd{managerNumberCheck, overlappingAgendaCheck}
|
||||
)
|
||||
)(manager, tbl)
|
||||
end
|
||||
|
||||
return managerNumberCheck(manager, tbl)
|
||||
end,
|
||||
Listeners =
|
||||
tc.default({}, -- Default to empty table
|
||||
-- Checks for a table of valid teams that do not already have an
|
||||
-- agenda assigned
|
||||
fn.FAnd{
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The Listeners must be a table of existing teams.",
|
||||
{
|
||||
"Is there a job here that doesn't exist (anymore)?",
|
||||
"Are you trying to have multiple manager jobs in this agenda? In that case you must put the list of manager jobs in curly braces.",
|
||||
[[Like so: DarkRP.createAgenda("Some agenda", {TEAM_MANAGER1, TEAM_MANAGER2}, {TEAM_LISTENER1, TEAM_LISTENER2})]]
|
||||
}
|
||||
),
|
||||
tc.tableOf(overlappingAgendaCheck)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
--[[
|
||||
Validate Categories
|
||||
]]
|
||||
DarkRP.validateCategory = tc.checkTable{
|
||||
name =
|
||||
tc.addHint(
|
||||
isstring,
|
||||
"The name must be a string."
|
||||
),
|
||||
|
||||
categorises =
|
||||
tc.addHint(
|
||||
tc.oneOf{"jobs", "entities", "shipments", "weapons", "vehicles", "ammo"},
|
||||
[[The categorises must be one of "jobs", "entities", "shipments", "weapons", "vehicles", "ammo"]],
|
||||
{
|
||||
"Mind that this is case sensitive.",
|
||||
"Also mind the quotation marks."
|
||||
}
|
||||
),
|
||||
|
||||
startExpanded =
|
||||
tc.addHint(
|
||||
isbool,
|
||||
"The startExpanded must be either true or false."
|
||||
),
|
||||
|
||||
color =
|
||||
tc.addHint(
|
||||
tc.tableOf(isnumber),
|
||||
"The color must be a Color value."
|
||||
),
|
||||
|
||||
canSee =
|
||||
tc.addHint(
|
||||
tc.optional(isfunction),
|
||||
"The canSee must be a function."
|
||||
),
|
||||
|
||||
sortOrder =
|
||||
tc.addHint(
|
||||
tc.optional(isnumber),
|
||||
"The sortOrder must be a number."
|
||||
),
|
||||
}
|
||||
71
gamemodes/darkrp/gamemode/modules/base/sh_commands.lua
Normal file
71
gamemodes/darkrp/gamemode/modules/base/sh_commands.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
DarkRP.declareChatCommand{
|
||||
command = "rpname",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "name",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "nick",
|
||||
description = "Set your RP name",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buy",
|
||||
description = "Buy a pistol",
|
||||
delay = 1.5,
|
||||
condition = fn.FAnd {
|
||||
fn.Compose{fn.Curry(fn.GetValue, 2)("enablebuypistol"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode},
|
||||
fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode}
|
||||
}
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyshipment",
|
||||
description = "Buy a shipment",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyvehicle",
|
||||
description = "Buy a vehicle",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "buyammo",
|
||||
description = "Purchase ammo",
|
||||
delay = 1.5,
|
||||
condition = fn.Compose{fn.Not, fn.Curry(fn.GetValue, 2)("noguns"), fn.Curry(fn.GetValue, 2)("Config"), gmod.GetGamemode}
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "price",
|
||||
description = "Set the price of the microwave or gunlab you're looking at",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "setprice",
|
||||
description = "Set the price of the microwave or gunlab you're looking at",
|
||||
delay = 1.5
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "forcerpname",
|
||||
description = "Forcefully change a player's RP name",
|
||||
delay = 0.5,
|
||||
tableArgs = true
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = "freerpname",
|
||||
description = "Remove a RP name from the database so a player can use it",
|
||||
delay = 1.5
|
||||
}
|
||||
922
gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua
Normal file
922
gamemodes/darkrp/gamemode/modules/base/sh_createitems.lua
Normal file
@@ -0,0 +1,922 @@
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- Job commands --
|
||||
-----------------------------------------------------------
|
||||
local function declareTeamCommands(CTeam)
|
||||
local k = 0
|
||||
for num, v in pairs(RPExtraTeams) do
|
||||
if v.command == CTeam.command then
|
||||
k = num
|
||||
end
|
||||
end
|
||||
|
||||
local chatcommandCondition = function(ply)
|
||||
local plyTeam = ply:Team()
|
||||
|
||||
if plyTeam == k then return false end
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() or CTeam.admin == 2 and not ply:IsSuperAdmin() then return false end
|
||||
if isnumber(CTeam.NeedToChangeFrom) and plyTeam ~= CTeam.NeedToChangeFrom then return false end
|
||||
if istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, plyTeam) then return false end
|
||||
if CTeam.customCheck and CTeam.customCheck(ply) == false then return false end
|
||||
if ply:isArrested() then return false end
|
||||
local numPlayers = team.NumPlayers(k)
|
||||
if CTeam.max ~= 0 and ((CTeam.max % 1 == 0 and numPlayers >= CTeam.max) or (CTeam.max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > CTeam.max)) then return false end
|
||||
if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then return false end
|
||||
if ply.LastVoteCop and CurTime() - ply.LastVoteCop < 80 then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
if CTeam.vote or CTeam.RequiresVote then
|
||||
DarkRP.declareChatCommand{
|
||||
command = "vote" .. CTeam.command,
|
||||
description = "Vote to become " .. CTeam.name .. ".",
|
||||
delay = 1.5,
|
||||
condition =
|
||||
function(ply)
|
||||
if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then return false end
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end
|
||||
|
||||
return chatcommandCondition(ply)
|
||||
end
|
||||
}
|
||||
|
||||
DarkRP.declareChatCommand{
|
||||
command = CTeam.command,
|
||||
description = "Become " .. CTeam.name .. " and skip the vote.",
|
||||
delay = 1.5,
|
||||
condition =
|
||||
function(ply)
|
||||
local requiresVote = CTeam.RequiresVote and CTeam.RequiresVote(ply, k)
|
||||
|
||||
if requiresVote then return false end
|
||||
if requiresVote ~= false and CTeam.admin == 0 and not ply:IsAdmin() or CTeam.admin == 1 and not ply:IsSuperAdmin() then return false end
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then return false end
|
||||
|
||||
return chatcommandCondition(ply)
|
||||
end
|
||||
}
|
||||
else
|
||||
DarkRP.declareChatCommand{
|
||||
command = CTeam.command,
|
||||
description = "Become " .. CTeam.name .. ".",
|
||||
delay = 1.5,
|
||||
condition = chatcommandCondition
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
local function addTeamCommands(CTeam, max)
|
||||
if CLIENT then return end
|
||||
|
||||
local k = 0
|
||||
for num, v in pairs(RPExtraTeams) do
|
||||
if v.command == CTeam.command then
|
||||
k = num
|
||||
end
|
||||
end
|
||||
|
||||
if CTeam.vote or CTeam.RequiresVote then
|
||||
DarkRP.defineChatCommand("vote" .. CTeam.command, function(ply)
|
||||
if CTeam.RequiresVote and not CTeam.RequiresVote(ply, k) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("job_doesnt_require_vote_currently"))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.canStartVote and not CTeam.canStartVote(ply) then
|
||||
local reason = isfunction(CTeam.canStartVoteReason) and CTeam.canStartVoteReason(ply, CTeam) or CTeam.canStartVoteReason or ""
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/vote" .. CTeam.command, reason))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. "vote" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
elseif CTeam.admin > 1 and not ply:IsSuperAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. "vote" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if isnumber(CTeam.NeedToChangeFrom) and ply:Team() ~= CTeam.NeedToChangeFrom then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", team.GetName(CTeam.NeedToChangeFrom), CTeam.name))
|
||||
|
||||
return ""
|
||||
elseif istable(CTeam.NeedToChangeFrom) and not table.HasValue(CTeam.NeedToChangeFrom, ply:Team()) then
|
||||
local teamnames = ""
|
||||
|
||||
for _, b in pairs(CTeam.NeedToChangeFrom) do
|
||||
teamnames = teamnames .. " or " .. team.GetName(b)
|
||||
end
|
||||
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_be_before", string.sub(teamnames, 5), CTeam.name))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.customCheck and not CTeam.customCheck(ply) then
|
||||
local message = isfunction(CTeam.CustomCheckFailMsg) and CTeam.CustomCheckFailMsg(ply, CTeam) or CTeam.CustomCheckFailMsg or DarkRP.getPhrase("unable", team.GetName(t), "")
|
||||
DarkRP.notify(ply, 1, 4, message)
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
local allowed, time = ply:changeAllowed(k)
|
||||
if not allowed then
|
||||
local notif = time and DarkRP.getPhrase("have_to_wait", math.ceil(time), "/job, " .. DarkRP.getPhrase("banned_or_demoted")) or DarkRP.getPhrase("unable", team.GetName(k), DarkRP.getPhrase("banned_or_demoted"))
|
||||
DarkRP.notify(ply, 1, 4, notif)
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply:Team() == k then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", CTeam.command, ""))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
local numPlayers = team.NumPlayers(k)
|
||||
if max ~= 0 and ((max % 1 == 0 and numPlayers >= max) or (max % 1 ~= 0 and (numPlayers + 1) / player.GetCount() > max)) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("team_limit_reached", CTeam.name))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply.LastJob and 10 - (CurTime() - ply.LastJob) >= 0 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(10 - (CurTime() - ply.LastJob)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
ply.LastVoteCop = ply.LastVoteCop or -80
|
||||
|
||||
if CurTime() - ply.LastVoteCop < 80 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(80 - (CurTime() - ply:GetTable().LastVoteCop)), GAMEMODE.Config.chatCommandPrefix .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
DarkRP.createVote(DarkRP.getPhrase("wants_to_be", ply:Nick(), CTeam.name), "job", ply, 20, function(vote, choice)
|
||||
local target = vote.target
|
||||
if not IsValid(target) then return end
|
||||
|
||||
if choice >= 0 then
|
||||
target:changeTeam(k)
|
||||
else
|
||||
DarkRP.notifyAll(1, 4, DarkRP.getPhrase("has_not_been_made_team", target:Nick(), CTeam.name))
|
||||
end
|
||||
end, nil, nil, {
|
||||
targetTeam = k
|
||||
})
|
||||
|
||||
ply.LastVoteCop = CurTime()
|
||||
|
||||
return ""
|
||||
end)
|
||||
|
||||
local function onJobCommand(ply, hasPriv)
|
||||
if hasPriv then
|
||||
ply:changeTeam(k)
|
||||
return
|
||||
end
|
||||
|
||||
local a = CTeam.admin
|
||||
if a > 0 and not ply:IsAdmin()
|
||||
or a > 1 and not ply:IsSuperAdmin()
|
||||
then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", CTeam.name))
|
||||
return
|
||||
end
|
||||
|
||||
if not CTeam.RequiresVote and
|
||||
(a == 0 and not ply:IsAdmin()
|
||||
or a == 1 and not ply:IsSuperAdmin()
|
||||
or a == 2)
|
||||
or CTeam.RequiresVote and CTeam.RequiresVote(ply, k)
|
||||
then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_to_make_vote", CTeam.name))
|
||||
return
|
||||
end
|
||||
|
||||
ply:changeTeam(k)
|
||||
end
|
||||
DarkRP.defineChatCommand(CTeam.command, function(ply)
|
||||
CAMI.PlayerHasAccess(ply, "DarkRP_GetJob_" .. CTeam.command, fp{onJobCommand, ply})
|
||||
|
||||
return ""
|
||||
end)
|
||||
else
|
||||
DarkRP.defineChatCommand(CTeam.command, function(ply)
|
||||
if CTeam.admin == 1 and not ply:IsAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_admin", "/" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
if CTeam.admin > 1 and not ply:IsSuperAdmin() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_sadmin", "/" .. CTeam.command))
|
||||
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:changeTeam(k)
|
||||
|
||||
return ""
|
||||
end)
|
||||
end
|
||||
|
||||
concommand.Add("rp_" .. CTeam.command, function(ply, cmd, args)
|
||||
if ply:EntIndex() ~= 0 and not ply:IsAdmin() then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_admin", cmd))
|
||||
return
|
||||
end
|
||||
|
||||
if CTeam.admin > 1 and not ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd))
|
||||
return
|
||||
end
|
||||
|
||||
if CTeam.vote then
|
||||
if CTeam.admin >= 1 and ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_sadmin", cmd))
|
||||
return
|
||||
elseif CTeam.admin > 1 and ply:IsSuperAdmin() and ply:EntIndex() ~= 0 then
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, DarkRP.getPhrase("need_to_make_vote", CTeam.name))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not args or not args[1] then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
|
||||
local target = DarkRP.findPlayer(args[1])
|
||||
|
||||
if not target then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1])))
|
||||
return
|
||||
end
|
||||
|
||||
target:changeTeam(k, true)
|
||||
local nick
|
||||
if (ply:EntIndex() ~= 0) then
|
||||
nick = ply:Nick()
|
||||
else
|
||||
nick = "Console"
|
||||
end
|
||||
DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_made_you_a_y", nick, CTeam.name))
|
||||
end)
|
||||
end
|
||||
|
||||
local function addEntityCommands(tblEnt)
|
||||
DarkRP.declareChatCommand{
|
||||
command = tblEnt.cmd,
|
||||
description = "Purchase a " .. tblEnt.name,
|
||||
delay = tblEnt.delay or GAMEMODE.Config.EntitySpamTime,
|
||||
condition =
|
||||
function(ply)
|
||||
if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then return false end
|
||||
if ply:isArrested() then return false end
|
||||
if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then return false end
|
||||
if not ply:canAfford(tblEnt.price) then return false end
|
||||
if tblEnt.customCheck and tblEnt.customCheck(ply) == false then return false end
|
||||
|
||||
return true
|
||||
end
|
||||
}
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
-- Default spawning function of an entity
|
||||
-- used if tblEnt.spawn is not defined
|
||||
local function defaultSpawn(ply, tr, tblE)
|
||||
local ent = ents.Create(tblE.ent)
|
||||
|
||||
if not ent:IsValid() then error("Entity '" .. tblE.ent .. "' does not exist or is not valid.") end
|
||||
if ent.Setowning_ent then ent:Setowning_ent(ply) end
|
||||
|
||||
ent:SetPos(tr.HitPos)
|
||||
-- These must be set before :Spawn()
|
||||
ent.SID = ply.SID
|
||||
ent.allowed = tblE.allowed
|
||||
ent.DarkRPItem = tblE
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
|
||||
DarkRP.placeEntity(ent, tr, ply)
|
||||
|
||||
local phys = ent:GetPhysicsObject()
|
||||
if phys:IsValid() then phys:Wake() end
|
||||
|
||||
return ent
|
||||
end
|
||||
|
||||
local function buythis(ply, args)
|
||||
if ply:isArrested() then return "" end
|
||||
if not tblEnt.allowPurchaseWhileDead and not ply:Alive() then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", tblEnt.name)))
|
||||
return ""
|
||||
end
|
||||
if istable(tblEnt.allowed) and not table.HasValue(tblEnt.allowed, ply:Team()) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
if tblEnt.customCheck and not tblEnt.customCheck(ply) then
|
||||
local message = isfunction(tblEnt.CustomCheckFailMsg) and tblEnt.CustomCheckFailMsg(ply, tblEnt) or
|
||||
tblEnt.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
DarkRP.notify(ply, 1, 4, message)
|
||||
return ""
|
||||
end
|
||||
|
||||
if ply:customEntityLimitReached(tblEnt) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyCustomEntity", nil, ply, tblEnt)
|
||||
|
||||
local cost = price or tblEnt.getPrice and tblEnt.getPrice(ply, tblEnt.price) or tblEnt.price
|
||||
|
||||
if not ply:canAfford(cost) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_afford", tblEnt.name))
|
||||
return ""
|
||||
end
|
||||
|
||||
if canbuy == false then
|
||||
if not suppress and message then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:addMoney(-cost)
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = (tblEnt.spawn or defaultSpawn)(ply, tr, tblEnt)
|
||||
ent.onlyremover = not tblEnt.allowTools
|
||||
-- Repeat these properties to alleviate work in tblEnt.spawn:
|
||||
ent.SID = ply.SID
|
||||
ent.allowed = tblEnt.allowed
|
||||
ent.DarkRPItem = tblEnt
|
||||
|
||||
hook.Call("playerBoughtCustomEntity", nil, ply, tblEnt, ent, cost)
|
||||
|
||||
if cost == 0 then
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_got_yourself", tblEnt.name))
|
||||
else
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", tblEnt.name, DarkRP.formatMoney(cost), ""))
|
||||
end
|
||||
|
||||
ply:addCustomEntity(tblEnt)
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand(tblEnt.cmd, buythis)
|
||||
end
|
||||
|
||||
RPExtraTeams = {}
|
||||
local jobByCmd = {}
|
||||
DarkRP.getJobByCommand = function(cmd)
|
||||
if not jobByCmd[cmd] then return nil, nil end
|
||||
return RPExtraTeams[jobByCmd[cmd]], jobByCmd[cmd]
|
||||
end
|
||||
plyMeta.getJobTable = function(ply)
|
||||
local tbl = RPExtraTeams[ply:Team()]
|
||||
-- don't error when the player has not fully joined yet
|
||||
if not tbl and (ply.DarkRPInitialised or ply.DarkRPDataRetrievalFailed) then
|
||||
DarkRP.error(
|
||||
string.format("There is a player with an invalid team!\n\nThe player's name is %s, their team number is \"%s\", which has the name \"%s\"",
|
||||
ply:EntIndex() == 0 and "Console" or IsValid(ply) and ply:Nick() or "unknown",
|
||||
ply:Team(),
|
||||
team.GetName(ply:Team())),
|
||||
1,
|
||||
{
|
||||
"It is the server owner's responsibility to figure out why that player has no valid team.",
|
||||
"This error is very likely to be caused by an earlier error. If you don't see any errors in your own console, look at the server console."
|
||||
}
|
||||
)
|
||||
end
|
||||
return tbl
|
||||
end
|
||||
|
||||
function DarkRP.createJob(Name, colorOrTable, model, Description, Weapons, command, maximum_amount_of_this_class, Salary, admin, Vote, Haslicense, NeedToChangeFrom, CustomCheck)
|
||||
local tableSyntaxUsed = not IsColor(colorOrTable)
|
||||
|
||||
local CustomTeam = tableSyntaxUsed and colorOrTable or
|
||||
{color = colorOrTable, model = model, description = Description, weapons = Weapons, command = command,
|
||||
max = maximum_amount_of_this_class, salary = Salary, admin = admin or 0, vote = tobool(Vote), hasLicense = Haslicense,
|
||||
NeedToChangeFrom = NeedToChangeFrom, customCheck = CustomCheck
|
||||
}
|
||||
CustomTeam.name = Name
|
||||
CustomTeam.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
-- Disabled job
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["jobs"][CustomTeam.command] then return end
|
||||
|
||||
local valid, err, hints = DarkRP.validateJob(CustomTeam)
|
||||
if not valid then DarkRP.error(string.format("Corrupt team: %s!\n%s", CustomTeam.name or "", err), 2, hints) end
|
||||
|
||||
if not (GM or GAMEMODE):CustomObjFitsMap(CustomTeam) then return end
|
||||
|
||||
local jobCount = #RPExtraTeams + 1
|
||||
|
||||
CustomTeam.team = jobCount
|
||||
|
||||
CustomTeam.salary = math.floor(CustomTeam.salary)
|
||||
|
||||
CustomTeam.customCheck = CustomTeam.customCheck and fp{DarkRP.simplerrRun, CustomTeam.customCheck}
|
||||
CustomTeam.CustomCheckFailMsg = isfunction(CustomTeam.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, CustomTeam.CustomCheckFailMsg} or CustomTeam.CustomCheckFailMsg
|
||||
CustomTeam.CanPlayerSuicide = CustomTeam.CanPlayerSuicide and fp{DarkRP.simplerrRun, CustomTeam.CanPlayerSuicide}
|
||||
CustomTeam.PlayerCanPickupWeapon = CustomTeam.PlayerCanPickupWeapon and fp{DarkRP.simplerrRun, CustomTeam.PlayerCanPickupWeapon}
|
||||
CustomTeam.PlayerDeath = CustomTeam.PlayerDeath and fp{DarkRP.simplerrRun, CustomTeam.PlayerDeath}
|
||||
CustomTeam.PlayerLoadout = CustomTeam.PlayerLoadout and fp{DarkRP.simplerrRun, CustomTeam.PlayerLoadout}
|
||||
CustomTeam.PlayerSelectSpawn = CustomTeam.PlayerSelectSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSelectSpawn}
|
||||
CustomTeam.PlayerSetModel = CustomTeam.PlayerSetModel and fp{DarkRP.simplerrRun, CustomTeam.PlayerSetModel}
|
||||
CustomTeam.PlayerSpawn = CustomTeam.PlayerSpawn and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawn}
|
||||
CustomTeam.PlayerSpawnProp = CustomTeam.PlayerSpawnProp and fp{DarkRP.simplerrRun, CustomTeam.PlayerSpawnProp}
|
||||
CustomTeam.RequiresVote = CustomTeam.RequiresVote and fp{DarkRP.simplerrRun, CustomTeam.RequiresVote}
|
||||
CustomTeam.ShowSpare1 = CustomTeam.ShowSpare1 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare1}
|
||||
CustomTeam.ShowSpare2 = CustomTeam.ShowSpare2 and fp{DarkRP.simplerrRun, CustomTeam.ShowSpare2}
|
||||
CustomTeam.canStartVote = CustomTeam.canStartVote and fp{DarkRP.simplerrRun, CustomTeam.canStartVote}
|
||||
|
||||
jobByCmd[CustomTeam.command] = table.insert(RPExtraTeams, CustomTeam)
|
||||
DarkRP.addToCategory(CustomTeam, "jobs", CustomTeam.category)
|
||||
|
||||
team.SetUp(jobCount, Name, CustomTeam.color)
|
||||
|
||||
timer.Simple(0, function()
|
||||
declareTeamCommands(CustomTeam)
|
||||
addTeamCommands(CustomTeam, CustomTeam.max)
|
||||
end)
|
||||
|
||||
-- Precache model here. Not right before the job change is done
|
||||
if istable(CustomTeam.model) then
|
||||
for _, v in pairs(CustomTeam.model) do util.PrecacheModel(v) end
|
||||
else
|
||||
util.PrecacheModel(CustomTeam.model)
|
||||
end
|
||||
return jobCount
|
||||
end
|
||||
AddExtraTeam = DarkRP.createJob
|
||||
|
||||
local function removeCustomItem(tbl, category, hookName, reloadF4, i)
|
||||
local item = tbl[i]
|
||||
tbl[i] = nil
|
||||
if category then DarkRP.removeFromCategory(item, category) end
|
||||
if istable(item) and (item.command or item.cmd) then DarkRP.removeChatCommand(item.command or item.cmd) end
|
||||
hook.Run(hookName, i, item)
|
||||
if CLIENT and reloadF4 and IsValid(DarkRP.getF4MenuPanel()) then DarkRP.getF4MenuPanel():Remove() end -- Rebuild entire F4 menu frame
|
||||
end
|
||||
|
||||
function DarkRP.removeJob(i)
|
||||
local job = RPExtraTeams[i]
|
||||
jobByCmd[job.command] = nil
|
||||
|
||||
DarkRP.removeChatCommand("vote" .. job.command)
|
||||
removeCustomItem(RPExtraTeams, "jobs", "onJobRemoved", true, i)
|
||||
end
|
||||
|
||||
RPExtraTeamDoors = {}
|
||||
RPExtraTeamDoorIDs = {}
|
||||
local maxTeamDoorID = 0
|
||||
function DarkRP.createEntityGroup(name, ...)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["doorgroups"][name] then return end
|
||||
RPExtraTeamDoors[name] = {...}
|
||||
RPExtraTeamDoors[name].name = name
|
||||
|
||||
maxTeamDoorID = maxTeamDoorID + 1
|
||||
RPExtraTeamDoorIDs[name] = maxTeamDoorID
|
||||
end
|
||||
AddDoorGroup = DarkRP.createEntityGroup
|
||||
|
||||
DarkRP.removeEntityGroup = fp{removeCustomItem, RPExtraTeamDoors, nil, "onEntityGroupRemoved", false}
|
||||
|
||||
CustomVehicles = {}
|
||||
CustomShipments = {}
|
||||
local shipByName = {}
|
||||
DarkRP.getShipmentByName = function(name)
|
||||
name = string.lower(name or "")
|
||||
|
||||
if not shipByName[name] then return nil, nil end
|
||||
return CustomShipments[shipByName[name]], shipByName[name]
|
||||
end
|
||||
|
||||
function DarkRP.createShipment(name, model, entity, price, Amount_of_guns_in_one_shipment, Sold_separately, price_separately, noshipment, classes, shipmodel, CustomCheck)
|
||||
local tableSyntaxUsed = istable(model)
|
||||
|
||||
price = tonumber(price)
|
||||
local shipmentmodel = shipmodel or "models/Items/item_item_crate.mdl"
|
||||
|
||||
local customShipment = tableSyntaxUsed and model or
|
||||
{model = model, entity = entity, price = price, amount = Amount_of_guns_in_one_shipment,
|
||||
seperate = Sold_separately, pricesep = price_separately, noship = noshipment, allowed = classes,
|
||||
shipmodel = shipmentmodel, customCheck = CustomCheck, weight = 5}
|
||||
|
||||
-- The pains of backwards compatibility when dealing with ancient spelling errors...
|
||||
if customShipment.separate ~= nil then
|
||||
customShipment.seperate = customShipment.separate
|
||||
end
|
||||
customShipment.separate = customShipment.seperate
|
||||
|
||||
if customShipment.allowed == nil then
|
||||
customShipment.allowed = {}
|
||||
for k in pairs(team.GetAllTeams()) do
|
||||
table.insert(customShipment.allowed, k)
|
||||
end
|
||||
end
|
||||
|
||||
customShipment.name = name
|
||||
customShipment.default = DarkRP.DARKRP_LOADING
|
||||
customShipment.shipmodel = customShipment.shipmodel or shipmentmodel
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["shipments"][customShipment.name] then return end
|
||||
|
||||
local valid, err, hints = DarkRP.validateShipment(customShipment)
|
||||
if not valid then DarkRP.error(string.format("Corrupt shipment: %s!\n%s", name or "", err), 2, hints) end
|
||||
|
||||
customShipment.spawn = customShipment.spawn and fp{DarkRP.simplerrRun, customShipment.spawn}
|
||||
customShipment.allowed = isnumber(customShipment.allowed) and {customShipment.allowed} or customShipment.allowed
|
||||
customShipment.customCheck = customShipment.customCheck and fp{DarkRP.simplerrRun, customShipment.customCheck}
|
||||
customShipment.CustomCheckFailMsg = isfunction(customShipment.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, customShipment.CustomCheckFailMsg} or customShipment.CustomCheckFailMsg
|
||||
|
||||
if not customShipment.noship then DarkRP.addToCategory(customShipment, "shipments", customShipment.category) end
|
||||
if customShipment.separate then DarkRP.addToCategory(customShipment, "weapons", customShipment.category) end
|
||||
|
||||
shipByName[string.lower(name or "")] = table.insert(CustomShipments, customShipment)
|
||||
util.PrecacheModel(customShipment.model)
|
||||
end
|
||||
AddCustomShipment = DarkRP.createShipment
|
||||
|
||||
function DarkRP.removeShipment(i)
|
||||
local ship = CustomShipments[i]
|
||||
shipByName[ship.name] = nil
|
||||
removeCustomItem(CustomShipments, "shipments", "onShipmentRemoved", true, i)
|
||||
end
|
||||
|
||||
function DarkRP.createVehicle(Name_of_vehicle, model, price, Jobs_that_can_buy_it, customcheck)
|
||||
local vehicle = istable(Name_of_vehicle) and Name_of_vehicle or
|
||||
{name = Name_of_vehicle, model = model, price = price, allowed = Jobs_that_can_buy_it, customCheck = customcheck}
|
||||
|
||||
vehicle.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["vehicles"][vehicle.name] then return end
|
||||
|
||||
local found = false
|
||||
for k in pairs(DarkRP.getAvailableVehicles()) do
|
||||
if string.lower(k) == string.lower(vehicle.name) then found = true break end
|
||||
end
|
||||
|
||||
local valid, err, hints = DarkRP.validateVehicle(vehicle)
|
||||
if not valid then DarkRP.error(string.format("Corrupt vehicle: %s!\n%s", vehicle.name or "", err), 2, hints) end
|
||||
|
||||
if not found then DarkRP.error("Vehicle invalid: " .. vehicle.name .. ". Unknown vehicle name.", 2) end
|
||||
|
||||
vehicle.customCheck = vehicle.customCheck and fp{DarkRP.simplerrRun, vehicle.customCheck}
|
||||
vehicle.CustomCheckFailMsg = isfunction(vehicle.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, vehicle.CustomCheckFailMsg} or vehicle.CustomCheckFailMsg
|
||||
|
||||
table.insert(CustomVehicles, vehicle)
|
||||
DarkRP.addToCategory(vehicle, "vehicles", vehicle.category)
|
||||
end
|
||||
AddCustomVehicle = DarkRP.createVehicle
|
||||
|
||||
DarkRP.removeVehicle = fp{removeCustomItem, CustomVehicles, "vehicles", "onVehicleRemoved", true}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether a custom job or shipmet or whatever can be used in a certain map
|
||||
---------------------------------------------------------------------------]]
|
||||
function GM:CustomObjFitsMap(obj)
|
||||
if not obj or not obj.maps then return true end
|
||||
|
||||
local map = string.lower(game.GetMap())
|
||||
for _, v in pairs(obj.maps) do
|
||||
if string.lower(v) == map then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRPEntities = {}
|
||||
function DarkRP.createEntity(name, entity, model, price, max, command, classes, CustomCheck)
|
||||
local tableSyntaxUsed = istable(entity)
|
||||
|
||||
local tblEnt = tableSyntaxUsed and entity or
|
||||
{ent = entity, model = model, price = price, max = max,
|
||||
cmd = command, allowed = classes, customCheck = CustomCheck}
|
||||
tblEnt.name = name
|
||||
tblEnt.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["entities"][tblEnt.name] then return end
|
||||
|
||||
if isnumber(tblEnt.allowed) then
|
||||
tblEnt.allowed = {tblEnt.allowed}
|
||||
end
|
||||
|
||||
local valid, err, hints = DarkRP.validateEntity(tblEnt)
|
||||
if not valid then DarkRP.error(string.format("Corrupt entity: %s!\n%s", name or "", err), 2, hints) end
|
||||
|
||||
tblEnt.customCheck = tblEnt.customCheck and fp{DarkRP.simplerrRun, tblEnt.customCheck}
|
||||
tblEnt.CustomCheckFailMsg = isfunction(tblEnt.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, tblEnt.CustomCheckFailMsg} or tblEnt.CustomCheckFailMsg
|
||||
tblEnt.getPrice = tblEnt.getPrice and fp{DarkRP.simplerrRun, tblEnt.getPrice}
|
||||
tblEnt.getMax = tblEnt.getMax and fp{DarkRP.simplerrRun, tblEnt.getMax}
|
||||
tblEnt.spawn = tblEnt.spawn and fp{DarkRP.simplerrRun, tblEnt.spawn}
|
||||
|
||||
-- if SERVER and FPP then
|
||||
-- FPP.AddDefaultBlocked(blockTypes, tblEnt.ent)
|
||||
-- end
|
||||
|
||||
table.insert(DarkRPEntities, tblEnt)
|
||||
DarkRP.addToCategory(tblEnt, "entities", tblEnt.category)
|
||||
timer.Simple(0, function() addEntityCommands(tblEnt) end)
|
||||
end
|
||||
AddEntity = DarkRP.createEntity
|
||||
|
||||
DarkRP.removeEntity = fp{removeCustomItem, DarkRPEntities, "entities", "onEntityRemoved", true}
|
||||
|
||||
-- here for backwards compatibility
|
||||
DarkRPAgendas = {}
|
||||
|
||||
local agendas = {}
|
||||
-- Returns the agenda managed by the player
|
||||
plyMeta.getAgenda = fn.Compose{fn.Curry(fn.Flip(fn.GetValue), 2)(DarkRPAgendas), plyMeta.Team}
|
||||
|
||||
-- Returns the agenda this player is member of
|
||||
function plyMeta:getAgendaTable()
|
||||
return agendas[self:Team()]
|
||||
end
|
||||
|
||||
DarkRP.getAgendas = fp{fn.Id, agendas}
|
||||
|
||||
function DarkRP.createAgenda(Title, Manager, Listeners)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["agendas"][Title] then return end
|
||||
|
||||
local agenda = {Manager = Manager, Title = Title, Listeners = Listeners, ManagersByKey = {}}
|
||||
agenda.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
local valid, err, hints = DarkRP.validateAgenda(agenda)
|
||||
if not valid then DarkRP.error(string.format("Corrupt agenda: %s!\n%s", agenda.Title or "", err), 2, hints) end
|
||||
|
||||
for _, v in pairs(agenda.Listeners) do
|
||||
agendas[v] = agenda
|
||||
end
|
||||
|
||||
for _, v in pairs(istable(agenda.Manager) and agenda.Manager or {agenda.Manager}) do
|
||||
agendas[v] = agenda
|
||||
DarkRPAgendas[v] = agenda -- backwards compat
|
||||
agenda.ManagersByKey[v] = true
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
timer.Simple(0, function()
|
||||
-- Run after scripts have loaded
|
||||
agenda.text = hook.Run("agendaUpdated", nil, agenda, "")
|
||||
end)
|
||||
end
|
||||
end
|
||||
AddAgenda = DarkRP.createAgenda
|
||||
|
||||
function DarkRP.removeAgenda(title)
|
||||
local agenda
|
||||
for k, v in pairs(agendas) do
|
||||
if v.Title == title then
|
||||
agenda = v
|
||||
agendas[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in pairs(DarkRPAgendas) do
|
||||
if v.Title == title then DarkRPAgendas[k] = nil end
|
||||
end
|
||||
hook.Run("onAgendaRemoved", title, agenda)
|
||||
end
|
||||
|
||||
GM.DarkRPGroupChats = {}
|
||||
local groupChatNumber = 0
|
||||
function DarkRP.createGroupChat(funcOrTeam, ...)
|
||||
local gm = GM or GAMEMODE
|
||||
gm.DarkRPGroupChats = gm.DarkRPGroupChats or {}
|
||||
if DarkRP.DARKRP_LOADING then
|
||||
groupChatNumber = groupChatNumber + 1
|
||||
if DarkRP.disabledDefaults["groupchat"][groupChatNumber] then return end
|
||||
end
|
||||
-- People can enter either functions or a list of teams as parameter(s)
|
||||
if isfunction(funcOrTeam) then
|
||||
table.insert(gm.DarkRPGroupChats, fp{DarkRP.simplerrRun, funcOrTeam})
|
||||
else
|
||||
local teams = {funcOrTeam, ...}
|
||||
table.insert(gm.DarkRPGroupChats, function(ply) return table.HasValue(teams, ply:Team()) end)
|
||||
end
|
||||
end
|
||||
GM.AddGroupChat = function(_, ...) DarkRP.createGroupChat(...) end
|
||||
|
||||
DarkRP.removeGroupChat = fp{removeCustomItem, GM.DarkRPGroupChats, nil, "onGroupChatRemoved", false}
|
||||
|
||||
DarkRP.getGroupChats = fp{fn.Id, GM.DarkRPGroupChats}
|
||||
|
||||
GM.AmmoTypes = {}
|
||||
|
||||
function DarkRP.createAmmoType(ammoType, name, model, price, amountGiven, customCheck)
|
||||
local gm = GM or GAMEMODE
|
||||
gm.AmmoTypes = gm.AmmoTypes or {}
|
||||
local ammo = istable(name) and name or {
|
||||
name = name,
|
||||
model = model,
|
||||
price = price,
|
||||
amountGiven = amountGiven,
|
||||
customCheck = customCheck
|
||||
}
|
||||
ammo.ammoType = ammoType
|
||||
ammo.default = DarkRP.DARKRP_LOADING
|
||||
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["ammo"][ammo.name] then return end
|
||||
|
||||
ammo.customCheck = ammo.customCheck and fp{DarkRP.simplerrRun, ammo.customCheck}
|
||||
ammo.CustomCheckFailMsg = isfunction(ammo.CustomCheckFailMsg) and fp{DarkRP.simplerrRun, ammo.CustomCheckFailMsg} or ammo.CustomCheckFailMsg
|
||||
ammo.id = table.insert(gm.AmmoTypes, ammo)
|
||||
|
||||
DarkRP.addToCategory(ammo, "ammo", ammo.category)
|
||||
end
|
||||
GM.AddAmmoType = function(_, ...) DarkRP.createAmmoType(...) end
|
||||
|
||||
DarkRP.removeAmmoType = fp{removeCustomItem, GM.AmmoTypes, "ammo", "onAmmoTypeRemoved", true}
|
||||
|
||||
local demoteGroups = {}
|
||||
function DarkRP.createDemoteGroup(name, tbl)
|
||||
if DarkRP.DARKRP_LOADING and DarkRP.disabledDefaults["demotegroups"][name] then return end
|
||||
if not tbl or not tbl[1] then error("No members in the demote group!") end
|
||||
|
||||
local set = demoteGroups[tbl[1]] or disjoint.MakeSet(tbl[1])
|
||||
set.name = name
|
||||
for i = 2, #tbl do
|
||||
set = (demoteGroups[tbl[i]] or disjoint.MakeSet(tbl[i])) + set
|
||||
set.name = name
|
||||
end
|
||||
|
||||
for _, teamNr in ipairs(tbl) do
|
||||
if demoteGroups[teamNr] then
|
||||
-- Unify the sets if there was already one there
|
||||
demoteGroups[teamNr] = demoteGroups[teamNr] + set
|
||||
else
|
||||
demoteGroups[teamNr] = set
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.removeDemoteGroup(name)
|
||||
local foundSet
|
||||
for k, v in pairs(demoteGroups) do
|
||||
local set = disjoint.FindSet(v)
|
||||
if set.name == name then
|
||||
foundSet = set
|
||||
demoteGroups[k] = nil
|
||||
end
|
||||
end
|
||||
hook.Run("onDemoteGroupRemoved", name, foundSet)
|
||||
end
|
||||
|
||||
function DarkRP.getDemoteGroup(teamNr)
|
||||
demoteGroups[teamNr] = demoteGroups[teamNr] or disjoint.MakeSet(teamNr)
|
||||
return disjoint.FindSet(demoteGroups[teamNr])
|
||||
end
|
||||
|
||||
DarkRP.getDemoteGroups = fp{fn.Id, demoteGroups}
|
||||
|
||||
local categories = {
|
||||
jobs = {},
|
||||
entities = {},
|
||||
shipments = {},
|
||||
weapons = {},
|
||||
vehicles = {},
|
||||
ammo = {},
|
||||
}
|
||||
local categoriesMerged = false -- whether categories and custom items are merged.
|
||||
|
||||
DarkRP.getCategories = fp{fn.Id, categories}
|
||||
|
||||
local categoryOrder = function(a, b)
|
||||
local aso = a.sortOrder or 100
|
||||
local bso = b.sortOrder or 100
|
||||
return aso < bso or aso == bso and a.name < b.name
|
||||
end
|
||||
|
||||
local function insertCategory(destination, tbl)
|
||||
-- Override existing category of applicable
|
||||
for k, cat in pairs(destination) do
|
||||
if cat.name ~= tbl.name then continue end
|
||||
|
||||
destination[k] = tbl
|
||||
tbl.members = cat.members
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(destination, tbl)
|
||||
local i = #destination
|
||||
|
||||
while i > 1 do
|
||||
if categoryOrder(destination[i - 1], tbl) then break end
|
||||
destination[i - 1], destination[i] = destination[i], destination[i - 1]
|
||||
i = i - 1
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.createCategory(tbl)
|
||||
local valid, err, hints = DarkRP.validateCategory(tbl)
|
||||
if not valid then DarkRP.error(string.format("Corrupt category: %s!\n%s", tbl.name or "", err), 2, hints) end
|
||||
tbl.members = {}
|
||||
|
||||
local destination = categories[tbl.categorises]
|
||||
insertCategory(destination, tbl)
|
||||
|
||||
-- Too many people made the mistake of not creating a category for weapons as well as shipments
|
||||
-- when having shipments that can also be sold separately.
|
||||
if tbl.categorises == "shipments" then
|
||||
insertCategory(categories.weapons, table.Copy(tbl))
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.addToCategory(item, kind, cat)
|
||||
cat = cat or "Другое"
|
||||
item.category = cat
|
||||
|
||||
-- The merge process will take care of the category:
|
||||
if not categoriesMerged then return end
|
||||
|
||||
-- Post-merge: manual insertion into category
|
||||
local cats = categories[kind]
|
||||
for _, c in ipairs(cats) do
|
||||
if c.name ~= cat then continue end
|
||||
|
||||
insertCategory(c.members, item)
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], item.name, cat), 2, {
|
||||
"Make sure the category is created with DarkRP.createCategory.",
|
||||
"The category name is case sensitive!",
|
||||
"Categories must be created before DarkRP finished loading.",
|
||||
})
|
||||
end
|
||||
|
||||
function DarkRP.removeFromCategory(item, kind)
|
||||
local cats = categories[kind]
|
||||
if not cats then DarkRP.error(string.format("Invalid category kind '%s'.", kind), 2) end
|
||||
local cat = item.category
|
||||
if not cat then return end
|
||||
for _, v in pairs(cats) do
|
||||
if v.name ~= item.category then continue end
|
||||
for k, mem in pairs(v.members) do
|
||||
if mem ~= item then continue end
|
||||
table.remove(v.members, k)
|
||||
break
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- Assign custom stuff to their categories
|
||||
local function mergeCategories(customs, catKind, path)
|
||||
local cats = categories[catKind]
|
||||
local catByName = {}
|
||||
for _, v in pairs(cats) do catByName[v.name] = v end
|
||||
for _, v in pairs(customs) do
|
||||
-- Override default thing categories:
|
||||
local catName = v.default and (GAMEMODE.Config.CategoryOverride[catKind] or {})[v.name] or v.category or "Другое"
|
||||
local cat = catByName[catName]
|
||||
if not cat then
|
||||
DarkRP.errorNoHalt(string.format([[The category of "%s" ("%s") does not exist!]], v.name, catName), 3, {
|
||||
"Make sure the category is created with DarkRP.createCategory.",
|
||||
"The category name is case sensitive!",
|
||||
"Categories must be created before DarkRP finished loading."
|
||||
}, path, -1, path)
|
||||
cat = catByName.Другое
|
||||
end
|
||||
|
||||
cat.members = cat.members or {}
|
||||
table.insert(cat.members, v)
|
||||
end
|
||||
|
||||
-- Sort category members
|
||||
for _, v in pairs(cats) do table.sort(v.members, categoryOrder) end
|
||||
end
|
||||
|
||||
hook.Add("loadCustomDarkRPItems", "mergeCategories", function()
|
||||
local shipments = fn.Filter(fc{fn.Not, fp{fn.GetValue, "noship"}}, CustomShipments)
|
||||
local guns = fn.Filter(fp{fn.GetValue, "separate"}, CustomShipments)
|
||||
|
||||
mergeCategories(RPExtraTeams, "jobs", "your jobs")
|
||||
mergeCategories(DarkRPEntities, "entities", "your custom entities")
|
||||
mergeCategories(shipments, "shipments", "your custom shipments")
|
||||
mergeCategories(guns, "weapons", "your custom weapons")
|
||||
mergeCategories(CustomVehicles, "vehicles", "your custom vehicles")
|
||||
mergeCategories(GAMEMODE.AmmoTypes, "ammo", "your custom ammo")
|
||||
|
||||
categoriesMerged = true
|
||||
end)
|
||||
100
gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua
Normal file
100
gamemodes/darkrp/gamemode/modules/base/sh_entityvars.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId or 0
|
||||
DarkRP.RegisteredDarkRPVars = DarkRP.RegisteredDarkRPVars or {}
|
||||
DarkRP.RegisteredDarkRPVarsById = DarkRP.RegisteredDarkRPVarsById or {}
|
||||
|
||||
-- the amount of bits assigned to the value that determines which DarkRPVar we're sending/receiving
|
||||
local DARKRP_ID_BITS = 8
|
||||
local UNKNOWN_DARKRPVAR = 255 -- Should be equal to 2^DARKRP_ID_BITS - 1
|
||||
DarkRP.DARKRP_ID_BITS = DARKRP_ID_BITS
|
||||
|
||||
function DarkRP.registerDarkRPVar(name, writeFn, readFn)
|
||||
-- After a reload, only update the write and read function
|
||||
if DarkRP.RegisteredDarkRPVars[name] then
|
||||
DarkRP.RegisteredDarkRPVars[name].writeFn = writeFn
|
||||
DarkRP.RegisteredDarkRPVars[name].readFn = readFn
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.RegisteredDarkRPVarsMaxId = DarkRP.RegisteredDarkRPVarsMaxId + 1
|
||||
|
||||
-- UNKNOWN_DARKRPVAR is reserved for unknown values
|
||||
if DarkRP.RegisteredDarkRPVarsMaxId >= UNKNOWN_DARKRPVAR then DarkRP.error(string.format("Too many DarkRPVar registrations! DarkRPVar '%s' triggered this error", name), 2) end
|
||||
|
||||
DarkRP.RegisteredDarkRPVars[name] = {id = DarkRP.RegisteredDarkRPVarsMaxId, name = name, writeFn = writeFn, readFn = readFn}
|
||||
DarkRP.RegisteredDarkRPVarsById[DarkRP.RegisteredDarkRPVarsMaxId] = DarkRP.RegisteredDarkRPVars[name]
|
||||
end
|
||||
|
||||
-- Unknown values have unknown types and unknown identifiers, so this is sent inefficiently
|
||||
local function writeUnknown(name, value)
|
||||
net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS)
|
||||
net.WriteString(name)
|
||||
net.WriteType(value)
|
||||
end
|
||||
|
||||
-- Read the value of a DarkRPVar that was not registered
|
||||
local function readUnknown()
|
||||
return net.ReadString(), net.ReadType(net.ReadUInt(8))
|
||||
end
|
||||
|
||||
function DarkRP.writeNetDarkRPVar(name, value)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if not DarkRPVar then return writeUnknown(name, value) end
|
||||
|
||||
net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS)
|
||||
return DarkRPVar.writeFn(value)
|
||||
end
|
||||
|
||||
function DarkRP.writeNetDarkRPVarRemoval(name)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if not DarkRPVar then
|
||||
net.WriteUInt(UNKNOWN_DARKRPVAR, DARKRP_ID_BITS)
|
||||
net.WriteString(name)
|
||||
return
|
||||
end
|
||||
|
||||
net.WriteUInt(DarkRPVar.id, DARKRP_ID_BITS)
|
||||
end
|
||||
|
||||
function DarkRP.readNetDarkRPVar()
|
||||
local DarkRPVarId = net.ReadUInt(DARKRP_ID_BITS)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVarsById[DarkRPVarId]
|
||||
|
||||
if DarkRPVarId == UNKNOWN_DARKRPVAR then
|
||||
local name, value = readUnknown()
|
||||
|
||||
return name, value
|
||||
end
|
||||
|
||||
local val = DarkRPVar.readFn(value)
|
||||
|
||||
return DarkRPVar.name, val
|
||||
end
|
||||
|
||||
function DarkRP.readNetDarkRPVarRemoval()
|
||||
local id = net.ReadUInt(DARKRP_ID_BITS)
|
||||
return id == UNKNOWN_DARKRPVAR and net.ReadString() or DarkRP.RegisteredDarkRPVarsById[id].name
|
||||
end
|
||||
|
||||
-- The money is a double because it accepts higher values than Int and UInt, which are undefined for >32 bits
|
||||
DarkRP.registerDarkRPVar("money", net.WriteDouble, net.ReadDouble)
|
||||
DarkRP.registerDarkRPVar("salary", fp{fn.Flip(net.WriteInt), 32}, fp{net.ReadInt, 32})
|
||||
DarkRP.registerDarkRPVar("rpname", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("job", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("HasGunlicense", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("Arrested", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("wanted", net.WriteBit, fc{tobool, net.ReadBit})
|
||||
DarkRP.registerDarkRPVar("wantedReason", net.WriteString, net.ReadString)
|
||||
DarkRP.registerDarkRPVar("agenda", net.WriteString, net.ReadString)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
RP name override
|
||||
---------------------------------------------------------------------------]]
|
||||
local pmeta = FindMetaTable("Player")
|
||||
pmeta.SteamName = pmeta.SteamName or pmeta.Name
|
||||
function pmeta:Name()
|
||||
if not self:IsValid() then DarkRP.error("Attempt to call Name/Nick/GetName on a non-existing player!", SERVER and 1 or 2) end
|
||||
return GAMEMODE.Config.allowrpnames and self:getDarkRPVar("rpname")
|
||||
or self:SteamName()
|
||||
end
|
||||
pmeta.GetName = pmeta.Name
|
||||
pmeta.Nick = pmeta.Name
|
||||
@@ -0,0 +1,86 @@
|
||||
function GM:SetupMove(ply, mv, cmd)
|
||||
if ply:isArrested() then
|
||||
mv:SetMaxClientSpeed(self.Config.arrestspeed)
|
||||
end
|
||||
return self.Sandbox.SetupMove(self, ply, mv, cmd)
|
||||
end
|
||||
|
||||
function GM:StartCommand(ply, usrcmd)
|
||||
-- Used in arrest_stick and unarrest_stick but addons can use it too!
|
||||
local wep = ply:GetActiveWeapon()
|
||||
if wep:IsValid() and isfunction(wep.startDarkRPCommand) then
|
||||
wep:startDarkRPCommand(usrcmd)
|
||||
end
|
||||
end
|
||||
|
||||
function GM:OnPlayerChangedTeam(ply, oldTeam, newTeam)
|
||||
if RPExtraTeams[oldTeam] and RPExtraTeams[oldTeam].OnPlayerLeftTeam then
|
||||
RPExtraTeams[oldTeam].OnPlayerLeftTeam(ply, newTeam)
|
||||
end
|
||||
|
||||
if RPExtraTeams[newTeam] and RPExtraTeams[newTeam].OnPlayerChangedTeam then
|
||||
RPExtraTeams[newTeam].OnPlayerChangedTeam(ply, oldTeam, newTeam)
|
||||
end
|
||||
|
||||
if CLIENT then return end
|
||||
|
||||
local agenda = ply:getAgendaTable()
|
||||
|
||||
-- Remove agenda text when last manager left
|
||||
if agenda and agenda.ManagersByKey[oldTeam] then
|
||||
local found = false
|
||||
for man, _ in pairs(agenda.ManagersByKey) do
|
||||
if team.NumPlayers(man) > 0 then found = true break end
|
||||
end
|
||||
if not found then agenda.text = nil end
|
||||
end
|
||||
|
||||
ply:setSelfDarkRPVar("agenda", agenda and agenda.text or nil)
|
||||
end
|
||||
|
||||
hook.Add("loadCustomDarkRPItems", "CAMI privs", function()
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SeeEvents",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_GetAdminWeapons",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetDoorOwner",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_ChangeDoorSettings",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_AdminCommands",
|
||||
MinAccess = "admin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetMoney",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_SetLicense",
|
||||
MinAccess = "superadmin"
|
||||
}
|
||||
|
||||
for _, v in pairs(RPExtraTeams) do
|
||||
if not v.vote or v.admin and v.admin > 1 then continue end
|
||||
|
||||
local toAdmin = {[0] = "admin", [1] = "superadmin"}
|
||||
CAMI.RegisterPrivilege{
|
||||
Name = "DarkRP_GetJob_" .. v.command,
|
||||
MinAccess = toAdmin[v.admin or 0]-- Add privileges for the teams that are voted for
|
||||
}
|
||||
end
|
||||
end)
|
||||
1527
gamemodes/darkrp/gamemode/modules/base/sh_interface.lua
Normal file
1527
gamemodes/darkrp/gamemode/modules/base/sh_interface.lua
Normal file
File diff suppressed because it is too large
Load Diff
44
gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua
Normal file
44
gamemodes/darkrp/gamemode/modules/base/sh_playerclass.lua
Normal file
@@ -0,0 +1,44 @@
|
||||
local PLAYER_CLASS = {}
|
||||
|
||||
-- Value of -1 = set to config value, if a corresponding setting exists
|
||||
PLAYER_CLASS.DisplayName = "DarkRP Base Player Class"
|
||||
PLAYER_CLASS.WalkSpeed = -1
|
||||
PLAYER_CLASS.RunSpeed = -1
|
||||
PLAYER_CLASS.DuckSpeed = 0.3
|
||||
PLAYER_CLASS.UnDuckSpeed = 0.3
|
||||
PLAYER_CLASS.TeammateNoCollide = false
|
||||
PLAYER_CLASS.StartHealth = -1
|
||||
|
||||
function PLAYER_CLASS:Loadout()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:SetModel()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:ShouldDrawLocal()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:CreateMove(cmd)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:CalcView(view)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:GetHandsModel()
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:StartMove(mv, cmd)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
function PLAYER_CLASS:FinishMove(mv)
|
||||
-- Let gamemode decide
|
||||
end
|
||||
|
||||
player_manager.RegisterClass("player_darkrp", PLAYER_CLASS, "player_sandbox")
|
||||
86
gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua
Normal file
86
gamemodes/darkrp/gamemode/modules/base/sh_simplerr.lua
Normal file
@@ -0,0 +1,86 @@
|
||||
-- simplerrRun: Run a function with the given parameters and send any runtime errors to admins
|
||||
DarkRP.simplerrRun = fc{
|
||||
fn.Snd, -- On success ignore the first return value
|
||||
simplerr.wrapError,
|
||||
simplerr.wrapHook,
|
||||
simplerr.wrapLog,
|
||||
simplerr.safeCall
|
||||
}
|
||||
|
||||
-- error: throw a runtime error without exiting the stack
|
||||
-- parameters: msg, [stackNr], [hints], [path], [line]
|
||||
DarkRP.errorNoHalt = fc{
|
||||
simplerr.wrapHook,
|
||||
simplerr.wrapLog,
|
||||
simplerr.runError,
|
||||
function(msg, err, ...) return msg, err and err + 3 or 4, ... end -- Raise error level one higher
|
||||
}
|
||||
|
||||
-- error: throw a runtime error
|
||||
-- parameters: msg, [stackNr], [hints], [path], [line]
|
||||
DarkRP.error = fc{
|
||||
simplerr.wrapError,
|
||||
DarkRP.errorNoHalt
|
||||
}
|
||||
|
||||
-- Print errors from the server in the console and show a message in chat
|
||||
if CLIENT then
|
||||
local function showError(count, errs)
|
||||
local one = count == 1
|
||||
chat.AddText(Color(255, 0, 0), string.format("There %s %i Lua problem%s!", one and "is" or "are", count, one and "" or 's'))
|
||||
chat.AddText(color_white, "\tPlease check your console for more information!")
|
||||
chat.AddText(color_white, "\tNote: This error likely breaks your server. Make sure to solve the error!")
|
||||
|
||||
for i = 1, count do
|
||||
MsgC(Color(137, 222, 255), errs[i] .. "\n")
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive("DarkRP_simplerrError", function()
|
||||
local count = net.ReadUInt(16)
|
||||
local errs = {}
|
||||
|
||||
for i = 1, count do
|
||||
table.insert(errs, net.ReadString())
|
||||
end
|
||||
|
||||
showError(count, errs)
|
||||
end)
|
||||
hook.Add("onSimplerrError", "DarkRP_Simplerr", function(err) showError(1, {err}) end)
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Serverside part
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
util.AddNetworkString("DarkRP_simplerrError")
|
||||
|
||||
-- Send all errors to the client
|
||||
local function sendErrors(plys, errs)
|
||||
local count = #errs
|
||||
local one = count == 1
|
||||
|
||||
DarkRP.notify(plys, 1, 120, string.format("There %s %i Lua problem%s!\nPlease check your console for more information!", one and "is" or "are", count, one and "" or 's'))
|
||||
net.Start("DarkRP_simplerrError")
|
||||
net.WriteUInt(#errs, 16)
|
||||
fn.ForEach(fn.Flip(net.WriteString), errs)
|
||||
net.Send(plys)
|
||||
end
|
||||
|
||||
-- Annoy all admins when an error occurs
|
||||
local function annoyAdmins(err)
|
||||
local admins = fn.Filter(plyMeta.IsAdmin, player.GetAll())
|
||||
sendErrors(admins, {err})
|
||||
end
|
||||
hook.Add("onSimplerrError", "DarkRP_Simplerr", annoyAdmins)
|
||||
|
||||
-- Annoy joining admin with errors
|
||||
local function annoyAdmin(ply)
|
||||
if not IsValid(ply) or not ply:IsAdmin() then return end
|
||||
local errs = table.Copy(simplerr.getLog())
|
||||
if table.IsEmpty(errs) then return end
|
||||
|
||||
fn.Map(fp{fn.GetValue, "err"}, errs)
|
||||
sendErrors(ply, errs)
|
||||
end
|
||||
hook.Add("PlayerInitialSpawn", "DarkRP_Simplerr", function(ply) timer.Simple(1, fp{annoyAdmin, ply}) end)
|
||||
432
gamemodes/darkrp/gamemode/modules/base/sh_util.lua
Normal file
432
gamemodes/darkrp/gamemode/modules/base/sh_util.lua
Normal file
@@ -0,0 +1,432 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Utility functions
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local vector = FindMetaTable("Vector")
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Decides whether the vector could be seen by the player if they were to look at it
|
||||
---------------------------------------------------------------------------]]
|
||||
function vector:isInSight(filter, ply)
|
||||
ply = ply or LocalPlayer()
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = self
|
||||
trace.filter = filter
|
||||
trace.mask = -1
|
||||
local TheTrace = util.TraceLine(trace)
|
||||
|
||||
return not TheTrace.Hit, TheTrace.HitPos
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Turn a money amount into a pretty string
|
||||
---------------------------------------------------------------------------]]
|
||||
local function attachCurrency(str)
|
||||
local config = GAMEMODE.Config
|
||||
return config.currencyLeft and config.currency .. str or str .. config.currency
|
||||
end
|
||||
|
||||
function DarkRP.formatMoney(n)
|
||||
if not n then return attachCurrency("0") end
|
||||
|
||||
if n >= 1e14 then return attachCurrency(tostring(n)) end
|
||||
if n <= -1e14 then return "-" .. attachCurrency(tostring(math.abs(n))) end
|
||||
|
||||
local config = GAMEMODE.Config
|
||||
|
||||
local negative = n < 0
|
||||
|
||||
n = tostring(math.abs(n))
|
||||
|
||||
local dp = string.find(n, ".", 1, true) or #n + 1
|
||||
|
||||
for i = dp - 4, 1, -3 do
|
||||
n = n:sub(1, i) .. config.currencyThousandSeparator .. n:sub(i + 1)
|
||||
end
|
||||
|
||||
-- Make sure the amount is padded with zeroes
|
||||
if n[#n - 1] == "." then
|
||||
n = n .. "0"
|
||||
end
|
||||
|
||||
return (negative and "-" or "") .. attachCurrency(n)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find a player based on given information
|
||||
|
||||
Note that there is a searching priority:
|
||||
* UserID
|
||||
* SteamID64
|
||||
* SteamID
|
||||
* Nick
|
||||
* SteamName
|
||||
|
||||
Note also that there are _separate_ loops. This is to make sure the function
|
||||
gives the same result, regardless of the order in which players are iterated
|
||||
over.
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.findPlayer(info)
|
||||
if not info or info == "" then return nil end
|
||||
local pls = player.GetAll()
|
||||
|
||||
local count = #pls
|
||||
local numberInfo = tonumber(info)
|
||||
|
||||
-- First check if the input matches a player by UserID or SteamID64. This is
|
||||
-- only necessary if the input can be parsed as a number.
|
||||
if numberInfo then
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if numberInfo == v:UserID() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if info == v:SteamID64() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local lowerInfo = string.lower(tostring(info))
|
||||
if string.StartsWith(lowerInfo, "steam_") then
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if info == v:SteamID() then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if string.find(string.lower(v:Nick()), lowerInfo, 1, true) ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
for k = 1, count do
|
||||
local v = pls[k]
|
||||
|
||||
if string.find(string.lower(v:SteamName()), lowerInfo, 1, true) ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find multiple players based on a string criterium
|
||||
Taken from FAdmin]]
|
||||
---------------------------------------------------------------------------*/
|
||||
function DarkRP.findPlayers(info)
|
||||
if not info then return nil end
|
||||
local pls = player.GetAll()
|
||||
local found = {}
|
||||
local players
|
||||
|
||||
if string.lower(info) == "*" or string.lower(info) == "<all>" then return pls end
|
||||
|
||||
local InfoPlayers = {}
|
||||
for A in string.gmatch(info .. ";", "([a-zA-Z0-9:_.]*)[;(,%s)%c]") do
|
||||
if A ~= "" then
|
||||
table.insert(InfoPlayers, A)
|
||||
end
|
||||
end
|
||||
|
||||
for _, PlayerInfo in ipairs(InfoPlayers) do
|
||||
-- Playerinfo is always to be treated as UserID when it's a number
|
||||
-- otherwise people with numbers in their names could get confused with UserID's of other players
|
||||
if tonumber(PlayerInfo) then
|
||||
local foundPlayer = Player(PlayerInfo)
|
||||
if IsValid(foundPlayer) and not found[foundPlayer] then
|
||||
found[foundPlayer] = true
|
||||
players = players or {}
|
||||
table.insert(players, foundPlayer)
|
||||
end
|
||||
continue
|
||||
end
|
||||
|
||||
local stringPlayerInfo = string.lower(PlayerInfo)
|
||||
for _, v in ipairs(pls) do
|
||||
-- Prevent duplicates
|
||||
if found[v] then continue end
|
||||
local steamId = v:SteamID()
|
||||
|
||||
-- Find by Steam ID
|
||||
if (PlayerInfo == steamId or steamId == "UNKNOWN") or
|
||||
-- Find by Partial Nick
|
||||
string.find(string.lower(v:Nick()), stringPlayerInfo, 1, true) ~= nil or
|
||||
-- Find by steam name
|
||||
(v.SteamName and string.find(string.lower(v:SteamName()), stringPlayerInfo, 1, true) ~= nil) then
|
||||
found[v] = true
|
||||
players = players or {}
|
||||
table.insert(players, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
||||
end
|
||||
|
||||
function meta:getEyeSightHitEntity(searchDistance, hitDistance, filter)
|
||||
searchDistance = searchDistance or 100
|
||||
hitDistance = (hitDistance or 15) * (hitDistance or 15)
|
||||
|
||||
filter = filter or function(p) return p:IsPlayer() and p ~= self end
|
||||
|
||||
self:LagCompensation(true)
|
||||
|
||||
local shootPos = self:GetShootPos()
|
||||
local entities = ents.FindInSphere(shootPos, searchDistance)
|
||||
local aimvec = self:GetAimVector()
|
||||
|
||||
local smallestDistance = math.huge
|
||||
local foundEnt
|
||||
|
||||
for _, ent in ipairs(entities) do
|
||||
if not IsValid(ent) or filter(ent) == false then continue end
|
||||
|
||||
local center = ent:GetPos()
|
||||
|
||||
-- project the center vector on the aim vector
|
||||
local projected = shootPos + (center - shootPos):Dot(aimvec) * aimvec
|
||||
|
||||
if aimvec:Dot((projected - shootPos):GetNormalized()) < 0 then continue end
|
||||
|
||||
-- the point on the model that has the smallest distance to your line of sight
|
||||
local nearestPoint = ent:NearestPoint(projected)
|
||||
local distance = nearestPoint:DistToSqr(projected)
|
||||
|
||||
if distance < smallestDistance then
|
||||
local trace = {
|
||||
start = self:GetShootPos(),
|
||||
endpos = nearestPoint,
|
||||
filter = {self, ent}
|
||||
}
|
||||
local traceLine = util.TraceLine(trace)
|
||||
if traceLine.Hit then continue end
|
||||
|
||||
smallestDistance = distance
|
||||
foundEnt = ent
|
||||
end
|
||||
end
|
||||
|
||||
self:LagCompensation(false)
|
||||
|
||||
if smallestDistance < hitDistance then
|
||||
return foundEnt, math.sqrt(smallestDistance)
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Print the currently available vehicles
|
||||
---------------------------------------------------------------------------]]
|
||||
local function GetAvailableVehicles(ply)
|
||||
if SERVER and IsValid(ply) and not ply:IsAdmin() then return end
|
||||
local print = SERVER and ServerLog or Msg
|
||||
|
||||
print(DarkRP.getPhrase("rp_getvehicles") .. "\n")
|
||||
for k in pairs(DarkRP.getAvailableVehicles()) do
|
||||
print("\"" .. k .. "\"" .. "\n")
|
||||
end
|
||||
end
|
||||
if SERVER then
|
||||
concommand.Add("rp_getvehicles_sv", GetAvailableVehicles)
|
||||
else
|
||||
concommand.Add("rp_getvehicles", GetAvailableVehicles)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Whether a player has a DarkRP privilege
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:hasDarkRPPrivilege(priv)
|
||||
if FAdmin then
|
||||
return FAdmin.Access.PlayerHasPrivilege(self, priv)
|
||||
end
|
||||
return self:IsAdmin()
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Convenience function to return the players sorted by name
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.nickSortedPlayers()
|
||||
local plys = player.GetAll()
|
||||
table.sort(plys, function(a,b) return a:Nick() < b:Nick() end)
|
||||
return plys
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Convert a string to a table of arguments
|
||||
---------------------------------------------------------------------------]]
|
||||
local bitlshift, stringgmatch, stringsub, tableinsert = bit.lshift, string.gmatch, string.sub, table.insert
|
||||
function DarkRP.explodeArg(arg)
|
||||
local args = {}
|
||||
|
||||
local from, to, diff = 1, 0, 0
|
||||
local inQuotes, wasQuotes = false, false
|
||||
|
||||
for c in stringgmatch(arg, '.') do
|
||||
to = to + 1
|
||||
|
||||
if c == '"' then
|
||||
inQuotes = not inQuotes
|
||||
wasQuotes = true
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
if c == ' ' and not inQuotes then
|
||||
diff = wasQuotes and 1 or 0
|
||||
wasQuotes = false
|
||||
tableinsert(args, stringsub(arg, from + diff, to - 1 - diff))
|
||||
from = to + 1
|
||||
end
|
||||
end
|
||||
diff = wasQuotes and 1 or 0
|
||||
|
||||
if from ~= to + 1 then tableinsert(args, stringsub(arg, from + diff, to + 1 - bitlshift(diff, 1))) end
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Initialize Physics, throw an error on failure
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.ValidatedPhysicsInit(ent, solidType, hint)
|
||||
solidType = solidType or SOLID_VPHYSICS
|
||||
|
||||
if ent:PhysicsInit(solidType) then return true end
|
||||
|
||||
local class = ent:GetClass()
|
||||
|
||||
if solidType == SOLID_BSP then
|
||||
DarkRP.errorNoHalt(string.format("%s has no physics and will be motionless", class), 2, {
|
||||
"Is this a brush model? SOLID_BSP physics cannot initialize on entities that don't have brush models",
|
||||
"The physics limit may have been hit",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if solidType == SOLID_VPHYSICS then
|
||||
local mdl = ent:GetModel()
|
||||
|
||||
if not mdl or mdl == "" then
|
||||
DarkRP.errorNoHalt(string.format("Cannot init physics on entity \"%s\" because it has no model", class), 2, {hint})
|
||||
return false
|
||||
end
|
||||
|
||||
mdl = string.lower(mdl)
|
||||
|
||||
if util.IsValidProp(mdl) then
|
||||
-- Has physics, we must have hit the limit
|
||||
DarkRP.errorNoHalt(string.format("physics limit hit - %s will be motionless", class), 2, {hint})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
if not file.Exists(mdl, "GAME") then
|
||||
DarkRP.errorNoHalt(string.format("%s has missing model \"%s\" and will be invisible and motionless", class, mdl), 2, {
|
||||
"Is the model path correct?",
|
||||
"Is the model from an addon that is not installed?",
|
||||
"Is the model from a game that isn't (properly) mounted? E.g. Counter Strike: Source",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format("%s has model \"%s\" with no physics and will be motionless", class, mdl), 2, {
|
||||
"Does this model have an associated physics model (modelname.phy)?",
|
||||
"Is this model supposed to have physics? Many models, like effects and view models aren't made to have physics",
|
||||
hint
|
||||
})
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
DarkRP.errorNoHalt(string.format("Unable to initilize physics on entity \"%s\"", class, {hint}), 2)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Like tonumber, but makes sure it's an integer
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.toInt(value)
|
||||
value = tonumber(value)
|
||||
return value and math.floor(value)
|
||||
end
|
||||
|
||||
--[[-------------------------------------------------------------------------
|
||||
Check the database for integrity errors. Use in cases when stuff doesn't load
|
||||
on restart, or you get corruption errors.
|
||||
---------------------------------------------------------------------------]]
|
||||
if SERVER then util.AddNetworkString("DarkRP_databaseCheckMessage") end
|
||||
if CLIENT then net.Receive("DarkRP_databaseCheckMessage", fc{print, net.ReadString}) end
|
||||
|
||||
local function checkDatabase(ply)
|
||||
local dbFile = SERVER and "sv.db" or "cl.db"
|
||||
local display = (CLIENT or not IsValid(ply)) and print or function(msg)
|
||||
net.Start("DarkRP_databaseCheckMessage")
|
||||
net.WriteString(msg)
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
if SERVER and IsValid(ply) and not ply:IsSuperAdmin() then
|
||||
display("You must be superadmin")
|
||||
return
|
||||
end
|
||||
|
||||
if MySQLite and MySQLite.isMySQL() then
|
||||
display(string.format([[WARNING: DarkRP is using MySQL. This only
|
||||
checks the local SQLite database stored in the %s file in the
|
||||
garrysmod/ folder. The check will continue.]], dbFile))
|
||||
end
|
||||
|
||||
local check = sql.QueryValue("PRAGMA INTEGRITY_CHECK")
|
||||
if check == false then
|
||||
display([[The query to check the database failed. Shit's surely
|
||||
fucked, but the cause is unknown.]])
|
||||
return
|
||||
end
|
||||
|
||||
if check == "ok" then
|
||||
display(string.format("Your %s database file is good.", dbFile))
|
||||
return
|
||||
end
|
||||
|
||||
display(string.format([[There are errors in your %s database file. It's corrupt!
|
||||
|
||||
This can cause the following problems:
|
||||
- Data not loading, think of blocked models, doors, players' money and RP names
|
||||
- Settings resetting to their default values
|
||||
- Lua errors on startup
|
||||
|
||||
The cause of the problem is that the %s file in your garrysmod/ folder on
|
||||
%s is corrupt. How this came to be is unknown, but here's what you can do to solve it:
|
||||
|
||||
- Delete %s, and run a file integrity check. Warning: You will lose ALL data stored in it!
|
||||
- Take the file and try to repair it. This is sadly something that requires some technical knowledge,
|
||||
and may not always succeed.
|
||||
|
||||
The specific error, by the way, is as follows:
|
||||
%s
|
||||
]], dbFile, dbFile, SERVER and "the server" or "your own computer", dbFile, check))
|
||||
|
||||
end
|
||||
concommand.Add("darkrp_check_db_" .. (SERVER and "sv" or "cl"), checkDatabase)
|
||||
627
gamemodes/darkrp/gamemode/modules/base/sv_data.lua
Normal file
627
gamemodes/darkrp/gamemode/modules/base/sv_data.lua
Normal file
@@ -0,0 +1,627 @@
|
||||
--[[---------------------------------------------------------------------------
|
||||
Functions and variables
|
||||
---------------------------------------------------------------------------]]
|
||||
local setUpNonOwnableDoors,
|
||||
setUpTeamOwnableDoors,
|
||||
setUpGroupDoors,
|
||||
migrateDB
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Database initialize
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.initDatabase()
|
||||
MySQLite.begin()
|
||||
-- Gotta love the difference between SQLite and MySQL
|
||||
local is_mysql = MySQLite.isMySQL()
|
||||
local AUTOINCREMENT = is_mysql and "AUTO_INCREMENT" or "AUTOINCREMENT"
|
||||
-- in MySQL, the engine is set to InnoDB. InnoDB has been the default
|
||||
-- for a while, but people might be running old versions of MySQL.
|
||||
-- SQLite has no database engine, so it is not explicitly set.
|
||||
local ENGINE_INNODB = is_mysql and "ENGINE=InnoDB" or ""
|
||||
|
||||
-- Table that holds all position data (jail, spawns etc.)
|
||||
-- Queue these queries because other queries depend on the existence of the darkrp_position table
|
||||
-- Race conditions could occur if the queries are executed simultaneously
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_position(
|
||||
id INTEGER NOT NULL PRIMARY KEY ]] .. AUTOINCREMENT .. [[,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
type CHAR(1) NOT NULL,
|
||||
x INTEGER NOT NULL,
|
||||
y INTEGER NOT NULL,
|
||||
z INTEGER NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- team spawns require extra data
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_jobspawn(
|
||||
id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
teamcmd VARCHAR(255) NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- This table is kept for compatibility with older addons and websites
|
||||
-- See https://github.com/FPtje/DarkRP/issues/819
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS playerinformation(
|
||||
uid BIGINT NOT NULL,
|
||||
steamID VARCHAR(50) NOT NULL PRIMARY KEY
|
||||
) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
-- Player information
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_player(
|
||||
uid BIGINT NOT NULL PRIMARY KEY,
|
||||
rpname VARCHAR(45),
|
||||
salary INTEGER NOT NULL DEFAULT 45,
|
||||
wallet BIGINT NOT NULL
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Door data
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_door(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
title VARCHAR(25),
|
||||
isLocked BOOLEAN,
|
||||
isDisabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY(idx, map)
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Some doors are owned by certain teams
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_doorjobs(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
job VARCHAR(255) NOT NULL,
|
||||
|
||||
PRIMARY KEY(idx, map, job)
|
||||
) ]] .. ENGINE_INNODB .. [[;
|
||||
]])
|
||||
|
||||
-- Door groups
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_doorgroups(
|
||||
idx INTEGER NOT NULL,
|
||||
map VARCHAR(45) NOT NULL,
|
||||
doorgroup VARCHAR(100) NOT NULL,
|
||||
|
||||
PRIMARY KEY(idx, map)
|
||||
) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS darkrp_dbversion(version INTEGER NOT NULL PRIMARY KEY) ]] .. ENGINE_INNODB .. [[
|
||||
]])
|
||||
|
||||
-- Load the last DBVersion into DarkRP.DBVersion, to allow checks to see whether migration is needed.
|
||||
MySQLite.queueQuery([[
|
||||
SELECT MAX(version) AS version FROM darkrp_dbversion
|
||||
]], function(data)
|
||||
-- The database is created with the schema of the latest version. On
|
||||
-- initialization the version is not set yet. Set it to the latest
|
||||
-- version.
|
||||
if not data or not data[1] or not tonumber(data[1].version) then
|
||||
DarkRP.DBVersion = 20211228
|
||||
MySQLite.query([[
|
||||
REPLACE INTO darkrp_dbversion VALUES(20211228)
|
||||
]])
|
||||
else
|
||||
DarkRP.DBVersion = tonumber(data[1].version)
|
||||
end
|
||||
end)
|
||||
|
||||
MySQLite.commit(fp{migrateDB, -- Migrate the database
|
||||
function() -- Initialize the data after all the tables have been created
|
||||
setUpNonOwnableDoors()
|
||||
setUpTeamOwnableDoors()
|
||||
setUpGroupDoors()
|
||||
|
||||
if MySQLite.isMySQL() then -- In a listen server, the connection with the external database is often made AFTER the listen server host has joined,
|
||||
--so he walks around with the settings from the SQLite database
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
DarkRP.offlinePlayerData(v:SteamID(), function(data)
|
||||
local Data = data and data[1]
|
||||
if not IsValid(v) or not Data then return end
|
||||
|
||||
v:setDarkRPVar("rpname", Data.rpname)
|
||||
v:setSelfDarkRPVar("salary", Data.salary)
|
||||
v:setDarkRPVar("money", Data.wallet)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Call("DarkRPDBInitialized")
|
||||
end})
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Database migration
|
||||
backwards compatibility with older versions of DarkRP
|
||||
---------------------------------------------------------------------------]]
|
||||
function migrateDB(callback)
|
||||
-- Simple function that checks the database version, migrates if
|
||||
-- necessary, and recurses to perform the next migration, until the last
|
||||
-- migration has been performed.
|
||||
-- Calls callback when the migration is finished or not necessary.
|
||||
local function migrate(version)
|
||||
if version < 20160610 then
|
||||
MySQLite.begin()
|
||||
if MySQLite.isMySQL() then
|
||||
-- if only SQLite were this easy
|
||||
MySQLite.queueQuery([[DROP INDEX rpname ON darkrp_player]])
|
||||
else
|
||||
-- darkrp_player used to have a UNIQUE rpname field.
|
||||
-- This sucks, get rid of it
|
||||
MySQLite.queueQuery([[PRAGMA foreign_keys=OFF]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE IF NOT EXISTS new_darkrp_player(
|
||||
uid BIGINT NOT NULL PRIMARY KEY,
|
||||
rpname VARCHAR(45),
|
||||
salary INTEGER NOT NULL DEFAULT 45,
|
||||
wallet INTEGER NOT NULL
|
||||
);
|
||||
]])
|
||||
|
||||
MySQLite.queueQuery([[INSERT INTO new_darkrp_player SELECT * FROM darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[DROP TABLE darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[ALTER TABLE new_darkrp_player RENAME TO darkrp_player]])
|
||||
|
||||
MySQLite.queueQuery([[PRAGMA foreign_keys=ON]])
|
||||
end
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20160610)]])
|
||||
MySQLite.commit(fp{migrate, 20160610})
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20181013 then
|
||||
-- migrate from darkrp_jobown to darkrp_doorjobs
|
||||
MySQLite.tableExists("darkrp_jobown", function(exists)
|
||||
if not exists then migrate(20181013) return end
|
||||
|
||||
MySQLite.begin()
|
||||
-- Create a temporary table that links job IDs to job commands
|
||||
MySQLite.queueQuery("CREATE TABLE IF NOT EXISTS TempJobCommands(id INT NOT NULL PRIMARY KEY, cmd VARCHAR(255) NOT NULL);")
|
||||
if MySQLite.isMySQL() then
|
||||
local jobCommands = {}
|
||||
for k, v in pairs(RPExtraTeams) do
|
||||
table.insert(jobCommands, "(" .. k .. "," .. MySQLite.SQLStr(v.command) .. ")")
|
||||
end
|
||||
|
||||
-- This WOULD work with SQLite if the implementation in GMod wasn't out of date.
|
||||
MySQLite.queueQuery("INSERT IGNORE INTO TempJobCommands VALUES " .. table.concat(jobCommands, ",") .. ";")
|
||||
else
|
||||
for k, v in pairs(RPExtraTeams) do
|
||||
MySQLite.queueQuery("INSERT INTO TempJobCommands VALUES(" .. k .. ", " .. MySQLite.SQLStr(v.command) .. ");")
|
||||
end
|
||||
end
|
||||
|
||||
MySQLite.queueQuery("REPLACE INTO darkrp_doorjobs SELECT darkrp_jobown.idx AS idx, darkrp_jobown.map AS map, TempJobCommands.cmd AS job FROM darkrp_jobown JOIN TempJobCommands ON darkrp_jobown.job = TempJobCommands.id;")
|
||||
|
||||
-- Clean up the transition table and the old table
|
||||
MySQLite.queueQuery("DROP TABLE TempJobCommands;")
|
||||
MySQLite.queueQuery("DROP TABLE darkrp_jobown;")
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181013)]])
|
||||
MySQLite.commit(fp{migrate, 20181013})
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20181014 then
|
||||
MySQLite.query([[SELECT * FROM darkrp_jobspawn]], function(oldData)
|
||||
oldData = oldData or {}
|
||||
MySQLite.begin()
|
||||
|
||||
MySQLite.queueQuery([[DROP TABLE darkrp_jobspawn]])
|
||||
|
||||
MySQLite.queueQuery([[
|
||||
CREATE TABLE darkrp_jobspawn(
|
||||
id INTEGER NOT NULL PRIMARY KEY REFERENCES darkrp_position(id)
|
||||
ON UPDATE CASCADE
|
||||
ON DELETE CASCADE,
|
||||
|
||||
teamcmd VARCHAR(255) NOT NULL
|
||||
);
|
||||
]])
|
||||
|
||||
for i, row in pairs(oldData) do
|
||||
local teamcmd = (RPExtraTeams[tonumber(row.team)] or {}).command
|
||||
if not teamcmd then continue end
|
||||
|
||||
MySQLite.queueQuery(string.format([[INSERT INTO darkrp_jobspawn(id, teamcmd) VALUES(%s, %s)]], row.id, MySQLite.SQLStr(teamcmd)))
|
||||
end
|
||||
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20181014)]])
|
||||
|
||||
MySQLite.commit(fp{migrate, 20181014})
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20190914 then
|
||||
MySQLite.begin()
|
||||
-- Migration not necessary for SQLite, since BIGINT and
|
||||
-- INTEGER are considered the same in SQLite
|
||||
-- https://www.sqlite.org/datatype3.html
|
||||
if MySQLite.isMySQL() then
|
||||
MySQLite.queueQuery([[DROP TRIGGER IF EXISTS JobPositionFKDelete]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_player MODIFY wallet BIGINT;]])
|
||||
end
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20190914)]])
|
||||
MySQLite.commit(fp{migrate, 20190914})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if version < 20211228 then
|
||||
MySQLite.begin()
|
||||
-- Migrate all tables to InnoDB if they weren't already.
|
||||
-- See https://github.com/FPtje/DarkRP/issues/3157
|
||||
if MySQLite.isMySQL() then
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_dbversion ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_door ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_doorgroups ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_doorjobs ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_jobspawn ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_player ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE darkrp_position ENGINE = InnoDB;]])
|
||||
MySQLite.queueQuery([[ALTER TABLE playerinformation ENGINE = InnoDB;]])
|
||||
end
|
||||
|
||||
MySQLite.queueQuery([[REPLACE INTO darkrp_dbversion VALUES(20211228)]])
|
||||
MySQLite.commit(fp{migrate, 20211228})
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- All migrations finished
|
||||
callback()
|
||||
end
|
||||
migrate(DarkRP.DBVersion)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Players
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.storeRPName(ply, name)
|
||||
if not name or string.len(name) < 2 then return end
|
||||
hook.Call("onPlayerChangedName", nil, ply, ply:getDarkRPVar("rpname"), name)
|
||||
ply:setDarkRPVar("rpname", name)
|
||||
|
||||
MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:SteamID64() .. ";")
|
||||
MySQLite.query([[UPDATE darkrp_player SET rpname = ]] .. MySQLite.SQLStr(name) .. [[ WHERE UID = ]] .. ply:UniqueID() .. ";")
|
||||
end
|
||||
|
||||
function DarkRP.retrieveRPNames(name, callback)
|
||||
MySQLite.query("SELECT COUNT(*) AS count FROM darkrp_player WHERE rpname = " .. MySQLite.SQLStr(name) .. ";", function(r)
|
||||
callback(tonumber(r[1].count) > 0)
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.offlinePlayerData(steamid, callback, failed)
|
||||
local sid64 = util.SteamIDTo64(steamid)
|
||||
local uniqueid = util.CRC("gm_" .. string.upper(steamid) .. "_gm")
|
||||
|
||||
MySQLite.query(string.format([[REPLACE INTO playerinformation VALUES(%s, %s);]], MySQLite.SQLStr(sid64), MySQLite.SQLStr(steamid)), nil, failed)
|
||||
|
||||
local query = [[
|
||||
SELECT rpname, wallet, salary, "SID64" AS kind
|
||||
FROM darkrp_player
|
||||
where uid = %s
|
||||
|
||||
UNION
|
||||
|
||||
SELECT rpname, wallet, salary, "UniqueID" AS kind
|
||||
FROM darkrp_player
|
||||
where uid = %s
|
||||
;
|
||||
]]
|
||||
|
||||
MySQLite.query(
|
||||
query:format(sid64, uniqueid),
|
||||
function(data, ...)
|
||||
-- The database has no record of the player data in SteamID64 form
|
||||
-- Otherwise the first row would have kind SID64
|
||||
if data and data[1] and data[1].kind == "UniqueID" then
|
||||
-- The rpname must be unique
|
||||
-- adding a new row with uid = SteamID64, but the same rpname will remove the uid=UniqueID row
|
||||
|
||||
local replquery = [[
|
||||
REPLACE INTO darkrp_player(uid, rpname, wallet, salary)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
]]
|
||||
|
||||
MySQLite.begin()
|
||||
MySQLite.queueQuery(
|
||||
replquery:format(
|
||||
sid64,
|
||||
data[1].rpname == "NULL" and "NULL" or MySQLite.SQLStr(data[1].rpname),
|
||||
data[1].wallet,
|
||||
data[1].salary
|
||||
),
|
||||
nil,
|
||||
failed
|
||||
)
|
||||
MySQLite.commit()
|
||||
end
|
||||
|
||||
return callback and callback(data, ...)
|
||||
end
|
||||
, failed
|
||||
)
|
||||
end
|
||||
|
||||
function DarkRP.retrievePlayerData(ply, callback, failed, attempts, err)
|
||||
attempts = attempts or 0
|
||||
|
||||
if attempts > 3 then return failed(err) end
|
||||
|
||||
DarkRP.offlinePlayerData(ply:SteamID(), callback, function(sqlErr)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
DarkRP.retrievePlayerData(ply, callback, failed, attempts + 1, sqlErr)
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.createPlayerData(ply, name, wallet, salary, onError)
|
||||
MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] ..
|
||||
ply:SteamID64() .. [[, ]] ..
|
||||
MySQLite.SQLStr(utf8.force(name)) .. [[, ]] ..
|
||||
salary .. [[, ]] ..
|
||||
wallet .. ");", nil, onError)
|
||||
|
||||
-- Backwards compatibility
|
||||
MySQLite.query([[REPLACE INTO darkrp_player VALUES(]] ..
|
||||
ply:UniqueID() .. [[, ]] ..
|
||||
MySQLite.SQLStr(utf8.force(name)) .. [[, ]] ..
|
||||
salary .. [[, ]] ..
|
||||
wallet .. ");", nil, onError)
|
||||
end
|
||||
|
||||
function DarkRP.storeMoney(ply, amount)
|
||||
if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then
|
||||
DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for Player " .. ply:Nick() .. " (" .. ply:SteamID() .. ")", 1, {
|
||||
"This money amount will not be stored in the database, but it may be set in the game.",
|
||||
"The database simply stores the last valid, non-negative amount of money.",
|
||||
"Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.",
|
||||
"That will tell you which addon is causing this.",
|
||||
"IMPORTANT: This is NOT a DarkRP bug!",
|
||||
"Note: The player can simply rejoin to fix their negative money, until whatever causes this happens again."
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Also keep deprecated UniqueID data at least somewhat up to date
|
||||
MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. ply:UniqueID() .. [[ OR uid = ]] .. ply:SteamID64())
|
||||
end
|
||||
|
||||
function DarkRP.storeOfflineMoney(sid64, amount)
|
||||
if isnumber(sid64) or isstring(sid64) and string.len(sid64) < 17 then -- smaller than 76561197960265728 is not a SteamID64
|
||||
DarkRP.errorNoHalt([[Some addon is giving DarkRP.storeOfflineMoney a UniqueID as its first argument, but this function now expects a SteamID64]], 1, {
|
||||
"The function used to take UniqueIDs, but it does not anymore.",
|
||||
"If you are a server owner, please look closely to the files mentioned in this error",
|
||||
"After all, these files will tell you WHICH addon is doing it",
|
||||
"This is NOT a DarkRP bug!",
|
||||
"Your server will continue working normally",
|
||||
"But whichever addon just tried to store an offline player's money",
|
||||
"Will NOT take effect!"
|
||||
})
|
||||
end
|
||||
|
||||
if not isnumber(amount) or amount < 0 or amount >= 1 / 0 then
|
||||
DarkRP.errorNoHalt("Some addon attempted to store a invalid money amount " .. tostring(amount) .. " for an offline player with steamID64 " .. sid64, 1, {
|
||||
"This money amount will not be stored in the database.",
|
||||
"Please try to find the very first time this error happened for this player. Then look at the files mentioned in this error.",
|
||||
"That will tell you which addon is causing this.",
|
||||
"IMPORTANT: This is NOT a DarkRP bug!"
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
-- Also store on deprecated UniqueID
|
||||
local uniqueid = util.CRC("gm_" .. string.upper(util.SteamIDFrom64(sid64)) .. "_gm")
|
||||
MySQLite.query([[UPDATE darkrp_player SET wallet = ]] .. amount .. [[ WHERE uid = ]] .. uniqueid .. [[ OR uid = ]] .. sid64)
|
||||
end
|
||||
|
||||
local function resetAllMoney(ply, cmd, args)
|
||||
if ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then return end
|
||||
MySQLite.query("UPDATE darkrp_player SET wallet = " .. GAMEMODE.Config.startingmoney .. " ;")
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:setDarkRPVar("money", GAMEMODE.Config.startingmoney)
|
||||
end
|
||||
if ply:IsPlayer() then
|
||||
DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", ply:Nick()))
|
||||
else
|
||||
DarkRP.notifyAll(0, 4, DarkRP.getPhrase("reset_money", "Console"))
|
||||
end
|
||||
end
|
||||
concommand.Add("rp_resetallmoney", resetAllMoney)
|
||||
|
||||
function DarkRP.storeSalary(ply, amount)
|
||||
ply:setSelfDarkRPVar("salary", math.floor(amount))
|
||||
|
||||
return amount
|
||||
end
|
||||
|
||||
function DarkRP.retrieveSalary(ply, callback)
|
||||
local val =
|
||||
ply:getJobTable() and ply:getJobTable().salary or
|
||||
RPExtraTeams[GAMEMODE.DefaultTeam].salary or
|
||||
(GM or GAMEMODE).Config.normalsalary
|
||||
|
||||
if callback then callback(val) end
|
||||
|
||||
return val
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Players
|
||||
---------------------------------------------------------------------------]]
|
||||
local meta = FindMetaTable("Player")
|
||||
function meta:restorePlayerData()
|
||||
self.DarkRPUnInitialized = true
|
||||
|
||||
local function onError(err)
|
||||
if not IsValid(self) then return end
|
||||
self.DarkRPUnInitialized = true -- no information should be saved from here, or the playerdata might be reset
|
||||
|
||||
self:setDarkRPVar("money", GAMEMODE.Config.startingmoney)
|
||||
self:setSelfDarkRPVar("salary", DarkRP.retrieveSalary(self))
|
||||
local name = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30)
|
||||
self:setDarkRPVar("rpname", name)
|
||||
|
||||
self.DarkRPDataRetrievalFailed = true -- marker on the player that says shit is fucked
|
||||
DarkRP.error("Failed to retrieve player information from the database. ", nil, {"This means your database or the connection to your database is fucked.", "This is the error given by the database:\n\t\t" .. tostring(err)})
|
||||
end
|
||||
|
||||
DarkRP.retrievePlayerData(self, function(data)
|
||||
if not IsValid(self) then return end
|
||||
|
||||
self.DarkRPUnInitialized = nil
|
||||
|
||||
local info = data and data[1] or {}
|
||||
if not info.rpname or info.rpname == "NULL" then info.rpname = string.sub(string.gsub(self:SteamName(), "\\\"", "\""), 1, 30) end
|
||||
|
||||
info.wallet = info.wallet or GAMEMODE.Config.startingmoney
|
||||
info.salary = DarkRP.retrieveSalary(self)
|
||||
|
||||
self:setDarkRPVar("money", tonumber(info.wallet))
|
||||
self:setSelfDarkRPVar("salary", tonumber(info.salary))
|
||||
|
||||
self:setDarkRPVar("rpname", info.rpname)
|
||||
|
||||
if not data then
|
||||
info = hook.Call("onPlayerFirstJoined", nil, self, info) or info
|
||||
DarkRP.createPlayerData(self, info.rpname, info.wallet, info.salary, onError)
|
||||
end
|
||||
end, onError)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------
|
||||
Doors
|
||||
---------------------------------------------------------]]
|
||||
function DarkRP.storeDoorData(ent)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
local nonOwnable = ent:getKeysNonOwnable()
|
||||
local title = ent:getKeysTitle()
|
||||
|
||||
MySQLite.query([[REPLACE INTO darkrp_door VALUES(]] .. ent:doorIndex() .. [[, ]] .. MySQLite.SQLStr(map) .. [[, ]] .. (title and MySQLite.SQLStr(title) or "NULL") .. [[, ]] .. "NULL" .. [[, ]] .. (nonOwnable and 1 or 0) .. [[);]])
|
||||
end
|
||||
|
||||
function setUpNonOwnableDoors()
|
||||
MySQLite.query("SELECT idx, title, isLocked, isDisabled FROM darkrp_door WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r)
|
||||
if not r then return end
|
||||
|
||||
for _, row in pairs(r) do
|
||||
local e = DarkRP.doorIndexToEnt(tonumber(row.idx))
|
||||
|
||||
if not IsValid(e) then continue end
|
||||
if e:isKeysOwnable() then
|
||||
if tobool(row.isDisabled) then
|
||||
e:setKeysNonOwnable(tobool(row.isDisabled))
|
||||
end
|
||||
if row.isLocked and row.isLocked ~= "NULL" then
|
||||
e:Fire((tobool(row.isLocked) and "" or "un") .. "lock", "", 0)
|
||||
end
|
||||
e:setKeysTitle(row.title ~= "NULL" and row.title or nil)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local keyValueActions = {
|
||||
["DarkRPNonOwnable"] = function(ent, val) ent:setKeysNonOwnable(tobool(val)) end,
|
||||
["DarkRPTitle"] = function(ent, val) ent:setKeysTitle(val) end,
|
||||
["DarkRPDoorGroup"] = function(ent, val) if RPExtraTeamDoors[val] then ent:setDoorGroup(val) end end,
|
||||
["DarkRPCanLockpick"] = function(ent, val) ent.DarkRPCanLockpick = tobool(val) end
|
||||
}
|
||||
|
||||
local function onKeyValue(ent, key, value)
|
||||
if not ent:isDoor() then return end
|
||||
|
||||
if keyValueActions[key] then
|
||||
keyValueActions[key](ent, value)
|
||||
end
|
||||
end
|
||||
hook.Add("EntityKeyValue", "darkrp_doors", onKeyValue)
|
||||
|
||||
function DarkRP.storeTeamDoorOwnability(ent)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
|
||||
MySQLite.query("DELETE FROM darkrp_doorjobs WHERE idx = " .. ent:doorIndex() .. " AND map = " .. MySQLite.SQLStr(map) .. ";")
|
||||
for k in pairs(ent:getKeysDoorTeams() or {}) do
|
||||
MySQLite.query("INSERT INTO darkrp_doorjobs VALUES(" .. ent:doorIndex() .. ", " .. MySQLite.SQLStr(map) .. ", " .. MySQLite.SQLStr(RPExtraTeams[k].command) .. ");")
|
||||
end
|
||||
end
|
||||
|
||||
function setUpTeamOwnableDoors()
|
||||
MySQLite.query("SELECT idx, job FROM darkrp_doorjobs WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";", function(r)
|
||||
if not r then return end
|
||||
local map = string.lower(game.GetMap())
|
||||
|
||||
for _, row in pairs(r) do
|
||||
row.idx = tonumber(row.idx)
|
||||
|
||||
local e = DarkRP.doorIndexToEnt(row.idx)
|
||||
if not IsValid(e) then continue end
|
||||
|
||||
local _, job = DarkRP.getJobByCommand(row.job)
|
||||
|
||||
if job then
|
||||
e:addKeysDoorTeam(job)
|
||||
else
|
||||
print(("can't find job %s for door %d, removing from database"):format(row.job, row.idx))
|
||||
MySQLite.query(("DELETE FROM darkrp_doorjobs WHERE idx = %d AND map = %s AND job = %s;"):format(row.idx, MySQLite.SQLStr(map), MySQLite.SQLStr(row.job)))
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function DarkRP.storeDoorGroup(ent, group)
|
||||
if not ent:CreatedByMap() then return end
|
||||
local map = MySQLite.SQLStr(string.lower(game.GetMap()))
|
||||
local index = ent:doorIndex()
|
||||
|
||||
if group == "" or not group then
|
||||
MySQLite.query("DELETE FROM darkrp_doorgroups WHERE map = " .. map .. " AND idx = " .. index .. ";")
|
||||
return
|
||||
end
|
||||
|
||||
MySQLite.query("REPLACE INTO darkrp_doorgroups VALUES(" .. index .. ", " .. map .. ", " .. MySQLite.SQLStr(group) .. ");");
|
||||
end
|
||||
|
||||
function setUpGroupDoors()
|
||||
local map = MySQLite.SQLStr(string.lower(game.GetMap()))
|
||||
MySQLite.query("SELECT idx, doorgroup FROM darkrp_doorgroups WHERE map = " .. map, function(data)
|
||||
if not data then return end
|
||||
|
||||
for _, row in pairs(data) do
|
||||
local ent = DarkRP.doorIndexToEnt(tonumber(row.idx))
|
||||
|
||||
if not IsValid(ent) or not ent:isKeysOwnable() then
|
||||
continue
|
||||
end
|
||||
|
||||
if not RPExtraTeamDoorIDs[row.doorgroup] then continue end
|
||||
ent:setDoorGroup(row.doorgroup)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
hook.Add("PostCleanupMap", "DarkRP.hooks", function()
|
||||
setUpNonOwnableDoors()
|
||||
setUpTeamOwnableDoors()
|
||||
setUpGroupDoors()
|
||||
end)
|
||||
304
gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua
Normal file
304
gamemodes/darkrp/gamemode/modules/base/sv_entityvars.lua
Normal file
@@ -0,0 +1,304 @@
|
||||
local meta = FindMetaTable("Player")
|
||||
|
||||
DarkRP.ServerDarkRPVars = DarkRP.ServerDarkRPVars or {}
|
||||
DarkRP.ServerPrivateDarkRPVars = DarkRP.ServerPrivateDarkRPVars or {}
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Pooled networking strings
|
||||
---------------------------------------------------------------------------]]
|
||||
util.AddNetworkString("DarkRP_InitializeVars")
|
||||
util.AddNetworkString("DarkRP_PlayerVar")
|
||||
util.AddNetworkString("DarkRP_PlayerVarRemoval")
|
||||
util.AddNetworkString("DarkRP_DarkRPVarDisconnect")
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Player vars
|
||||
---------------------------------------------------------------------------]]
|
||||
|
||||
local warningsShown = {}
|
||||
local function checkDarkRPVarRegistration(name)
|
||||
local DarkRPVar = DarkRP.RegisteredDarkRPVars[name]
|
||||
if DarkRPVar then return end
|
||||
|
||||
if warningsShown[name] then return end
|
||||
warningsShown[name] = true
|
||||
|
||||
DarkRP.errorNoHalt(string.format([[Warning! DarkRPVar '%s' wasn't registered!
|
||||
Please contact the author of the DarkRP Addon to fix this.
|
||||
Until this is fixed you don't need to worry about anything. Everything will keep working.
|
||||
It's just that registering DarkRPVars would make DarkRP faster.]], name), 4)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Remove a player's DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:removeDarkRPVar(var, target)
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], nil)
|
||||
target = target or player.GetAll()
|
||||
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
DarkRP.ServerDarkRPVars[self][var] = nil
|
||||
|
||||
checkDarkRPVarRegistration(var)
|
||||
|
||||
net.Start("DarkRP_PlayerVarRemoval")
|
||||
net.WriteUInt(self:UserID(), 16)
|
||||
DarkRP.writeNetDarkRPVarRemoval(var)
|
||||
net.Send(target)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Set a player's DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setDarkRPVar(var, value, target)
|
||||
target = target or player.GetAll()
|
||||
|
||||
if value == nil then return self:removeDarkRPVar(var, target) end
|
||||
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
hook.Call("DarkRPVarChanged", nil, self, var, vars and vars[var], value)
|
||||
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
DarkRP.ServerDarkRPVars[self][var] = value
|
||||
|
||||
checkDarkRPVarRegistration(var)
|
||||
|
||||
net.Start("DarkRP_PlayerVar")
|
||||
net.WriteUInt(self:UserID(), 16)
|
||||
DarkRP.writeNetDarkRPVar(var, value)
|
||||
net.Send(target)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Set a private DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setSelfDarkRPVar(var, value)
|
||||
DarkRP.ServerPrivateDarkRPVars[self] = DarkRP.ServerPrivateDarkRPVars[self] or {}
|
||||
DarkRP.ServerPrivateDarkRPVars[self][var] = true
|
||||
|
||||
self:setDarkRPVar(var, value, self)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Get a DarkRPVar
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:getDarkRPVar(var, fallback)
|
||||
local vars = DarkRP.ServerDarkRPVars[self]
|
||||
if vars == nil then return fallback end
|
||||
|
||||
local results = vars[var]
|
||||
if results == nil then return fallback end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Backwards compatibility: Set ply.DarkRPVars attribute
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setDarkRPVarsAttribute()
|
||||
DarkRP.ServerDarkRPVars[self] = DarkRP.ServerDarkRPVars[self] or {}
|
||||
-- With a reference to the table, ply.DarkRPVars should always remain
|
||||
-- up-to-date. One needs only be careful that DarkRP.ServerDarkRPVars[ply]
|
||||
-- is never replaced by a different table.
|
||||
self.DarkRPVars = DarkRP.ServerDarkRPVars[self]
|
||||
end
|
||||
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Send the DarkRPVars to a client
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:sendDarkRPVars()
|
||||
if self:EntIndex() == 0 then return end
|
||||
|
||||
local plys = player.GetAll()
|
||||
|
||||
net.Start("DarkRP_InitializeVars")
|
||||
net.WriteUInt(#plys, 8)
|
||||
for _, target in ipairs(plys) do
|
||||
net.WriteUInt(target:UserID(), 16)
|
||||
|
||||
local vars = {}
|
||||
for var, value in pairs(DarkRP.ServerDarkRPVars[target] or {}) do
|
||||
if self ~= target and (DarkRP.ServerPrivateDarkRPVars[target] or {})[var] then continue end
|
||||
table.insert(vars, var)
|
||||
end
|
||||
|
||||
local vars_cnt = #vars
|
||||
net.WriteUInt(vars_cnt, DarkRP.DARKRP_ID_BITS + 2) -- Allow for three times as many unknown DarkRPVars than the limit
|
||||
for i = 1, vars_cnt, 1 do
|
||||
DarkRP.writeNetDarkRPVar(vars[i], DarkRP.ServerDarkRPVars[target][vars[i]])
|
||||
end
|
||||
end
|
||||
net.Send(self)
|
||||
end
|
||||
concommand.Add("_sendDarkRPvars", function(ply)
|
||||
if ply.DarkRPVarsSent and ply.DarkRPVarsSent > (CurTime() - 3) then return end -- prevent spammers
|
||||
ply.DarkRPVarsSent = CurTime()
|
||||
ply:sendDarkRPVars()
|
||||
end)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Admin DarkRPVar commands
|
||||
---------------------------------------------------------------------------]]
|
||||
local function setRPName(ply, args)
|
||||
if not args[2] or string.len(args[2]) < 2 or string.len(args[2]) > 30 then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "<2/>30"))
|
||||
return
|
||||
end
|
||||
|
||||
local name = table.concat(args, " ", 2)
|
||||
|
||||
local target = DarkRP.findPlayer(args[1])
|
||||
|
||||
if not target then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args[1]))
|
||||
return
|
||||
end
|
||||
|
||||
local oldname = target:Nick()
|
||||
|
||||
DarkRP.retrieveRPNames(name, function(taken)
|
||||
if not IsValid(target) then return end
|
||||
|
||||
if taken then
|
||||
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("unable", "RPname", DarkRP.getPhrase("already_taken")))
|
||||
return
|
||||
end
|
||||
|
||||
DarkRP.storeRPName(target, name)
|
||||
target:setDarkRPVar("rpname", name)
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_set_x_name", oldname, name))
|
||||
|
||||
local nick = ""
|
||||
if ply:EntIndex() == 0 then
|
||||
nick = "Console"
|
||||
else
|
||||
nick = ply:Nick()
|
||||
end
|
||||
DarkRP.notify(target, 0, 4, DarkRP.getPhrase("x_set_your_name", nick, name))
|
||||
if ply:EntIndex() == 0 then
|
||||
DarkRP.log("Console set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30))
|
||||
else
|
||||
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") set " .. target:SteamName() .. "'s name to " .. name, Color(30, 30, 30))
|
||||
end
|
||||
end)
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("forcerpname", "DarkRP_AdminCommands", setRPName)
|
||||
|
||||
local function freerpname(ply, args)
|
||||
local name = args ~= "" and args or IsValid(ply) and ply:Nick() or ""
|
||||
|
||||
MySQLite.query(("UPDATE darkrp_player SET rpname = NULL WHERE rpname = %s"):format(MySQLite.SQLStr(name)))
|
||||
|
||||
local nick = IsValid(ply) and ply:Nick() or "Console"
|
||||
DarkRP.log(("%s has freed the rp name '%s'"):format(nick, name), Color(30, 30, 30))
|
||||
DarkRP.notify(ply, 0, 4, ("'%s' has been freed"):format(name))
|
||||
end
|
||||
DarkRP.definePrivilegedChatCommand("freerpname", "DarkRP_AdminCommands", freerpname)
|
||||
|
||||
local function RPName(ply, args)
|
||||
if ply.LastNameChange and ply.LastNameChange > (CurTime() - 5) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("have_to_wait", math.ceil(5 - (CurTime() - ply.LastNameChange)), "/rpname"))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.allowrpnames then
|
||||
DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("disabled", "/rpname", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
args = args:find"^%s*$" and '' or args:match"^%s*(.*%S)"
|
||||
|
||||
local canChangeName, reason = hook.Call("CanChangeRPName", GAMEMODE, ply, args)
|
||||
if canChangeName == false then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/rpname", reason or ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
ply:setRPName(args)
|
||||
ply.LastNameChange = CurTime()
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("rpname", RPName)
|
||||
DarkRP.defineChatCommand("name", RPName)
|
||||
DarkRP.defineChatCommand("nick", RPName)
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Setting the RP name
|
||||
---------------------------------------------------------------------------]]
|
||||
function meta:setRPName(name, firstRun)
|
||||
-- Make sure nobody on this server already has this RP name
|
||||
local lowername = string.lower(tostring(name))
|
||||
DarkRP.retrieveRPNames(name, function(taken)
|
||||
if not IsValid(self) or string.len(lowername) < 2 and not firstrun then return end
|
||||
-- If we found that this name exists for another player
|
||||
if taken then
|
||||
if firstRun then
|
||||
-- If we just connected and another player happens to be using our steam name as their RP name
|
||||
-- Put a 1 after our steam name
|
||||
DarkRP.storeRPName(self, name .. " 1")
|
||||
DarkRP.notify(self, 0, 12, DarkRP.getPhrase("someone_stole_steam_name"))
|
||||
else
|
||||
DarkRP.notify(self, 1, 5, DarkRP.getPhrase("unable", "/rpname", DarkRP.getPhrase("already_taken")))
|
||||
return ""
|
||||
end
|
||||
else
|
||||
if not firstRun then -- Don't save the steam name in the database
|
||||
DarkRP.notifyAll(2, 6, DarkRP.getPhrase("rpname_changed", self:SteamName(), name))
|
||||
DarkRP.storeRPName(self, name)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Maximum entity values
|
||||
---------------------------------------------------------------------------]]
|
||||
local maxEntities = {}
|
||||
function meta:addCustomEntity(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] + 1
|
||||
end
|
||||
|
||||
function meta:removeCustomEntity(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] - 1
|
||||
end
|
||||
|
||||
function meta:customEntityLimitReached(entTable)
|
||||
maxEntities[self] = maxEntities[self] or {}
|
||||
maxEntities[self][entTable.cmd] = maxEntities[self][entTable.cmd] or 0
|
||||
local max = entTable.getMax and entTable.getMax(self) or entTable.max
|
||||
|
||||
return max ~= 0 and maxEntities[self][entTable.cmd] >= max
|
||||
end
|
||||
|
||||
function meta:customEntityCount(entTable)
|
||||
local entities = maxEntities[self]
|
||||
if entities == nil then return 0 end
|
||||
|
||||
entities = entities[entTable.cmd]
|
||||
if entities == nil then return 0 end
|
||||
|
||||
return entities
|
||||
end
|
||||
|
||||
-- We use EntityRemoved to clear players of tables, because it is always called
|
||||
-- after the PlayerDisconnected hook. This is called _after_ the GAMEMODE
|
||||
-- function, to make sure that all regular hooks can still use DarkRPVars until
|
||||
-- the very end. See https://github.com/FPtje/DarkRP/pull/3270
|
||||
(GAMEMODE or GM).DarkRPPostEntityRemoved = function(_gm, ent)
|
||||
if not ent:IsPlayer() then return end
|
||||
|
||||
maxEntities[ent] = nil
|
||||
DarkRP.ServerDarkRPVars[ent] = nil
|
||||
DarkRP.ServerPrivateDarkRPVars[ent] = nil
|
||||
|
||||
net.Start("DarkRP_DarkRPVarDisconnect")
|
||||
net.WriteUInt(ent:UserID(), 16)
|
||||
net.Broadcast()
|
||||
end
|
||||
1146
gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua
Normal file
1146
gamemodes/darkrp/gamemode/modules/base/sv_gamemode_functions.lua
Normal file
File diff suppressed because it is too large
Load Diff
1325
gamemodes/darkrp/gamemode/modules/base/sv_interface.lua
Normal file
1325
gamemodes/darkrp/gamemode/modules/base/sv_interface.lua
Normal file
File diff suppressed because it is too large
Load Diff
38
gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua
Normal file
38
gamemodes/darkrp/gamemode/modules/base/sv_jobmodels.lua
Normal file
@@ -0,0 +1,38 @@
|
||||
util.AddNetworkString("DarkRP_preferredjobmodels")
|
||||
util.AddNetworkString("DarkRP_preferredjobmodel")
|
||||
|
||||
local preferredJobModels = {}
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
local received = {}
|
||||
net.Receive("DarkRP_preferredjobmodels", function(len, ply)
|
||||
preferredJobModels[ply] = {}
|
||||
|
||||
for i in pairs(RPExtraTeams) do
|
||||
if net.ReadBit() == 0 then continue end
|
||||
|
||||
preferredJobModels[ply][i] = net.ReadString()
|
||||
end
|
||||
|
||||
if not received[ply] and preferredJobModels[ply][ply:Team()] then
|
||||
gamemode.Call("PlayerSetModel", ply)
|
||||
end
|
||||
|
||||
received[ply] = true
|
||||
end)
|
||||
|
||||
net.Receive("DarkRP_preferredjobmodel", function(len, ply)
|
||||
local teamNr = net.ReadUInt(8)
|
||||
local model = net.ReadString()
|
||||
|
||||
if not RPExtraTeams[teamNr] then return end
|
||||
|
||||
preferredJobModels[ply] = preferredJobModels[ply] or {}
|
||||
preferredJobModels[ply][teamNr] = model
|
||||
end)
|
||||
|
||||
function plyMeta:getPreferredModel(TeamNr)
|
||||
preferredJobModels[self] = preferredJobModels[self] or {}
|
||||
|
||||
return preferredJobModels[self][TeamNr]
|
||||
end
|
||||
466
gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua
Normal file
466
gamemodes/darkrp/gamemode/modules/base/sv_purchasing.lua
Normal file
@@ -0,0 +1,466 @@
|
||||
function DarkRP.hooks:canBuyPistol(ply, shipment)
|
||||
local price = shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0
|
||||
|
||||
if not GAMEMODE:CustomObjFitsMap(shipment) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buy", "")
|
||||
end
|
||||
|
||||
if shipment.customCheck and not shipment.customCheck(ply) then
|
||||
local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or
|
||||
shipment.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
if not ply:canAfford(price) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", "/buy")
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.restrictbuypistol or
|
||||
(GAMEMODE.Config.restrictbuypistol and (not shipment.allowed[1] or table.HasValue(shipment.allowed, ply:Team()))) then
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function BuyPistol(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if not GAMEMODE.Config.enablebuypistol then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if GAMEMODE.Config.noguns then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", "/buy", ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local shipment = DarkRP.getShipmentByName(args)
|
||||
if not shipment or not shipment.separate then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("weapon_")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyPistol", DarkRP.hooks, ply, shipment)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or shipment.getPrice and shipment.getPrice(ply, shipment.pricesep) or shipment.pricesep or 0
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local defaultClip, clipSize
|
||||
local wep_tbl = weapons.Get(shipment.entity)
|
||||
if wep_tbl and wep_tbl.Primary then
|
||||
defaultClip = wep_tbl.Primary.DefaultClip
|
||||
clipSize = wep_tbl.Primary.ClipSize
|
||||
end
|
||||
|
||||
local weapon = ents.Create("spawned_weapon")
|
||||
weapon:SetModel(shipment.model)
|
||||
weapon:SetWeaponClass(shipment.entity)
|
||||
weapon:SetPos(tr.HitPos)
|
||||
weapon.ammoadd = shipment.spareammo or defaultClip
|
||||
weapon.clip1 = shipment.clip1 or clipSize
|
||||
weapon.clip2 = shipment.clip2
|
||||
weapon.nodupe = true
|
||||
weapon:Spawn()
|
||||
|
||||
DarkRP.placeEntity(weapon, tr, ply)
|
||||
|
||||
if shipment.onBought then
|
||||
shipment.onBought(ply, shipment, weapon)
|
||||
end
|
||||
hook.Call("playerBoughtPistol", nil, ply, shipment, weapon, cost)
|
||||
|
||||
if IsValid(weapon) then
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost)))
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buy", args))
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buy", BuyPistol, 0.2)
|
||||
|
||||
function DarkRP.hooks:canBuyShipment(ply, shipment)
|
||||
if not GAMEMODE:CustomObjFitsMap(shipment) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply.LastShipmentSpawn and ply.LastShipmentSpawn > (CurTime() - GAMEMODE.Config.ShipmentSpamTime) then
|
||||
return false, false, DarkRP.getPhrase("shipment_antispam_wait")
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyshipment", "")
|
||||
end
|
||||
|
||||
if shipment.customCheck and not shipment.customCheck(ply) then
|
||||
local message = isfunction(shipment.CustomCheckFailMsg) and shipment.CustomCheckFailMsg(ply, shipment) or
|
||||
shipment.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
local canbecome = false
|
||||
for _, b in pairs(shipment.allowed) do
|
||||
if ply:Team() == b then
|
||||
canbecome = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not canbecome then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyshipment")
|
||||
end
|
||||
|
||||
local cost = shipment.getPrice and shipment.getPrice(ply, shipment.price) or shipment.price
|
||||
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("shipment"))
|
||||
end
|
||||
|
||||
if not shipment.allowPurchaseWhileDead and not ply:Alive() then
|
||||
return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", DarkRP.getPhrase("shipments")))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyShipment(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found, foundKey = DarkRP.getShipmentByName(args)
|
||||
if not found or found.noship or not GAMEMODE:CustomObjFitsMap(found) then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("shipment")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyShipment", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local crate = ents.Create(found.shipmentClass or "spawned_shipment")
|
||||
crate.SID = ply.SID
|
||||
crate:Setowning_ent(ply)
|
||||
crate:SetContents(foundKey, found.amount)
|
||||
|
||||
crate:SetPos(Vector(tr.HitPos.x, tr.HitPos.y, tr.HitPos.z))
|
||||
crate.nodupe = true
|
||||
crate.ammoadd = found.spareammo
|
||||
crate.clip1 = found.clip1
|
||||
crate.clip2 = found.clip2
|
||||
crate:Spawn()
|
||||
crate:SetPlayer(ply)
|
||||
|
||||
DarkRP.placeEntity(crate, tr, ply)
|
||||
|
||||
local phys = crate:GetPhysicsObject()
|
||||
phys:Wake()
|
||||
if found.weight then
|
||||
phys:SetMass(found.weight)
|
||||
end
|
||||
|
||||
if CustomShipments[foundKey].onBought then
|
||||
CustomShipments[foundKey].onBought(ply, CustomShipments[foundKey], crate)
|
||||
end
|
||||
hook.Call("playerBoughtShipment", nil, ply, CustomShipments[foundKey], crate, cost)
|
||||
|
||||
if IsValid(crate) then
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", args, DarkRP.formatMoney(cost)))
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "/buyshipment", arg))
|
||||
end
|
||||
|
||||
ply.LastShipmentSpawn = CurTime()
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyshipment", BuyShipment)
|
||||
|
||||
function DarkRP.hooks:canBuyVehicle(ply, vehicle)
|
||||
if not vehicle.allowPurchaseWhileDead and not ply:Alive() then
|
||||
return false, false, DarkRP.getPhrase("must_be_alive_to_do_x", DarkRP.getPhrase("buy_x", vehicle.name))
|
||||
end
|
||||
if not GAMEMODE:CustomObjFitsMap(vehicle) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyvehicle", "")
|
||||
end
|
||||
|
||||
if vehicle.allowed and not table.HasValue(vehicle.allowed, ply:Team()) then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyvehicle")
|
||||
end
|
||||
|
||||
if vehicle.customCheck and not vehicle.customCheck(ply) then
|
||||
local message = isfunction(vehicle.CustomCheckFailMsg) and vehicle.CustomCheckFailMsg(ply, vehicle) or
|
||||
vehicle.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
ply.Vehicles = ply.Vehicles or 0
|
||||
if GAMEMODE.Config.maxvehicles and ply.Vehicles >= GAMEMODE.Config.maxvehicles then
|
||||
return false, false, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle"))
|
||||
end
|
||||
|
||||
local cost = vehicle.getPrice and vehicle.getPrice(ply, vehicle.price) or vehicle.price
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("vehicle"))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyVehicle(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found = false
|
||||
-- Allow people to have multiple vehicles with the same name
|
||||
-- vehicles are bought through the command
|
||||
for k, v in pairs(CustomVehicles) do
|
||||
if v.command and string.lower(v.command) == string.lower(args) then
|
||||
found = CustomVehicles[k]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
for k,v in pairs(CustomVehicles) do
|
||||
if string.lower(v.name) == string.lower(args) then found = CustomVehicles[k] break end
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("vehicle")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local Vehicle = DarkRP.getAvailableVehicles()[found.name]
|
||||
if not Vehicle then DarkRP.notify(ply, 1, 4, "Incorrect vehicle, fix your vehicles.") return "" end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyVehicle", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
ply:addMoney(-cost)
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.label or found.name, DarkRP.formatMoney(cost)))
|
||||
|
||||
local trace = {}
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * (found.distance or 85)
|
||||
trace.filter = ply
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = ents.Create(Vehicle.Class)
|
||||
if not ent:IsValid() then error("Vehicle '" .. Vehicle.Class .. "' does not exist or is not valid.") end
|
||||
|
||||
ent:SetModel(Vehicle.Model)
|
||||
if Vehicle.KeyValues then
|
||||
for k, v in pairs(Vehicle.KeyValues) do
|
||||
ent:SetKeyValue(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
ent:SetPos(tr.HitPos)
|
||||
ent.VehicleName = found.name
|
||||
ent.VehicleTable = Vehicle
|
||||
ent:Spawn()
|
||||
ent:Activate()
|
||||
ent.SID = ply.SID
|
||||
ent.ClassOverride = Vehicle.Class
|
||||
if Vehicle.Members then
|
||||
table.Merge(ent, Vehicle.Members)
|
||||
end
|
||||
ent:CPPISetOwner(ply)
|
||||
ent:keysOwn(ply)
|
||||
|
||||
DarkRP.placeEntity(ent, tr, ply)
|
||||
|
||||
local angOff = found.angle or Angle(0, 0, 0)
|
||||
ent:SetAngles(ent:GetAngles() + angOff)
|
||||
|
||||
hook.Call("PlayerSpawnedVehicle", GAMEMODE, ply, ent) -- VUMod compatability
|
||||
hook.Call("playerBoughtCustomVehicle", nil, ply, found, ent, cost)
|
||||
|
||||
if found.onBought then
|
||||
found.onBought(ply, found, ent)
|
||||
end
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyvehicle", BuyVehicle)
|
||||
|
||||
function DarkRP.hooks:canBuyAmmo(ply, ammo)
|
||||
if not GAMEMODE:CustomObjFitsMap(ammo) then
|
||||
return false, false, "Custom object does not fit map"
|
||||
end
|
||||
|
||||
if ply:isArrested() then
|
||||
return false, false, DarkRP.getPhrase("unable", "/buyammo", "")
|
||||
end
|
||||
|
||||
if ammo.allowed and not table.HasValue(ammo.allowed, ply:Team()) then
|
||||
return false, false, DarkRP.getPhrase("incorrect_job", "/buyammo")
|
||||
end
|
||||
|
||||
if ammo.customCheck and not ammo.customCheck(ply) then
|
||||
local message = isfunction(ammo.CustomCheckFailMsg) and ammo.CustomCheckFailMsg(ply, ammo) or
|
||||
ammo.CustomCheckFailMsg or
|
||||
DarkRP.getPhrase("not_allowed_to_purchase")
|
||||
return false, false, message
|
||||
end
|
||||
|
||||
local cost = ammo.getPrice and ammo.getPrice(ply, ammo.price) or ammo.price
|
||||
if not ply:canAfford(cost) then
|
||||
return false, false, DarkRP.getPhrase("cant_afford", DarkRP.getPhrase("ammo"))
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function BuyAmmo(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
if GAMEMODE.Config.noguns then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ammo"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local found
|
||||
local num = tonumber(args)
|
||||
if num and GAMEMODE.AmmoTypes[num] then
|
||||
found = GAMEMODE.AmmoTypes[num]
|
||||
else
|
||||
for _, v in pairs(GAMEMODE.AmmoTypes) do
|
||||
if v.ammoType ~= args then continue end
|
||||
|
||||
found = v
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unavailable", DarkRP.getPhrase("ammo")))
|
||||
return ""
|
||||
end
|
||||
|
||||
local canbuy, suppress, message, price = hook.Call("canBuyAmmo", DarkRP.hooks, ply, found)
|
||||
|
||||
if not canbuy then
|
||||
message = message or DarkRP.getPhrase("incorrect_job", "/buy")
|
||||
if not suppress then DarkRP.notify(ply, 1, 4, message) end
|
||||
return ""
|
||||
end
|
||||
|
||||
local cost = price or found.getPrice and found.getPrice(ply, found.price) or found.price
|
||||
|
||||
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("you_bought", found.name, DarkRP.formatMoney(cost)))
|
||||
ply:addMoney(-cost)
|
||||
|
||||
-- local trace = {}
|
||||
-- trace.start = ply:EyePos()
|
||||
-- trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
-- trace.filter = ply
|
||||
|
||||
-- local tr = util.TraceLine(trace)
|
||||
|
||||
-- local ammo = ents.Create("spawned_ammo")
|
||||
-- ammo:SetModel(found.model)
|
||||
-- ammo:SetPos(tr.HitPos)
|
||||
-- ammo.nodupe = true
|
||||
-- ammo.amountGiven, ammo.ammoType = found.amountGiven, found.ammoType
|
||||
-- ammo:Spawn()
|
||||
|
||||
-- DarkRP.placeEntity(ammo, tr, ply)
|
||||
|
||||
-- hook.Call("playerBoughtAmmo", nil, ply, found, ammo, cost)
|
||||
--
|
||||
ply:GiveAmmo(found.amountGiven, found.ammoType)
|
||||
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("buyammo", BuyAmmo, 1)
|
||||
|
||||
local function SetPrice(ply, args)
|
||||
if args == "" then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
|
||||
local price = DarkRP.toInt(args)
|
||||
if not price then
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return ""
|
||||
end
|
||||
price = math.Clamp(price, GAMEMODE.Config.pricemin, (GAMEMODE.Config.pricecap ~= 0 and GAMEMODE.Config.pricecap) or 500)
|
||||
local trace = {}
|
||||
|
||||
trace.start = ply:EyePos()
|
||||
trace.endpos = trace.start + ply:GetAimVector() * 85
|
||||
trace.filter = ply
|
||||
|
||||
local tr = util.TraceLine(trace)
|
||||
|
||||
local ent = tr.Entity
|
||||
|
||||
if IsValid(ent) and ent.CanSetPrice and ent.SID == ply.SID then
|
||||
ent:Setprice(price)
|
||||
else
|
||||
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("any_lab")))
|
||||
end
|
||||
return ""
|
||||
end
|
||||
DarkRP.defineChatCommand("price", SetPrice)
|
||||
DarkRP.defineChatCommand("setprice", SetPrice)
|
||||
235
gamemodes/darkrp/gamemode/modules/base/sv_util.lua
Normal file
235
gamemodes/darkrp/gamemode/modules/base/sv_util.lua
Normal file
@@ -0,0 +1,235 @@
|
||||
function DarkRP.notify(ply, msgtype, len, msg)
|
||||
if not istable(ply) then
|
||||
if not IsValid(ply) then
|
||||
-- Dedicated server console
|
||||
print(msg)
|
||||
return
|
||||
end
|
||||
|
||||
ply = {ply}
|
||||
end
|
||||
|
||||
local rcp = RecipientFilter()
|
||||
for _, v in pairs(ply) do
|
||||
rcp:AddPlayer(v)
|
||||
end
|
||||
|
||||
if hook.Run("onNotify", rcp:GetPlayers(), msgtype, len, msg) == true then return end
|
||||
|
||||
umsg.Start("_Notify", rcp)
|
||||
umsg.String(msg)
|
||||
umsg.Short(msgtype)
|
||||
umsg.Long(len)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
function DarkRP.notifyAll(msgtype, len, msg)
|
||||
if hook.Run("onNotify", player.GetAll(), msgtype, len, msg) == true then return end
|
||||
|
||||
umsg.Start("_Notify")
|
||||
umsg.String(msg)
|
||||
umsg.Short(msgtype)
|
||||
umsg.Long(len)
|
||||
umsg.End()
|
||||
end
|
||||
|
||||
function DarkRP.printMessageAll(msgtype, msg)
|
||||
for _, v in ipairs(player.GetAll()) do
|
||||
v:PrintMessage(msgtype, msg)
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.printConsoleMessage(ply, msg)
|
||||
if ply:EntIndex() == 0 then
|
||||
print(msg)
|
||||
else
|
||||
ply:PrintMessage(HUD_PRINTCONSOLE, msg)
|
||||
end
|
||||
end
|
||||
|
||||
util.AddNetworkString("DarkRP_Chat")
|
||||
|
||||
function DarkRP.talkToRange(ply, PlayerName, Message, size)
|
||||
local ents = player.GetHumans()
|
||||
local col = team.GetColor(ply:Team())
|
||||
local filter = {}
|
||||
|
||||
local plyPos = ply:EyePos()
|
||||
local sizeSqr = size * size
|
||||
|
||||
for _, v in ipairs(ents) do
|
||||
if (v:EyePos():DistToSqr(plyPos) <= sizeSqr) and (v == ply or hook.Run("PlayerCanSeePlayersChat", PlayerName .. ": " .. Message, false, v, ply) ~= false) then
|
||||
table.insert(filter, v)
|
||||
end
|
||||
end
|
||||
|
||||
if PlayerName == ply:Nick() then PlayerName = "" end -- If it's just normal chat, why not cut down on networking and get the name on the client
|
||||
|
||||
net.Start("DarkRP_Chat")
|
||||
net.WriteUInt(col.r, 8)
|
||||
net.WriteUInt(col.g, 8)
|
||||
net.WriteUInt(col.b, 8)
|
||||
net.WriteString(PlayerName)
|
||||
net.WriteEntity(ply)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteUInt(255, 8)
|
||||
net.WriteString(Message)
|
||||
net.Send(filter)
|
||||
end
|
||||
|
||||
function DarkRP.talkToPerson(receiver, col1, text1, col2, text2, sender)
|
||||
if not IsValid(receiver) then return end
|
||||
if receiver:IsBot() then return end
|
||||
local concatenatedText = (text1 or "") .. ": " .. (text2 or "")
|
||||
|
||||
if sender == receiver or hook.Run("PlayerCanSeePlayersChat", concatenatedText, false, receiver, sender) ~= false then
|
||||
net.Start("DarkRP_Chat")
|
||||
net.WriteUInt(col1.r, 8)
|
||||
net.WriteUInt(col1.g, 8)
|
||||
net.WriteUInt(col1.b, 8)
|
||||
net.WriteString(text1)
|
||||
|
||||
sender = sender or Entity(0)
|
||||
net.WriteEntity(sender)
|
||||
|
||||
col2 = col2 or color_black
|
||||
net.WriteUInt(col2.r, 8)
|
||||
net.WriteUInt(col2.g, 8)
|
||||
net.WriteUInt(col2.b, 8)
|
||||
net.WriteString(text2 or "")
|
||||
net.Send(receiver)
|
||||
end
|
||||
end
|
||||
|
||||
function DarkRP.isEmpty(vector, ignore)
|
||||
ignore = ignore or {}
|
||||
|
||||
local point = util.PointContents(vector)
|
||||
local a = point ~= CONTENTS_SOLID
|
||||
and point ~= CONTENTS_MOVEABLE
|
||||
and point ~= CONTENTS_LADDER
|
||||
and point ~= CONTENTS_PLAYERCLIP
|
||||
and point ~= CONTENTS_MONSTERCLIP
|
||||
if not a then return false end
|
||||
|
||||
local b = true
|
||||
|
||||
for _, v in ipairs(ents.FindInSphere(vector, 35)) do
|
||||
if (v:IsNPC() or v:IsPlayer() or v:GetClass() == "prop_physics" or v.NotEmptyPos) and not table.HasValue(ignore, v) then
|
||||
b = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return a and b
|
||||
end
|
||||
|
||||
function DarkRP.placeEntity(ent, tr, ply)
|
||||
if IsValid(ply) then
|
||||
local ang = ply:EyeAngles()
|
||||
ang.pitch = 0
|
||||
ang.yaw = ang.yaw + 180
|
||||
ang.roll = 0
|
||||
ent:SetAngles(ang)
|
||||
end
|
||||
|
||||
local vFlushPoint = tr.HitPos - (tr.HitNormal * 512)
|
||||
vFlushPoint = ent:NearestPoint(vFlushPoint)
|
||||
vFlushPoint = ent:GetPos() - vFlushPoint
|
||||
vFlushPoint = tr.HitPos + vFlushPoint
|
||||
ent:SetPos(vFlushPoint)
|
||||
end
|
||||
|
||||
--[[---------------------------------------------------------------------------
|
||||
Find an empty position near the position given in the first parameter
|
||||
pos - The position to use as a center for looking around
|
||||
ignore - what entities to ignore when looking for the position (the position can be within the entity)
|
||||
distance - how far to look
|
||||
step - how big the steps are
|
||||
area - the position relative to pos that should also be free
|
||||
|
||||
Performance: O(N^2) (The Lua part, that is, I don't know about the C++ counterpart)
|
||||
Don't call this function too often or with big inputs.
|
||||
---------------------------------------------------------------------------]]
|
||||
function DarkRP.findEmptyPos(pos, ignore, distance, step, area)
|
||||
if DarkRP.isEmpty(pos, ignore) and DarkRP.isEmpty(pos + area, ignore) then
|
||||
return pos
|
||||
end
|
||||
|
||||
for j = step, distance, step do
|
||||
for i = -1, 1, 2 do -- alternate in direction
|
||||
local k = j * i
|
||||
|
||||
-- Look North/South
|
||||
if DarkRP.isEmpty(pos + Vector(k, 0, 0), ignore) and DarkRP.isEmpty(pos + Vector(k, 0, 0) + area, ignore) then
|
||||
return pos + Vector(k, 0, 0)
|
||||
end
|
||||
|
||||
-- Look East/West
|
||||
if DarkRP.isEmpty(pos + Vector(0, k, 0), ignore) and DarkRP.isEmpty(pos + Vector(0, k, 0) + area, ignore) then
|
||||
return pos + Vector(0, k, 0)
|
||||
end
|
||||
|
||||
-- Look Up/Down
|
||||
if DarkRP.isEmpty(pos + Vector(0, 0, k), ignore) and DarkRP.isEmpty(pos + Vector(0, 0, k) + area, ignore) then
|
||||
return pos + Vector(0, 0, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pos
|
||||
end
|
||||
|
||||
local meta = FindMetaTable("Player")
|
||||
function meta:applyPlayerClassVars(applyHealth)
|
||||
local playerClass = baseclass.Get(player_manager.GetPlayerClass(self))
|
||||
|
||||
self:SetWalkSpeed(playerClass.WalkSpeed >= 0 and playerClass.WalkSpeed or GAMEMODE.Config.walkspeed)
|
||||
self:SetRunSpeed(playerClass.RunSpeed >= 0 and playerClass.RunSpeed or (self:isCP() and GAMEMODE.Config.runspeedcp or GAMEMODE.Config.runspeed))
|
||||
|
||||
hook.Call("UpdatePlayerSpeed", GAMEMODE, self) -- Backwards compatitibly, do not use
|
||||
|
||||
self:SetCrouchedWalkSpeed(playerClass.CrouchedWalkSpeed)
|
||||
self:SetDuckSpeed(playerClass.DuckSpeed)
|
||||
self:SetUnDuckSpeed(playerClass.UnDuckSpeed)
|
||||
self:SetJumpPower(playerClass.JumpPower)
|
||||
self:AllowFlashlight(playerClass.CanUseFlashlight)
|
||||
|
||||
self:SetMaxHealth(playerClass.MaxHealth >= 0 and playerClass.MaxHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100))
|
||||
if applyHealth then
|
||||
self:SetHealth(playerClass.StartHealth >= 0 and playerClass.StartHealth or (tonumber(GAMEMODE.Config.startinghealth) or 100))
|
||||
end
|
||||
self:SetArmor(playerClass.StartArmor)
|
||||
|
||||
self.dropWeaponOnDeath = playerClass.DropWeaponOnDie
|
||||
self:SetNoCollideWithTeammates(playerClass.TeammateNoCollide)
|
||||
self:SetAvoidPlayers(playerClass.AvoidPlayers)
|
||||
|
||||
hook.Call("playerClassVarsApplied", nil, self)
|
||||
end
|
||||
|
||||
local function LookPersonUp(ply, cmd, args)
|
||||
if not args[1] then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
|
||||
return
|
||||
end
|
||||
local P = DarkRP.findPlayer(args[1])
|
||||
if not IsValid(P) then
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("could_not_find", tostring(args[1])))
|
||||
return
|
||||
end
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("name", P:Nick()))
|
||||
DarkRP.printConsoleMessage(ply, "Steam " .. DarkRP.getPhrase("name", P:SteamName()))
|
||||
DarkRP.printConsoleMessage(ply, "Steam ID: " .. P:SteamID())
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("job", team.GetName(P:Team())))
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("kills", P:Frags()))
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("deaths", P:Deaths()))
|
||||
|
||||
CAMI.PlayerHasAccess(ply, "DarkRP_AdminCommands", function(access)
|
||||
if not access then return end
|
||||
|
||||
DarkRP.printConsoleMessage(ply, DarkRP.getPhrase("wallet", DarkRP.formatMoney(P:getDarkRPVar("money")), ""))
|
||||
end)
|
||||
end
|
||||
concommand.Add("rp_lookup", LookPersonUp)
|
||||
Reference in New Issue
Block a user