Initial commit

This commit is contained in:
pavetr
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View 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

View 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)

View 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()

View File

@@ -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)

View 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 = {
}
}

View 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)

View 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

View 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."
),
}

View 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
}

View 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)

View 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

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View 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")

View 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)

View 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)

View 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)

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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)

View 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)