Initial commit

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

View File

@@ -0,0 +1,4 @@
Do not add, edit or remove anything from this folder.
Use the DarkRPMod addon instead
https://github.com/FPtje/DarkRPModification

View File

@@ -0,0 +1,17 @@
local TextColor = Color(GetConVar("Healthforeground1"):GetFloat(), GetConVar("Healthforeground2"):GetFloat(), GetConVar("Healthforeground3"):GetFloat(), GetConVar("Healthforeground4"):GetFloat())
local function AFKHUDPaint()
if not LocalPlayer():getDarkRPVar("AFK") then return end
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_mode"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 100, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("salary_frozen"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 60, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if not LocalPlayer():getDarkRPVar("AFKDemoted") then
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("no_auto_demote"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
else
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("youre_afk_demoted"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) - 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
draw.DrawNonParsedSimpleText(DarkRP.getPhrase("afk_cmd_to_exit"), "DarkRPHUD2", ScrW() / 2, (ScrH() / 2) + 20, TextColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
hook.Add("HUDPaint", "AFK_HUD", AFKHUDPaint)

View File

@@ -0,0 +1,8 @@
DarkRP.registerDarkRPVar("AFK", net.WriteBit, fn.Compose{tobool, net.ReadBit})
DarkRP.registerDarkRPVar("AFKDemoted", net.WriteBit, fn.Compose{tobool, net.ReadBit})
DarkRP.declareChatCommand{
command = "afk",
description = "Go AFK",
delay = 1.5
}

View File

@@ -0,0 +1,129 @@
-- How to use:
-- If a player uses /afk, they go into AFK mode, they will not be autodemoted and their salary is set to $0 (you can still be killed/vote demoted though!).
-- If a player does not use /afk, and they don't do anything for the demote time specified, they will be automatically demoted to hobo.
local function AFKDemote(ply)
local shouldDemote, demoteTeam, suppressMsg, msg = hook.Call("playerAFKDemoted", nil, ply)
demoteTeam = demoteTeam or GAMEMODE.DefaultTeam
if ply:Team() ~= demoteTeam and shouldDemote ~= false then
local rpname = ply:getDarkRPVar("rpname")
ply:changeTeam(demoteTeam, true)
if not suppressMsg then DarkRP.notifyAll(0, 5, msg or DarkRP.getPhrase("hes_afk_demoted", rpname)) end
end
ply:setSelfDarkRPVar("AFKDemoted", true)
ply:setDarkRPVar("job", "AFK")
end
local function SetAFK(ply)
local rpname = ply:getDarkRPVar("rpname")
ply:setSelfDarkRPVar("AFK", not ply:getDarkRPVar("AFK"))
ply.blackScreen = ply:getDarkRPVar("AFK")
SendUserMessage("blackScreen", ply, ply:getDarkRPVar("AFK"))
if ply:getDarkRPVar("AFK") then
DarkRP.retrieveSalary(ply, function(amount) ply.OldSalary = amount end)
ply.OldJob = ply:getDarkRPVar("job")
ply.lastHealth = ply:Health()
DarkRP.notifyAll(0, 5, DarkRP.getPhrase("player_now_afk", rpname))
ply.AFKDemote = math.huge
ply:KillSilent()
ply:Lock()
else
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
DarkRP.notifyAll(1, 5, DarkRP.getPhrase("player_no_longer_afk", rpname))
DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("salary_restored"))
ply:Spawn()
ply:UnLock()
ply:SetHealth(ply.lastHealth and ply.lastHealth > 0 and ply.lastHealth or 100)
ply.lastHealth = nil
end
if not ply.demotedWhileDead then
ply:setDarkRPVar("job", ply:getDarkRPVar("AFK") and "AFK" or ply:getDarkRPVar("AFKDemoted") and team.GetName(ply:Team()) or ply.OldJob)
ply:setSelfDarkRPVar("salary", ply:getDarkRPVar("AFK") and 0 or ply.OldSalary or 0)
end
hook.Run("playerSetAFK", ply, ply:getDarkRPVar("AFK"))
end
DarkRP.defineChatCommand("afk", function(ply)
if ply.DarkRPLastAFK and not ply:getDarkRPVar("AFK") and ply.DarkRPLastAFK > CurTime() - GAMEMODE.Config.AFKDelay then
DarkRP.notify(ply, 0, 5, DarkRP.getPhrase("unable_afk_spam_prevention"))
return ""
end
local canAFK = hook.Run("canGoAFK", ply, not ply:getDarkRPVar("AFK"))
if canAFK == false then return "" end
ply.DarkRPLastAFK = CurTime()
SetAFK(ply)
return ""
end)
local function StartAFKOnPlayer(ply)
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
end
hook.Add("PlayerInitialSpawn", "StartAFKOnPlayer", StartAFKOnPlayer)
local function AFKTimer(ply, key)
ply.AFKDemote = CurTime() + GAMEMODE.Config.afkdemotetime
if ply:getDarkRPVar("AFKDemoted") then
ply:setDarkRPVar("job", team.GetName(ply:Team()))
timer.Simple(3, function() if IsValid(ply) then ply:setSelfDarkRPVar("AFKDemoted", nil) end end)
end
end
hook.Add("KeyPress", "DarkRPKeyReleasedCheck", AFKTimer)
local function KillAFKTimer()
for _, ply in ipairs(player.GetAll()) do
if ply.AFKDemote and CurTime() > ply.AFKDemote and not ply:getDarkRPVar("AFK") and not ply:IsBot() then
SetAFK(ply)
AFKDemote(ply)
ply.AFKDemote = math.huge
end
end
end
timer.Create("DarkRPKeyPressedCheck", 1, 0, function()
KillAFKTimer()
end)
local function BlockAFKTeamChange(ply, t, force)
if ply:getDarkRPVar("AFK") and (not force or t ~= GAMEMODE.DefaultTeam) then
local TEAM = RPExtraTeams[t]
if TEAM then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", GAMEMODE.Config.chatCommandPrefix .. TEAM.command, DarkRP.getPhrase("afk_mode"))) end
return false
end
end
hook.Add("playerCanChangeTeam", "AFKCanChangeTeam", BlockAFKTeamChange)
-- Freeze AFK player's salary
hook.Add("playerGetSalary", "AFKGetSalary", function(ply, amount)
if ply:getDarkRPVar("AFK") then
return true, "", 0
end
end)
-- For when a player's team is changed by force
hook.Add("OnPlayerChangedTeam", "AFKCanChangeTeam", function(ply)
if not ply:getDarkRPVar("AFK") then return end
ply.OldSalary = ply:getDarkRPVar("salary")
ply.OldJob = nil
ply:setSelfDarkRPVar("salary", 0)
end)
local function unAFKPlayer(ply)
if ply:getDarkRPVar("AFK") then
SetAFK(ply)
end
end
hook.Add("playerArrested", "DarkRP_AFK", unAFKPlayer)
hook.Add("playerUnArrested", "DarkRP_AFK", unAFKPlayer)

View File

@@ -0,0 +1,76 @@
DarkRP.hookStub{
name = "playerAFKDemoted",
description = "When a player is demoted for being AFK.",
parameters = {
{
name = "ply",
description = "The player being demoted.",
type = "Player"
}
},
returns = {
{
name = "shouldDemote",
description = "Prevent the player from being actually demoted.",
type = "boolean"
},
{
name = "team",
description = "The team the player is to be demoted to (shouldDemote must be true.)",
type = "number"
},
{
name = "suppressMessage",
description = "Suppress the demote message.",
type = "boolean"
},
{
name = "demoteMessage",
description = "Replacement of the demote message text.",
type = "string"
}
}
}
DarkRP.hookStub{
name = "playerSetAFK",
description = "When a player is set to AFK or returns from AFK.",
parameters = {
{
name = "ply",
description = "The player.",
type = "Player"
},
{
name = "afk",
description = "True when the player starts being AFK, false when the player stops being AFK.",
type = "boolean"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "canGoAFK",
description = "When a player can MANUALLY start being AFK by entering the chat command. Note: this hook does NOT get called when a player is set to AFK automatically! That hook will not be added, because I don't want asshole server owners to make AFK rules not apply to admins.",
parameters = {
{
name = "ply",
description = "The player.",
type = "Player"
},
{
name = "afk",
description = "True when the player starts being AFK, false when the player stops being AFK.",
type = "boolean"
}
},
returns = {
{
name = "canGoAFK",
description = "Whether the player is allowed to go AFK",
type = "boolean"
}
}
}

View File

@@ -0,0 +1,152 @@
local Anims = {}
-- Load animations after the languages for translation purposes
hook.Add("loadCustomDarkRPItems", "loadAnimations", function()
Anims[ACT_GMOD_GESTURE_BOW] = DarkRP.getPhrase("bow")
Anims[ACT_GMOD_TAUNT_MUSCLE] = DarkRP.getPhrase("sexy_dance")
Anims[ACT_GMOD_GESTURE_BECON] = DarkRP.getPhrase("follow_me")
Anims[ACT_GMOD_TAUNT_LAUGH] = DarkRP.getPhrase("laugh")
Anims[ACT_GMOD_TAUNT_PERSISTENCE] = DarkRP.getPhrase("lion_pose")
Anims[ACT_GMOD_GESTURE_DISAGREE] = DarkRP.getPhrase("nonverbal_no")
Anims[ACT_GMOD_GESTURE_AGREE] = DarkRP.getPhrase("thumbs_up")
Anims[ACT_GMOD_GESTURE_WAVE] = DarkRP.getPhrase("wave")
Anims[ACT_GMOD_TAUNT_DANCE] = DarkRP.getPhrase("dance")
end)
function DarkRP.addPlayerGesture(anim, text)
if not anim then DarkRP.error("Argument #1 of DarkRP.addPlayerGesture (animation/gesture) does not exist.", 2) end
if not text then DarkRP.error("Argument #2 of DarkRP.addPlayerGesture (text) does not exist.", 2) end
Anims[anim] = text
end
function DarkRP.removePlayerGesture(anim)
if not anim then DarkRP.error("Argument #1 of DarkRP.removePlayerGesture (animation/gesture) does not exist.", 2) end
Anims[anim] = nil
end
local function physGunCheck(ply)
local hookName = "darkrp_anim_physgun_" .. ply:EntIndex()
hook.Add("Think", hookName, function()
if IsValid(ply) and
ply:Alive() and
ply:GetActiveWeapon():IsValid() and
ply:GetActiveWeapon():GetClass() == "weapon_physgun" and
ply:KeyDown(IN_ATTACK) and
(ply:GetAllowWeaponsInVehicle() or not ply:InVehicle()) then
local ent = ply:GetEyeTrace().Entity
if IsValid(ent) and ent:IsPlayer() and not ply.SaidHi then
ply.SaidHi = true
ply:DoAnimationEvent(ACT_SIGNAL_GROUP)
end
else
if IsValid(ply) then
ply.SaidHi = nil
end
hook.Remove("Think", hookName)
end
end)
end
hook.Add("KeyPress", "darkrp_animations", function(ply, key)
if key == IN_ATTACK then
local weapon = ply:GetActiveWeapon()
if weapon:IsValid() then
local class = weapon:GetClass()
-- Saying hi/hello to a player
if class == "weapon_physgun" then
physGunCheck(ply)
-- Hobo throwing poop!
elseif class == "weapon_bugbait" then
local Team = ply:Team()
if RPExtraTeams[Team] and RPExtraTeams[Team].hobo then
ply:DoAnimationEvent(ACT_GMOD_GESTURE_ITEM_THROW)
end
end
end
end
end)
if SERVER then
local function CustomAnim(ply, cmd, args)
if ply:EntIndex() == 0 then return end
local Gesture = tonumber(args[1] or 0)
if not Anims[Gesture] then return end
local RP = RecipientFilter()
RP:AddAllPlayers()
umsg.Start("_DarkRP_CustomAnim", RP)
umsg.Entity(ply)
umsg.Short(Gesture)
umsg.End()
end
concommand.Add("_DarkRP_DoAnimation", CustomAnim)
return
end
local function KeysAnims(um)
local ply = um:ReadEntity()
local act = um:ReadString()
if not IsValid(ply) then return end
ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act == "usekeys" and ACT_GMOD_GESTURE_ITEM_PLACE or ACT_HL2MP_GESTURE_RANGE_ATTACK_FIST, true)
end
usermessage.Hook("anim_keys", KeysAnims)
local function CustomAnimation(um)
local ply = um:ReadEntity()
local act = um:ReadShort()
if not IsValid(ply) then return end
ply:AnimRestartGesture(GESTURE_SLOT_CUSTOM, act, true)
end
usermessage.Hook("_DarkRP_CustomAnim", CustomAnimation)
local AnimFrame
local function AnimationMenu()
if AnimFrame then return end
local Panel = vgui.Create("Panel")
Panel:SetPos(0,0)
Panel:SetSize(ScrW(), ScrH())
function Panel:OnMousePressed()
AnimFrame:Close()
end
AnimFrame = AnimFrame or vgui.Create("DFrame", Panel)
local Height = table.Count(Anims) * 55 + 32
AnimFrame:SetSize(130, Height)
AnimFrame:SetPos(ScrW() / 2 + ScrW() * 0.1, ScrH() / 2 - (Height / 2))
AnimFrame:SetTitle(DarkRP.getPhrase("custom_animation"))
AnimFrame.btnMaxim:SetVisible(false)
AnimFrame.btnMinim:SetVisible(false)
AnimFrame:SetVisible(true)
AnimFrame:MakePopup()
AnimFrame:ParentToHUD()
function AnimFrame:Close()
Panel:Remove()
AnimFrame:Remove()
AnimFrame = nil
end
local i = 0
for k, v in SortedPairs(Anims) do
i = i + 1
local button = vgui.Create("DButton", AnimFrame)
button:SetPos(10, (i - 1) * 55 + 30)
button:SetSize(110, 50)
button:SetText(v)
button.DoClick = function()
RunConsoleCommand("_DarkRP_DoAnimation", k)
end
end
AnimFrame:SetSkin(GAMEMODE.Config.DarkRPSkin)
end
concommand.Add("_DarkRP_AnimationMenu", AnimationMenu)

View File

@@ -0,0 +1,37 @@
DarkRP.addPlayerGesture = DarkRP.stub{
name = "addPlayerGesture",
description = "Add a player gesture to the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!",
parameters = {
{
name = "anim",
description = "The gesture enumeration.",
type = "number",
optional = false
},
{
name = "text",
description = "The textual description of the animation. This is what players see on the button in the menu.",
type = "string",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.removePlayerGesture = DarkRP.stub{
name = "removePlayerGesture",
description = "Removes a player gesture from the DarkRP animations menu (the one that opens with the keys weapon.). Note: This function must be called BOTH serverside AND clientside!",
parameters = {
{
name = "anim",
description = "The gesture enumeration.",
type = "number",
optional = false
}
},
returns = {
},
metatable = DarkRP
}

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)

View File

@@ -0,0 +1,90 @@
--[[---------------------------------------------------------------------------
Gamemode function
---------------------------------------------------------------------------]]
function GM:OnPlayerChat()
end
--[[---------------------------------------------------------------------------
Add a message to chat
---------------------------------------------------------------------------]]
local function AddToChat(bits)
local col1 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8))
local prefixText = net.ReadString()
local ply = net.ReadEntity()
ply = IsValid(ply) and ply or LocalPlayer()
if not IsValid(ply) then return end
if prefixText == "" or not prefixText then
prefixText = ply:Nick()
prefixText = prefixText ~= "" and prefixText or ply:SteamName()
end
local col2 = Color(net.ReadUInt(8), net.ReadUInt(8), net.ReadUInt(8))
local text = net.ReadString()
local shouldShow
if text and text ~= "" then
if IsValid(ply) then
shouldShow = hook.Call("OnPlayerChat", GAMEMODE, ply, text, false, not ply:Alive(), prefixText, col1, col2)
end
if shouldShow ~= true then
chat.AddNonParsedText(col1, prefixText, col2, ": " .. text)
end
else
shouldShow = hook.Call("ChatText", GAMEMODE, "0", prefixText, prefixText, "darkrp")
if shouldShow ~= true then
chat.AddNonParsedText(col1, prefixText)
end
end
chat.PlaySound()
end
net.Receive("DarkRP_Chat", AddToChat)
--[[---------------------------------------------------------------------------
Credits
Please only ADD to the credits.
---------------------------------------------------------------------------]]
local creds =
[[
LightRP was created by Rick darkalonio. LightRP was sandbox with some added RP elements.
LightRP was released at the end of January 2007
DarkRP was created as a spoof of LightRP by Rickster, somewhere during the summer of 2007.
Note: There was a DarkRP in 2006, but that was an entirely different gamemode.
Rickster went to serve his country and went to Afghanistan. During that time, the following people updated DarkRP:
Picwizdan
Sibre
[GNC] Matt
PhilXYZ
Chromebolt A.K.A. Unib5 (STEAM_0:1:19045957)
In 2008, Unib5 was administrator on a DarkRP server called EuroRP, owned by Jiggu. FPtje frequently joined this server to prop kill en masse. While Jiggu loved watching the chaos unfold, Unib5 hated it and banned FPtje on sight. Since Jiggu kept unbanning FPtje, Unib5 felt powerless. In an attempt to stop FPtje, Unib5 put FPtje's favourite prop killing props (the locker and the sawblade) in the default blacklist of DarkRP in an update. This in turn enraged FPtje, as he swore to make an update in secret that would suddenly pop up and overthrow the established version. As a result, DarkRP 2.3.1 was released in December 2008. After a bit of a fight, FPtje became the official updater of DarkRP.
Current developer:
Falco A.K.A. FPtje Atheos (STEAM_0:0:8944068)
People who have contributed (ordered by commits, with at least two commits)
Bo98
Drakehawke (STEAM_0:0:22342869) (64 commits on old SVN)
FiG-Scorn
Noiwex
KoZ
Eusion (STEAM_0:0:20450406) (3 commits on old SVN)
Gangleider
MattWalton12
TypicalRookie
]]
local function credits(um)
chat.AddNonParsedText(Color(255, 0, 0, 255), "[", Color(50,50,50,255), GAMEMODE.Name, Color(255, 0, 0, 255), "] ", color_white, DarkRP.getPhrase("credits_see_console"))
MsgC(Color(255, 0, 0, 255), DarkRP.getPhrase("credits_for", GAMEMODE.Name), color_white, creds)
end
usermessage.Hook("DarkRP_Credits", credits)

View File

@@ -0,0 +1,207 @@
--[[---------------------------------------------------------------------------
This module finds out for you who can see you talk or speak through the microphone
---------------------------------------------------------------------------]]
--[[---------------------------------------------------------------------------
Variables
---------------------------------------------------------------------------]]
local receivers
local currentChatText = {}
local receiverConfigs = {}
local currentConfig = {text = "", hearFunc = fn.Id} -- Default config is not loaded yet
--[[---------------------------------------------------------------------------
addChatReceiver
Add a chat command with specific receivers
prefix: the chat command itself ("/pm", "/ooc", "/me" are some examples)
text: the text that shows up when it says "Some people can hear you X"
hearFunc: a function(ply, splitText) that decides whether this player can or cannot hear you.
return true if the player can hear you
false if the player cannot
nil if you want to prevent the text from showing up temporarily
---------------------------------------------------------------------------]]
function DarkRP.addChatReceiver(prefix, text, hearFunc)
receiverConfigs[prefix] = {
text = text,
hearFunc = hearFunc
}
end
--[[---------------------------------------------------------------------------
removeChatReceiver
Remove a chat command.
prefix: the command, like in addChatReceiver
---------------------------------------------------------------------------]]
function DarkRP.removeChatReceiver(prefix)
receiverConfigs[prefix] = nil
end
--[[---------------------------------------------------------------------------
Draw the results to the screen
---------------------------------------------------------------------------]]
local function drawChatReceivers()
if not receivers then return end
local fontHeight = draw.GetFontHeight("DarkRPHUD1")
local x, y = chat.GetChatBoxPos()
y = y - fontHeight - 4
local receiversCount = #receivers
-- No one hears you
if receiversCount == 0 then
draw.WordBox(2, x, y, DarkRP.getPhrase("hear_noone", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(255,0,0,255))
return
-- Everyone hears you
elseif receiversCount == player.GetCount() - 1 then
draw.WordBox(2, x, y, DarkRP.getPhrase("hear_everyone"), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255))
return
end
draw.WordBox(2, x, y - (receiversCount * (fontHeight + 4)), DarkRP.getPhrase("hear_certain_persons", currentConfig.text), "DarkRPHUD1", Color(0,0,0,160), Color(0,255,0,255))
for i = 1, receiversCount, 1 do
if not IsValid(receivers[i]) then
receivers[i] = receivers[#receivers]
receivers[#receivers] = nil
continue
end
draw.WordBox(2, x, y - (i - 1) * (fontHeight + 4), receivers[i]:Nick(), "DarkRPHUD1", Color(0, 0, 0, 160), color_white)
end
end
--[[---------------------------------------------------------------------------
Find out who could hear the player if they were to speak now
---------------------------------------------------------------------------]]
local function chatGetRecipients()
if not currentConfig then return end
receivers = {}
for _, ply in ipairs(player.GetAll()) do
local hidePly = hook.Run("chatHideRecipient", ply)
if not IsValid(ply) or ply == LocalPlayer() or ply:GetNoDraw() or hidePly then continue end
local val = currentConfig.hearFunc(ply, currentChatText)
-- Return nil to disable the chat recipients temporarily.
if val == nil then
receivers = nil
return
elseif val == true then
table.insert(receivers, ply)
end
end
end
--[[---------------------------------------------------------------------------
Called when the player starts typing
---------------------------------------------------------------------------]]
local function startFind()
local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers")
if shouldDraw == false then return end
currentConfig = receiverConfigs[""]
hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients)
hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers)
end
hook.Add("StartChat", "DarkRP_StartFindChatReceivers", startFind)
--[[---------------------------------------------------------------------------
Called when the player stops typing
---------------------------------------------------------------------------]]
local function stopFind()
hook.Remove("Think", "DarkRP_chatRecipients")
hook.Remove("HUDPaint", "DarkRP_DrawChatReceivers")
end
hook.Add("FinishChat", "DarkRP_StopFindChatReceivers", stopFind)
--[[---------------------------------------------------------------------------
Find out which chat command the user is typing
---------------------------------------------------------------------------]]
local function findConfig(text)
local split = string.Explode(' ', text)
local prefix = string.lower(split[1])
currentChatText = split
currentConfig = receiverConfigs[prefix] or receiverConfigs[""]
end
hook.Add("ChatTextChanged", "DarkRP_FindChatRecipients", findConfig)
--[[---------------------------------------------------------------------------
Default chat receievers. If you want to add your own ones, don't add them to this file. Add them to a clientside module file instead.
---------------------------------------------------------------------------]]
-- Load after the custom languages have been loaded
local function loadChatReceivers()
-- Default talk chat receiver has no prefix
DarkRP.addChatReceiver("", DarkRP.getPhrase("talk"), function(ply)
if GAMEMODE.Config.alltalk then return nil end
return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) <
GAMEMODE.Config.talkDistance * GAMEMODE.Config.talkDistance
end)
DarkRP.addChatReceiver("/ooc", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
DarkRP.addChatReceiver("//", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
DarkRP.addChatReceiver("/a", DarkRP.getPhrase("speak_in_ooc"), function(ply) return true end)
DarkRP.addChatReceiver("/w", DarkRP.getPhrase("whisper"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.whisperDistance * GAMEMODE.Config.whisperDistance end)
DarkRP.addChatReceiver("/y", DarkRP.getPhrase("yell"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.yellDistance * GAMEMODE.Config.yellDistance end)
DarkRP.addChatReceiver("/me", DarkRP.getPhrase("perform_your_action"), function(ply) return LocalPlayer():GetPos():DistToSqr(ply:GetPos()) < GAMEMODE.Config.meDistance * GAMEMODE.Config.meDistance end)
DarkRP.addChatReceiver("/g", DarkRP.getPhrase("talk_to_your_group"), function(ply)
for _, func in pairs(GAMEMODE.DarkRPGroupChats) do
if func(LocalPlayer()) and func(ply) then
return true
end
end
return false
end)
DarkRP.addChatReceiver("/pm", "PM", function(ply, text)
if not isstring(text[2]) then return false end
text[2] = string.lower(tostring(text[2]))
return string.find(string.lower(ply:Nick()), text[2], 1, true) ~= nil or
string.find(string.lower(ply:SteamName()), text[2], 1, true) ~= nil or
string.lower(ply:SteamID()) == text[2]
end)
--[[---------------------------------------------------------------------------
Voice chat receivers
---------------------------------------------------------------------------]]
local voiceDistance = GM.Config.voiceDistance * GM.Config.voiceDistance
DarkRP.addChatReceiver("speak", DarkRP.getPhrase("speak"), function(ply)
if not LocalPlayer().DRPIsTalking then return nil end
if LocalPlayer():GetPos():DistToSqr(ply:GetPos()) > voiceDistance then return false end
return not GAMEMODE.Config.dynamicvoice or ply:isInRoom()
end)
end
hook.Add("loadCustomDarkRPItems", "loadChatListeners", loadChatReceivers)
--[[---------------------------------------------------------------------------
Called when the player starts using their voice
---------------------------------------------------------------------------]]
local function startFindVoice(ply)
if ply ~= LocalPlayer() then return end
local shouldDraw = hook.Call("HUDShouldDraw", GAMEMODE, "DarkRP_ChatReceivers")
if shouldDraw == false then return end
currentConfig = receiverConfigs["speak"]
hook.Add("Think", "DarkRP_chatRecipients", chatGetRecipients)
hook.Add("HUDPaint", "DarkRP_DrawChatReceivers", drawChatReceivers)
end
hook.Add("PlayerStartVoice", "DarkRP_VoiceChatReceiverFinder", startFindVoice)
--[[---------------------------------------------------------------------------
Called when the player stops using their voice
---------------------------------------------------------------------------]]
local function stopFindVoice(ply)
if ply ~= LocalPlayer() then return end
stopFind()
end
hook.Add("PlayerEndVoice", "DarkRP_VoiceChatReceiverFinder", stopFindVoice)

View File

@@ -0,0 +1,56 @@
DarkRP.addChatReceiver = DarkRP.stub{
name = "addChatReceiver",
description = "Add a chat command with specific receivers",
parameters = {
{
name = "prefix",
description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)",
type = "string",
optional = false
},
{
name = "text",
description = "The text that shows up when it says \"Some people can hear you X\"",
type = "string",
optional = false
},
{
name = "hearFunc",
description = "A function(ply, splitText) that decides whether this player can or cannot hear you.",
type = "function",
optional = false
}
},
returns = {},
metatable = DarkRP
}
DarkRP.removeChatReceiver = DarkRP.stub{
name = "removeChatReceiver",
description = "Remove a chat command receiver",
parameters = {
{
name = "prefix",
description = "The chat command itself (\"/pm\", \"/ooc\", \"/me\" are some examples)",
type = "string",
optional = false
}
},
returns = {},
metatable = DarkRP
}
DarkRP.hookStub{
name = "chatHideRecipient",
description = "Hide a receipent from who can hear/see your text GUI.",
parameters = {
{
name = "ply",
description = "The player who spoke.",
type = "Player"
}
},
returns = {
}
}

View File

@@ -0,0 +1,216 @@
local plyMeta = FindMetaTable("Player")
DarkRP.chatCommands = DarkRP.chatCommands or {}
local validChatCommand = {
command = isstring,
description = isstring,
condition = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isfunction},
delay = isnumber,
tableArgs = fn.FOr{fn.Curry(fn.Eq, 2)(nil), isbool},
}
local checkChatCommand = function(tbl)
for k in pairs(validChatCommand) do
if not validChatCommand[k](tbl[k]) then
return false, k
end
end
return true
end
function DarkRP.declareChatCommand(tbl)
local valid, element = checkChatCommand(tbl)
if not valid then
DarkRP.error("Incorrect chat command! " .. element .. " is invalid!", 2)
end
tbl.command = string.lower(tbl.command)
DarkRP.chatCommands[tbl.command] = DarkRP.chatCommands[tbl.command] or tbl
for k, v in pairs(tbl) do
DarkRP.chatCommands[tbl.command][k] = v
end
end
function DarkRP.removeChatCommand(command)
DarkRP.chatCommands[string.lower(command)] = nil
end
function DarkRP.chatCommandAlias(command, ...)
local name
for k, v in ipairs{...} do
name = string.lower(v)
DarkRP.chatCommands[name] = {command = name}
setmetatable(DarkRP.chatCommands[name], {
__index = DarkRP.chatCommands[command]
})
end
end
function DarkRP.getChatCommand(command)
return DarkRP.chatCommands[string.lower(command)]
end
function DarkRP.getChatCommands()
return DarkRP.chatCommands
end
function DarkRP.getSortedChatCommands()
local tbl = fn.Compose{table.ClearKeys, table.Copy, DarkRP.getChatCommands}()
table.SortByMember(tbl, "command", true)
return tbl
end
-- chat commands that have been defined, but not declared
DarkRP.getIncompleteChatCommands = fn.Curry(fn.Filter, 3)(fn.Compose{fn.Not, checkChatCommand})(DarkRP.chatCommands)
--[[---------------------------------------------------------------------------
Chat commands
---------------------------------------------------------------------------]]
DarkRP.declareChatCommand{
command = "pm",
description = "Send a private message to someone.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "w",
description = "Say something in whisper voice.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "y",
description = "Yell something out loud.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "me",
description = "Chat roleplay to say you're doing things that you can't show otherwise.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "/",
description = "Global server chat.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "a",
description = "Global server chat.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "ooc",
description = "Global server chat.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "broadcast",
description = "Broadcast something as a mayor.",
delay = 1.5,
condition = plyMeta.isMayor
}
DarkRP.declareChatCommand{
command = "channel",
description = "Tune into a radio channel.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "radio",
description = "Say something through the radio.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "g",
description = "Group chat.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "credits",
description = "Send the DarkRP credits to someone.",
delay = 1.5
}
local function init()
if not DarkRP then
MsgC(Color(255,0,0), "DarkRP Classic Advert tried to run, but DarkRP wasn't declared!\n")
return
end
DarkRP.removeChatCommand("advert")
DarkRP.declareChatCommand({
command = "advert",
description = "Displays an advertisement to everyone in chat.",
delay = 1.5
})
if SERVER then
DarkRP.defineChatCommand("advert",function(ply,args)
if args == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", ""))
return ""
end
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", "argument", ""))
return
end
for k,v in pairs(player.GetAll()) do
local col = team.GetColor(ply:Team())
DarkRP.talkToPerson(v, col, "[Реклама] " .. ply:Nick(), Color(255, 255, 0, 255), text, ply)
end
end
hook.Call("playerAdverted", nil, ply, args)
return args, DoSay
end, 1.5)
else
DarkRP.addChatReceiver("/advert", "advertise", function(ply) return true end)
end
end
if SERVER then
if #player.GetAll() > 0 then
init()
else
hook.Add("PlayerInitialSpawn", "dfca-load", init)
end
else
hook.Add("InitPostEntity", "dfca-load", init)
end
if SERVER then
local function Roll(ply, args)
local DoSay = function()
if GAMEMODE.Config.alltalk then
for _, target in pairs(player.GetAll()) do
DarkRP.talkToPerson(target, team.GetColor(ply:Team()), ply:Nick().. " выпало " ..math.random(1,100).." из 100.")
end
else
DarkRP.talkToRange(ply, ply:Nick().. " выпало " ..math.random(1,100).." из 100.", "", 250)
end
end
return args, DoSay
end
DarkRP.defineChatCommand("roll", Roll, 1.5)
DarkRP.declareChatCommand{
command = "roll",
description = "write an roll",
delay = 1.5
}
end

View File

@@ -0,0 +1,121 @@
DarkRP.declareChatCommand = DarkRP.stub{
name = "declareChatCommand",
description = "Declare a chat command (describe it)",
parameters = {
{
name = "table",
description = "The description of the chat command. Has to contain a string: command, string: description, number: delay, optional function: condition",
type = "table",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.removeChatCommand = DarkRP.stub{
name = "removeChatCommand",
description = "Remove a chat command",
parameters = {
{
name = "command",
description = "The chat command to remove",
type = "string",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.chatCommandAlias = DarkRP.stub{
name = "chatCommandAlias",
description = "Create an alias for a chat command",
parameters = {
{
name = "command",
description = "An already existing chat command.",
type = "string",
optional = false
},
{
name = "alias",
description = "One or more aliases for the chat command.",
type = "vararg",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.getChatCommand = DarkRP.stub{
name = "getChatCommand",
description = "Get the information on a chat command.",
parameters = {
{
name = "command",
description = "The chat command",
type = "string",
optional = false
}
},
returns = {
{
name = "chatTable",
description = "A table containing the information of the chat command.",
type = "table"
}
},
metatable = DarkRP
}
DarkRP.getChatCommands = DarkRP.stub{
name = "getChatCommands",
description = "Get every chat command.",
parameters = {
},
returns = {
{
name = "commands",
description = "A table containing every command. Table indices are the command strings.",
type = "table"
}
},
metatable = DarkRP
}
DarkRP.getSortedChatCommands = DarkRP.stub{
name = "getSortedChatCommands",
description = "Get every chat command, sorted by their name.",
parameters = {
},
returns = {
{
name = "commands",
description = "A table containing every command.",
type = "table"
}
},
metatable = DarkRP
}
DarkRP.getIncompleteChatCommands = DarkRP.stub{
name = "getIncompleteChatCommands",
description = "chat commands that have been defined, but not declared. Information about these chat commands is missing.",
parameters = {
},
returns = {
{
name = "commands",
description = "A table containing the undeclared chat commands.",
type = "table"
}
},
metatable = DarkRP
}

View File

@@ -0,0 +1,183 @@
local function registerCommandDefinition(cmd, callback)
local chatcommands = DarkRP.getChatCommands()
chatcommands[cmd] = chatcommands[cmd] or {}
chatcommands[cmd].callback = callback
chatcommands[cmd].command = chatcommands[cmd].command or cmd
end
function DarkRP.defineChatCommand(cmd, callback)
cmd = string.lower(cmd)
local detour = function(ply, arg, ...)
local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg, ...)
if not canChatCommand then
return ""
end
local ret = {callback(ply, arg, ...)}
local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, ret, ...)
if overrideTxt then return overrideTxt, overrideDoSayFunc end
return unpack(ret)
end
registerCommandDefinition(cmd, detour)
end
function DarkRP.definePrivilegedChatCommand(cmd, priv, callback, extraInfoTbl)
cmd = string.lower(cmd)
local function onCAMIResult(ply, arg, hasAccess, reason)
if hasAccess then return callback(ply, arg) end
local notify = ply:EntIndex() == 0 and print or fp{DarkRP.notify, ply, 1, 4}
notify(DarkRP.getPhrase("no_privilege"))
end
local function callbackdetour(ply, arg, ...)
local canChatCommand = gamemode.Call("canChatCommand", ply, cmd, arg)
if not canChatCommand then
return ""
end
CAMI.PlayerHasAccess(ply, priv, fp{onCAMIResult, ply, arg}, nil, extraInfoTbl)
local overrideTxt, overrideDoSayFunc = hook.Run("onChatCommand", ply, cmd, arg, {""})
if overrideTxt then return overrideTxt, overrideDoSayFunc end
return ""
end
registerCommandDefinition(cmd, callbackdetour)
end
local function RP_PlayerChat(ply, text, teamonly)
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. "): " .. text)
local callback = ""
local DoSayFunc
local groupSay = DarkRP.getChatCommand("g")
-- Extract the chat command
local tblCmd = fn.Compose{
DarkRP.getChatCommand,
string.lower,
fn.Curry(fn.Flip(string.sub), 2)(2), -- extract prefix
fn.Curry(fn.GetValue, 2)(1), -- Get the first word
fn.Curry(string.Explode, 2)(' ') -- split by spaces
}(text)
if string.sub(text, 1, 1) == GAMEMODE.Config.chatCommandPrefix and tblCmd then
local args = string.sub(text, string.len(tblCmd.command) + 3, string.len(text))
args = tblCmd.tableArgs and DarkRP.explodeArg(args) or args
ply.DrpCommandDelays = ply.DrpCommandDelays or {}
if tblCmd.delay and ply.DrpCommandDelays[tblCmd.command] and ply.DrpCommandDelays[tblCmd.command] > CurTime() - tblCmd.delay then
return ""
end
ply.DrpCommandDelays[tblCmd.command] = CurTime()
callback, DoSayFunc = tblCmd.callback(ply, args)
if callback == "" then
return "", "", DoSayFunc
end
text = string.sub(text, string.len(tblCmd.command) + 3, string.len(text))
elseif teamonly and groupSay then
callback, DoSayFunc = groupSay.callback(ply, text)
return text, "", DoSayFunc
end
if callback ~= "" then
callback = callback or "" .. " "
end
return text, callback, DoSayFunc;
end
local function RP_ActualDoSay(ply, text, callback)
callback = callback or ""
if text == "" then return "" end
local col = team.GetColor(ply:Team())
local col2 = color_white
if not ply:Alive() then
col2 = Color(255, 200, 200, 255)
col = col2
end
if GAMEMODE.Config.alltalk then
local name = ply:Nick()
for _, v in ipairs(player.GetAll()) do
DarkRP.talkToPerson(v, col, callback .. name, col2, text, ply)
end
else
DarkRP.talkToRange(ply, callback .. ply:Nick(), text, GAMEMODE.Config.talkDistance)
end
return ""
end
function GM:canChatCommand(ply, cmd, ...)
if not ply.DarkRPUnInitialized then return true end
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_one"))
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("data_not_loaded_two"))
return false
end
g_DarkRPOldHookCall = g_DarkRPOldHookCall or hook.Call
local GM = GM
function hook.Call(name, gm, ply, text, teamonly, ...)
if name == "PlayerSay" then
local dead = not ply:Alive()
local text2 = text
local callback
local DoSayFunc
text2 = g_DarkRPOldHookCall(name, gm, ply, text, teamonly, dead) or text2
text2, callback, DoSayFunc = RP_PlayerChat(ply, text2, teamonly)
if tostring(text2) == " " then text2, callback = callback, text2 end
if not GM.Config.deadtalk and dead then return "" end
if game.IsDedicated() then
ServerLog("\"" .. ply:Nick() .. "<" .. ply:UserID() .. ">" .. "<" .. ply:SteamID() .. ">" .. "<" .. team.GetName(ply:Team()) .. ">\" say \"" .. text .. "\"\n" .. "\n")
end
if DoSayFunc then DoSayFunc(text2) return "" end
RP_ActualDoSay(ply, text2, callback)
hook.Call("PostPlayerSay", nil, ply, text2, teamonly, dead)
return ""
end
return g_DarkRPOldHookCall(name, gm, ply, text, teamonly, ...)
end
local function ConCommand(ply, _, args)
if not args[1] then return end
local cmd = string.lower(args[1])
local tbl = DarkRP.getChatCommand(cmd)
if not tbl then return end
table.remove(args, 1) -- Remove subcommand
local arg = tbl.tableArgs and args or table.concat(args, ' ')
local time = CurTime()
if not tbl then return end
ply.DrpCommandDelays = ply.DrpCommandDelays or {}
if IsValid(ply) then -- Server console isn't valid
if tbl.delay and ply.DrpCommandDelays[cmd] and ply.DrpCommandDelays[cmd] > time - tbl.delay then
return
end
ply.DrpCommandDelays[cmd] = time
end
tbl.callback(ply, arg)
end
concommand.Add("darkrp", ConCommand)

View File

@@ -0,0 +1,241 @@
--[[---------------------------------------------------------
Talking
---------------------------------------------------------]]
local function PM(ply, args)
local namepos = string.find(args, " ")
if not namepos then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local name = string.sub(args, 1, namepos - 1)
local msg = string.sub(args, namepos + 1)
if msg == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local target = DarkRP.findPlayer(name)
if target == ply then return "" end
if target then
local col = team.GetColor(ply:Team())
local pname = ply:Nick()
local col2 = color_white
DarkRP.talkToPerson(target, col, "(PM) " .. pname, col2, msg, ply)
DarkRP.talkToPerson(ply, col, "(PM) " .. pname, col2, msg, ply)
else
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(name)))
end
return ""
end
DarkRP.defineChatCommand("pm", PM, 1.5)
local function Whisper(ply, args)
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("whisper") .. ") " .. ply:Nick(), text, GAMEMODE.Config.whisperDistance)
end
return args, DoSay
end
DarkRP.defineChatCommand("w", Whisper, 1.5)
local function Yell(ply, args)
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
DarkRP.talkToRange(ply, "(" .. DarkRP.getPhrase("yell") .. ") " .. ply:Nick(), text, GAMEMODE.Config.yellDistance)
end
return args, DoSay
end
DarkRP.defineChatCommand("y", Yell, 1.5)
local function Me(ply, args)
if args == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
if GAMEMODE.Config.alltalk then
local col = team.GetColor(ply:Team())
local name = ply:Nick()
for _, target in ipairs(player.GetAll()) do
DarkRP.talkToPerson(target, col, name .. " " .. text)
end
else
DarkRP.talkToRange(ply, ply:Nick() .. " " .. text, "", GAMEMODE.Config.meDistance)
end
end
return args, DoSay
end
DarkRP.defineChatCommand("me", Me, 1.5)
local function OOC(ply, args)
if not GAMEMODE.Config.ooc then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("disabled", DarkRP.getPhrase("ooc"), ""))
return ""
end
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local col = team.GetColor(ply:Team())
local col2 = color_white
if not ply:Alive() then
col2 = Color(255, 200, 200, 255)
col = col2
end
local phrase = DarkRP.getPhrase("ooc")
local name = ply:Nick()
for _, v in ipairs(player.GetAll()) do
DarkRP.talkToPerson(v, col, "(" .. phrase .. ") " .. name, col2, text, ply)
end
end
return args, DoSay
end
DarkRP.defineChatCommand("/", OOC, true, 1.5)
DarkRP.defineChatCommand("a", OOC, true, 1.5)
DarkRP.defineChatCommand("ooc", OOC, true, 1.5)
local function MayorBroadcast(ply, args)
if args == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local Team = ply:Team()
if not RPExtraTeams[Team] or not RPExtraTeams[Team].mayor then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("incorrect_job", DarkRP.getPhrase("broadcast")))
return ""
end
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return
end
local col = team.GetColor(ply:Team())
local col2 = Color(170, 0, 0, 255)
local phrase = DarkRP.getPhrase("broadcast")
local name = ply:Nick()
for _, v in ipairs(player.GetAll()) do
DarkRP.talkToPerson(v, col, phrase .. " " .. name, col2, text, ply)
end
end
return args, DoSay
end
DarkRP.defineChatCommand("broadcast", MayorBroadcast, 1.5)
local function SetRadioChannel(ply,args)
local channel = DarkRP.toInt(args)
if channel == nil or channel < 0 or channel > 100 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), "0<" .. DarkRP.getPhrase("channel") .. "<100"))
return ""
end
DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("channel_set_to_x", args))
ply.RadioChannel = channel
return ""
end
DarkRP.defineChatCommand("channel", SetRadioChannel)
local function SayThroughRadio(ply,args)
if not ply.RadioChannel then ply.RadioChannel = 1 end
local radioChannel = ply.RadioChannel
if not args or args == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return ""
end
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return
end
local col = Color(180, 180, 180, 255)
local phrase = DarkRP.getPhrase("radio_x", radioChannel)
for _, v in ipairs(player.GetAll()) do
if v.RadioChannel == radioChannel then
DarkRP.talkToPerson(v, col, phrase, col, text, ply)
end
end
end
return args, DoSay
end
DarkRP.defineChatCommand("radio", SayThroughRadio, 1.5)
local function GroupMsg(ply, args)
local DoSay = function(text)
if text == "" then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("invalid_x", DarkRP.getPhrase("arguments"), ""))
return
end
local col = team.GetColor(ply:Team())
local groupChats = {}
for _, func in pairs(GAMEMODE.DarkRPGroupChats) do
-- not the group of the player
if not func(ply) then continue end
table.insert(groupChats, func)
end
if table.IsEmpty(groupChats) then return "" end
local phrase = DarkRP.getPhrase("group")
local name = ply:Nick()
local color = color_white
for _, target in ipairs(player.GetAll()) do
-- The target is in any of the group chats
for _, func in ipairs(groupChats) do
if not func(target, ply) then continue end
DarkRP.talkToPerson(target, col, phrase .. " " .. name, color, text, ply)
break
end
end
end
return args, DoSay
end
DarkRP.defineChatCommand("g", GroupMsg, 0)
-- here's the new easter egg. Easier to find, more subtle, doesn't only credit FPtje and unib5
-- WARNING: DO NOT EDIT THIS
-- You can edit DarkRP but you HAVE to credit the original authors!
-- You even have to credit all the previous authors when you rename the gamemode.
-- local CreditsWait = true
local function GetDarkRPAuthors(ply, args)
local target = DarkRP.findPlayer(args) -- Only send to one player. Prevents spamming
target = IsValid(target) and target or ply
if target ~= ply then
if ply.CreditsWait then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("wait_with_that")) return "" end
ply.CreditsWait = true
timer.Simple(60, function() if IsValid(ply) then ply.CreditsWait = nil end end) -- so people don't spam it
end
local rf = RecipientFilter()
rf:AddPlayer(target)
if ply ~= target then
rf:AddPlayer(ply)
end
umsg.Start("DarkRP_Credits", rf)
umsg.End()
return ""
end
DarkRP.defineChatCommand("credits", GetDarkRPAuthors, 50)

View File

@@ -0,0 +1,145 @@
DarkRP.defineChatCommand = DarkRP.stub{
name = "defineChatCommand",
description = "Create a chat command that calls the function",
parameters = {
{
name = "chat command",
description = "The registered chat command",
type = "string",
optional = false
},
{
name = "callback",
description = "The function that is called when the chat command is executed",
type = "function",
optional = false
}
},
returns = {},
metatable = DarkRP
}
DarkRP.definePrivilegedChatCommand = DarkRP.stub{
name = "definePrivilegedChatCommand",
description = "Create a chat command that calls the function if the player has the right CAMI privilege. Will automatically notify the user when they don't have access. Note that chat command functions registered with this function can NOT override the chat that will appear after the command has been executed.",
parameters = {
{
name = "chat command",
description = "The registered chat command",
type = "string",
optional = false
},
{
name = "privilege",
description = "The name of the CAMI privilege",
type = "string",
optional = false
},
{
name = "callback",
description = "The function that is called when the chat command is executed",
type = "function",
optional = false
}
},
returns = {},
metatable = DarkRP
}
DarkRP.hookStub{
name = "PostPlayerSay",
description = "Called after a player has said something.",
parameters = {
{
name = "ply",
description = "The player who spoke.",
type = "Player"
},
{
name = "text",
description = "The thing they said.",
type = "string"
},
{
name = "teamonly",
description = "Whether they said it to their team only.",
type = "boolean"
},
{
name = "dead",
description = "Whether they are dead.",
type = "boolean"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "canChatCommand",
description = "Called when a player tries to run any chat command or uses the DarkRP console command. ",
parameters = {
{
name = "ply",
description = "The player who spoke.",
type = "Player"
},
{
name = "command",
description = "The thing they said.",
type = "string"
},
{
name = "arguments",
description = "The arguments of the chat command, given as one string.",
type = "string"
}
},
returns = {
{
name = "canChatCommand",
description = "Whether the player is allowed to run the chat command.",
type = "boolean"
},
}
}
DarkRP.hookStub{
name = "onChatCommand",
description = "Called after a player has run any chat command or uses the DarkRP console command. Note: the chat command has already been run. Use canChatCommand if you want to stop chat commands from being run.",
parameters = {
{
name = "ply",
description = "The player who spoke.",
type = "Player"
},
{
name = "command",
description = "The thing they said.",
type = "string"
},
{
name = "arguments",
description = "The arguments of the chat command, given either as one string or a table of strings. That depends on whether the command is declared to use table arguments.",
type = "string"
},
{
name = "return",
description = "The return value of the chat command function. Should contain a chat text override and/or a say function. See the return values of this hook for a description",
type = "table"
}
},
returns = {
{
name = "overrideText",
description = "Overrides the text a chat command will put in everyone's chat box. Return nil to not change behaviour.",
type = "string"
},
{
name = "overrideSayFunc",
description = "Say functions handle what needs to be said to whom. The say function for PMs for example make sure only the sender and receiver see the message. You can override this behaviour by returning a different say function in this hook. Return nil to not change behaviour.",
type = "function"
},
}
}

View File

@@ -0,0 +1,70 @@
local function drawIndicator(ply)
if not ply:IsTyping() then
if ply.indicator then
ply.indicator:Remove()
ply.indicator = nil
end
return
end
local chatIndicator = hook.Call("DrawChatIndicator", nil, ply)
if chatIndicator == true then return end
local indicator = ply.indicator
if not IsValid(indicator) then
indicator = ClientsideModel("models/extras/info_speech.mdl", RENDERGROUP_OPAQUE)
if not IsValid(indicator) then return end -- In case the non networked entity limit is hit (still prints a red error message, but doesn't spam client console with lua errors)
ply.indicator = indicator
end
indicator:SetNoDraw(true)
indicator:SetModelScale(0.6)
local ragdoll = ply:GetRagdollEntity()
if IsValid(ragdoll) then
local maxs = ragdoll:OBBMaxs()
indicator:SetPos(ragdoll:GetPos() + Vector(0, 0, maxs.z) + Vector(0, 0, 12))
else
indicator:SetPos(ply:GetPos() + Vector(0, 0, 72 * ply:GetModelScale()) + Vector(0, 0, 12))
end
local curTime = CurTime()
local angle = indicator:GetAngles()
angle.y = (angle.y + (360 * (curTime - (indicator.lastDraw or 0)))) % 360
indicator:SetAngles(angle)
indicator.lastDraw = curTime
indicator:SetupBones()
indicator:DrawModel()
end
hook.Add("PostPlayerDraw", "DarkRP_ChatIndicator", drawIndicator)
hook.Add("CreateClientsideRagdoll", "DarkRP_ChatIndicator", function(ent, ragdoll)
if not ent:IsPlayer() then return end
local oldRenderOverride = ragdoll.RenderOverride -- Just in case - best be safe
ragdoll.RenderOverride = function(self)
if ent:IsValid() then
drawIndicator(ent)
end
if oldRenderOverride then
oldRenderOverride(self)
else
self:DrawModel()
end
end
end)
-- CSEnts aren't GC'd.
-- https://github.com/Facepunch/garrysmod-issues/issues/1387
gameevent.Listen("player_disconnect")
hook.Add("player_disconnect", "DarkRP_ChatIndicator", function(data)
local ply = Player(data.userid)
if not IsValid(ply) then return end -- disconnected while joining
if ply.indicator then
ply.indicator:Remove()
ply.indicator = nil
end
end)

View File

@@ -0,0 +1,18 @@
DarkRP.hookStub{
name = "DrawChatIndicator",
description = "Call when the Chat Indicator is drawn. Return to overwrite.",
parameters = {
{
name = "ply",
description = "The player the indicator should be drawn for.",
type = "Player"
}
},
returns = {
{
name = "override",
description = "Return true in your hook to disable the default drawing.",
type = "boolean"
}
}
}

View File

@@ -0,0 +1,377 @@
-- This module will make voice sounds play when certain words are typed in the chat
-- You can add/remove sounds as you wish using DarkRP.setChatSound, just follow the format used here
-- To disable them completely, set GM.Config.chatsounds to false
-- TODO: Add female sounds & detect gender of model, and use combine sounds for CPs
local sounds = {}
sounds["ammo"] = {"vo/npc/male01/ammo03.wav", "vo/npc/male01/ammo04.wav", "vo/npc/male01/ammo05.wav"}
sounds["behind you"] = {"vo/npc/male01/behindyou01.wav", "vo/npc/male01/behindyou02.wav"}
sounds["better reload"] = {"vo/npc/male01/youdbetterreload01.wav"}
sounds["bullshit"] = {"vo/npc/male01/question26.wav"}
sounds["bull shit"] = sounds["bullshit"]
sounds["cheese"] = {"vo/npc/male01/question06.wav"}
sounds["combine"] = {"vo/npc/male01/combine01.wav", "vo/npc/male01/combine02.wav"}
sounds["coming"] = {"vo/npc/male01/squad_approach04.wav"}
sounds["cops"] = {"vo/npc/male01/civilprotection01.wav", "vo/npc/male01/civilprotection02.wav", "vo/npc/male01/cps01.wav", "vo/npc/male01/cps02.wav"}
sounds["cp"] = sounds["cops"]
sounds["cps"] = sounds["cops"]
sounds["cut it"] = {"vo/trainyard/male01/cit_hit01.wav", "vo/trainyard/male01/cit_hit02.wav", "vo/trainyard/male01/cit_hit03.wav", "vo/trainyard/male01/cit_hit04.wav", "vo/trainyard/male01/cit_hit05.wav"}
sounds["dont tell me"] = {"vo/npc/male01/gordead_ans03.wav"}
sounds["de ja vu"] = {"vo/npc/male01/question05.wav"}
sounds["dejavu"] = sounds["de ja vu"]
sounds["excuse me"] = {"vo/npc/male01/excuseme01.wav", "vo/npc/male01/excuseme02.wav"}
sounds["fantastic"] = {"vo/npc/male01/fantastic01.wav", "vo/npc/male01/fantastic02.wav"}
sounds["figures"] = {"vo/npc/male01/answer03.wav"}
sounds["finally"] = {"vo/npc/male01/finally.wav"}
sounds["follow"] = {"vo/coast/odessa/male01/stairman_follow01.wav", "vo/npc/male01/squad_away03.wav", "vo/coast/cardock/le_followme.wav"}
sounds["focus"] = {"vo/npc/male01/answer18.wav", "vo/npc/male01/answer19.wav"}
sounds["freeman"] = {"vo/npc/male01/freeman.wav", "vo/npc/male01/docfreeman01.wav", "vo/npc/male01/docfreeman02.wav"}
sounds["get down"] = {"vo/npc/male01/getdown02.wav"}
sounds["get in"] = {"vo/canals/gunboat_getin.wav"}
sounds["get out"] = {"vo/npc/male01/gethellout.wav"}
sounds["good god"] = {"vo/npc/male01/goodgod.wav", "vo/npc/male01/gordead_ans04.wav"}
sounds["gosh"] = sounds["good god"]
sounds["got one"] = {"vo/npc/male01/gotone01.wav", "vo/npc/male01/gotone01.wav"}
sounds["gotta reload"] = {"vo/npc/male01/gottareload01.wav"}
sounds["gtfo"] = sounds["get out"]
sounds["hacks"] = {"vo/npc/male01/hacks01.wav", "vo/npc/male01/hacks02.wav", "vo/npc/male01/thehacks01.wav", "vo/npc/male01/thehacks02.wav"}
sounds["hax"] = sounds["hacks"]
sounds["haxx"] = sounds["hacks"]
sounds["help"] = {"vo/npc/male01/help01.wav"}
sounds["here they come"] = {"vo/npc/male01/heretheycome01.wav", "vo/npc/male01/incoming02.wav"}
sounds["hello"] = {"vo/npc/male01/hi01.wav", "vo/npc/male01/hi02.wav"}
sounds["hey"] = sounds["hello"]
sounds["hi"] = sounds["hello"]
sounds["heads up"] = {"vo/npc/male01/headsup01.wav", "vo/npc/male01/headsup02.wav"}
sounds["he's dead"] = {"vo/npc/male01/gordead_ques01.wav", "vo/npc/male01/gordead_ques07.wav"}
sounds["he is dead"] = sounds["he's dead"]
sounds["how about that"] = {"vo/npc/male01/answer25.wav"}
sounds["i know"] = {"vo/npc/male01/answer08.wav"}
sounds["ill stay here"] = {"vo/npc/male01/illstayhere01.wav", "vo/npc/male01/holddownspot01.wav", "vo/npc/male01/holddownspot02.wav", "vo/npc/male01/imstickinghere01.wav", "vo/npc/male01/littlecorner01.wav"}
sounds["i'll stay here"] = sounds["ill stay here"]
sounds["i will stay here"] = sounds["ill stay here"]
sounds["im busy"] = {"vo/npc/male01/busy02.wav"}
sounds["i'm busy"] = sounds["im busy"]
sounds["im with you"] = {"vo/npc/male01/answer13.wav"}
sounds["i'm with you"] = sounds["im with you"]
sounds["isnt good"] = {"vo/trainyard/male01/cit_window_use01.wav"}
sounds["isn't good"] = sounds["isnt good"]
sounds["incoming"] = sounds["here they come"]
sounds["it cant be"] = {"vo/npc/male01/gordead_ques06.wav"}
sounds["it can't be"] = sounds["it cant be"]
sounds["it is okay"] = {"vo/npc/male01/answer02.wav"}
sounds["it's okay"] = sounds["it is okay"]
sounds["kay"] = {"vo/npc/male01/ok01.wav", "vo/npc/male01/ok02.wav"}
sounds["kk"] = sounds["kay"]
sounds["lead the way"] = {"vo/npc/male01/leadtheway01.wav", "vo/npc/male01/leadtheway02.wav"}
sounds["lead on"] = sounds["lead the way"]
sounds["lets go"] = {"vo/npc/male01/letsgo01.wav", "vo/npc/male01/letsgo02.wav"}
sounds["let's go"] = sounds["lets go"]
sounds["never"] = {"vo/Citadel/eli_nonever.wav"}
sounds["never can tell"] = {"vo/npc/male01/answer23.wav"}
sounds["nice"] = {"vo/npc/male01/nice.wav"}
sounds["no"] = {"vo/Citadel/br_no.wav", "vo/Citadel/eli_notobreen.wav"}
sounds["not good"] = sounds["isnt good"]
sounds["not sure"] = {"vo/npc/male01/answer21.wav"}
sounds["now what"] = {"vo/npc/male01/gordead_ans01.wav", "vo/npc/male01/gordead_ans15.wav"}
sounds["oh no"] = {"vo/npc/male01/gordead_ans05.wav", "vo/npc/male01/ohno.wav"}
sounds["oh my god"] = sounds["good god"]
sounds["omg"] = sounds["good god"]
sounds["omfg"] = sounds["good god"]
sounds["ok"] = sounds["kay"]
sounds["okay"] = sounds["kay"]
sounds["oops"] = {"vo/npc/male01/whoops01.wav"}
sounds["over here"] = {"vo/npc/male01/overhere01.wav", "vo/npc/male01/squad_away02.wav"}
sounds["over there"] = {"vo/npc/male01/overthere01.wav", "vo/npc/male01/overthere02.wav"}
sounds["pardon me"] = {"vo/npc/male01/pardonme01.wav", "vo/npc/male01/pardonme02.wav"}
sounds["please no"] = {"vo/npc/male01/gordead_ans06.wav"}
sounds["right on"] = {"vo/npc/male01/answer18.wav"}
sounds["run"] = {"vo/npc/male01/strider_run.wav"}
sounds["same here"] = {"vo/npc/male01/answer07.wav"}
sounds["shut up"] = {"vo/npc/male01/answer17.wav"}
sounds["spread the word"] = {"vo/npc/male01/gordead_ans10.wav"}
sounds["stop it"] = sounds["cut it"]
sounds["stop that"] = sounds["cut it"]
sounds["stop looking at me"] = {"vo/npc/male01/vquestion01.wav"}
sounds["sorry"] = {"vo/npc/male01/sorry01.wav", "vo/npc/male01/sorry02.wav", "vo/npc/male01/sorry03.wav"}
sounds["sry"] = sounds["sorry"]
sounds["take cover"] = {"vo/npc/male01/takecover02.wav"}
sounds["take this medkit"] = {"vo/npc/male01/health01.wav", "vo/npc/male01/health02.wav", "vo/npc/male01/health03.wav", "vo/npc/male01/health04.wav"}
sounds["task at hand"] = {"vo/npc/male01/answer18.wav"}
sounds["talking to me"] = {"vo/npc/male01/answer30.wav"}
sounds["thats you"] = {"vo/npc/male01/answer01.wav"}
sounds["this cant be"] = sounds["it cant be"]
sounds["this can't be"] = sounds["it cant be"]
sounds["this is bad"] = {"vo/npc/male01/gordead_ques10.wav"}
sounds["too much info"] = {"vo/npc/male01/answer26.wav"}
sounds["too much information"] = sounds["too much info"]
sounds["uhoh"] = {"vo/npc/male01/uhoh.wav"}
sounds["uh oh"] = sounds["uhoh"]
sounds["wait"] = {"vo/trainyard/man_waitaminute.wav"}
sounds["wait for me"] = {"vo/npc/male01/squad_reinforce_single04.wav"}
sounds["wait for us"] = {"vo/npc/male01/squad_reinforce_group04.wav"}
sounds["wanna bet"] = {"vo/npc/male01/answer27.wav"}
sounds["watch out"] = {"vo/npc/male01/watchout.wav"}
sounds["we are done for"] = {"vo/npc/male01/gordead_ans14.wav"}
sounds["we're done for"] = sounds["we are done for"]
sounds["what now"] = {"vo/npc/male01/gordead_ques16.wav"}
sounds["whatever you say"] = {"vo/npc/male01/squad_affirm03.wav"}
sounds["whats the use"] = {"vo/npc/male01/gordead_ans11.wav"}
sounds["what's the use"] = sounds["whats the use"]
sounds["whats the point"] = {"vo/npc/male01/gordead_ans12.wav"}
sounds["what's the point"] = sounds["whats the point"]
sounds["whoops"] = sounds["oops"]
sounds["why go on"] = {"vo/npc/male01/gordead_ans13.wav"}
sounds["why telling me"] = {"vo/npc/male01/answer24.wav"}
sounds["yeah"] = {"vo/npc/male01/yeah02.wav"}
sounds["yes"] = sounds["yeah"]
sounds["you and me both"] = {"vo/npc/male01/answer14.wav"}
sounds["you never know"] = {"vo/npc/male01/answer22.wav"}
sounds["you sure"] = {"vo/npc/male01/answer37.wav"}
DarkRP.hookStub{
name = "canChatSound",
description = "Whether a chat sound can be played.",
parameters = {
{
name = "ply",
description = "The player who triggered the chat sound.",
type = "Player"
},
{
name = "chatPhrase",
description = "The chat sound phrase that has been detected.",
type = "string"
},
{
name = "chatText",
description = "The whole chat text the player sent that contains the chat sound phrase.",
type = "string"
}
},
returns = {
{
name = "canChatSound",
description = "False if the chat sound should not be played.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "onChatSound",
description = "When a chat sound is played.",
parameters = {
{
name = "ply",
description = "The player who triggered the chat sound.",
type = "Player"
},
{
name = "chatPhrase",
description = "The chat sound phrase that was detected.",
type = "string"
},
{
name = "chatText",
description = "The whole chat text the player sent that contains the chat sound phrase.",
type = "string"
}
},
returns = {
}
}
local function CheckChat(ply, text)
if not GAMEMODE.Config.chatsounds or ply.nextSpeechSound and ply.nextSpeechSound > CurTime() then return end
local prefix = string.sub(text, 0, 1)
if prefix == "/" or prefix == "!" or prefix == "@" then return end -- should cover most chat commands for various mods/addons
local longestMatch = nil
local longestMatchLength = 0
for k, v in pairs(sounds) do
local res1, res2 = string.find(string.lower(text), k)
if not res1 then continue end
local charBefore = text[res1 - 1]
local charAfter = text[res2 + 1]
local length = res2 - res1
-- Check whether the match is not part of a larger word (e.g. "no" should not match when "know" is said)
if charBefore and charBefore ~= "" and charBefore ~= " " then continue end
if charAfter and charAfter ~= "" and charAfter ~= " " then continue end
if length > longestMatchLength then
longestMatch = k
longestMatchLength = length
end
end
if not longestMatch then return end
local canChatSound = hook.Call("canChatSound", nil, ply, longestMatch, text)
if canChatSound == false then return end
ply:EmitSound(table.Random(sounds[longestMatch]), 80, 100)
ply.nextSpeechSound = CurTime() + GAMEMODE.Config.chatsoundsdelay -- make sure they don't spam HAX HAX HAX, if the server owner so desires
hook.Call("onChatSound", nil, ply, longestMatch, text)
end
hook.Add("PostPlayerSay", "ChatSounds", CheckChat)
DarkRP.getChatSound = DarkRP.stub{
name = "getChatSound",
description = "Get a chat sound (play a noise when someone says something) associated with the given phrase.",
parameters = {
{
name = "text",
description = "The text that triggers the chat sound.",
type = "string",
optional = false
}
},
returns = {
{
name = "soundPaths",
description = "A table of string sound paths associated with the given text.",
type = "table"
}
},
metatable = DarkRP
}
function DarkRP.getChatSound(text)
return sounds[string.lower(text or "")]
end
DarkRP.setChatSound = DarkRP.stub{
name = "setChatSound",
description = "Set a chat sound (play a noise when someone says something)",
parameters = {
{
name = "text",
description = "The text that should trigger the sound.",
type = "string",
optional = false
},
{
name = "sounds",
description = "A table of string sound paths.",
type = "table",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
function DarkRP.setChatSound(text, sndTable)
sounds[string.lower(text or "")] = sndTable
end

View File

@@ -0,0 +1,56 @@
if CPPI then return end
CPPI = {}
CPPI.CPPI_DEFER = 100100 --\100\100 = dd
CPPI.CPPI_NOTIMPLEMENTED = 7080
function CPPI:GetName()
return "DarkRP"
end
function CPPI:GetVersion()
return CPPI.CPPI_NOTIMPLEMENTED
end
function CPPI:GetInterfaceVersion()
return CPPI.CPPI_NOTIMPLEMENTED
end
function CPPI:GetNameFromUID(uid)
return CPPI.CPPI_NOTIMPLEMENTED
end
local PLAYER = FindMetaTable("Player")
function PLAYER:CPPIGetFriends()
return CPPI.CPPI_NOTIMPLEMENTED
end
local ENTITY = FindMetaTable("Entity")
function ENTITY:CPPIGetOwner()
return NULL, CPPI.CPPI_NOTIMPLEMENTED
end
if SERVER then
function ENTITY:CPPISetOwner(ply)
return CPPI.CPPI_NOTIMPLEMENTED
end
function ENTITY:CPPISetOwnerUID(UID)
return CPPI.CPPI_NOTIMPLEMENTED
end
function ENTITY:CPPICanTool(ply, tool)
return CPPI.CPPI_NOTIMPLEMENTED
end
function ENTITY:CPPICanPhysgun(ply)
return CPPI.CPPI_NOTIMPLEMENTED
end
function ENTITY:CPPICanPickup(ply)
return CPPI.CPPI_NOTIMPLEMENTED
end
function ENTITY:CPPICanPunt(ply)
return CPPI.CPPI_NOTIMPLEMENTED
end
end

View File

@@ -0,0 +1,26 @@
local MotdMessage =
[[
---------------------------------------------------------------------------
DarkRP Message of the day!
---------------------------------------------------------------------------
]]
local endMOTD = "---------------------------------------------------------------------------\n"
local function drawMOTD(text)
MsgC(Color(255, 20, 20, 255), MotdMessage, color_white, text, Color(255, 20, 20, 255), endMOTD)
end
local function receiveMOTD(html, len, headers, code)
if not headers or headers.Status and string.sub(headers.Status, 1, 3) ~= "200" then return end
drawMOTD(html)
end
local function showMOTD()
http.Fetch("https://raw.githubusercontent.com/FPtje/DarkRPMotd/master/motd.txt", receiveMOTD, fn.Id)
end
timer.Simple(5, showMOTD)
concommand.Add("DarkRP_motd", showMOTD)

View File

@@ -0,0 +1,44 @@
local view = {
origin = Vector(0, 0, 0),
angles = Angle(0, 0, 0),
fov = 90,
znear = 1
}
local deathpov = GM.Config.deathpov
hook.Add("CalcView", "rp_deathPOV", function(ply, origin, angles, fov)
-- Entity:Alive() is being slow as hell, we might actually see ourselves from third person for frame or two
if not deathpov or ply:Health() > 0 then return end
local Ragdoll = ply:GetRagdollEntity()
if not IsValid(Ragdoll) then return end
local head = Ragdoll:LookupAttachment("eyes")
head = Ragdoll:GetAttachment(head)
if not head or not head.Pos then return end
if not Ragdoll.BonesRattled then
Ragdoll.BonesRattled = true
Ragdoll:InvalidateBoneCache()
Ragdoll:SetupBones()
local matrix
for bone = 0, (Ragdoll:GetBoneCount() or 1) do
if Ragdoll:GetBoneName(bone):lower():find("head") then
matrix = Ragdoll:GetBoneMatrix(bone)
break
end
end
if IsValid(matrix) then
matrix:SetScale(Vector(0, 0, 0))
end
end
view.origin = head.Pos + head.Ang:Up() * 8
view.angles = head.Ang
return view
end)

View File

@@ -0,0 +1,248 @@
-- Skin for DarkRP gui's
SKIN = {}
SKIN.PrintName = "DarkRP"
SKIN.Author = "FPtje Falco"
SKIN.DermaVersion = 1
SKIN.GwenTexture = Material("darkrp/darkrpderma.png")
SKIN.colTextEntryText = color_white
SKIN.colTextEntryTextCursor = color_white
SKIN.colTextEntryTextPlaceholder = Color(200, 200, 200, 200) -- Unofficial but will probably be named this
SKIN.tex = {}
SKIN.tex.Selection = GWEN.CreateTextureBorder(384, 32, 31, 31, 4, 4, 4, 4)
SKIN.tex.Panels = {}
SKIN.tex.Panels.Normal = GWEN.CreateTextureBorder(256, 0, 63, 63, 16, 16, 16, 16)
SKIN.tex.Panels.Bright = GWEN.CreateTextureBorder(256 + 64, 0, 63, 63, 16, 16, 16, 16)
SKIN.tex.Panels.Dark = GWEN.CreateTextureBorder(256, 64, 63, 63, 16, 16, 16, 16)
SKIN.tex.Panels.Highlight = GWEN.CreateTextureBorder(256 + 64, 64, 63, 63, 16, 16, 16, 16)
SKIN.tex.Button = GWEN.CreateTextureBorder(480, 0, 31, 31, 8, 8, 8, 8)
SKIN.tex.Button_Hovered = GWEN.CreateTextureBorder(480, 32, 31, 31, 8, 8, 8, 8)
SKIN.tex.Button_Dead = GWEN.CreateTextureBorder(480, 64, 31, 31, 8, 8, 8, 8)
SKIN.tex.Button_Down = GWEN.CreateTextureBorder(480, 96, 31, 31, 8, 8, 8, 8)
SKIN.tex.Shadow = GWEN.CreateTextureBorder(448, 0, 31, 31, 8, 8, 8, 8)
SKIN.tex.Tree = GWEN.CreateTextureBorder(256, 128, 127, 127, 16, 16, 16, 16)
SKIN.tex.Checkbox_Checked = GWEN.CreateTextureNormal(448, 32, 15, 15)
SKIN.tex.Checkbox = GWEN.CreateTextureNormal(464, 32, 15, 15)
SKIN.tex.CheckboxD_Checked = GWEN.CreateTextureNormal(448, 48, 15, 15)
SKIN.tex.CheckboxD = GWEN.CreateTextureNormal(464, 48, 15, 15)
--SKIN.tex.RadioButton_Checked = GWEN.CreateTextureNormal(448, 64, 15, 15)
--SKIN.tex.RadioButton = GWEN.CreateTextureNormal(464, 64, 15, 15)
--SKIN.tex.RadioButtonD_Checked = GWEN.CreateTextureNormal(448, 80, 15, 15)
--SKIN.tex.RadioButtonD = GWEN.CreateTextureNormal(464, 80, 15, 15)
SKIN.tex.TreePlus = GWEN.CreateTextureNormal(448, 96, 15, 15)
SKIN.tex.TreeMinus = GWEN.CreateTextureNormal(464, 96, 15, 15)
--SKIN.tex.Menu_Strip = GWEN.CreateTextureBorder(0, 128, 127, 21, 1, 1, 1, 1)
SKIN.tex.TextBox = GWEN.CreateTextureBorder(0, 150, 127, 21, 4, 4, 4, 4)
SKIN.tex.TextBox_Focus = GWEN.CreateTextureBorder(0, 172, 127, 21, 4, 4, 4, 4)
SKIN.tex.TextBox_Disabled = GWEN.CreateTextureBorder(0, 193, 127, 21, 4, 4, 4, 4)
SKIN.tex.MenuBG_Margin = GWEN.CreateTextureBorder(128, 128, 127, 63, 24, 8, 8, 8)
SKIN.tex.MenuBG = GWEN.CreateTextureBorder(128, 192, 127, 63, 8, 8, 8, 8)
SKIN.tex.MenuBG_Hover = GWEN.CreateTextureBorder(128, 256, 127, 31, 8, 8, 8, 8)
SKIN.tex.MenuBG_Spacer = GWEN.CreateTextureNormal(128, 288, 127, 3)
SKIN.tex.Tab_Control = GWEN.CreateTextureBorder(0, 256, 127, 127, 8, 8, 8, 8)
SKIN.tex.TabB_Active = GWEN.CreateTextureBorder(0, 416, 63, 31, 8, 8, 8, 8)
SKIN.tex.TabB_Inactive = GWEN.CreateTextureBorder(0 + 128, 416, 63, 31, 8, 8, 8, 8)
SKIN.tex.TabT_Active = GWEN.CreateTextureBorder(0, 384, 63, 31, 8, 8, 8, 8)
SKIN.tex.TabT_Inactive = GWEN.CreateTextureBorder(0 + 128, 384, 63, 31, 8, 8, 8, 8)
SKIN.tex.TabL_Active = GWEN.CreateTextureBorder(64, 384, 31, 63, 8, 8, 8, 8)
SKIN.tex.TabL_Inactive = GWEN.CreateTextureBorder(64 + 128, 384, 31, 63, 8, 8, 8, 8)
SKIN.tex.TabR_Active = GWEN.CreateTextureBorder(96, 384, 31, 63, 8, 8, 8, 8)
SKIN.tex.TabR_Inactive = GWEN.CreateTextureBorder(96 + 128, 384, 31, 63, 8, 8, 8, 8)
SKIN.tex.Tab_Bar = GWEN.CreateTextureBorder(128, 352, 127, 31, 4, 4, 4, 4)
SKIN.tex.Window = {}
SKIN.tex.Window.Normal = GWEN.CreateTextureBorder(0, 0, 127, 127, 8, 32, 8, 8)
SKIN.tex.Window.Inactive = GWEN.CreateTextureBorder(128, 0, 127, 127, 8, 32, 8, 8)
SKIN.tex.Window.Close = GWEN.CreateTextureNormal(0, 224, 24, 24)
SKIN.tex.Window.Close_Hover = GWEN.CreateTextureNormal(32, 224, 24, 24)
SKIN.tex.Window.Close_Down = GWEN.CreateTextureNormal(64, 224, 24, 24)
SKIN.tex.Window.Close_Disabled = GWEN.CreateTextureNormal(96, 224, 24, 24)
SKIN.tex.Window.Maxi = GWEN.CreateTextureNormal(32 + 96 * 2, 448, 31, 31)
SKIN.tex.Window.Maxi_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448, 31, 31)
SKIN.tex.Window.Maxi_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448, 31, 31)
SKIN.tex.Window.Restore = GWEN.CreateTextureNormal(32 + 96 * 2, 448 + 32, 31, 31)
SKIN.tex.Window.Restore_Hover = GWEN.CreateTextureNormal(64 + 96 * 2, 448 + 32, 31, 31)
SKIN.tex.Window.Restore_Down = GWEN.CreateTextureNormal(96 + 96 * 2, 448 + 32, 31, 31)
SKIN.tex.Window.Mini = GWEN.CreateTextureNormal(32 + 96, 448, 31, 31)
SKIN.tex.Window.Mini_Hover = GWEN.CreateTextureNormal(64 + 96, 448, 31, 31)
SKIN.tex.Window.Mini_Down = GWEN.CreateTextureNormal(96 + 96, 448, 31, 31)
SKIN.tex.Scroller = {}
SKIN.tex.Scroller.TrackV = GWEN.CreateTextureBorder(384, 208, 15, 127, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonV_Normal = GWEN.CreateTextureBorder(384 + 16, 208, 15, 127, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonV_Hover = GWEN.CreateTextureBorder(384 + 32, 208, 15, 127, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonV_Down = GWEN.CreateTextureBorder(384 + 48, 208, 15, 127, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonV_Disabled = GWEN.CreateTextureBorder(384 + 64, 208, 15, 127, 4, 4, 4, 4)
SKIN.tex.Scroller.TrackH = GWEN.CreateTextureBorder(384, 128, 127, 15, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonH_Normal = GWEN.CreateTextureBorder(384, 128 + 16, 127, 15, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonH_Hover = GWEN.CreateTextureBorder(384, 128 + 32, 127, 15, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonH_Down = GWEN.CreateTextureBorder(384, 128 + 48, 127, 15, 4, 4, 4, 4)
SKIN.tex.Scroller.ButtonH_Disabled = GWEN.CreateTextureBorder(384, 128 + 64, 127, 15, 4, 4, 4, 4)
SKIN.tex.Scroller.LeftButton_Normal = GWEN.CreateTextureBorder(464, 208, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.LeftButton_Hover = GWEN.CreateTextureBorder(480, 208, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.LeftButton_Down = GWEN.CreateTextureBorder(464, 272, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.LeftButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.UpButton_Normal = GWEN.CreateTextureBorder(464, 208 + 16, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.UpButton_Hover = GWEN.CreateTextureBorder(480, 208 + 16, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.UpButton_Down = GWEN.CreateTextureBorder(464, 272 + 16, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.UpButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 16, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.RightButton_Normal = GWEN.CreateTextureBorder(464, 208 + 32, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.RightButton_Hover = GWEN.CreateTextureBorder(480, 208 + 32, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.RightButton_Down = GWEN.CreateTextureBorder(464, 272 + 32, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.RightButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 32, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.DownButton_Normal = GWEN.CreateTextureBorder(464, 208 + 48, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.DownButton_Hover = GWEN.CreateTextureBorder(480, 208 + 48, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.DownButton_Down = GWEN.CreateTextureBorder(464, 272 + 48, 15, 15, 2, 2, 2, 2)
SKIN.tex.Scroller.DownButton_Disabled = GWEN.CreateTextureBorder(480 + 48, 272 + 48, 15, 15, 2, 2, 2, 2)
SKIN.tex.Menu = {}
SKIN.tex.Menu.RightArrow = GWEN.CreateTextureNormal(464, 112, 15, 15)
SKIN.tex.Input = {}
SKIN.tex.Input.ListBox = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8)
SKIN.tex.Input.ComboBox = {}
SKIN.tex.Input.ComboBox.Normal = GWEN.CreateTextureBorder(384, 336, 127, 31, 8, 8, 32, 8)
SKIN.tex.Input.ComboBox.Hover = GWEN.CreateTextureBorder(384, 336 + 32, 127, 31, 8, 8, 32, 8)
SKIN.tex.Input.ComboBox.Down = GWEN.CreateTextureBorder(384, 336 + 64, 127, 31, 8, 8, 32, 8)
SKIN.tex.Input.ComboBox.Disabled = GWEN.CreateTextureBorder(384, 336 + 96, 127, 31, 8, 8, 32, 8)
SKIN.tex.Input.ComboBox.Button = {}
SKIN.tex.Input.ComboBox.Button.Normal = GWEN.CreateTextureNormal(496, 272, 15, 15)
SKIN.tex.Input.ComboBox.Button.Hover = GWEN.CreateTextureNormal(496, 272 + 16, 15, 15)
SKIN.tex.Input.ComboBox.Button.Down = GWEN.CreateTextureNormal(496, 272 + 32, 15, 15)
SKIN.tex.Input.ComboBox.Button.Disabled = GWEN.CreateTextureNormal(496, 272 + 48, 15, 15)
SKIN.tex.Input.UpDown = {}
SKIN.tex.Input.UpDown.Up = {}
SKIN.tex.Input.UpDown.Up.Normal = GWEN.CreateTextureCentered(384, 112, 7, 7)
SKIN.tex.Input.UpDown.Up.Hover = GWEN.CreateTextureCentered(384 + 8, 112, 7, 7)
SKIN.tex.Input.UpDown.Up.Down = GWEN.CreateTextureCentered(384 + 16, 112, 7, 7)
SKIN.tex.Input.UpDown.Up.Disabled = GWEN.CreateTextureCentered(384 + 24, 112, 7, 7)
SKIN.tex.Input.UpDown.Down = {}
SKIN.tex.Input.UpDown.Down.Normal = GWEN.CreateTextureCentered(384, 120, 7, 7)
SKIN.tex.Input.UpDown.Down.Hover = GWEN.CreateTextureCentered(384 + 8, 120, 7, 7)
SKIN.tex.Input.UpDown.Down.Down = GWEN.CreateTextureCentered(384 + 16, 120, 7, 7)
SKIN.tex.Input.UpDown.Down.Disabled = GWEN.CreateTextureCentered(384 + 24, 120, 7, 7)
SKIN.tex.Input.Slider = {}
SKIN.tex.Input.Slider.H = {}
SKIN.tex.Input.Slider.H.Normal = GWEN.CreateTextureNormal(416, 32, 15, 15)
SKIN.tex.Input.Slider.H.Hover = GWEN.CreateTextureNormal(416, 32 + 16, 15, 15)
SKIN.tex.Input.Slider.H.Down = GWEN.CreateTextureNormal(416, 32 + 32, 15, 15)
SKIN.tex.Input.Slider.H.Disabled = GWEN.CreateTextureNormal(416, 32 + 48, 15, 15)
SKIN.tex.Input.Slider.V = {}
SKIN.tex.Input.Slider.V.Normal = GWEN.CreateTextureNormal(416 + 16, 32, 15, 15)
SKIN.tex.Input.Slider.V.Hover = GWEN.CreateTextureNormal(416 + 16, 32 + 16, 15, 15)
SKIN.tex.Input.Slider.V.Down = GWEN.CreateTextureNormal(416 + 16, 32 + 32, 15, 15)
SKIN.tex.Input.Slider.V.Disabled = GWEN.CreateTextureNormal(416 + 16, 32 + 48, 15, 15)
SKIN.tex.Input.ListBox = {}
SKIN.tex.Input.ListBox.Background = GWEN.CreateTextureBorder(256, 256, 63, 127, 8, 8, 8, 8)
SKIN.tex.Input.ListBox.Hovered = GWEN.CreateTextureBorder(320, 320, 31, 31, 8, 8, 8, 8)
SKIN.tex.Input.ListBox.EvenLine = GWEN.CreateTextureBorder(352, 256, 31, 31, 8, 8, 8, 8)
SKIN.tex.Input.ListBox.OddLine = GWEN.CreateTextureBorder(352, 288, 31, 31, 8, 8, 8, 8)
SKIN.tex.Input.ListBox.EvenLineSelected = GWEN.CreateTextureBorder(320, 270, 31, 31, 8, 8, 8, 8)
SKIN.tex.Input.ListBox.OddLineSelected = GWEN.CreateTextureBorder(320, 288, 31, 31, 8, 8, 8, 8)
SKIN.tex.ProgressBar = {}
SKIN.tex.ProgressBar.Back = GWEN.CreateTextureBorder(384, 0, 31, 31, 8, 8, 8, 8)
SKIN.tex.ProgressBar.Front = GWEN.CreateTextureBorder(384 + 32, 0, 31, 31, 8, 8, 8, 8)
SKIN.tex.CategoryList = {}
SKIN.tex.CategoryList.Outer = GWEN.CreateTextureBorder(256, 384, 63, 63, 8, 8, 8, 8)
SKIN.tex.CategoryList.Inner = GWEN.CreateTextureBorder(256 + 64, 384, 63, 63, 8, 21, 8, 8)
SKIN.tex.CategoryList.Header = GWEN.CreateTextureBorder(320, 352, 63, 31, 8, 8, 8, 8)
SKIN.tex.Tooltip = GWEN.CreateTextureBorder(384, 64, 31, 31, 8, 8, 8, 8)
SKIN.Colours = {}
SKIN.Colours.Window = {}
SKIN.Colours.Window.TitleActive = GWEN.TextureColor(4 + 8 * 0, 508)
SKIN.Colours.Window.TitleInactive = GWEN.TextureColor(4 + 8 * 1, 508)
SKIN.Colours.Button = {}
SKIN.Colours.Button.Normal = GWEN.TextureColor(4 + 8 * 2, 508)
SKIN.Colours.Button.Hover = GWEN.TextureColor(4 + 8 * 3, 508)
SKIN.Colours.Button.Down = GWEN.TextureColor(4 + 8 * 2, 500)
SKIN.Colours.Button.Disabled = GWEN.TextureColor(4 + 8 * 3, 500)
SKIN.Colours.Tab = {}
SKIN.Colours.Tab.Active = {}
SKIN.Colours.Tab.Active.Normal = GWEN.TextureColor(4 + 8 * 4, 508)
SKIN.Colours.Tab.Active.Hover = GWEN.TextureColor(4 + 8 * 5, 508)
SKIN.Colours.Tab.Active.Down = GWEN.TextureColor(4 + 8 * 4, 500)
SKIN.Colours.Tab.Active.Disabled = GWEN.TextureColor(4 + 8 * 5, 500)
SKIN.Colours.Tab.Inactive = {}
SKIN.Colours.Tab.Inactive.Normal = GWEN.TextureColor(4 + 8 * 6, 508)
SKIN.Colours.Tab.Inactive.Hover = GWEN.TextureColor(4 + 8 * 7, 508)
SKIN.Colours.Tab.Inactive.Down = GWEN.TextureColor(4 + 8 * 6, 500)
SKIN.Colours.Tab.Inactive.Disabled = GWEN.TextureColor(4 + 8 * 7, 500)
SKIN.Colours.Label = {}
SKIN.Colours.Label.Default = GWEN.TextureColor(4 + 8 * 8, 508)
SKIN.Colours.Label.Bright = GWEN.TextureColor(4 + 8 * 9, 508)
SKIN.Colours.Label.Dark = GWEN.TextureColor(4 + 8 * 8, 500)
SKIN.Colours.Label.Highlight = GWEN.TextureColor(4 + 8 * 9, 500)
SKIN.Colours.Tree = {}
SKIN.Colours.Tree.Lines = GWEN.TextureColor(4 + 8 * 10, 508)
---- !!!
SKIN.Colours.Tree.Normal = GWEN.TextureColor(4 + 8 * 11, 508)
SKIN.Colours.Tree.Hover = GWEN.TextureColor(4 + 8 * 10, 500)
SKIN.Colours.Tree.Selected = GWEN.TextureColor(4 + 8 * 11, 500)
SKIN.Colours.Properties = {}
SKIN.Colours.Properties.Line_Normal = GWEN.TextureColor(4 + 8 * 12, 508)
SKIN.Colours.Properties.Line_Selected = GWEN.TextureColor(4 + 8 * 13, 508)
SKIN.Colours.Properties.Line_Hover = GWEN.TextureColor(4 + 8 * 12, 500)
SKIN.Colours.Properties.Title = GWEN.TextureColor(4 + 8 * 13, 500)
SKIN.Colours.Properties.Column_Normal = GWEN.TextureColor(4 + 8 * 14, 508)
SKIN.Colours.Properties.Column_Selected = GWEN.TextureColor(4 + 8 * 15, 508)
SKIN.Colours.Properties.Column_Hover = GWEN.TextureColor(4 + 8 * 14, 500)
SKIN.Colours.Properties.Border = GWEN.TextureColor(4 + 8 * 15, 500)
SKIN.Colours.Properties.Label_Normal = GWEN.TextureColor(4 + 8 * 16, 508)
SKIN.Colours.Properties.Label_Selected = GWEN.TextureColor(4 + 8 * 17, 508)
SKIN.Colours.Properties.Label_Hover = GWEN.TextureColor(4 + 8 * 16, 500)
SKIN.Colours.Category = {}
SKIN.Colours.Category.Header = GWEN.TextureColor(4 + 8 * 18, 500)
SKIN.Colours.Category.Header_Closed = GWEN.TextureColor(4 + 8 * 19, 500)
SKIN.Colours.Category.Line = {}
SKIN.Colours.Category.Line.Text = GWEN.TextureColor(4 + 8 * 20, 508)
SKIN.Colours.Category.Line.Text_Hover = GWEN.TextureColor(4 + 8 * 21, 508)
SKIN.Colours.Category.Line.Text_Selected = GWEN.TextureColor(4 + 8 * 20, 500)
SKIN.Colours.Category.Line.Button = GWEN.TextureColor(4 + 8 * 21, 500)
SKIN.Colours.Category.Line.Button_Hover = GWEN.TextureColor(4 + 8 * 22, 508)
SKIN.Colours.Category.Line.Button_Selected = GWEN.TextureColor(4 + 8 * 23, 508)
SKIN.Colours.Category.LineAlt = {}
SKIN.Colours.Category.LineAlt.Text = GWEN.TextureColor(4 + 8 * 22, 500)
SKIN.Colours.Category.LineAlt.Text_Hover = GWEN.TextureColor(4 + 8 * 23, 500)
SKIN.Colours.Category.LineAlt.Text_Selected = GWEN.TextureColor(4 + 8 * 24, 508)
SKIN.Colours.Category.LineAlt.Button = GWEN.TextureColor(4 + 8 * 25, 508)
SKIN.Colours.Category.LineAlt.Button_Hover = GWEN.TextureColor(4 + 8 * 24, 500)
SKIN.Colours.Category.LineAlt.Button_Selected = GWEN.TextureColor(4 + 8 * 25, 500)
derma.DefineSkin("DarkRP", "The official SKIN for DarkRP", SKIN)

View File

@@ -0,0 +1 @@
resource.AddFile("materials/darkrp/darkrpderma.png")

View File

@@ -0,0 +1,165 @@
local meta = FindMetaTable("Entity")
local black = color_black
local white = Color(255, 255, 255, 200)
local red = Color(128, 30, 30, 255)
local changeDoorAccess = false
local function updatePrivs()
CAMI.PlayerHasAccess(LocalPlayer(), "DarkRP_ChangeDoorSettings", function(b, _)
changeDoorAccess = b
end)
end
-- Timer due to lack of "on privilege changed" hook
hook.Add("InitPostEntity", "Load door privileges", function()
updatePrivs()
timer.Create("Door changeDoorAccess checker", 1, 0, updatePrivs)
end)
function meta:drawOwnableInfo()
local ply = LocalPlayer()
if ply:InVehicle() and not ply:GetAllowWeaponsInVehicle() then return end
-- Look, if you want to change the way door ownership is drawn, don't edit this file, use the hook instead!
local doorDrawing = hook.Call("HUDDrawDoorData", nil, self)
if doorDrawing == true then return end
local blocked = self:getKeysNonOwnable()
local doorTeams = self:getKeysDoorTeams()
local doorGroup = self:getKeysDoorGroup()
local playerOwned = self:isKeysOwned() or table.GetFirstValue(self:getKeysCoOwners() or {}) ~= nil
local owned = playerOwned or doorGroup or doorTeams
local doorInfo = {}
local title = self:getKeysTitle()
if title then table.insert(doorInfo, title) end
if owned then
table.insert(doorInfo, DarkRP.getPhrase("keys_owned_by"))
end
if playerOwned then
if self:isKeysOwned() then table.insert(doorInfo, self:getDoorOwner():Nick()) end
for k in pairs(self:getKeysCoOwners() or {}) do
local ent = Player(k)
if not IsValid(ent) or not ent:IsPlayer() then continue end
table.insert(doorInfo, ent:Nick())
end
local allowedCoOwn = self:getKeysAllowedToOwn()
if allowedCoOwn and not fn.Null(allowedCoOwn) then
table.insert(doorInfo, DarkRP.getPhrase("keys_other_allowed"))
for k in pairs(allowedCoOwn) do
local ent = Player(k)
if not IsValid(ent) or not ent:IsPlayer() then continue end
table.insert(doorInfo, ent:Nick())
end
end
elseif doorGroup then
table.insert(doorInfo, doorGroup)
elseif doorTeams then
for k, v in pairs(doorTeams) do
if not v or not RPExtraTeams[k] then continue end
table.insert(doorInfo, RPExtraTeams[k].name)
end
elseif blocked and changeDoorAccess then
table.insert(doorInfo, DarkRP.getPhrase("keys_allow_ownership"))
elseif not blocked then
table.insert(doorInfo, DarkRP.getPhrase("keys_unowned"))
if changeDoorAccess then
table.insert(doorInfo, DarkRP.getPhrase("keys_disallow_ownership"))
end
end
if self:IsVehicle() then
local driver = self:GetDriver()
if driver:IsPlayer() then
table.insert(doorInfo, DarkRP.getPhrase("driver", driver:Nick()))
end
end
local x, y = ScrW() / 2, ScrH() / 2
local text = table.concat(doorInfo, "\n")
draw.DrawNonParsedText(text, "Roboto20", x , y + 1 , black, 1)
draw.DrawNonParsedText(text, "Roboto20", x, y, (blocked or owned) and white or red, 1)
end
--[[---------------------------------------------------------------------------
Door data
---------------------------------------------------------------------------]]
DarkRP.doorData = DarkRP.doorData or {}
--[[---------------------------------------------------------------------------
Interface functions
---------------------------------------------------------------------------]]
function meta:getDoorData()
local doorData = DarkRP.doorData[self:EntIndex()] or {}
self.DoorData = doorData -- Backwards compatibility
return doorData
end
--[[---------------------------------------------------------------------------
Networking
---------------------------------------------------------------------------]]
--[[---------------------------------------------------------------------------
Retrieve all the data for all doors
---------------------------------------------------------------------------]]
local function retrieveAllDoorData(len)
local count = net.ReadUInt(16)
for i = 1, count do
local ix = net.ReadUInt(16)
local varCount = net.ReadUInt(8)
DarkRP.doorData[ix] = DarkRP.doorData[ix] or {}
for vc = 1, varCount do
local name, value = DarkRP.readNetDoorVar()
DarkRP.doorData[ix][name] = value
end
end
end
net.Receive("DarkRP_AllDoorData", retrieveAllDoorData)
--[[---------------------------------------------------------------------------
Update changed variables
---------------------------------------------------------------------------]]
local function updateDoorData()
local door = net.ReadUInt(32)
DarkRP.doorData[door] = DarkRP.doorData[door] or {}
local var, value = DarkRP.readNetDoorVar()
DarkRP.doorData[door][var] = value
end
net.Receive("DarkRP_UpdateDoorData", updateDoorData)
--[[---------------------------------------------------------------------------
Set a value of a single doorvar to nil
---------------------------------------------------------------------------]]
local function removeDoorVar()
local door = net.ReadUInt(16)
local id = net.ReadUInt(8)
local name = id == 0 and net.ReadString() or DarkRP.getDoorVars()[id].name
if not DarkRP.doorData[door] then return end
DarkRP.doorData[door][name] = nil
end
net.Receive("DarkRP_RemoveDoorVar", removeDoorVar)
--[[---------------------------------------------------------------------------
Remove doordata of removed entity
---------------------------------------------------------------------------]]
local function removeDoorData()
local door = net.ReadUInt(32)
DarkRP.doorData[door] = nil
end
net.Receive("DarkRP_RemoveDoorData", removeDoorData)

View File

@@ -0,0 +1,48 @@
DarkRP.readNetDoorVar = DarkRP.stub{
name = "readNetDoorVar",
description = "Internal function. You probably shouldn't need this. DarkRP calls this function when reading DoorVar net messages. This function reads the net data for a specific DoorVar.",
parameters = {
},
returns = {
{
name = "name",
description = "The name of the DoorVar.",
type = "string"
},
{
name = "value",
description = "The value of the DoorVar.",
type = "any"
}
},
metatable = DarkRP
}
DarkRP.ENTITY.drawOwnableInfo = DarkRP.stub{
name = "drawOwnableInfo",
description = "Draw the ownability information on a door or vehicle.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.hookStub{
name = "HUDDrawDoorData",
description = "Called when DarkRP is about to draw the door ownability information of a door or vehicle. Override this hook to ",
parameters = {
{
name = "ent",
description = "The door or vehicle of which the ownability information is about to be drawn.",
type = "Entity"
}
},
returns = {
{
name = "override",
description = "Return true in your hook to disable the default drawing and use your own.",
type = "boolean"
}
}
}

View File

@@ -0,0 +1,323 @@
local meta = FindMetaTable("Entity")
local plyMeta = FindMetaTable("Player")
local ownableDoors = {
["func_door"] = true,
["func_door_rotating"] = true,
["prop_door_rotating"] = true
}
local unOwnableDoors = {
["func_door"] = true,
["func_door_rotating"] = true,
["prop_door_rotating"] = true,
["func_movelinear"] = true,
["prop_dynamic"] = true
}
function meta:isKeysOwnable()
if not IsValid(self) then return false end
local class = self:GetClass()
if (ownableDoors[class] or
(GAMEMODE.Config.allowvehicleowning and self:IsVehicle() and (not IsValid(self:GetParent()) or not self:GetParent():IsVehicle()))) then
return true
end
return false
end
function meta:isDoor()
local class = self:GetClass()
if unOwnableDoors[class] then
return true
end
return false
end
function meta:isKeysOwned()
if IsValid(self:getDoorOwner()) then return true end
return false
end
function meta:getDoorOwner()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.owner and Player(doorData.owner) or nil
end
function meta:isMasterOwner(ply)
return ply == self:getDoorOwner()
end
function meta:isKeysOwnedBy(ply)
if self:isMasterOwner(ply) then return true end
local coOwners = self:getKeysCoOwners()
return coOwners and coOwners[ply:UserID()] or false
end
function meta:isKeysAllowedToOwn(ply)
local doorData = self:getDoorData()
if not doorData then return false end
return doorData.allowedToOwn and doorData.allowedToOwn[ply:UserID()] or false
end
function meta:getKeysNonOwnable()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.nonOwnable
end
function meta:getKeysTitle()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.title
end
function meta:getKeysDoorGroup()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.groupOwn
end
function meta:getKeysDoorTeams()
local doorData = self:getDoorData()
if not doorData or table.IsEmpty(doorData.teamOwn or {}) then return nil end
return doorData.teamOwn
end
function meta:getKeysAllowedToOwn()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.allowedToOwn
end
function meta:getKeysCoOwners()
local doorData = self:getDoorData()
if not doorData then return nil end
return doorData.extraOwners
end
local function canLockUnlock(ply, ent)
local Team = ply:Team()
local group = ent:getKeysDoorGroup()
local teamOwn = ent:getKeysDoorTeams()
return ent:isKeysOwnedBy(ply) or
(group and table.HasValue(RPExtraTeamDoors[group] or {}, Team)) or
(teamOwn and teamOwn[Team])
end
function plyMeta:canKeysLock(ent)
local canLock = hook.Run("canKeysLock", self, ent)
if canLock ~= nil then return canLock end
return canLockUnlock(self, ent)
end
function plyMeta:canKeysUnlock(ent)
local canUnlock = hook.Run("canKeysUnlock", self, ent)
if canUnlock ~= nil then return canUnlock end
return canLockUnlock(self, ent)
end
local netDoorVars = {}
local netDoorVarsByName = {}
DarkRP.getDoorVars = fp{fn.Id, netDoorVars}
DarkRP.getDoorVarsByName = fp{fn.Id, netDoorVarsByName}
function DarkRP.registerDoorVar(name, writeFn, readFn)
netDoorVarsByName[name] = {name = name, write = writeFn, read = readFn}
netDoorVarsByName[name].id = table.insert(netDoorVars, netDoorVarsByName[name])
end
if SERVER then
function DarkRP.writeNetDoorVar(name, value)
local var = netDoorVarsByName[name]
-- Not registered, send inefficiently
if not var then
net.WriteUInt(0, 8) -- indicate unregistered
net.WriteString(name)
net.WriteType(value)
return
end
net.WriteUInt(var.id, 8)
var.write(value)
end
end
if CLIENT then
function DarkRP.readNetDoorVar()
local id = net.ReadUInt(8)
-- unregistered var
if id == 0 then
return net.ReadString(), net.ReadType(net.ReadUInt(8))
end
if not netDoorVars[id] then
DarkRP.error("Unregistered DarkRP Doorvar clientside: " .. id, 2, {"Some addon is registering some DoorVar serverside, but not clientside."})
end
return netDoorVars[id].name, netDoorVars[id].read()
end
end
DarkRP.registerDoorVar("groupOwn",
function(val)
net.WriteUInt(RPExtraTeamDoorIDs[val], 16)
end,
function()
local id = net.ReadUInt(16)
for name, id2 in pairs(RPExtraTeamDoorIDs) do
if id == id2 then return name end
end
end
)
-- Net helper function for writing tables with numbers as keys and bools as values
local function writeNumBoolTbl(tbl)
net.WriteUInt(table.Count(tbl), 10)
for num, _ in pairs(tbl) do
net.WriteUInt(num, 16)
end
end
-- Net helper function for reading tables with numbers as keys and bools as values
local function readNumBoolTbl(tbl)
local res = {}
local count = net.ReadUInt(10)
for i = 1, count do
res[net.ReadUInt(16)] = true
end
return res
end
DarkRP.registerDoorVar("owner", fp{fn.Flip(net.WriteInt), 16}, fp{net.ReadUInt, 16})
DarkRP.registerDoorVar("nonOwnable", net.WriteBool, net.ReadBool)
DarkRP.registerDoorVar("teamOwn", writeNumBoolTbl, readNumBoolTbl)
DarkRP.registerDoorVar("allowedToOwn", writeNumBoolTbl, readNumBoolTbl)
DarkRP.registerDoorVar("extraOwners", writeNumBoolTbl, readNumBoolTbl)
DarkRP.registerDoorVar("title", net.WriteString, net.ReadString)
--[[---------------------------------------------------------------------------
Commands
---------------------------------------------------------------------------]]
DarkRP.declareChatCommand{
command = "toggleownable",
description = "Toggle ownability status on this door.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "togglegroupownable",
description = "Set this door group ownable.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "toggleteamownable",
description = "Toggle this door ownable by a given team.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "toggleown",
description = "Own or unown the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "unownalldoors",
description = "Sell all of your doors.",
delay = 1.5
}
DarkRP.chatCommandAlias("unownalldoors", "sellalldoors")
DarkRP.declareChatCommand{
command = "title",
description = "Set the title of the door you're looking at.",
delay = 1.5
}
DarkRP.declareChatCommand{
command = "removeowner",
description = "Remove an owner from the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "ro",
description = "Remove an owner from the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "addowner",
description = "Invite someone to co-own the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "ao",
description = "Invite someone to co-own the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "forceunlock",
description = "Force the door you're looking at to be unlocked. This is saved.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "forceremoveowner",
description = "Forcefully remove an owner from the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "forceunownall",
description = "Force a player to unown all the doors and vehicles they have.",
delay = 0.5,
tableArgs = true
}
DarkRP.declareChatCommand{
command = "forcelock",
description = "Force the door you're looking at to be locked. This is saved.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "forceunown",
description = "Forcefully remove any owners from the door you're looking at.",
delay = 0.5
}
DarkRP.declareChatCommand{
command = "forceown",
description = "Forcefully make someone own the door you're looking at.",
delay = 0.5
}

View File

@@ -0,0 +1,377 @@
DarkRP.ENTITY.getDoorData = DarkRP.stub{
name = "getDoorData",
description = "Internal function to get the door/vehicle data.",
parameters = {
},
returns = {
{
name = "doordata",
description = "All the DarkRP information on a door or vehicle.",
type = "table"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isKeysOwnable = DarkRP.stub{
name = "isKeysOwnable",
description = "Whether this door can be bought.",
parameters = {
},
returns = {
{
name = "answer",
description = "Whether the door can be bought.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isDoor = DarkRP.stub{
name = "isDoor",
description = "Whether this entity is considered a door in DarkRP.",
parameters = {
},
returns = {
{
name = "answer",
description = "Whether it's a door.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isKeysOwned = DarkRP.stub{
name = "isKeysOwned",
description = "Whether this door is owned by someone.",
parameters = {
},
returns = {
{
name = "answer",
description = "Whether it's owned.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getDoorOwner = DarkRP.stub{
name = "getDoorOwner",
description = "Get the owner of a door.",
parameters = {
},
returns = {
{
name = "owner",
description = "The owner of the door.",
type = "Player"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isMasterOwner = DarkRP.stub{
name = "isMasterOwner",
description = "Whether the player is the main owner of the door (as opposed to a co-owner).",
parameters = {
{
name = "ply",
description = "The player to query.",
type = "Player",
optional = false
}
},
returns = {
{
name = "answer",
description = "Whether this player is the master owner.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isKeysOwnedBy = DarkRP.stub{
name = "isKeysOwnedBy",
description = "Whether this door is owned or co-owned by this player",
parameters = {
{
name = "ply",
description = "The player to query.",
type = "Player",
optional = false
}
},
returns = {
{
name = "answer",
description = "Whether this door is (co-)owned by the player.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isKeysAllowedToOwn = DarkRP.stub{
name = "isKeysAllowedToOwn",
description = "Whether this player is allowed to co-own a door, as decided by the master door owner.",
parameters = {
{
name = "ply",
description = "The player to query.",
type = "Player",
optional = false
}
},
returns = {
{
name = "answer",
description = "Whether this door is (co-)ownable by the player.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysNonOwnable = DarkRP.stub{
name = "getKeysNonOwnable",
description = "Whether ownability of this door/vehicle is disabled.",
parameters = {
},
returns = {
{
name = "title",
description = "The ownability status.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysTitle = DarkRP.stub{
name = "getKeysTitle",
description = "Get the title of this door or vehicle.",
parameters = {
},
returns = {
{
name = "title",
description = "The title of the door or vehicle.",
type = "string"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysDoorGroup = DarkRP.stub{
name = "getKeysDoorGroup",
description = "The door group of a door if it exists.",
parameters = {
},
returns = {
{
name = "group",
description = "The door group.",
type = "string"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysDoorTeams = DarkRP.stub{
name = "getKeysDoorTeams",
description = "The teams that are allowed to open this door.",
parameters = {
},
returns = {
{
name = "teams",
description = "The door teams.",
type = "table"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysAllowedToOwn = DarkRP.stub{
name = "getKeysAllowedToOwn",
description = "The list of people of which the master door owner has added as allowed to own.",
parameters = {
},
returns = {
{
name = "players",
description = "The list of people allowed to own.",
type = "table"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.getKeysCoOwners = DarkRP.stub{
name = "getKeysCoOwners",
description = "The list of people who co-own the door.",
parameters = {
},
returns = {
{
name = "players",
description = "The list of people allowed to own. The keys of this table are UserIDs, the values are booleans.",
type = "table"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.PLAYER.canKeysLock = DarkRP.stub{
name = "canKeysLock",
description = "Whether the player can lock a given door.",
parameters = {
{
name = "door",
description = "The door",
optional = false,
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to lock the door.",
type = "boolean"
}
},
metatable = DarkRP.PLAYER
}
DarkRP.PLAYER.canKeysUnlock = DarkRP.stub{
name = "canKeysUnlock",
description = "Whether the player can unlock a given door.",
parameters = {
{
name = "door",
description = "The door",
optional = false,
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to unlock the door.",
type = "boolean"
}
},
metatable = DarkRP.PLAYER
}
DarkRP.registerDoorVar = DarkRP.stub{
name = "registerDoorVar",
description = "Register a door variable by name. You should definitely register door variables. Registering DarkRPVars will make networking much more efficient.",
parameters = {
{
name = "name",
description = "The name of the door var.",
type = "string",
optional = false
},
{
name = "writeFn",
description = "The function that writes a value for this door var. Examples: net.WriteString, function(val) net.WriteUInt(val, 8) end.",
type = "function",
optional = false
},
{
name = "readFn",
description = "The function that reads and returns a value for this door var. Examples: net.ReadString, function() return net.ReadUInt(8) end.",
type = "function",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.getDoorVars = DarkRP.stub{
name = "getDoorVars",
description = "Internal function, retrieves all the registered door variables.",
parameters = {
},
returns = {
{
name = "doorvars",
description = "The door variables, indexed by number",
type = "table"
}
},
metatable = DarkRP
}
DarkRP.getDoorVarsByName = DarkRP.stub{
name = "getDoorVarsByName",
description = "Internal function, retrieves all the registered door variables, indeded by their names.",
parameters = {
},
returns = {
{
name = "doorvars",
description = "The door variables, indexed by name",
type = "table"
}
},
metatable = DarkRP
}
DarkRP.hookStub{
name = "canKeysLock",
description = "Whether the player can lock a given door. This hook is run when ply:canKeysLock is called.",
parameters = {
{
name = "ply",
description = "The player",
type = "Player"
},
{
name = "door",
description = "The door",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to lock the door.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "canKeysUnlock",
description = "Whether the player can unlock a given door. This hook is run when ply:canKeysUnlock is called.",
parameters = {
{
name = "ply",
description = "The player",
type = "Player"
},
{
name = "door",
description = "The door",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to unlock the door.",
type = "boolean"
}
}
}

View File

@@ -0,0 +1,172 @@
local function ccDoorUnOwn(ply, args)
if ply:EntIndex() == 0 then
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
return
end
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or not ent:getDoorOwner() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
ent:Fire("unlock", "", 0)
ent:keysUnOwn()
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned a door with forceunown", Color(30, 30, 30))
DarkRP.notify(ply, 0, 4, "Forcefully unowned")
end
DarkRP.definePrivilegedChatCommand("forceunown", "DarkRP_SetDoorOwner", ccDoorUnOwn)
local function unownAll(ply, args)
local target = DarkRP.findPlayer(args[1])
if not IsValid(target) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
return
end
target:keysUnOwnAll()
if ply:EntIndex() == 0 then
DarkRP.log("Console force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30))
else
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unowned all doors owned by " .. target:Nick(), Color(30, 30, 30))
end
DarkRP.notify(ply, 0, 4, "All doors of " .. target:Nick() .. " are now unowned")
end
DarkRP.definePrivilegedChatCommand("forceunownall", "DarkRP_SetDoorOwner", unownAll)
local function ccAddOwner(ply, args)
if ply:EntIndex() == 0 then
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
return
end
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
local target = DarkRP.findPlayer(args)
if not target then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
return
end
if ent:isKeysOwned() then
if not ent:isKeysOwnedBy(target) and not ent:isKeysAllowedToOwn(target) then
ent:addKeysAllowedToOwn(target)
else
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target))
end
return
end
ent:keysOwn(target)
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-added a door owner with forceown", Color(30, 30, 30))
DarkRP.notify(ply, 0, 4, "Forcefully added " .. target:Nick())
end
DarkRP.definePrivilegedChatCommand("forceown", "DarkRP_SetDoorOwner", ccAddOwner)
local function ccRemoveOwner(ply, args)
if ply:EntIndex() == 0 then
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
return
end
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or ent:getKeysDoorTeams() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
local target = DarkRP.findPlayer(args)
if not target then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", args))
return
end
if ent:isKeysAllowedToOwn(target) then
ent:removeKeysAllowedToOwn(target)
end
if ent:isMasterOwner(target) then
ent:keysUnOwn()
elseif ent:isKeysOwnedBy(target) then
ent:removeKeysDoorOwner(target)
end
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-removed a door owner with forceremoveowner", Color(30, 30, 30))
DarkRP.notify(ply, 0, 4, "Forcefully removed " .. target:Nick())
end
DarkRP.definePrivilegedChatCommand("forceremoveowner", "DarkRP_SetDoorOwner", ccRemoveOwner)
local function ccLock(ply, args)
if ply:EntIndex() == 0 then
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
return
end
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("locked"))
ent:keysLock()
if not ent:CreatedByMap() then return end
MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 1, %s);]],
MySQLite.SQLStr(ent:doorIndex()),
MySQLite.SQLStr(string.lower(game.GetMap())),
MySQLite.SQLStr(ent:getKeysTitle() or ""),
ent:getKeysNonOwnable() and 1 or 0
))
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-locked a door with forcelock (locked door is saved)", Color(30, 30, 30))
DarkRP.notify(ply, 0, 4, "Forcefully locked")
end
DarkRP.definePrivilegedChatCommand("forcelock", "DarkRP_ChangeDoorSettings", ccLock)
local function ccUnLock(ply, args)
if ply:EntIndex() == 0 then
print(DarkRP.getPhrase("cmd_cant_be_run_server_console"))
return
end
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:EyePos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("unlocked"))
ent:keysUnLock()
if not ent:CreatedByMap() then return end
MySQLite.query(string.format([[REPLACE INTO darkrp_door VALUES(%s, %s, %s, 0, %s);]],
MySQLite.SQLStr(ent:doorIndex()),
MySQLite.SQLStr(string.lower(game.GetMap())),
MySQLite.SQLStr(ent:getKeysTitle() or ""),
ent:getKeysNonOwnable() and 1 or 0
))
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") force-unlocked a door with forcelock (unlocked door is saved)", Color(30, 30, 30))
DarkRP.notify(ply, 0, 4, "Forcefully unlocked")
end
DarkRP.definePrivilegedChatCommand("forceunlock", "DarkRP_ChangeDoorSettings", ccUnLock)

View File

@@ -0,0 +1,592 @@
local meta = FindMetaTable("Entity")
local pmeta = FindMetaTable("Player")
--[[---------------------------------------------------------------------------
Functions
---------------------------------------------------------------------------]]
function meta:doorIndex()
return self:CreatedByMap() and self:MapCreationID() or nil
end
function DarkRP.doorToEntIndex(num)
local ent = ents.GetMapCreatedEntity(num)
return IsValid(ent) and ent:EntIndex() or nil
end
function DarkRP.doorIndexToEnt(num)
return ents.GetMapCreatedEntity(num) or NULL
end
function meta:isLocked()
local save = self:GetSaveTable()
return save and ((self:isDoor() and save.m_bLocked) or (self:IsVehicle() and save.VehicleLocked))
end
function meta:keysLock()
self:Fire("lock", "", 0)
if isfunction(self.Lock) then self:Lock(true) end -- SCars
if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysLock() end -- SCars
hook.Call("onKeysLocked", nil, self)
end
function meta:keysUnLock()
self:Fire("unlock", "", 0)
if isfunction(self.UnLock) then self:UnLock(true) end -- SCars
if IsValid(self.EntOwner) and self.EntOwner ~= self then return self.EntOwner:keysUnLock() end -- SCars
hook.Call("onKeysUnlocked", nil, self)
end
function meta:keysOwn(ply)
if self:isKeysAllowedToOwn(ply) then
self:addKeysDoorOwner(ply)
return
end
local Owner = self:CPPIGetOwner()
-- Increase vehicle count
if self:IsVehicle() then
if IsValid(ply) then
ply.Vehicles = ply.Vehicles or 0
ply.Vehicles = ply.Vehicles + 1
self.SID = ply.SID
end
-- Decrease vehicle count of the original owner
if IsValid(Owner) and Owner ~= ply then
Owner.Vehicles = Owner.Vehicles or 1
Owner.Vehicles = Owner.Vehicles - 1
end
end
if self:IsVehicle() then
self:CPPISetOwner(ply)
end
if not self:isKeysOwned() and not self:isKeysOwnedBy(ply) then
local doorData = self:getDoorData()
doorData.owner = ply:UserID()
DarkRP.updateDoorData(self, "owner")
end
ply.OwnedNumz = ply.OwnedNumz or 0
if ply.OwnedNumz == 0 and GAMEMODE.Config.propertytax then
timer.Create(ply:SteamID64() .. "propertytax", 270, 0, function() ply.doPropertyTax(ply) end)
end
ply.OwnedNumz = ply.OwnedNumz + 1
ply.Ownedz[self:EntIndex()] = self
end
function meta:keysUnOwn(ply)
if not ply then
ply = self:getDoorOwner()
if not IsValid(ply) then return end
end
if self:isMasterOwner(ply) then
local doorData = self:getDoorData()
self:removeAllKeysExtraOwners()
self:setKeysTitle(nil)
doorData.owner = nil
DarkRP.updateDoorData(self, "owner")
else
self:removeKeysDoorOwner(ply)
end
ply.Ownedz[self:EntIndex()] = nil
ply.OwnedNumz = math.Clamp((ply.OwnedNumz or 1) - 1, 0, math.huge)
end
function pmeta:keysUnOwnAll()
for entIndex, ent in pairs(self.Ownedz or {}) do
if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end
if ent:isMasterOwner(self) then
ent:Fire("unlock", "", 0.6)
end
ent:keysUnOwn(self)
end
for _, ply in ipairs(player.GetAll()) do
if ply == self then continue end
for _, ent in pairs(ply.Ownedz or {}) do
if IsValid(ent) and ent:isKeysAllowedToOwn(self) then
ent:removeKeysAllowedToOwn(self)
end
end
end
self.OwnedNumz = 0
end
local function taxesUnOwnAll(ply, taxables)
for _, ent in ipairs(taxables) do
if ent:isMasterOwner(ply) then
ent:Fire("unlock", "", 0.6)
end
ent:keysUnOwn(ply)
end
end
function pmeta:doPropertyTax()
if not GAMEMODE.Config.propertytax then return end
if self:isCP() and GAMEMODE.Config.cit_propertytax then return end
local taxables = {}
for entIndex, ent in pairs(self.Ownedz or {}) do
if not IsValid(ent) or not ent:isKeysOwnable() then self.Ownedz[entIndex] = nil continue end
local isAllowed = hook.Call("canTaxEntity", nil, self, ent)
if isAllowed == false then continue end
table.insert(taxables, ent)
end
-- co-owned doors
for _, ply in ipairs(player.GetAll()) do
if ply == self then continue end
for _, ent in pairs(ply.Ownedz or {}) do
if not IsValid(ent) or not ent:isKeysOwnedBy(self) then continue end
local isAllowed = hook.Call("canTaxEntity", nil, self, ent)
if isAllowed == false then continue end
table.insert(taxables, ent)
end
end
local numowned = #taxables
if numowned <= 0 then return end
local price = 10
local tax = price * numowned + math.random(-5, 5)
local shouldTax, taxOverride = hook.Call("canPropertyTax", nil, self, tax)
if shouldTax == false then return end
tax = taxOverride or tax
if tax == 0 then return end
local canAfford = self:canAfford(tax)
if canAfford then
self:addMoney(-tax)
DarkRP.notify(self, 0, 5, DarkRP.getPhrase("property_tax", DarkRP.formatMoney(tax)))
else
taxesUnOwnAll(self, taxables)
DarkRP.notify(self, 1, 8, DarkRP.getPhrase("property_tax_cant_afford"))
end
hook.Call("onPropertyTax", nil, self, tax, canAfford)
end
function pmeta:initiateTax()
local taxtime = GAMEMODE.Config.wallettaxtime
local uid = self:SteamID64() -- so we can destroy the timer if the player leaves
timer.Create("rp_tax_" .. uid, taxtime or 600, 0, function()
if not IsValid(self) then
timer.Remove("rp_tax_" .. uid)
return
end
if not GAMEMODE.Config.wallettax then
return -- Don't remove the hook in case it's turned on afterwards.
end
local money = self:getDarkRPVar("money")
local mintax = GAMEMODE.Config.wallettaxmin / 100
local maxtax = GAMEMODE.Config.wallettaxmax / 100 -- convert to decimals for percentage calculations
local startMoney = GAMEMODE.Config.startingmoney
-- Variate the taxes between twice the starting money ($1000 by default) and 200 - 2 times the starting money (100.000 by default)
local tax = (money - (startMoney * 2)) / (startMoney * 198)
tax = math.Min(maxtax, mintax + (maxtax - mintax) * tax)
local taxAmount = tax * money
local shouldTax, amount = hook.Call("canTax", GAMEMODE, self, taxAmount)
if shouldTax == false then return end
taxAmount = amount or taxAmount
taxAmount = math.Max(0, taxAmount)
self:addMoney(-taxAmount)
DarkRP.notify(self, 3, 7, DarkRP.getPhrase("taxday", math.Round(taxAmount / money * 100, 3)))
hook.Call("onPaidTax", DarkRP.hooks, self, tax, money)
end)
end
function GM:canTax(ply)
-- Don't tax players if they have less than twice the starting amount
if ply:getDarkRPVar("money") < (GAMEMODE.Config.startingmoney * 2) then return false end
end
--[[---------------------------------------------------------------------------
Commands
---------------------------------------------------------------------------]]
local function SetDoorOwnable(ply)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
if IsValid(ent:getDoorOwner()) then
ent:keysUnOwn(ent:getDoorOwner())
end
ent:setKeysNonOwnable(not ent:getKeysNonOwnable())
ent:removeAllKeysExtraOwners()
ent:removeAllKeysAllowedToOwn()
ent:removeAllKeysDoorTeams()
ent:setDoorGroup(nil)
ent:setKeysTitle(nil)
-- Save it for future map loads
DarkRP.storeDoorData(ent)
DarkRP.storeDoorGroup(ent, nil)
DarkRP.storeTeamDoorOwnability(ent)
return ""
end
DarkRP.definePrivilegedChatCommand("toggleownable", "DarkRP_ChangeDoorSettings", SetDoorOwnable)
local function SetDoorGroupOwnable(ply, arg)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return
end
if not RPExtraTeamDoors[arg] and arg ~= "" then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("door_group_doesnt_exist")) return "" end
ent:keysUnOwn()
ent:removeAllKeysDoorTeams()
local group = arg ~= "" and arg or nil
ent:setDoorGroup(group)
-- Save it for future map loads
DarkRP.storeDoorGroup(ent, group)
DarkRP.storeTeamDoorOwnability(ent)
DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set"))
return ""
end
DarkRP.definePrivilegedChatCommand("togglegroupownable", "DarkRP_ChangeDoorSettings", SetDoorGroupOwnable)
local function SetDoorTeamOwnable(ply, arg)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or (not ent:isDoor() and not ent:IsVehicle()) or ply:GetPos():DistToSqr(ent:GetPos()) > 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return ""
end
arg = tonumber(arg)
if not arg then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end
if not RPExtraTeams[arg] and arg ~= nil then DarkRP.notify(ply, 1, 10, DarkRP.getPhrase("job_doesnt_exist")) return "" end
if IsValid(ent:getDoorOwner()) then
ent:keysUnOwn(ent:getDoorOwner())
end
ent:setDoorGroup(nil)
DarkRP.storeDoorGroup(ent, nil)
local doorTeams = ent:getKeysDoorTeams()
if not doorTeams or not doorTeams[arg] then
ent:addKeysDoorTeam(arg)
else
ent:removeKeysDoorTeam(arg)
end
DarkRP.notify(ply, 0, 8, DarkRP.getPhrase("door_group_set"))
DarkRP.storeTeamDoorOwnability(ent)
ent:keysUnOwn()
return ""
end
DarkRP.definePrivilegedChatCommand("toggleteamownable", "DarkRP_ChangeDoorSettings", SetDoorTeamOwnable)
local function OwnDoor(ply)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 40000 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return ""
end
local Owner = ent:CPPIGetOwner()
if ply:isArrested() then
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unown_arrested"))
return ""
end
if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or not fn.Null(ent:getKeysDoorTeams() or {}) then
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("door_unownable"))
return ""
end
if ent:isKeysOwnedBy(ply) then
local bAllowed, strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
if bAllowed == false then
if strReason and strReason ~= "" then
DarkRP.notify(ply, 1, 4, strReason)
end
return ""
end
if ent:isMasterOwner(ply) then
ent:removeAllKeysExtraOwners()
ent:removeAllKeysAllowedToOwn()
ent:Fire("unlock", "", 0)
end
ent:keysUnOwn(ply)
ent:setKeysTitle(nil)
local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5)
hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack)
ply:addMoney(GiveMoneyBack)
local bSuppress = hook.Call("hideSellDoorMessage", GAMEMODE, ply, ent)
if not bSuppress then
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("door_sold", DarkRP.formatMoney(GiveMoneyBack)))
end
else
if ent:isKeysOwned() and not ent:isKeysAllowedToOwn(ply) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_already_owned"))
return ""
end
local iCost = hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent)
if not ply:canAfford(iCost) then
DarkRP.notify(ply, 1, 4, ent:IsVehicle() and DarkRP.getPhrase("vehicle_cannot_afford") or DarkRP.getPhrase("door_cannot_afford"))
return ""
end
local bAllowed, strReason, bSuppress = hook.Call("playerBuy" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
if bAllowed == false then
if strReason and strReason ~= "" then
DarkRP.notify(ply, 1, 4, strReason)
end
return ""
end
local bVehicle = ent:IsVehicle()
if bVehicle and (ply.Vehicles or 0) >= GAMEMODE.Config.maxvehicles and Owner ~= ply then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("vehicle")))
return ""
end
if not bVehicle and (ply.OwnedNumz or 0) >= GAMEMODE.Config.maxdoors then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("limit", DarkRP.getPhrase("door")))
return ""
end
ply:addMoney(-iCost)
if not bSuppress then
DarkRP.notify(ply, 0, 4, bVehicle and DarkRP.getPhrase("vehicle_bought", DarkRP.formatMoney(iCost), "") or DarkRP.getPhrase("door_bought", DarkRP.formatMoney(iCost), ""))
end
ent:keysOwn(ply)
hook.Call("playerBought" .. (bVehicle and "Vehicle" or "Door"), GAMEMODE, ply, ent, iCost)
end
return ""
end
DarkRP.defineChatCommand("toggleown", OwnDoor)
local function UnOwnAll(ply, cmd, args)
local amount = 0
local cost = 0
local unownables = {}
for entIndex, ent in pairs(ply.Ownedz or {}) do
if not IsValid(ent) or not ent:isKeysOwnable() then ply.Ownedz[entIndex] = nil continue end
table.insert(unownables, ent)
end
for _, otherPly in ipairs(player.GetAll()) do
if ply == otherPly then continue end
for _, ent in pairs(otherPly.Ownedz or {}) do
if IsValid(ent) and ent:isKeysOwnedBy(ply) then
table.insert(unownables, ent)
end
end
end
for entIndex, ent in ipairs(unownables) do
local bAllowed, _strReason = hook.Call("playerSell" .. (ent:IsVehicle() and "Vehicle" or "Door"), GAMEMODE, ply, ent)
if bAllowed == false then continue end
if ent:isMasterOwner(ply) then
ent:Fire("unlock", "", 0)
end
ent:keysUnOwn(ply)
amount = amount + 1
local GiveMoneyBack = math.floor((hook.Call("get" .. (ent:IsVehicle() and "Vehicle" or "Door") .. "Cost", GAMEMODE, ply, ent) * 0.666) + 0.5)
hook.Call("playerKeysSold", GAMEMODE, ply, ent, GiveMoneyBack)
cost = cost + GiveMoneyBack
end
if amount == 0 then DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("no_doors_owned")) return "" end
ply:addMoney(math.floor(cost))
DarkRP.notify(ply, 2, 4, DarkRP.getPhrase("sold_x_doors", amount, DarkRP.formatMoney(math.floor(cost))))
return ""
end
DarkRP.defineChatCommand("unownalldoors", UnOwnAll)
local function SetDoorTitle(ply, args)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return ""
end
if ent:isKeysOwnedBy(ply) then
ent:setKeysTitle(args)
return ""
end
local function onCAMIResult(allowed)
if not allowed then
DarkRP.notify(ply, 1, 6, DarkRP.getPhrase("no_privilege"))
return
end
local hasTeams = not fn.Null(ent:getKeysDoorTeams() or {})
if ent:isKeysOwned() or ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then
ent:setKeysTitle(args)
end
if ent:getKeysNonOwnable() or ent:getKeysDoorGroup() or hasTeams then
DarkRP.storeDoorData(trace.Entity)
end
end
CAMI.PlayerHasAccess(ply, "DarkRP_ChangeDoorSettings", onCAMIResult)
return ""
end
DarkRP.defineChatCommand("title", SetDoorTitle)
local function RemoveDoorOwner(ply, args)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return ""
end
local target = DarkRP.findPlayer(args)
if ent:getKeysNonOwnable() then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_rem_owners_unownable"))
return ""
end
if not target then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args)))
return ""
end
if not ent:isKeysOwnedBy(ply) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent"))
return ""
end
local canDo = hook.Call("onAllowedToOwnRemoved", nil, ply, ent, target)
if canDo == false then return "" end
if ent:isKeysAllowedToOwn(target) then
ent:removeKeysAllowedToOwn(target)
end
if ent:isKeysOwnedBy(target) then
ent:removeKeysDoorOwner(target)
end
return ""
end
DarkRP.defineChatCommand("removeowner", RemoveDoorOwner)
DarkRP.defineChatCommand("ro", RemoveDoorOwner)
local function AddDoorOwner(ply, args)
local trace = ply:GetEyeTrace()
local ent = trace.Entity
if not IsValid(ent) or not ent:isKeysOwnable() or ply:GetPos():DistToSqr(ent:GetPos()) >= 12100 then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("must_be_looking_at", DarkRP.getPhrase("door_or_vehicle")))
return ""
end
local target = DarkRP.findPlayer(args)
if ent:getKeysNonOwnable() then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("door_add_owners_unownable"))
return ""
end
if not target then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("could_not_find", tostring(args)))
return ""
end
if not ent:isKeysOwnedBy(ply) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("do_not_own_ent"))
return ""
end
if ent:isKeysOwnedBy(target) or ent:isKeysAllowedToOwn(target) then
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("rp_addowner_already_owns_door", target:Nick()))
return ""
end
local canDo = hook.Call("onAllowedToOwnAdded", nil, ply, ent, target)
if canDo == false then return "" end
ent:addKeysAllowedToOwn(target)
return ""
end
DarkRP.defineChatCommand("addowner", AddDoorOwner)
DarkRP.defineChatCommand("ao", AddDoorOwner)

View File

@@ -0,0 +1,179 @@
util.AddNetworkString("DarkRP_UpdateDoorData")
util.AddNetworkString("DarkRP_RemoveDoorData")
util.AddNetworkString("DarkRP_RemoveDoorVar")
util.AddNetworkString("DarkRP_AllDoorData")
--[[---------------------------------------------------------------------------
Interface functions
---------------------------------------------------------------------------]]
local eMeta = FindMetaTable("Entity")
function eMeta:getDoorData()
if not self:isKeysOwnable() then return {} end
self.DoorData = self.DoorData or {}
return self.DoorData
end
function eMeta:setKeysNonOwnable(ownable)
self:getDoorData().nonOwnable = ownable or nil
DarkRP.updateDoorData(self, "nonOwnable")
end
function eMeta:setKeysTitle(title)
self:getDoorData().title = title ~= "" and title or nil
DarkRP.updateDoorData(self, "title")
end
function eMeta:setDoorGroup(group)
self:getDoorData().groupOwn = group
DarkRP.updateDoorData(self, "groupOwn")
end
function eMeta:addKeysDoorTeam(t)
local doorData = self:getDoorData()
doorData.teamOwn = doorData.teamOwn or {}
doorData.teamOwn[t] = true
DarkRP.updateDoorData(self, "teamOwn")
end
function eMeta:removeKeysDoorTeam(t)
local doorData = self:getDoorData()
doorData.teamOwn = doorData.teamOwn or {}
doorData.teamOwn[t] = nil
if fn.Null(doorData.teamOwn) then
doorData.teamOwn = nil
end
DarkRP.updateDoorData(self, "teamOwn")
end
function eMeta:removeAllKeysDoorTeams()
local doorData = self:getDoorData()
doorData.teamOwn = nil
DarkRP.updateDoorData(self, "teamOwn")
end
function eMeta:addKeysAllowedToOwn(ply)
local doorData = self:getDoorData()
doorData.allowedToOwn = doorData.allowedToOwn or {}
doorData.allowedToOwn[ply:UserID()] = true
DarkRP.updateDoorData(self, "allowedToOwn")
end
function eMeta:removeKeysAllowedToOwn(ply)
local doorData = self:getDoorData()
doorData.allowedToOwn = doorData.allowedToOwn or {}
doorData.allowedToOwn[ply:UserID()] = nil
if fn.Null(doorData.allowedToOwn) then
doorData.allowedToOwn = nil
end
DarkRP.updateDoorData(self, "allowedToOwn")
end
function eMeta:removeAllKeysAllowedToOwn()
local doorData = self:getDoorData()
doorData.allowedToOwn = nil
DarkRP.updateDoorData(self, "allowedToOwn")
end
function eMeta:addKeysDoorOwner(ply)
local doorData = self:getDoorData()
doorData.extraOwners = doorData.extraOwners or {}
doorData.extraOwners[ply:UserID()] = true
DarkRP.updateDoorData(self, "extraOwners")
self:removeKeysAllowedToOwn(ply)
end
function eMeta:removeKeysDoorOwner(ply)
local doorData = self:getDoorData()
doorData.extraOwners = doorData.extraOwners or {}
doorData.extraOwners[ply:UserID()] = nil
if fn.Null(doorData.extraOwners) then
doorData.extraOwners = nil
end
DarkRP.updateDoorData(self, "extraOwners")
end
function eMeta:removeAllKeysExtraOwners()
local doorData = self:getDoorData()
doorData.extraOwners = nil
DarkRP.updateDoorData(self, "extraOwners")
end
function eMeta:removeDoorData()
net.Start("DarkRP_RemoveDoorData")
net.WriteUInt(self:EntIndex(), 32)
net.Send(player.GetAll())
end
--[[---------------------------------------------------------------------------
Networking
---------------------------------------------------------------------------]]
local plyMeta = FindMetaTable("Player")
function plyMeta:sendDoorData()
if self:EntIndex() == 0 then return end
local res = {}
for _, v in ipairs(ents.GetAll()) do
if not v:getDoorData() or table.IsEmpty(v:getDoorData()) then continue end
res[v:EntIndex()] = v:getDoorData()
end
net.Start("DarkRP_AllDoorData")
net.WriteUInt(table.Count(res), 16)
for ix, vars in pairs(res) do
net.WriteUInt(ix, 16)
net.WriteUInt(table.Count(vars), 8)
for varName, value in pairs(vars) do
DarkRP.writeNetDoorVar(varName, value)
end
end
net.Send(self)
end
concommand.Add("_sendAllDoorData", fn.Id) -- Backwards compatibility
hook.Add("PlayerInitialSpawn", "DarkRP_DoorData", plyMeta.sendDoorData)
function DarkRP.updateDoorData(door, member)
if not IsValid(door) or not door:getDoorData() then error("Calling updateDoorData on a door that has no data!") end
local value = door:getDoorData()[member]
if value == nil then
local doorvar = DarkRP.getDoorVarsByName()[member]
net.Start("DarkRP_RemoveDoorVar")
net.WriteUInt(door:EntIndex(), 16)
if not doorvar then
net.WriteUInt(0, 8)
net.WriteString(member)
else
net.WriteUInt(doorvar.id, 8)
end
net.Broadcast()
return
end
net.Start("DarkRP_UpdateDoorData")
net.WriteUInt(door:EntIndex(), 32)
DarkRP.writeNetDoorVar(member, value)
net.Broadcast()
end

View File

@@ -0,0 +1,851 @@
DarkRP.doorToEntIndex = DarkRP.stub{
name = "doorToEntIndex",
description = "Get an ENT index from a door index.",
parameters = {
{
name = "index",
description = "The door index",
type = "number",
optional = false
}
},
returns = {
{
name = "index",
description = "The ENT index",
type = "number",
}
},
metatable = DarkRP
}
DarkRP.doorIndexToEnt = DarkRP.stub{
name = "doorIndexToEnt",
description = "Get the entity of a door index (inverse of ent:doorIndexToEnt()). Note: the door MUST have been created by the map!",
parameters = {
{
name = "index",
description = "The door index",
type = "number",
optional = false
}
},
returns = {
{
name = "door",
description = "The door entity",
type = "Entity",
}
},
metatable = DarkRP
}
DarkRP.writeNetDoorVar = DarkRP.stub{
name = "writeNetDoorVar",
description = "Internal function. You probably shouldn't need this. DarkRP calls this function when sending DoorVar net messages. This function writes the net data for a specific DoorVar.",
parameters = {
{
name = "name",
description = "The name of the DoorVar.",
type = "string",
optional = false
},
{
name = "value",
description = "The value of the DoorVar.",
type = "any",
optional = false
}
},
returns = {
},
metatable = DarkRP
}
DarkRP.ENTITY.doorIndex = DarkRP.stub{
name = "doorIndex",
description = "Get the door index of a door. Use this to store door information in the database.",
parameters = {
},
returns = {
{
name = "index",
description = "The door index.",
type = "number"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.keysLock = DarkRP.stub{
name = "keysLock",
description = "Lock this door or vehicle.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.keysUnLock = DarkRP.stub{
name = "keysUnLock",
description = "Unlock this door or vehicle.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.addKeysAllowedToOwn = DarkRP.stub{
name = "addKeysAllowedToOwn",
description = "Make this player allowed to co-own the door or vehicle.",
parameters = {
{
name = "ply",
description = "The player to give permission to co-own.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.addKeysDoorOwner = DarkRP.stub{
name = "addKeysDoorOwner",
description = "Make this player a co-owner of the door.",
parameters = {
{
name = "ply",
description = "The player to add as co-owner.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeKeysDoorOwner = DarkRP.stub{
name = "removeKeysDoorOwner",
description = "Remove this player as co-owner",
parameters = {
{
name = "ply",
description = "The player to remove from the co-owners list.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.keysOwn = DarkRP.stub{
name = "keysOwn",
description = "Make the player the master owner of the door",
parameters = {
{
name = "ply",
description = "The player set as master owner.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.keysUnOwn = DarkRP.stub{
name = "keysUnOwn",
description = "Make this player unown the door/vehicle.",
parameters = {
{
name = "ply",
description = "The player.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.setKeysNonOwnable = DarkRP.stub{
name = "setKeysNonOwnable",
description = "Set whether this door or vehicle is ownable or not.",
parameters = {
{
name = "ownable",
description = "Whether the door or vehicle is blocked from ownership.",
type = "boolean",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.setKeysTitle = DarkRP.stub{
name = "setKeysTitle",
description = "Set the title of a door or vehicle.",
parameters = {
{
name = "title",
description = "The title of the door.",
type = "string",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.setDoorGroup = DarkRP.stub{
name = "setDoorGroup",
description = "Set the door group of a door.",
parameters = {
{
name = "group",
description = "The door group.",
type = "string",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.addKeysDoorTeam = DarkRP.stub{
name = "addKeysDoorTeam",
description = "Allow a team to lock/unlock a door..",
parameters = {
{
name = "team",
description = "The team to add to team owners.",
type = "number",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeKeysDoorTeam = DarkRP.stub{
name = "removeKeysDoorTeam",
description = "Disallow a team from locking/unlocking a door.",
parameters = {
{
name = "team",
description = "The team to remove from team owners.",
type = "number",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeAllKeysDoorTeams = DarkRP.stub{
name = "removeAllKeysDoorTeams",
description = "Disallow all teams from locking/unlocking a door.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeAllKeysExtraOwners = DarkRP.stub{
name = "removeAllKeysExtraOwners",
description = "Remove all co-owners from a door.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeAllKeysAllowedToOwn = DarkRP.stub{
name = "removeAllKeysAllowedToOwn",
description = "Disallow all people from owning the door.",
parameters = {
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.removeKeysAllowedToOwn = DarkRP.stub{
name = "removeKeysAllowedToOwn",
description = "Remove a player from being allowed to co-own a door.",
parameters = {
{
name = "ply",
description = "The player to be removed.",
type = "Player",
optional = false
}
},
returns = {
},
metatable = DarkRP.ENTITY
}
DarkRP.ENTITY.isLocked = DarkRP.stub{
name = "isLocked",
description = "Whether this door/vehicle is locked.",
parameters = {
},
returns = {
{
name = "answer",
description = "Whether it's locked.",
type = "boolean"
}
},
metatable = DarkRP.ENTITY
}
DarkRP.PLAYER.keysUnOwnAll = DarkRP.stub{
name = "keysUnOwnAll",
description = "Unown every door and vehicle owned by this player.",
parameters = {
},
returns = {
},
metatable = DarkRP.PLAYER
}
DarkRP.PLAYER.sendDoorData = DarkRP.stub{
name = "sendDoorData",
description = "Internal function. Sends all door data to a player.",
parameters = {
},
returns = {
},
metatable = DarkRP.PLAYER
}
DarkRP.PLAYER.doPropertyTax = DarkRP.stub{
name = "doPropertyTax",
description = "Tax a player based on the amount of doors and vehicles they have.",
parameters = {
},
returns = {
},
metatable = DarkRP.PLAYER
}
DarkRP.PLAYER.initiateTax = DarkRP.stub{
name = "initiateTax",
description = "Internal function, starts the timer that taxes the player every once in a while.",
parameters = {
},
returns = {
},
metatable = DarkRP.PLAYER
}
DarkRP.hookStub{
name = "onKeysLocked",
description = "Called when a door or vehicle was locked.",
parameters = {
{
name = "ent",
description = "The entity that was locked.",
type = "Entity"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "onKeysUnlocked",
description = "Called when a door or vehicle was unlocked.",
parameters = {
{
name = "ent",
description = "The entity that was unlocked.",
type = "Entity"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "playerKeysSold",
description = "When a player sold a door or vehicle.",
parameters = {
{
name = "ply",
description = "The player who sold the door or vehicle.",
type = "Player"
},
{
name = "ent",
description = "The entity that was sold.",
type = "Entity"
},
{
name = "GiveMoneyBack",
description = "The amount of money refunded to the player",
type = "number"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "hideSellDoorMessage",
description = "Whether to hide the door/vehicle sold notification",
parameters = {
{
name = "ply",
description = "The player who sold the door or vehicle.",
type = "Player"
},
{
name = "ent",
description = "The entity that was sold.",
type = "Player"
},
},
returns = {
{
name = "hide",
description = "Whether to hide the notification.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "getDoorCost",
description = "Get the cost of a door.",
parameters = {
{
name = "ply",
description = "The player who has the intention to purchase the door.",
type = "Player"
},
{
name = "ent",
description = "The door",
type = "Entity"
}
},
returns = {
{
name = "cost",
description = "The price of the door.",
type = "number"
}
}
}
DarkRP.hookStub{
name = "getVehicleCost",
description = "Get the cost of a vehicle.",
parameters = {
{
name = "ply",
description = "The player who has the intention to purchase the vehicle.",
type = "Player"
},
{
name = "ent",
description = "The vehicle",
type = "Entity"
}
},
returns = {
{
name = "cost",
description = "The price of the vehicle.",
type = "number"
}
}
}
DarkRP.hookStub{
name = "playerBuyDoor",
description = "When a player purchases a door.",
parameters = {
{
name = "ply",
description = "The player who is to buy the door.",
type = "Player"
},
{
name = "ent",
description = "The door.",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to buy the door.",
type = "boolean"
},
{
name = "reason",
description = "The reason why a player is not allowed to buy the door, if applicable.",
type = "string"
},
{
name = "surpress",
description = "Whether to show the reason in a notification to the player, return true here to surpress the message.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "playerSellDoor",
description = "When a player is about to sell a door.",
parameters = {
{
name = "ply",
description = "The player who is to sell the door.",
type = "Player"
},
{
name = "ent",
description = "The door.",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to sell the door.",
type = "boolean"
},
{
name = "reason",
description = "The reason why a player is not allowed to sell the door, if applicable.",
type = "string"
}
}
}
DarkRP.hookStub{
name = "onAllowedToOwnAdded",
description = "When a player adds a co-owner to a door.",
parameters = {
{
name = "ply",
description = "The player who adds the co-owner.",
type = "Player"
},
{
name = "ent",
description = "The door.",
type = "Entity"
},
{
name = "target",
description = "The target who will be allowed to own the door.",
type = "Player"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to add this player as co-owner.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "onAllowedToOwnRemoved",
description = "When a player removes a co-owner to a door.",
parameters = {
{
name = "ply",
description = "The player who removes the co-owner.",
type = "Player"
},
{
name = "ent",
description = "The door.",
type = "Entity"
},
{
name = "target",
description = "The target who will not be allowed to own the door anymore.",
type = "Player"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to remove this player as co-owner.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "playerBuyVehicle",
description = "When a player purchases a vehicle.",
parameters = {
{
name = "ply",
description = "The player who is to buy the vehicle.",
type = "Player"
},
{
name = "ent",
description = "The vehicle.",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to buy the vehicle.",
type = "boolean"
},
{
name = "reason",
description = "The reason why a player is not allowed to buy the vehicle, if applicable.",
type = "string"
},
{
name = "surpress",
description = "Whether to show the reason in a notification to the player, return true here to surpress the message.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "playerSellVehicle",
description = "When a player is about to sell a vehicle.",
parameters = {
{
name = "ply",
description = "The player who is to sell the vehicle.",
type = "Player"
},
{
name = "ent",
description = "The vehicle.",
type = "Entity"
}
},
returns = {
{
name = "allowed",
description = "Whether the player is allowed to sell the vehicle.",
type = "boolean"
},
{
name = "reason",
description = "The reason why a player is not allowed to sell the vehicle, if applicable.",
type = "string"
}
}
}
DarkRP.hookStub{
name = "playerBoughtDoor",
description = "Called when a player has purchased a door.",
parameters = {
{
name = "ply",
description = "The player who has purchased the door.",
type = "Player"
},
{
name = "ent",
description = "The purchased door.",
type = "Entity"
},
{
name = "cost",
description = "The cost of the purchased door.",
type = "number"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "playerBoughtVehicle",
description = "Called when a player has purchased a vehicle.",
parameters = {
{
name = "ply",
description = "The player who has purchased the vehicle.",
type = "Player"
},
{
name = "ent",
description = "The purchased vehicle.",
type = "Entity"
},
{
name = "cost",
description = "The cost of the purchased vehicle.",
type = "number"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "canTax",
description = "Called before a player pays taxes. This hook differs from onPaidTax in that this hook can prevent the taxing and change the tax amount.",
parameters = {
{
name = "ply",
description = "The player who was taxed.",
type = "Player"
},
{
name = "tax",
description = "The amount of money that is about to be taxed from the player.",
type = "number"
}
},
returns = {
{
name = "shouldTax",
description = "Whether the player is to be taxed. Return false here to prevent the player from being taxed.",
type = "boolean"
},
{
name = "tax",
description = "Overrides the amount of money that is taxed.",
type = "number"
}
}
}
DarkRP.hookStub{
name = "onPaidTax",
description = "Called when a player has paid tax.",
parameters = {
{
name = "ply",
description = "The player who was taxed.",
type = "Player"
},
{
name = "tax",
description = "The percentage of tax taken from his wallet.",
type = "number"
},
{
name = "wallet",
description = "The amount of money the player had before the tax was applied.",
type = "number"
}
},
returns = {
}
}
DarkRP.hookStub{
name = "canTaxEntity",
description = "Called right before a player's property is taxed. Decides per entity whether it can be taxed.",
parameters = {
{
name = "ply",
description = "The player whose property will be taxed.",
type = "Player"
},
{
name = "ent",
description = "The door or vehicle that is to be taxed",
type = "Entity"
}
},
returns = {
{
name = "shouldTax",
description = "Return false here to prevent this specific entity from being taxed.",
type = "boolean"
}
}
}
DarkRP.hookStub{
name = "canPropertyTax",
description = "Called right before a player's property is taxed. This hook differs from onPropertyTax in that onPropertyTax is called AFTER the taxing. With this hook, one can influence the taxing process.",
parameters = {
{
name = "ply",
description = "The player whose property will be taxed.",
type = "Player"
},
{
name = "tax",
description = "The amount of money that will be taxed (unless overridden by this hook).",
type = "number"
}
},
returns = {
{
name = "shouldTax",
description = "Return false here to prevent the doors from being taxed.",
type = "boolean"
},
{
name = "taxOverride",
description = "Override the tax amount.",
type = "number"
}
}
}
DarkRP.hookStub{
name = "onPropertyTax",
description = "Called right AFTER a player's property is taxed. Please use canPropertyTax if you want to influence the taxing process.",
parameters = {
{
name = "ply",
description = "The player whose property has been taxed.",
type = "Player"
},
{
name = "tax",
description = "The amount of money that has been taxed.",
type = "number"
},
{
name = "couldAfford",
description = "Whether the player was able to afford the tax.",
type = "boolean"
}
},
returns = {
}
}

View File

@@ -0,0 +1,13 @@
DarkRP.declareChatCommand{
command = "enablestorm",
description = "Enable meteor storms.",
delay = 1.5,
condition = hasCommandsPriv
}
DarkRP.declareChatCommand{
command = "disablestorm",
description = "Disable meteor storms.",
delay = 1.5,
condition = hasCommandsPriv
}

View File

@@ -0,0 +1,224 @@
resource.AddFile("sound/earthquake.mp3")
util.PrecacheSound("earthquake.mp3")
--[[---------------------------------------------------------
Variables
---------------------------------------------------------]]
local timeLeft = 10
local stormOn = false
--[[---------------------------------------------------------
Meteor storm
---------------------------------------------------------]]
local function StormStart()
local phrase = DarkRP.getPhrase("meteor_approaching")
for _, v in ipairs(player.GetAll()) do
if v:Alive() then
v:PrintMessage(HUD_PRINTCENTER, phrase)
v:PrintMessage(HUD_PRINTTALK, phrase)
end
end
end
local function StormEnd()
local phrase = DarkRP.getPhrase("meteor_passing")
for _, v in ipairs(player.GetAll()) do
if v:Alive() then
v:PrintMessage(HUD_PRINTCENTER, phrase)
v:PrintMessage(HUD_PRINTTALK, phrase)
end
end
end
local function ControlStorm()
timeLeft = timeLeft - 1
if timeLeft < 1 then
if stormOn then
timeLeft = math.random(300, 500)
stormOn = false
timer.Stop("start")
StormEnd()
else
timeLeft = math.random(60, 90)
stormOn = true
timer.Start("start")
StormStart()
end
end
end
local function AttackEnt(ent)
local meteor = ents.Create("meteor")
meteor.nodupe = true
meteor:Spawn()
meteor:SetMeteorTarget(ent)
end
local function StartShower()
timer.Adjust("start", math.random(0.1, 1), 0, StartShower)
for _, v in ipairs(player.GetAll()) do
if math.random(0, 2) == 0 and v:Alive() then
AttackEnt(v)
end
end
end
local function StartStorm(ply)
timer.Start("stormControl")
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_enabled"))
end
DarkRP.definePrivilegedChatCommand("enablestorm", "DarkRP_AdminCommands", StartStorm)
local function StopStorm(ply)
timer.Stop("stormControl")
stormOn = false
timer.Stop("start")
StormEnd()
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("meteor_disabled"))
end
DarkRP.definePrivilegedChatCommand("disablestorm", "DarkRP_AdminCommands", StopStorm)
timer.Create("start", 1, 0, StartShower)
timer.Create("stormControl", 1, 0, ControlStorm)
timer.Stop("start")
timer.Stop("stormControl")
--[[---------------------------------------------------------
Earthquake
---------------------------------------------------------]]
local lastmagnitudes = {} -- The magnitudes of the last tremors
local tremor
local function createTremor()
tremor = ents.Create("env_physexplosion")
tremor:SetPos(Vector(0, 0, 0))
tremor:SetKeyValue("radius", 9999999999)
tremor:SetKeyValue("spawnflags", 7)
tremor.nodupe = true
tremor:Spawn()
end
hook.Add("PostCleanupMap", "DarkRP_events", createTremor)
hook.Add("InitPostEntity", "DarkRP_SetupTremor", function()
createTremor()
end)
local function TremorReport()
local mag = table.remove(lastmagnitudes, 1)
if mag then
if mag < 6.5 then
DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthtremor_report", tostring(mag)))
return
end
DarkRP.notifyAll(0, 3, DarkRP.getPhrase("earthquake_report", tostring(mag)))
end
end
local function EarthQuakeTest()
if not GAMEMODE.Config.earthquakes then return end
if GAMEMODE.Config.quakechance and math.random(0, GAMEMODE.Config.quakechance) < 1 then
local en = ents.FindByClass("prop_physics")
local plys = player.GetAll()
local force = math.random(10, 1000)
tremor:SetKeyValue("magnitude", force / 6)
for _, v in ipairs(plys) do
v:EmitSound("earthquake.mp3", force / 6, 100)
end
tremor:Fire("explode","",0.5)
util.ScreenShake(Vector(0, 0, 0), force, math.random(25, 50), math.random(5, 12), 9999999999)
table.insert(lastmagnitudes, math.floor((force / 10) + .5) / 10)
timer.Simple(10, function() TremorReport() end)
for _, e in ipairs(en) do
local rand = math.random(650, 1000)
if rand < force and rand % 2 == 0 then
e:Fire("enablemotion", "", 0)
constraint.RemoveAll(e)
end
if e:IsOnGround() then
e:TakeDamage((force / 100) + 15, game.GetWorld())
end
end
end
end
timer.Create("EarthquakeTest", 1, 0, EarthQuakeTest)
--[[---------------------------------------------------------
Flammable
---------------------------------------------------------]]
-- Class names as index
local flammablePropsKV = {
drug = true,
drug_lab = true,
food = true,
gunlab = true,
letter = true,
microwave = true,
money_printer = true,
spawned_shipment = true,
spawned_weapon = true,
spawned_money = true
}
local flammableProps = {} -- Numbers as index
for k in pairs(flammablePropsKV) do table.insert(flammableProps, k) end
local function IsFlammable(ent)
return flammablePropsKV[ent:GetClass()] ~= nil
end
-- FireSpread from SeriousRP
local function FireSpread(ent, chanceDiv)
if not ent:IsOnFire() then return end
if ent:isMoneyBag() then
ent:Remove()
end
local rand = math.random(0, 300 / chanceDiv)
if rand > 1 then return end
local en = ents.FindInSphere(ent:GetPos(), math.random(20, 90))
for _, v in ipairs(en) do
if not IsFlammable(v) or v == ent then continue end
if not v.burned then
v:Ignite(math.random(5,180), 0)
v.burned = true
break -- Don't ignite all entities in sphere at once, just one at a time
end
local color = v:GetColor()
if (color.r - 51) >= 0 then color.r = color.r - 51 end
if (color.g - 51) >= 0 then color.g = color.g - 51 end
if (color.b - 51) >= 0 then color.b = color.b - 51 end
v:SetColor(color)
if (color.r + color.g + color.b) < 103 and math.random(1, 100) < 35 then
v:Fire("enablemotion", "", 0)
constraint.RemoveAll(v)
end
end
end
local function FlammablePropThink()
local class = flammableProps[math.random(#flammableProps)]
local entities = ents.FindByClass(class)
local ent = entities[math.random(#entities)]
if class ~= "letter" then return end
if not ent then return end
-- The amount of classes and the amount of entities in a class
-- affect the chance of fire spreading. This should be minimized.
FireSpread(ent, #entities * #flammableProps)
end
timer.Create("FlammableProps", 0.1, 0, FlammablePropThink)

View File

@@ -0,0 +1,88 @@
local function IncludeFolder(fol)
fol = string.lower(fol)
local _, folders = file.Find(fol .. "*", "LUA")
for _, folder in SortedPairs(folders, true) do
if folder ~= "." and folder ~= ".." then
for _, File in SortedPairs(file.Find(fol .. folder .. "/sh_*.lua", "LUA"), true) do
include(fol .. folder .. "/" .. File)
end
for _, File in SortedPairs(file.Find(fol .. folder .. "/cl_*.lua", "LUA"), true) do
include(fol .. folder .. "/" .. File)
end
end
end
end
IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/")
IncludeFolder(GM.FolderName .. "/gamemode/modules/fadmin/fadmin/playeractions/")
--[[---------------------------------------------------------------------------
FAdmin global settings
---------------------------------------------------------------------------]]
net.Receive("FAdmin_GlobalSetting", function(len)
local setting, value = net.ReadString(), net.ReadType(net.ReadUInt(8))
FAdmin.GlobalSetting = FAdmin.GlobalSetting or {}
FAdmin.GlobalSetting[setting] = value
end)
net.Receive("FAdmin_PlayerSetting", function(len)
local uid, setting, value = net.ReadUInt(16), net.ReadString(), net.ReadType(net.ReadUInt(8))
FAdmin.PlayerSettings = FAdmin.PlayerSettings or {}
FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {}
FAdmin.PlayerSettings[uid][setting] = value
end)
timer.Create("FAdmin_CleanPlayerSettings", 300, 0, function()
if not FAdmin.PlayerSettings then return end
-- find highest userID
local max = math.huge
for _, v in ipairs(player.GetAll()) do
if IsValid(v) and v:UserID() > max then max = v:UserID() end
end
-- Anything lower than the maximal UserID can be culled
-- This prevents data from joining players from being removed
-- New players always get a strictly higher UserID than any player before them
for uid in pairs(FAdmin.PlayerSettings) do
if IsValid(Player(uid)) or uid > max then continue end
FAdmin.PlayerSettings[uid] = nil
end
end)
local plyMeta = FindMetaTable("Player")
function plyMeta:FAdmin_GetGlobal(setting)
local uid = self:UserID()
return FAdmin.PlayerSettings and FAdmin.PlayerSettings[uid] and FAdmin.PlayerSettings[uid][setting] or nil
end
net.Receive("FAdmin_GlobalPlayerSettings", function(len)
local globalCount = net.ReadUInt(8)
FAdmin.GlobalSetting = FAdmin.GlobalSetting or {}
for i = 1, globalCount do
FAdmin.GlobalSetting[net.ReadString()] = net.ReadType(net.ReadUInt(8))
end
local plyCount = net.ReadUInt(8)
FAdmin.PlayerSettings = FAdmin.PlayerSettings or {}
for i = 1, plyCount do
local uid = net.ReadUInt(16)
local count = net.ReadUInt(8)
FAdmin.PlayerSettings[uid] = FAdmin.PlayerSettings[uid] or {}
for j = 1, count do
FAdmin.PlayerSettings[uid][net.ReadString()] = net.ReadType(net.ReadUInt(8))
end
end
end)

View File

@@ -0,0 +1,99 @@
if not FAdmin or not FAdmin.StartHooks then return end
FAdmin.StartHooks["DarkRP"] = function()
-- DarkRP information:
FAdmin.ScoreBoard.Player:AddInformation("Money", function(ply) if LocalPlayer():IsAdmin() then return DarkRP.formatMoney(ply:getDarkRPVar("money")) end end)
FAdmin.ScoreBoard.Player:AddInformation("Steam name", function(ply) return ply:SteamName() end)
FAdmin.ScoreBoard.Player:AddInformation("Wanted", function(ply) if ply:getDarkRPVar("wanted") then return tostring(ply:getDarkRPVar("wantedReason") or "N/A") end end)
FAdmin.ScoreBoard.Player:AddInformation("Community link", function(ply) return FAdmin.SteamToProfile(ply) end)
FAdmin.ScoreBoard.Player:AddInformation("Rank", function(ply)
if FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SeeAdmins") then
return ply:GetUserGroup()
end
end)
FAdmin.ScoreBoard.Player:AddInformation("Wanted reason", function(ply)
if ply:isWanted() and LocalPlayer():isCP() then
return ply:getWantedReason()
end
end)
-- Warrant
FAdmin.ScoreBoard.Player:AddActionButton("Warrant", "fadmin/icons/message", Color(0, 0, 200, 255),
function(ply) return LocalPlayer():isCP() end,
function(ply, button)
Derma_StringRequest("Warrant reason", "Enter the reason for the warrant", "", function(Reason)
RunConsoleCommand("darkrp", "warrant", ply:SteamID(), Reason)
end)
end)
--wanted
FAdmin.ScoreBoard.Player:AddActionButton(function(ply)
return ((ply:getDarkRPVar("wanted") and "Unw") or "W") .. "anted"
end,
function(ply) return "fadmin/icons/jail", ply:getDarkRPVar("wanted") and "fadmin/icons/disable" end,
Color(0, 0, 200, 255),
function(ply) return LocalPlayer():isCP() end,
function(ply, button)
if not ply:getDarkRPVar("wanted") then
Derma_StringRequest("wanted reason", "Enter the reason to arrest this player", "", function(Reason)
RunConsoleCommand("darkrp", "wanted", ply:SteamID(), Reason)
end)
else
RunConsoleCommand("darkrp", "unwanted", ply:UserID())
end
end)
--Teamban
local function teamban(ply, button)
local menu = DermaMenu()
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
menu:AddPanel(Padding)
local Title = vgui.Create("DLabel")
Title:SetText(" Jobs:\n")
Title:SetFont("UiBold")
Title:SizeToContents()
Title:SetTextColor(color_black)
menu:AddPanel(Title)
local command = "teamban"
local uid = ply:UserID()
for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do
local submenu = menu:AddSubMenu(v.name)
submenu:AddOption("2 minutes", function() RunConsoleCommand("darkrp", command, uid, k, 120) end)
submenu:AddOption("Half an hour", function() RunConsoleCommand("darkrp", command, uid, k, 1800) end)
submenu:AddOption("An hour", function() RunConsoleCommand("darkrp", command, uid, k, 3600) end)
submenu:AddOption("Until restart", function() RunConsoleCommand("darkrp", command, uid, k, 0) end)
end
menu:Open()
end
FAdmin.ScoreBoard.Player:AddActionButton("Ban from job", "fadmin/icons/changeteam", Color(200, 0, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamban)
local function teamunban(ply, button)
local menu = DermaMenu()
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
menu:AddPanel(Padding)
local Title = vgui.Create("DLabel")
Title:SetText(" Jobs:\n")
Title:SetFont("UiBold")
Title:SizeToContents()
Title:SetTextColor(color_black)
menu:AddPanel(Title)
local command = "teamunban"
local uid = ply:UserID()
for k, v in SortedPairsByMemberValue(RPExtraTeams, "name") do
menu:AddOption(v.name, function() RunConsoleCommand("darkrp", command, uid, k) end)
end
menu:Open()
end
FAdmin.ScoreBoard.Player:AddActionButton("Unban from job", function() return "fadmin/icons/changeteam", "fadmin/icons/disable" end, Color(200, 0, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "DarkRP_AdminCommands", ply) end, teamunban)
end

View File

@@ -0,0 +1,262 @@
local ContinueNewGroup
local EditGroups
local function RetrievePRIVS(len)
FAdmin.Access.Groups = net.ReadTable()
for k, v in pairs(FAdmin.Access.Groups) do
if CAMI.GetUsergroup(k) then continue end
CAMI.RegisterUsergroup({
Name = k,
Inherits = FAdmin.Access.ADMIN[v.ADMIN]
}, "FAdmin")
end
-- Remove any groups that are removed from FAdmin from CAMI.
for k in pairs(CAMI.GetUsergroups()) do
if FAdmin.Access.Groups[k] then continue end
CAMI.UnregisterUsergroup(k, "FAdmin")
end
end
net.Receive("FADMIN_SendGroups", RetrievePRIVS)
local function addPriv(um)
local group = um:ReadString()
FAdmin.Access.Groups[group] = FAdmin.Access.Groups[group] or {}
FAdmin.Access.Groups[group].PRIVS[um:ReadString()] = true
end
usermessage.Hook("FAdmin_AddPriv", addPriv)
local function removePriv(um)
FAdmin.Access.Groups[um:ReadString()].PRIVS[um:ReadString()] = nil
end
usermessage.Hook("FAdmin_RemovePriv", removePriv)
local function addGroupUI(ply, func)
Derma_StringRequest("Set name",
"What will be the name of the new group?",
"",
function(text)
if text == "" then return end
Derma_Query("On what access will this team be based? (the new group will inherit all the privileges from the group)", "Admin access",
"user", function() ContinueNewGroup(ply, text, 0, func) end,
"admin", function() ContinueNewGroup(ply, text, 1, func) end,
"superadmin", function() ContinueNewGroup(ply, text, 2, func) end)
end)
end
FAdmin.StartHooks["1SetAccess"] = function() -- 1 in hook name so it will be executed first.
FAdmin.Commands.AddCommand("setaccess", nil, "<Player>", "<Group name>", "[new group based on (number)]", "[new group privileges]")
FAdmin.ScoreBoard.Player:AddActionButton("Set access", "fadmin/icons/access", Color(155, 0, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetAccess") or LocalPlayer():IsSuperAdmin() end, function(ply)
local menu = DermaMenu()
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
menu:AddPanel(Padding)
local Title = vgui.Create("DLabel")
Title:SetText(" Set access:\n")
Title:SetFont("UiBold")
Title:SizeToContents()
Title:SetTextColor(color_black)
menu:AddPanel(Title)
for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "ADMIN", true) do
menu:AddOption(k, function()
if not IsValid(ply) then return end
RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), k)
end)
end
menu:AddOption("New...", function() addGroupUI(ply) end)
menu:Open()
end)
FAdmin.ScoreBoard.Server:AddPlayerAction("Edit groups", "fadmin/icons/access", Color(0, 155, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") or FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManagePrivileges") end, EditGroups)
-- Admin immunity
FAdmin.ScoreBoard.Server:AddServerSetting(function()
return (FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity"
end,
function()
return "fadmin/icons/access", FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable"
end, Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "ManageGroups") end, function(button)
button:SetImage2((not FAdmin.GlobalSetting.Immunity and "fadmin/icons/disable") or "null")
button:SetText((not FAdmin.GlobalSetting.Immunity and "Disable" or "Enable") .. " Admin immunity")
button:GetParent():InvalidateLayout()
RunConsoleCommand("_Fadmin", "immunity", (FAdmin.GlobalSetting.Immunity and 0) or 1)
end
)
end
ContinueNewGroup = function(ply, name, admin_access, func)
if IsValid(ply) then
RunConsoleCommand("_FAdmin", "setaccess", ply:UserID(), name, admin_access)
else
RunConsoleCommand("_FAdmin", "AddGroup", name, admin_access)
end
if func then
func(name, admin_access)
end
end
EditGroups = function()
local frame, SelectedGroup, AddGroup, RemGroup, Privileges, SelectedPrivs, AddPriv, RemPriv, lblImmunity, nmbrImmunity
frame = vgui.Create("DFrame")
frame:SetTitle("Create, edit and remove groups")
frame:MakePopup()
frame:SetVisible(true)
frame:SetSize(640, 480)
frame:Center()
SelectedGroup = vgui.Create("DComboBox", frame)
SelectedGroup:SetPos(5, 30)
SelectedGroup:SetWidth(145)
for _, v in pairs(FAdmin.Access.Groups) do
v.immunity = v.immunity or 0
end
for k in SortedPairsByMemberValue(FAdmin.Access.Groups, "immunity", true) do
SelectedGroup:AddChoice(k)
end
AddGroup = vgui.Create("DButton", frame)
AddGroup:SetPos(155, 30)
AddGroup:SetSize(60, 22)
AddGroup:SetText("Add Group")
AddGroup.DoClick = function()
addGroupUI(nil, function(name, admin, privs)
SelectedGroup:AddChoice(name)
SelectedGroup:SetValue(name)
RemGroup:SetDisabled(false)
Privileges:Clear()
SelectedPrivs:Clear()
nmbrImmunity:SetText(FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity)
nmbrImmunity:SetDisabled(false)
nmbrImmunity:SetEditable(true)
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
if am <= admin + 1 then
SelectedPrivs:AddLine(priv)
else
Privileges:AddLine(priv)
end
end
end)
end
RemGroup = vgui.Create("DButton", frame)
RemGroup:SetPos(220, 30)
RemGroup:SetSize(85, 22)
RemGroup:SetText("Remove Group")
RemGroup.DoClick = function()
RunConsoleCommand("_FAdmin", "RemoveGroup", SelectedGroup:GetValue())
for k, v in pairs(SelectedGroup.Choices) do
if v ~= SelectedGroup:GetValue() then continue end
SelectedGroup.Choices[k] = nil
break
end
table.ClearKeys(SelectedGroup.Choices)
SelectedGroup:SetValue("user")
SelectedGroup:OnSelect(1, "user")
end
Privileges = vgui.Create("DListView", frame)
Privileges:SetPos(5, 55)
Privileges:SetSize(300, 420)
Privileges:AddColumn("Available privileges")
SelectedPrivs = vgui.Create("DListView", frame)
SelectedPrivs:SetPos(340, 55)
SelectedPrivs:SetSize(295, 420)
SelectedPrivs:AddColumn("Selected Privileges")
function SelectedGroup:OnSelect(index, value, data)
if not FAdmin.Access.Groups[value] then return end
RemGroup:SetDisabled(false)
if table.HasValue(FAdmin.Access.ADMIN, value) then
RemGroup:SetDisabled(true)
end
Privileges:Clear()
SelectedPrivs:Clear()
for priv, _ in SortedPairs(FAdmin.Access.Privileges) do
if FAdmin.Access.Groups[value].PRIVS[priv] then
SelectedPrivs:AddLine(priv)
else
Privileges:AddLine(priv)
end
end
if nmbrImmunity then
nmbrImmunity:SetText(FAdmin.Access.Groups[value].immunity or "")
if table.HasValue({"superadmin", "admin", "user", "noaccess"}, string.lower(value)) then
nmbrImmunity:SetDisabled(true)
nmbrImmunity:SetEditable(false)
else
nmbrImmunity:SetDisabled(false)
nmbrImmunity:SetEditable(true)
end
end
end
SelectedGroup:SetValue("user")
SelectedGroup:OnSelect(1, "user")
AddPriv = vgui.Create("DButton", frame)
AddPriv:SetPos(310, 55)
AddPriv:SetSize(25, 25)
AddPriv:SetText(">")
AddPriv.DoClick = function()
for _, v in ipairs(Privileges:GetSelected()) do
local priv = v.Columns[1]:GetValue()
RunConsoleCommand("FAdmin", "AddPrivilege", SelectedGroup:GetValue(), priv)
SelectedPrivs:AddLine(priv)
Privileges:RemoveLine(v.m_iID)
end
end
RemPriv = vgui.Create("DButton", frame)
RemPriv:SetPos(310, 85)
RemPriv:SetSize(25, 25)
RemPriv:SetText("<")
RemPriv.DoClick = function()
for _, v in ipairs(SelectedPrivs:GetSelected()) do
local priv = v.Columns[1]:GetValue()
if SelectedGroup:GetValue() == LocalPlayer():GetUserGroup() and priv == "ManagePrivileges" then
return Derma_Message("You shouldn't be removing ManagePrivileges. It will make you unable to edit the groups. This is preventing you from locking yourself out of the system.", "Clever move.")
end
RunConsoleCommand("FAdmin", "RemovePrivilege", SelectedGroup:GetValue(), priv)
Privileges:AddLine(priv)
SelectedPrivs:RemoveLine(v.m_iID)
end
end
lblImmunity = vgui.Create("DLabel", frame)
lblImmunity:SetPos(340, 30)
lblImmunity:SetText("Immunity number (higher is more immune)")
lblImmunity:SizeToContents()
nmbrImmunity = vgui.Create("DTextEntry", frame)
nmbrImmunity:SetPos(545, 28)
nmbrImmunity:SetWide(90)
nmbrImmunity:SetNumeric(true)
nmbrImmunity:SetText(FAdmin.Access.Groups.user.immunity)
nmbrImmunity:SetDisabled(true)
nmbrImmunity:SetEditable(false)
nmbrImmunity.OnEnter = function(self) RunConsoleCommand("FAdmin", "SetImmunity", SelectedGroup:GetValue(), self:GetValue()) end
end

View File

@@ -0,0 +1,273 @@
CreateConVar("_FAdmin_immunity", 1, {FCVAR_GAMEDLL, FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE})
FAdmin.Access = FAdmin.Access or {}
FAdmin.Access.ADMIN = {"user", "admin", "superadmin"}
FAdmin.Access.ADMIN[0] = "user"
FAdmin.Access.Groups = FAdmin.Access.Groups or {}
FAdmin.Access.Privileges = FAdmin.Access.Privileges or {}
function FAdmin.Access.AddGroup(name, admin_access --[[0 = not admin, 1 = admin, 2 = superadmin]], privs, immunity, fromCAMI, CAMIsrc)
FAdmin.Access.Groups[name] = FAdmin.Access.Groups[name] or {ADMIN = admin_access, PRIVS = privs or {}, immunity = immunity}
--Make sure things that come from CAMI come with a CAMIsrc
assert((fromCAMI and CAMIsrc ~= nil) or ((not fromCAMI) and CAMIsrc == nil))
--If the CAMIsrc is a string, save it, otherwise save an empty string
if not isstring(CAMIsrc) then
CAMIsrc = ""
end
-- Register custom usergroups with CAMI
if name ~= "user" and name ~= "admin" and name ~= "superadmin" and not fromCAMI then
CAMI.RegisterUsergroup({
Name = name,
Inherits = FAdmin.Access.ADMIN[admin_access]
}, "FAdmin")
end
-- Add newly created privileges on server reload
for p, _ in pairs(privs or {}) do
FAdmin.Access.Groups[name].PRIVS[p] = true
end
if not SERVER then return end
MySQLite.queryValue("SELECT COUNT(*) FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(name) .. ";", function(val)
if tonumber(val or 0) > 0 then return end
MySQLite.query("REPLACE INTO FADMIN_GROUPS VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(admin_access) .. ");", function()
for priv, _ in pairs(privs or {}) do
MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(priv) .. ");")
end
if fromCAMI then
MySQLite.query("REPLACE INTO FADMIN_GROUPS_SRC VALUES(" .. MySQLite.SQLStr(name) .. ", " .. MySQLite.SQLStr(CAMIsrc) .. ");")
end
end)
end)
if immunity then
MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(name) .. ", " .. tonumber(immunity) .. ");")
end
if FAdmin.Access.SendGroups and privs then
for _, v in ipairs(player.GetAll()) do
FAdmin.Access.SendGroups(v)
end
end
end
function FAdmin.Access.OnUsergroupRegistered(usergroup, source)
-- Don't re-add usergroups coming from FAdmin itself
if source == "FAdmin" then return end
local inheritRoot = CAMI.InheritanceRoot(usergroup.Inherits)
local admin_access = table.KeyFromValue(FAdmin.Access.ADMIN, inheritRoot) or 1
-- Add groups registered to CAMI to FAdmin. Assume privileges from either the usergroup it inherits or its inheritance root.
-- Immunity is unknown and can be set by the user later. FAdmin immunity only applies to FAdmin anyway.
local parent = FAdmin.Access.Groups[usergroup.Inherits] or FAdmin.Access.Groups[inheritRoot] or {}
FAdmin.Access.AddGroup(usergroup.Name, admin_access - 1, table.Copy(parent.PRIVS) or {}, parent.immunity or 10, true, source)
end
function FAdmin.Access.OnUsergroupUnregistered(usergroup, source)
if table.HasValue({"superadmin", "admin", "user", "noaccess"}, usergroup.Name) then return end
FAdmin.Access.Groups[usergroup.Name] = nil
if not SERVER then return end
MySQLite.query("DELETE FROM FADMIN_GROUPS WHERE NAME = " .. MySQLite.SQLStr(usergroup.Name) .. ";")
for _, v in ipairs(player.GetAll()) do
FAdmin.Access.SendGroups(v)
end
end
function FAdmin.Access.RemoveGroup(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not args[1] then return false end
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
if not FAdmin.Access.Groups[args[1]] or table.HasValue({"superadmin", "admin", "user"}, string.lower(args[1])) then return true, args[1] end
-- Setting a group with a higher rank than one's own
if (not plyGroup or FAdmin.Access.Groups[args[1]].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to remove usergroups with a higher rank than your own")
return false
end
CAMI.UnregisterUsergroup(args[1], "FAdmin")
FAdmin.Messages.SendMessage(ply, 4, "Group succesfully removed")
end
local PLAYER = FindMetaTable("Player")
local oldplyIsAdmin = PLAYER.IsAdmin
function PLAYER:IsAdmin(...)
local usergroup = self:GetUserGroup()
if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsAdmin(self, ...) or game.SinglePlayer() end
if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 1 --[[1 = admin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then
return true
end
if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 1 then return true end
return oldplyIsAdmin(self, ...) or game.SinglePlayer()
end
local oldplyIsSuperAdmin = PLAYER.IsSuperAdmin
function PLAYER:IsSuperAdmin(...)
local usergroup = self:GetUserGroup()
if not FAdmin or not FAdmin.Access or not FAdmin.Access.Groups or not FAdmin.Access.Groups[usergroup] then return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer() end
if (FAdmin.Access.Groups[usergroup] and FAdmin.Access.Groups[usergroup].ADMIN >= 2 --[[2 = superadmin]]) or (self.IsListenServerHost and self:IsListenServerHost()) then
return true
end
if CLIENT and tonumber(self:FAdmin_GetGlobal("FAdmin_admin")) and self:FAdmin_GetGlobal("FAdmin_admin") >= 2 then return true end
return oldplyIsSuperAdmin(self, ...) or game.SinglePlayer()
end
--Privileges
function FAdmin.Access.AddPrivilege(Name, admin_access)
FAdmin.Access.Privileges[Name] = admin_access
end
hook.Add("CAMI.OnPrivilegeRegistered", "FAdmin", function(privilege)
FAdmin.Access.AddPrivilege(privilege.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(privilege.MinAccess)) or 3)
-- Register privilege and add to respective usergroups
if SERVER then FAdmin.Access.RegisterCAMIPrivilege(privilege) end
end)
for _, camipriv in pairs(CAMI.GetPrivileges()) do
FAdmin.Access.AddPrivilege(camipriv.Name, table.KeyFromValue(FAdmin.Access.ADMIN, CAMI.InheritanceRoot(camipriv.MinAccess)) or 3)
-- Register if the database has already loaded
if SERVER and FAdmin.Access.RegisterCAMIPrivilege then FAdmin.Access.RegisterCAMIPrivilege(camipriv) end
end
hook.Add("CAMI.OnPrivilegeUnregistered", "FAdmin", function(privilege)
FAdmin.Access.Privileges[privilege.Name] = nil
end)
function FAdmin.Access.PlayerIsHost(ply)
return ply:EntIndex() == 0 or game.SinglePlayer() or (ply.IsListenServerHost and ply:IsListenServerHost())
end
function FAdmin.Access.PlayerHasPrivilege(ply, priv, target, ignoreImmunity)
-- This is the server console
if FAdmin.Access.PlayerIsHost(ply) then return true end
-- Privilege does not exist
if not FAdmin.Access.Privileges[priv] then return ply:IsAdmin() end
local Usergroup = ply:GetUserGroup()
local canTarget = hook.Call("FAdmin_CanTarget", nil, ply, priv, target)
if canTarget ~= nil then
return canTarget
end
if FAdmin.GlobalSetting.Immunity and
not ignoreImmunity and
not isstring(target) and IsValid(target) and target ~= ply and
FAdmin.Access.Groups[Usergroup] and FAdmin.Access.Groups[target:GetUserGroup()] and
FAdmin.Access.Groups[Usergroup].immunity and FAdmin.Access.Groups[target:GetUserGroup()].immunity and
FAdmin.Access.Groups[target:GetUserGroup()].immunity >= FAdmin.Access.Groups[Usergroup].immunity then
return false
end
-- Defer answer when usergroup is unknown
if not FAdmin.Access.Groups[Usergroup] then return end
if FAdmin.Access.Groups[Usergroup].PRIVS[priv] then
return true
end
if CLIENT and ply.FADMIN_PRIVS and ply.FADMIN_PRIVS[priv] then return true end
return false
end
hook.Add("CAMI.PlayerHasAccess", "FAdmin", function(actor, privilegeName, callback, target, extraInfo)
-- FAdmin doesn't know. Defer answer.
if not FAdmin.Access.Privileges[privilegeName] then return end
local res = FAdmin.Access.PlayerHasPrivilege(actor, privilegeName, target, extraInfo and extraInfo.IgnoreImmunity)
-- Defer again
if res == nil then return end
-- Publish the answer
callback(res, "FAdmin")
-- FAdmin knows the answer. Prevent other hooks from running.
return true
end)
hook.Add("CAMI.SteamIDHasAccess", "FAdmin", function(actorSteam, privilegeName, callback, targetSteam, extraInfo)
-- The client just doesn't know
if CLIENT then return end
if not targetSteam or extraInfo and extraInfo.IgnoreImmunity then
MySQLite.query(string.format(
[[SELECT COUNT(*) AS c
FROM FAdmin_PlayerGroup l
JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME
WHERE l.steamid = %s AND r.PRIVILEGE = %s]],
MySQLite.SQLStr(actorSteam),
MySQLite.SQLStr(privilegeName)
), function(res) callback(tonumber(res[1].c) > 0) end)
return true
end
MySQLite.query(string.format(
[[SELECT ll.i AND rr.c AS res
FROM (SELECT li.immunity >= ri.immunity AS i
FROM FAdmin_PlayerGroup lg
JOIN FAdmin_Immunity li ON lg.groupname = li.groupname
JOIN FAdmin_PlayerGroup rg
JOIN FAdmin_Immunity ri ON rg.groupname = ri.groupname
WHERE lg.steamid = %s AND rg.steamid = %s) AS ll
JOIN (SELECT COUNT(*) AS c
FROM FAdmin_PlayerGroup l
JOIN FADMIN_PRIVILEGES r ON l.groupname = r.NAME
WHERE l.steamid = %s AND r.PRIVILEGE = %s) AS rr]],
MySQLite.SQLStr(actorSteam),
MySQLite.SQLStr(targetSteam),
MySQLite.SQLStr(actorSteam),
MySQLite.SQLStr(privilegeName)
), function(res) callback(res and res[1] and tobool(res[1].res) or false) end)
return true
end)
FAdmin.StartHooks["AccessFunctions"] = function()
FAdmin.Messages.RegisterNotification{
name = "setaccess",
hasTarget = true,
message = {"instigator", " set the usergroup of ", "targets", " to ", "extraInfo.1"},
receivers = "everyone",
writeExtraInfo = function(i) net.WriteString(i[1]) end,
readExtraInfo = function() return {net.ReadString()} end,
extraInfoColors = {Color(255, 102, 0)}
}
FAdmin.Access.AddPrivilege("SetAccess", 3) -- AddPrivilege is shared, run on both client and server
FAdmin.Access.AddPrivilege("ManagePrivileges", 3)
FAdmin.Access.AddPrivilege("ManageGroups", 3)
FAdmin.Access.AddPrivilege("SeeAdmins", 1)
FAdmin.Commands.AddCommand("RemoveGroup", FAdmin.Access.RemoveGroup)
FAdmin.Commands.AddCommand("Admins", function(ply)
if not FAdmin.Access.PlayerHasPrivilege(ply, "SeeAdmins") then return false end
for _, v in ipairs(player.GetAll()) do
ply:PrintMessage(HUD_PRINTCONSOLE, v:Nick() .. "\t|\t" .. v:GetUserGroup())
end
return true
end
)
end

View File

@@ -0,0 +1,449 @@
--Immunity
cvars.AddChangeCallback("_FAdmin_immunity", function(Cvar, Previous, New)
FAdmin.SetGlobalSetting("Immunity", (tonumber(New) == 1 and true) or false)
FAdmin.SaveSetting("_FAdmin_immunity", tonumber(New))
end)
hook.Add("DatabaseInitialized", "InitializeFAdminGroups", function()
MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS(NAME VARCHAR(40) NOT NULL PRIMARY KEY, ADMIN_ACCESS INTEGER NOT NULL);")
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_PlayerGroup(steamid VARCHAR(40) NOT NULL, groupname VARCHAR(40) NOT NULL, PRIMARY KEY(steamid));")
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_Immunity(groupname VARCHAR(40) NOT NULL, immunity INTEGER NOT NULL, PRIMARY KEY(groupname));")
MySQLite.query("CREATE TABLE IF NOT EXISTS FAdmin_CAMIPrivileges(privname VARCHAR(255) NOT NULL PRIMARY KEY);")
MySQLite.query("CREATE TABLE IF NOT EXISTS FADMIN_GROUPS_SRC(NAME VARCHAR(40) NOT NULL PRIMARY KEY REFERENCES FADMIN_GROUPS(NAME) ON DELETE CASCADE, SRC VARCHAR(40));")
MySQLite.query([[CREATE TABLE IF NOT EXISTS FADMIN_PRIVILEGES(
NAME VARCHAR(40),
PRIVILEGE VARCHAR(100),
PRIMARY KEY(NAME, PRIVILEGE),
FOREIGN KEY(NAME) REFERENCES FADMIN_GROUPS(NAME)
ON UPDATE CASCADE
ON DELETE CASCADE
);]], function()
-- Remove SetAccess workaround
MySQLite.query([[DELETE FROM FADMIN_PRIVILEGES WHERE NAME = "user" AND PRIVILEGE = "SetAccess";]])
MySQLite.query("SELECT g.NAME, g.ADMIN_ACCESS, p.PRIVILEGE, i.immunity, s.src FROM FADMIN_GROUPS g LEFT OUTER JOIN FADMIN_PRIVILEGES p ON g.NAME = p.NAME LEFT OUTER JOIN FAdmin_Immunity i ON g.NAME = i.groupname LEFT OUTER JOIN FADMIN_GROUPS_SRC s ON g.NAME = s.NAME;", function(data)
if not data then return end
for _, v in pairs(data) do
FAdmin.Access.Groups[v.NAME] = FAdmin.Access.Groups[v.NAME] or
{ADMIN = tonumber(v.ADMIN_ACCESS), PRIVS = {}}
if v.PRIVILEGE and v.PRIVILEGE ~= "NULL" then
FAdmin.Access.Groups[v.NAME].PRIVS[v.PRIVILEGE] = true
end
if v.immunity and v.immunity ~= "NULL" then
FAdmin.Access.Groups[v.NAME].immunity = tonumber(v.immunity)
end
if CAMI.GetUsergroup(v.NAME) then continue end
CAMI.RegisterUsergroup({
Name = v.NAME,
Inherits = FAdmin.Access.ADMIN[v.ADMIN_ACCESS] or "user"
}, v.SRC)
end
-- Send groups to early joiners and listen server hosts
for _, v in ipairs(player.GetAll()) do
FAdmin.Access.SendGroups(v)
end
-- See if there are any CAMI usergroups that FAdmin doesn't know about yet.
-- FAdmin doesn't start listening immediately because the database might not have initialised.
-- Besides, other admin mods might add usergroups before FAdmin's Lua files are even run
for _, v in pairs(CAMI.GetUsergroups()) do
if FAdmin.Access.Groups[v.Name] then continue end
FAdmin.Access.OnUsergroupRegistered(v,"")
end
-- Start listening for CAMI usergroup registrations.
hook.Add("CAMI.OnUsergroupRegistered", "FAdmin", FAdmin.Access.OnUsergroupRegistered)
hook.Add("CAMI.OnUsergroupUnregistered", "FAdmin", FAdmin.Access.OnUsergroupUnregistered)
FAdmin.Access.RegisterCAMIPrivileges()
end)
local function createGroups(privs)
FAdmin.Access.AddGroup("superadmin", 2, privs.superadmin, 100)
FAdmin.Access.AddGroup("admin", 1, privs.admin, 50)
FAdmin.Access.AddGroup("user", 0, privs.user, 10)
FAdmin.Access.AddGroup("noaccess", 0, privs.noaccess, 0)
end
MySQLite.query("SELECT DISTINCT PRIVILEGE FROM FADMIN_PRIVILEGES;", function(privTbl)
local privs = {}
local hasPrivs = {"noaccess", "user", "admin", "superadmin"}
-- No privileges registered to anyone. Reset everything
if not privTbl or table.IsEmpty(privTbl) then
for priv, access in pairs(FAdmin.Access.Privileges) do
for i = access + 1, #hasPrivs, 1 do
privs[hasPrivs[i]] = privs[hasPrivs[i]] or {}
privs[hasPrivs[i]][priv] = true
end
end
createGroups(privs)
return
end
-- Check for newly created privileges and assign them to the default usergroups
-- No privilege can be revoke from every group
local privSet = {}
for _, priv in ipairs(privTbl) do
privSet[priv.PRIVILEGE] = true
end
for priv, access in pairs(FAdmin.Access.Privileges) do
if privSet[priv] then continue end
for i = access + 1, #hasPrivs do
MySQLite.query(("REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);"):format(MySQLite.SQLStr(hasPrivs[i]), MySQLite.SQLStr(priv)))
end
end
createGroups(privs)
end)
end)
end)
-- Assign a privilege to its respective usergroups when they are seen for the first time
function FAdmin.Access.RegisterCAMIPrivilege(priv)
-- Privileges haven't been loaded yet or has already been seen
if not FAdmin.CAMIPrivs or FAdmin.CAMIPrivs[priv.Name] then return end
FAdmin.CAMIPrivs[priv.Name] = true
for groupName, groupdata in pairs(FAdmin.Access.Groups) do
if FAdmin.Access.Privileges[priv.Name] - 1 > groupdata.ADMIN then continue end
groupdata.PRIVS[priv.Name] = true
MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(priv.Name)))
end
MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(priv.Name)))
end
-- Assign privileges to their respective usergroups when they are seen for the first time
function FAdmin.Access.RegisterCAMIPrivileges()
MySQLite.query([[SELECT privname FROM FAdmin_CAMIPrivileges]], function(data)
FAdmin.CAMIPrivs = {}
for _, row in ipairs(data or {}) do
FAdmin.CAMIPrivs[row.privname] = true
end
for privName, _ in pairs(CAMI.GetPrivileges()) do
if FAdmin.CAMIPrivs[privName] then continue end
FAdmin.CAMIPrivs[privName] = true
for groupName, groupdata in pairs(FAdmin.Access.Groups) do
if FAdmin.Access.Privileges[privName] - 1 > groupdata.ADMIN then continue end
groupdata.PRIVS[privName] = true
MySQLite.query(string.format([[REPLACE INTO FADMIN_PRIVILEGES VALUES(%s, %s);]], MySQLite.SQLStr(groupName), MySQLite.SQLStr(privName)))
end
MySQLite.query(string.format([[REPLACE INTO FAdmin_CAMIPrivileges VALUES(%s);]], MySQLite.SQLStr(privName)))
end
end)
end
function FAdmin.Access.PlayerSetGroup(ply, group)
if not FAdmin.Access.Groups[group] then return end
ply = isstring(ply) and FAdmin.FindPlayer(ply) and FAdmin.FindPlayer(ply)[1] or ply
if not isstring(ply) and IsValid(ply) then
ply:SetUserGroup(group)
end
end
hook.Remove("PlayerInitialSpawn", "PlayerAuthSpawn") -- Remove Garry's usergroup setter.
-- Update the database only when an end users indicates that a player's usergroup is to be changed.
hook.Add("CAMI.PlayerUsergroupChanged", "FAdmin", function(ply, old, new, source)
MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(ply:SteamID()) .. ", " .. MySQLite.SQLStr(new) .. ");")
end)
hook.Add("CAMI.SteamIDUsergroupChanged", "FAdmin", function(steamId, old, new, source)
MySQLite.query("REPLACE INTO FAdmin_PlayerGroup VALUES(" .. MySQLite.SQLStr(steamId) .. ", " .. MySQLite.SQLStr(new) .. ");")
end)
function FAdmin.Access.SetRoot(ply, cmd, args) -- FAdmin setroot player. Sets the player to superadmin
if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then
FAdmin.Messages.SendMessage(ply, 5, "No access!")
FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server")
return false
end
local group = FAdmin.Access.Groups["superadmin"]
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
-- Setting a group with a higher rank than one's own
if (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
FAdmin.Messages.SendMessage(ply, 5, "Please use RCon to set yourself to superadmin if you are the owner of the server")
return false
end
local targets = FAdmin.FindPlayer(args[1])
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
for _, target in ipairs(targets) do
if not IsValid(target) then continue end
local target_previous_group = target:GetUserGroup()
FAdmin.Access.PlayerSetGroup(target, "superadmin")
-- An end user changed the usergroup. Register with CAMI
CAMI.SignalUserGroupChanged(target, target_previous_group, "superadmin", "FAdmin")
FAdmin.Messages.SendMessage(ply, 2, "User set to superadmin!")
end
FAdmin.Messages.FireNotification("setaccess", ply, targets, {"superadmin"})
return true, targets, "superadmin"
end
-- AddGroup <Groupname> <Adminstatus> <Privileges>
local function AddGroup(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local admin = tonumber(args[2])
if not args[1] or not admin then FAdmin.Messages.SendMessage(ply, 5, "Incorrect arguments!") return false end
local privs = {}
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
-- The user cannot create groups with privileges they don't have
if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then continue end
if am <= admin + 1 then privs[priv] = true end
end
local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to create usergroups with a higher rank than your own")
return false
end
FAdmin.Access.AddGroup(args[1], admin, privs, immunity) -- Add new group
FAdmin.Messages.SendMessage(ply, 4, "Group created")
FAdmin.Access.SendGroups()
return true, args[1]
end
local function AddPrivilege(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local group, priv = args[1], args[2]
if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then
FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments")
return false
end
-- The player cannot add privileges that they themselves do not have
if not FAdmin.Access.PlayerHasPrivilege(ply, priv) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign privileges that you don't have yourself")
return false
end
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
-- Setting a group with a higher rank than one's own
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own")
return false
end
FAdmin.Access.Groups[group].PRIVS[priv] = true
MySQLite.query("REPLACE INTO FADMIN_PRIVILEGES VALUES(" .. MySQLite.SQLStr(group) .. ", " .. MySQLite.SQLStr(priv) .. ");")
SendUserMessage("FAdmin_AddPriv", player.GetAll(), group, priv)
FAdmin.Messages.SendMessage(ply, 4, "Privilege Added!")
return true, group, priv
end
local function RemovePrivilege(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManagePrivileges") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local group, priv = args[1], args[2]
if not FAdmin.Access.Groups[group] or not FAdmin.Access.Privileges[priv] then
FAdmin.Messages.SendMessage(ply, 5, "Invalid arguments")
return false
end
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
-- Setting a group with a higher rank than one's own
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to manage the privileges of a usergroup with a higher rank than your own")
return false
end
FAdmin.Access.Groups[group].PRIVS[priv] = nil
MySQLite.query("DELETE FROM FADMIN_PRIVILEGES WHERE NAME = " .. MySQLite.SQLStr(group) .. " AND PRIVILEGE = " .. MySQLite.SQLStr(priv) .. ";")
SendUserMessage("FAdmin_RemovePriv", player.GetAll(), group, priv)
FAdmin.Messages.SendMessage(ply, 4, "Privilege Removed!")
return true, group, priv
end
function FAdmin.Access.SendGroups(ply)
if not FAdmin.Access.Groups then return end
net.Start("FADMIN_SendGroups")
net.WriteTable(FAdmin.Access.Groups)
net.Send(IsValid(ply) and ply or player.GetAll())
end
-- FAdmin SetAccess <player> <groupname> [new_groupadmin, new_groupprivs]
function FAdmin.Access.SetAccess(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "SetAccess") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local targets = FAdmin.FindPlayer(args[1])
local admin = tonumber(args[3])
local group = FAdmin.Access.Groups[args[2]]
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
if not args[2] or not group and not admin then
FAdmin.Messages.SendMessage(ply, 1, "Group not found")
return false
elseif args[2] and not group and admin then
local privs = {}
for priv, am in SortedPairs(FAdmin.Access.Privileges) do
if am <= admin + 1 then privs[priv] = true end
end
local immunity = FAdmin.Access.Groups[FAdmin.Access.ADMIN[admin + 1]].immunity
-- Creating and setting a group with a higher rank than one's own
if (not plyGroup or immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
return false
end
FAdmin.Access.AddGroup(args[2], tonumber(args[3]), privs, immunity) -- Add new group
FAdmin.Messages.SendMessage(ply, 4, "Group created")
FAdmin.Access.SendGroups()
end
-- Setting a group with a higher rank than one's own
if group and (not plyGroup or group.immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to assign anyone a usergroup with a higher rank than your own")
return false
end
if not targets and (string.find(args[1], "^STEAM_[0-9]:[01]:[0-9]+$") or args[1] == "BOT" or (string.find(args[1], "STEAM_") and #args == 6)) then
local target, groupname = args[1], args[2]
-- The console splits arguments on colons. Very annoying.
if args[1] == "STEAM_0" then
target = table.concat(args, "", 1, 5)
groupname = args[6]
end
FAdmin.Access.PlayerSetGroup(target, groupname)
MySQLite.queryValue(string.format("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = %s", MySQLite.SQLStr(target)), function(val)
CAMI.SignalSteamIDUserGroupChanged(target, val or "user", groupname, "FAdmin")
end)
FAdmin.Messages.SendMessage(ply, 4, "User access set!")
return true, target, groupname
elseif not targets then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
for _, target in ipairs(targets) do
if not IsValid(target) then continue end
local target_previous_group = target:GetUserGroup()
FAdmin.Access.PlayerSetGroup(target, args[2])
-- An end user changed the usergroup. Register with CAMI
CAMI.SignalUserGroupChanged(target, target_previous_group, args[2], "FAdmin")
end
FAdmin.Messages.SendMessage(ply, 4, "User access set!")
FAdmin.Messages.FireNotification("setaccess", ply, targets, {args[2]})
return true, targets, args[2]
end
--hooks and stuff
hook.Add("PlayerInitialSpawn", "FAdmin_SetAccess", function(ply)
MySQLite.queryValue("SELECT groupname FROM FAdmin_PlayerGroup WHERE steamid = " .. MySQLite.SQLStr(ply:SteamID()) .. ";", function(Group)
if not Group then return end
ply:SetUserGroup(Group)
if FAdmin.Access.Groups[Group] then
ply:FAdmin_SetGlobal("FAdmin_admin", FAdmin.Access.Groups[Group].ADMIN_ACCESS)
end
end, function(err) ErrorNoHalt(err) MsgN() end)
FAdmin.Access.SendGroups(ply)
end)
local function toggleImmunity(ply, cmd, args)
-- ManageGroups privilege because they can handle immunity settings
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not args[1] then FAdmin.Messages.SendMessage(ply, 5, "Invalid argument!") return false end
RunConsoleCommand("_FAdmin_immunity", args[1])
local OnOff = (tonumber(args[1]) == 1 and "on") or "off"
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned " .. OnOff .. " admin immunity!", "Admin immunity has been turned " .. OnOff, "Turned admin immunity " .. OnOff)
return true, OnOff
end
local function setImmunity(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ManageGroups") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local group, immunity = args[1], tonumber(args[2])
if not FAdmin.Access.Groups[group] or not immunity then return false end
local plyGroup = FAdmin.Access.Groups[ply:EntIndex() == 0 and "superadmin" or ply:GetUserGroup()]
-- Setting a group with a higher rank than one's own
if (not plyGroup or FAdmin.Access.Groups[group].immunity > plyGroup.immunity) and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to change the immunity of a group with a higher rank than your")
return false
end
if immunity > plyGroup.immunity and not FAdmin.Access.PlayerIsHost(ply) then
FAdmin.Messages.SendMessage(ply, 5, "You're not allowed to set the immunity to any value higher than your own group's immunity")
return false
end
FAdmin.Access.Groups[group].immunity = immunity
MySQLite.query("REPLACE INTO FAdmin_Immunity VALUES(" .. MySQLite.SQLStr(group) .. ", " .. tonumber(immunity) .. ");")
FAdmin.Access.SendGroups(ply)
return true, group, immunity
end
FAdmin.StartHooks["Access"] = function() --Run all functions that depend on other plugins
FAdmin.Commands.AddCommand("setroot", FAdmin.Access.SetRoot)
FAdmin.Commands.AddCommand("setaccess", FAdmin.Access.SetAccess)
FAdmin.Commands.AddCommand("AddGroup", AddGroup)
FAdmin.Commands.AddCommand("AddPrivilege", AddPrivilege)
FAdmin.Commands.AddCommand("RemovePrivilege", RemovePrivilege)
FAdmin.Commands.AddCommand("immunity", toggleImmunity)
FAdmin.Commands.AddCommand("SetImmunity", setImmunity)
FAdmin.SetGlobalSetting("Immunity", GetConVar("_FAdmin_immunity"):GetBool())
end

View File

@@ -0,0 +1,17 @@
net.Receive("FAdmin_ReceiveAdminMessage", function(len)
local FromPly = net.ReadEntity()
local Text = net.ReadString()
local Team = FromPly:IsPlayer() and FromPly:Team() or 1
local Nick = FromPly:IsPlayer() and FromPly:Nick() or "Console"
local prefix = (FAdmin.Access.PlayerHasPrivilege(FromPly, "AdminChat") or FromPly:IsAdmin()) and "[Admin Chat] " or "[To admins] "
chat.AddNonParsedText(Color(255, 0, 0, 255), prefix, team.GetColor(Team), Nick .. ": ", color_white, Text)
end)
FAdmin.StartHooks["Chatting"] = function()
FAdmin.Commands.AddCommand("adminhelp", nil, "<text>")
FAdmin.Commands.AddCommand("//", nil, "<text>")
FAdmin.Access.AddPrivilege("AdminChat", 2)
end

View File

@@ -0,0 +1,28 @@
util.AddNetworkString("FAdmin_ReceiveAdminMessage")
local function ToAdmins(ply, cmd, args)
if not args[1] then return false end
local text = table.concat(args, " ")
local send = {}
if IsValid(ply) then table.insert(send, ply) end
for _, v in ipairs(player.GetAll()) do
if FAdmin.Access.PlayerHasPrivilege(v, "AdminChat") or v:IsAdmin() then
table.insert(send, v)
end
end
net.Start("FAdmin_ReceiveAdminMessage")
net.WriteEntity(ply)
net.WriteString(text)
net.Send(send)
return true, text
end
FAdmin.StartHooks["Chatting"] = function()
FAdmin.Commands.AddCommand("adminhelp", ToAdmins)
FAdmin.Commands.AddCommand("//", ToAdmins)
FAdmin.Access.AddPrivilege("AdminChat", 2)
end

View File

@@ -0,0 +1,87 @@
local PANEL = {}
AccessorFunc(PANEL, "gamemodeList", "GamemodeList")
AccessorFunc(PANEL, "mapList", "MapList")
function PANEL:Init()
self:SetMouseInputEnabled(true)
self:SetKeyboardInputEnabled(false)
self:SetDeleteOnClose(false)
self:SetTitle("Change level")
self:SetSize(630, ScrH() * 0.8)
self.gamemodeList = {}
self.mapList = {}
self.catList = vgui.Create("DCategoryList", self)
self.catList:Dock(FILL)
self.topPanel = vgui.Create("DPanel", self)
self.topPanel:SetPaintBackground(false)
self.topPanel:DockMargin(0, 0, 0, 4)
self.topPanel:Dock(TOP)
self.gmLabel = vgui.Create("DLabel", self.topPanel)
self.gmLabel:SetText("Gamemode:")
self.gmLabel:Dock(LEFT)
self.gmComboBox = vgui.Create("DComboBox", self.topPanel)
self.gmComboBox:Dock(FILL)
self.gmComboBox:SetValue("(current)")
self.bottomPanel = vgui.Create("DPanel", self)
self.bottomPanel:SetPaintBackground(false)
self.bottomPanel:DockMargin(0, 4, 0, 0)
self.bottomPanel:Dock(BOTTOM)
self.changeButton = vgui.Create("DButton", self.bottomPanel)
self.changeButton:SetText("Change level")
self.changeButton:Dock(RIGHT)
self.changeButton:SetWidth(100)
self.changeButton:SetEnabled(false)
self.changeButton.DoClick = function()
if not IsValid(self.selectedIconPanel) then return end
local _,gmName = self.gmComboBox:GetSelected()
local mapName = self.selectedIconPanel:GetText()
RunConsoleCommand("_FAdmin", "Changelevel", gmName and gmName or mapName, gmName and mapName)
end
end
function PANEL:Refresh()
for _, gmInfo in ipairs(self:GetGamemodeList()) do
self.gmComboBox:AddChoice(gmInfo.title, gmInfo.name)
end
self.gmComboBox:SetValue("(current)")
for catName, maps in pairs(self:GetMapList()) do
local cat = self.catList:Add(catName)
local iconLayout = vgui.Create("DIconLayout")
iconLayout:SetSpaceX(5)
iconLayout:SetSpaceY(5)
for _, map in ipairs(maps) do
local icon = iconLayout:Add("FAdmin_MapIcon")
icon:SetText(map)
icon:SetDark(true)
local mat = Material("maps/thumb/" .. map .. ".png")
if mat:IsError() then mat = Material("maps/thumb/noicon.png") end
icon:SetMaterial(mat)
local onToggled = icon.OnToggled
icon.OnToggled = function(iconSelf, selected)
onToggled(iconSelf, selected)
if IsValid(self.selectedIconPanel) then
if selected and self.selectedIconPanel ~= iconSelf then
self.selectedIconPanel:Toggle()
elseif not selected and self.selectedIconPanel == iconSelf then
self.selectedIconPanel = nil
self.changeButton:SetEnabled(false)
return
end
end
self.selectedIconPanel = iconSelf
self.changeButton:SetEnabled(true)
end
end
cat:SetContents(iconLayout)
end
end
vgui.Register("FAdmin_Changelevel", PANEL, "DFrame")

View File

@@ -0,0 +1,46 @@
local mapList = {}
local gamemodeList = {}
net.Receive("FAdmin_ChangelevelInfo", function(len)
mapList = {}
local mapLen = net.ReadUInt(16)
for i = 1, mapLen, 1 do
local cat = net.ReadString()
mapList[cat] = {}
local catLen = net.ReadUInt(16)
for j = 1, catLen, 1 do
mapList[cat][j] = net.ReadString()
end
end
gamemodeList = {}
local gmLen = net.ReadUInt(16)
for i = 1, gmLen, 1 do
gamemodeList[i] = {
name = net.ReadString(),
title = net.ReadString()
}
end
end)
local Changelevel
FAdmin.StartHooks["ChangeLevel"] = function()
FAdmin.Access.AddPrivilege("changelevel", 2)
FAdmin.Commands.AddCommand("changelevel", "[gamemode]", "<map>")
FAdmin.ScoreBoard.Server:AddServerAction("Changelevel", "icon16/world.png", Color(155, 0, 0, 255), function() return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "changelevel") end,
function(ply, button)
local refresh = not Changelevel or table.Count(Changelevel:GetMapList()) ~= table.Count(mapList)
Changelevel = Changelevel or vgui.Create("FAdmin_Changelevel")
if refresh then
Changelevel:SetGamemodeList(gamemodeList)
Changelevel:SetMapList(mapList)
Changelevel:Refresh()
end
Changelevel:SetVisible(true)
Changelevel:Center()
Changelevel:MakePopup()
end)
end

View File

@@ -0,0 +1,43 @@
local PANEL = {}
function PANEL:Init()
self.BaseClass.Init(self)
self:SetPaintBackground(true)
self:SetIsToggle(true)
self:SetSize(96, 110)
end
function PANEL:Paint(w, h)
if self.m_bToggle then
surface.SetDrawColor(255, 155, 20, 255)
surface.DrawRect(0, 0, w, h)
end
return false
end
function PANEL:UpdateColours(skin)
return self:SetTextStyleColor(skin.Colours.Button.Normal)
end
function PANEL:OnToggled(selected)
self:InvalidateLayout(true)
end
function PANEL:OnMousePressed(code)
DButton.OnMousePressed(self, code)
end
function PANEL:OnMouseReleased(code)
DButton.OnMouseReleased(self, code)
end
function PANEL:PerformLayout()
self.m_Image:SetPos(0, 0)
local w,h = self:GetSize()
h = h - 14
self.m_Image:SetSize(w, h)
self:SetTextInset(5, w / 2)
DLabel.PerformLayout(self)
end
vgui.Register("FAdmin_MapIcon", PANEL, "DImageButton")

View File

@@ -0,0 +1,266 @@
util.AddNetworkString("FAdmin_ChangelevelInfo")
local function ChangeLevel(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "changelevel") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local map = args[2] or args[1] -- Changelevel gamemode map OR changelevel map
local gameMode = args[2] and args[1]
if gameMode then
RunConsoleCommand("gamemode", gameMode)
end
RunConsoleCommand("changelevel", map)
return true, map, gameMode
end
local mapNames = {}
local mapPatterns = {}
local ignorePatterns = {
"^asi-", "^background", "^c[%d]m", "^devtest", "^ep1_background", "^ep2_background", "^mp_coop_", "^sp_a", "^styleguide"
}
local ignoreMaps = {
-- Prefixes
["ddd_"] = true,
["sdk_"] = true,
["test_"] = true,
["vst_"] = true,
-- Maps
["c4a1y"] = true,
["cp_docks"] = true,
["cp_parkour"] = true,
["cp_sequence"] = true,
["cp_terrace"] = true,
["cp_test"] = true,
["credits"] = true,
["curling_stadium"] = true,
["d2_coast_02"] = true,
["d3_c17_02_camera"] = true,
["duel_"] = true,
["e1912"] = true,
["ep1_citadel_00_demo"] = true,
["ffa_community"] = true,
["free_"] = true,
["intro"] = true,
["lobby"] = true,
["practice_box"] = true,
["test"] = true,
["tut_training"] = true,
["tutorial_standards"] = true,
["tutorial_standards_vs"] = true
}
hook.Add("PlayerInitialSpawn", "FAdmin_ChangelevelInfo", function(ply)
local mapList = {}
local maps = file.Find("maps/*.bsp", "GAME")
for _, v in ipairs(maps) do
local name = string.lower(string.gsub(v, "%.bsp$", ""))
if ignoreMaps[name] then continue end
local prefix = string.match(name, "^(.-_)")
if ignoreMaps[prefix] then continue end
for _, ignore in ipairs(ignorePatterns) do
if string.find(name, ignore) then
goto mapContinue
end
end
-- Check if the map has a simple name or prefix
local mapCategory = mapNames[name] or mapNames[prefix]
-- Check if the map has an embedded prefix, or is TTT/Sandbox
if not mapCategory then
for pattern, category in pairs(mapPatterns) do
if string.find(name, pattern) then
mapCategory = category
end
end
end
-- Throw all uncategorized maps into Other
mapCategory = mapCategory or "Other"
-- Don't show CS:GO maps
if mapCategory == "Counter-Strike" and not file.Exists("maps/" .. name .. ".bsp", "cstrike") then
continue
end
if not mapList[mapCategory] then
mapList[mapCategory] = {}
end
table.insert(mapList[mapCategory], name)
::mapContinue::
end
local gamemodeList = engine.GetGamemodes()
net.Start("FAdmin_ChangelevelInfo")
net.WriteUInt(table.Count(mapList), 16) -- 65536 should be enough
for cat, mps in pairs(mapList) do
net.WriteString(cat)
net.WriteUInt(#mps, 16)
for _, map in pairs(mps) do
net.WriteString(map)
end
end
net.WriteUInt(#gamemodeList, 16)
for _, gmInfo in ipairs(gamemodeList) do
net.WriteString(gmInfo.name)
net.WriteString(gmInfo.title)
end
net.Send(ply)
end)
FAdmin.StartHooks["ChangeLevel"] = function()
FAdmin.Commands.AddCommand("changelevel", ChangeLevel)
FAdmin.Access.AddPrivilege("changelevel", 2)
mapNames = {}
mapPatterns = {}
mapNames["aoc_"] = "Age of Chivalry"
mapNames["ar_"] = "Counter-Strike"
mapNames["cs_"] = "Counter-Strike"
mapNames["de_"] = "Counter-Strike"
mapNames["es_"] = "Counter-Strike"
mapNames["fy_"] = "Counter-Strike"
mapNames["gd_"] = "Counter-Strike"
mapNames["training1"] = "Counter-Strike"
mapNames["dod_"] = "Day Of Defeat"
mapNames["de_dam"] = "DIPRIP"
mapNames["dm_city"] = "DIPRIP"
mapNames["dm_refinery"] = "DIPRIP"
mapNames["dm_supermarket"] = "DIPRIP"
mapNames["dm_village"] = "DIPRIP"
mapNames["ur_city"] = "DIPRIP"
mapNames["ur_refinery"] = "DIPRIP"
mapNames["ur_supermarket"] = "DIPRIP"
mapNames["ur_village"] = "DIPRIP"
mapNames["dys_"] = "Dystopia"
mapNames["pb_dojo"] = "Dystopia"
mapNames["pb_rooftop"] = "Dystopia"
mapNames["pb_round"] = "Dystopia"
mapNames["pb_urbandome"] = "Dystopia"
mapNames["sav_dojo6"] = "Dystopia"
mapNames["varena"] = "Dystopia"
mapNames["d1_"] = "Half-Life 2"
mapNames["d2_"] = "Half-Life 2"
mapNames["d3_"] = "Half-Life 2"
mapNames["dm_"] = "Half-Life 2: Deathmatch"
mapNames["halls3"] = "Half-Life 2: Deathmatch"
mapNames["ep1_"] = "Half-Life 2: Episode 1"
mapNames["ep2_"] = "Half-Life 2: Episode 2"
mapNames["ep3_"] = "Half-Life 2: Episode 3"
mapNames["d2_lostcoast"] = "Half-Life 2: Lost Coast"
mapPatterns["^c[%d]a"] = "Half-Life"
mapPatterns["^t0a"] = "Half-Life"
mapNames["boot_camp"] = "Half-Life Deathmatch"
mapNames["bounce"] = "Half-Life Deathmatch"
mapNames["crossfire"] = "Half-Life Deathmatch"
mapNames["datacore"] = "Half-Life Deathmatch"
mapNames["frenzy"] = "Half-Life Deathmatch"
mapNames["lambda_bunker"] = "Half-Life Deathmatch"
mapNames["rapidcore"] = "Half-Life Deathmatch"
mapNames["snarkpit"] = "Half-Life Deathmatch"
mapNames["stalkyard"] = "Half-Life Deathmatch"
mapNames["subtransit"] = "Half-Life Deathmatch"
mapNames["undertow"] = "Half-Life Deathmatch"
mapNames["ins_"] = "Insurgency"
mapNames["l4d_"] = "Left 4 Dead"
mapNames["clocktower"] = "Nuclear Dawn"
mapNames["coast"] = "Nuclear Dawn"
mapNames["downtown"] = "Nuclear Dawn"
mapNames["gate"] = "Nuclear Dawn"
mapNames["hydro"] = "Nuclear Dawn"
mapNames["metro"] = "Nuclear Dawn"
mapNames["metro_training"] = "Nuclear Dawn"
mapNames["oasis"] = "Nuclear Dawn"
mapNames["oilfield"] = "Nuclear Dawn"
mapNames["silo"] = "Nuclear Dawn"
mapNames["sk_metro"] = "Nuclear Dawn"
mapNames["training"] = "Nuclear Dawn"
mapNames["bt_"] = "Pirates, Vikings, & Knights II"
mapNames["lts_"] = "Pirates, Vikings, & Knights II"
mapNames["te_"] = "Pirates, Vikings, & Knights II"
mapNames["tw_"] = "Pirates, Vikings, & Knights II"
mapNames["escape_"] = "Portal"
mapNames["testchmb_"] = "Portal"
mapNames["achievement_"] = "Team Fortress 2"
mapNames["arena_"] = "Team Fortress 2"
mapNames["cp_"] = "Team Fortress 2"
mapNames["ctf_"] = "Team Fortress 2"
mapNames["itemtest"] = "Team Fortress 2"
mapNames["koth_"] = "Team Fortress 2"
mapNames["mvm_"] = "Team Fortress 2"
mapNames["pl_"] = "Team Fortress 2"
mapNames["plr_"] = "Team Fortress 2"
mapNames["rd_"] = "Team Fortress 2"
mapNames["pd_"] = "Team Fortress 2"
mapNames["sd_"] = "Team Fortress 2"
mapNames["tc_"] = "Team Fortress 2"
mapNames["tr_"] = "Team Fortress 2"
mapNames["trade_"] = "Team Fortress 2"
mapNames["pass_"] = "Team Fortress 2"
mapNames["zpa_"] = "Zombie Panic! Source"
mapNames["zpl_"] = "Zombie Panic! Source"
mapNames["zpo_"] = "Zombie Panic! Source"
mapNames["zps_"] = "Zombie Panic! Source"
mapNames["bhop_"] = "Bunny Hop"
mapNames["cinema_"] = "Cinema"
mapNames["theater_"] = "Cinema"
mapNames["xc_"] = "Climb"
mapNames["deathrun_"] = "Deathrun"
mapNames["dr_"] = "Deathrun"
mapNames["fm_"] = "Flood"
mapNames["gmt_"] = "GMod Tower"
mapNames["gg_"] = "Gun Game"
mapNames["scoutzknivez"] = "Gun Game"
mapNames["ba_"] = "Jailbreak"
mapNames["jail_"] = "Jailbreak"
mapNames["jb_"] = "Jailbreak"
mapNames["mg_"] = "Minigames"
mapNames["pw_"] = "Pirate Ship Wars"
mapNames["ph_"] = "Prop Hunt"
mapNames["rp_"] = "Roleplay"
mapNames["slb_"] = "Sled Build"
mapNames["sb_"] = "Spacebuild"
mapNames["slender_"] = "Stop it Slender"
mapNames["gms_"] = "Stranded"
mapNames["surf_"] = "Surf"
mapNames["ts_"] = "The Stalker"
mapNames["zm_"] = "Zombie Survival"
mapNames["zombiesurvival_"] = "Zombie Survival"
mapNames["zs_"] = "Zombie Survival"
for _, gm in ipairs(engine.GetGamemodes()) do
if gm.maps ~= "" then
for _, pattern in ipairs(string.Split(gm.maps, "|")) do
-- When in doubt, just try to match it with string.find later
mapPatterns[string.lower(pattern)] = gm.title or "Unnammed Gamemode"
end
end
end
end

View File

@@ -0,0 +1,41 @@
local function SortedPairsByFunction(Table, Sorted, SortDown)
local CopyTable = {}
for _, v in pairs(Table) do
table.insert(CopyTable, {NAME = tostring(v:Nick()), PLY = v})
end
table.SortByMember(CopyTable, "NAME", SortDown)
local SortedTable = {}
for _, v in ipairs(CopyTable) do
if not IsValid(v.PLY) or not v.PLY[Sorted] then continue end
local SortBy = (Sorted ~= "Team" and v.PLY[Sorted](v.PLY)) or (v.PLY:getDarkRPVar("job") or team.GetName(v.PLY[Sorted](v.PLY)))
SortedTable[SortBy] = SortedTable[SortBy] or {}
table.insert(SortedTable[SortBy], v.PLY)
end
local SecondSort = {}
for _, v in SortedPairs(SortedTable, SortDown) do
table.insert(SecondSort, v)
end
CopyTable = {}
for _, v in ipairs(SecondSort) do
for _, b in pairs(v) do
table.insert(CopyTable, b)
end
end
return ipairs(CopyTable)
end
function FAdmin.ScoreBoard.Main.PlayerListView(Sorted, SortDown)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
for _, ply in SortedPairsByFunction(player.GetAll(), Sorted, SortDown) do
local Row = vgui.Create("FadminPlayerRow")
Row:SetPlayer(ply)
Row:Dock(TOP)
Row:InvalidateLayout()
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:AddItem(Row)
end
end

View File

@@ -0,0 +1,308 @@
FAdmin.PlayerIcon = {}
FAdmin.PlayerIcon.RightClickOptions = {}
function FAdmin.PlayerIcon.AddRightClickOption(name, func)
FAdmin.PlayerIcon.RightClickOptions[name] = func
end
-- FAdminPanelList
local PANEL = {}
function PANEL:Init()
self.Padding = 5
end
function PANEL:SizeToContents()
local w, h = self:GetSize()
-- Fix size of w to have the same size as the scoreboard
w = math.Clamp(w, ScrW() * 0.9, ScrW() * 0.9)
h = math.Min(h, ScrH() * 0.95)
-- It fucks up when there's only one icon in
if #self:GetChildren() == 1 then
h = math.Max(0, 120)
end
self:SetSize(w, h)
self:PerformLayout()
end
function PANEL:Paint()
end
derma.DefineControl("FAdminPanelList", "DPanellist adapted for FAdmin", PANEL, "DPanelList")
-- FAdminPlayerCatagoryHeader
local PANEL2 = {}
function PANEL2:PerformLayout()
self:SetFont("Trebuchet24")
end
derma.DefineControl("FAdminPlayerCatagoryHeader", "DCatagoryCollapse header adapted for FAdmin", PANEL2, "DCategoryHeader")
-- FAdminPlayerCatagory
local PANEL3 = {}
function PANEL3:Init()
if self.Header then
self.Header:Remove() -- the old header is still there don't ask me why
end
self.Header = vgui.Create("FAdminPlayerCatagoryHeader", self)
self.Header:SetSize(20, 25)
self:SetPadding(5)
self.Header:Dock(TOP)
self:SetExpanded(true)
self:SetMouseInputEnabled(true)
self:SetAnimTime(0.2)
self.animSlide = Derma_Anim("Anim", self, self.AnimSlide)
self:SetPaintBackgroundEnabled(true)
end
function PANEL3:Paint()
if self.CatagoryColor then
draw.RoundedBox(4, 0, 0, self:GetWide(), self.Header:GetTall(), self.CatagoryColor)
end
end
derma.DefineControl("FAdminPlayerCatagory", "DCatagoryCollapse adapted for FAdmin", PANEL3, "DCollapsibleCategory")
-- FAdmin player row (from the sandbox player row)
PANEL = {}
local PlayerRowSize = CreateClientConVar("FAdmin_PlayerRowSize", 30, true, false)
function PANEL:Init()
self.Size = PlayerRowSize:GetInt()
self.lblName = vgui.Create("DLabel", self)
self.lblFrags = vgui.Create("DLabel", self)
self.lblTeam = vgui.Create("DLabel", self)
self.lblDeaths = vgui.Create("DLabel", self)
self.lblPing = vgui.Create("DLabel", self)
self.lblWanted = vgui.Create("DLabel", self)
-- If you don't do this it'll block your clicks
self.lblName:SetMouseInputEnabled(false)
self.lblTeam:SetMouseInputEnabled(false)
self.lblFrags:SetMouseInputEnabled(false)
self.lblDeaths:SetMouseInputEnabled(false)
self.lblPing:SetMouseInputEnabled(false)
self.lblWanted:SetMouseInputEnabled(false)
self.lblName:SetColor(Color(255,255,255,200))
self.lblTeam:SetColor(Color(255,255,255,200))
self.lblFrags:SetColor(Color(255,255,255,200))
self.lblDeaths:SetColor(Color(255,255,255,200))
self.lblPing:SetColor(Color(255,255,255,200))
self.lblWanted:SetColor(Color(255,255,255,200))
self.imgAvatar = vgui.Create("AvatarImage", self)
self:SetCursor("hand")
end
function PANEL:Paint()
if not IsValid(self.Player) then return end
self.Size = PlayerRowSize:GetInt()
self.imgAvatar:SetSize(self.Size - 4, self.Size - 4)
local color = Color(100, 150, 245, 255)
if GAMEMODE.Name == "Sandbox" then
color = Color(100, 150, 245, 255)
if self.Player:Team() == TEAM_CONNECTING then
color = Color(200, 120, 50, 255)
elseif self.Player:IsAdmin() then
color = Color(30, 200, 50, 255)
end
if self.Player:GetFriendStatus() == "friend" then
color = Color(236, 181, 113, 255)
end
else
color = team.GetColor(self.Player:Team())
end
local hooks = hook.GetTable().FAdmin_PlayerRowColour
if hooks then
for _, v in pairs(hooks) do
color = (v and v(self.Player, color)) or color
break
end
end
draw.RoundedBox(4, 0, 0, self:GetWide(), self.Size, color)
surface.SetTexture(0)
if self.Player == LocalPlayer() or self.Player:GetFriendStatus() == "friend" then
surface.SetDrawColor(255, 255, 255, 50 + math.sin(RealTime() * 2) * 50)
end
surface.DrawTexturedRect(0, 0, self:GetWide(), self.Size)
return true
end
function PANEL:SetPlayer(ply)
self.Player = ply
self.imgAvatar:SetPlayer(ply)
self:UpdatePlayerData()
end
function PANEL:UpdatePlayerData()
if not self.Player then return end
if not self.Player:IsValid() then return end
self.lblName:SetText(DarkRP.deLocalise(self.Player:Nick()))
self.lblTeam:SetText((self.Player.DarkRPVars and DarkRP.deLocalise(self.Player:getDarkRPVar("job") or "")) or team.GetName(self.Player:Team()))
self.lblTeam:SizeToContents()
self.lblFrags:SetText(self.Player:Frags())
self.lblDeaths:SetText(self.Player:Deaths())
self.lblPing:SetText(self.Player:Ping())
self.lblWanted:SetText(self.Player:isWanted() and DarkRP.getPhrase("Wanted_text") or "")
end
function PANEL:ApplySchemeSettings()
self.lblName:SetFont("ScoreboardPlayerNameBig")
self.lblTeam:SetFont("ScoreboardPlayerNameBig")
self.lblFrags:SetFont("ScoreboardPlayerName")
self.lblDeaths:SetFont("ScoreboardPlayerName")
self.lblPing:SetFont("ScoreboardPlayerName")
self.lblWanted:SetFont("ScoreboardPlayerNameBig")
self.lblName:SetFGColor(color_white)
self.lblTeam:SetFGColor(color_white)
self.lblFrags:SetFGColor(color_white)
self.lblDeaths:SetFGColor(color_white)
self.lblPing:SetFGColor(color_white)
self.lblWanted:SetFGColor(color_white)
end
function PANEL:DoClick(x, y)
if not IsValid(self.Player) then self:Remove() return end
FAdmin.ScoreBoard.ChangeView("Player", self.Player)
end
function PANEL:DoRightClick()
if table.IsEmpty(FAdmin.PlayerIcon.RightClickOptions) then return end
local menu = DermaMenu()
menu:SetPos(gui.MouseX(), gui.MouseY())
for Name, func in SortedPairs(FAdmin.PlayerIcon.RightClickOptions) do
menu:AddOption(Name, function() if IsValid(self.Player) then func(self.Player, self) end end)
end
menu:Open()
end
function PANEL:Think()
if not self.PlayerUpdate or self.PlayerUpdate < CurTime() then
self.PlayerUpdate = CurTime() + 0.5
self:UpdatePlayerData()
end
end
function PANEL:PerformLayout()
self.imgAvatar:SetPos(2, 2)
self.imgAvatar:SetSize(32, 32)
self:SetSize(self:GetWide(), self.Size)
self.lblName:SizeToContents()
self.lblName:SetPos(24, 2)
self.lblName:MoveRightOf(self.imgAvatar, 8)
local COLUMN_SIZE = 75
self.lblPing:SetPos(self:GetWide() - COLUMN_SIZE * 0.4, 0)
self.lblDeaths:SetPos(self:GetWide() - COLUMN_SIZE * 1.4, 0)
self.lblFrags:SetPos(self:GetWide() - COLUMN_SIZE * 2.4, 0)
self.lblTeam:SetPos(self:GetWide() / 2 - (0.5 * self.lblTeam:GetWide()))
self.lblWanted:SizeToContents()
self.lblWanted:SetPos(math.floor(self:GetWide() / 4), 2)
end
vgui.Register("FadminPlayerRow", PANEL, "Button")
-- FAdminActionButton
local PANEL6 = {}
function PANEL6:Init()
self:SetDrawBackground(false)
self:SetDrawBorder(false)
self:SetStretchToFit(false)
self:SetSize(120, 40)
self.TextLabel = vgui.Create("DLabel", self)
self.TextLabel:SetColor(Color(200,200,200,200))
self.TextLabel:SetFont("Roboto20")
self.m_Image2 = vgui.Create("DImage", self)
self.BorderColor = Color(190,40,0,255)
end
function PANEL6:SetText(text)
self.TextLabel:SetText(text)
self.TextLabel:SizeToContents()
self:SetWide(self.TextLabel:GetWide() + 44)
end
function PANEL6:PerformLayout()
self.m_Image:SetSize(32,32)
self.m_Image:SetPos(4,4)
self.m_Image2:SetSize(32, 32)
self.m_Image2:SetPos(4,4)
self.TextLabel:SetPos(38, 8)
end
function PANEL6:SetImage2(Mat, bckp)
self.m_Image2:SetImage(Mat, bckp)
end
function PANEL6:SetBorderColor(Col)
self.BorderColor = Col or Color(190,40,0,255)
end
function PANEL6:Paint()
local BorderColor = self.BorderColor
if self.Hovered then
BorderColor = Color(math.Min(BorderColor.r + 40, 255), math.Min(BorderColor.g + 40, 255), math.Min(BorderColor.b + 40, 255), BorderColor.a)
end
if self.Depressed then
BorderColor = color_transparent
end
draw.RoundedBox(4, 0, 0, self:GetWide(), self:GetTall(), BorderColor)
draw.RoundedBox(4, 2, 2, self:GetWide() - 4, self:GetTall() - 4, Color(40, 40, 40, 255))
end
function PANEL6:OnMousePressed(mouse)
if self:GetDisabled() then return end
self.m_Image:SetSize(24,24)
self.m_Image:SetPos(8,8)
self.Depressed = true
end
function PANEL6:OnMouseReleased(mouse)
if self:GetDisabled() then return end
self.m_Image:SetSize(32,32)
self.m_Image:SetPos(4,4)
self.Depressed = false
self:DoClick()
end
derma.DefineControl("FAdminActionButton", "Button for doing actions", PANEL6, "DImageButton")

View File

@@ -0,0 +1,146 @@
local OverrideScoreboard = CreateClientConVar("FAdmin_OverrideScoreboard", 0, true, false) -- Set if it's a scoreboard or not
function FAdmin.ScoreBoard.ChangeView(newView, ...)
if FAdmin.ScoreBoard.CurrentView == newView or not FAdmin.ScoreBoard.Visible then return end
for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do
v:SetVisible(false)
end
FAdmin.ScoreBoard.CurrentView = newView
FAdmin.ScoreBoard[newView].Show(...)
FAdmin.ScoreBoard.ChangeGmodLogo(FAdmin.ScoreBoard[newView].Logo)
FAdmin.ScoreBoard.Controls.BackButton = FAdmin.ScoreBoard.Controls.BackButton or vgui.Create("DButton")
FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true)
FAdmin.ScoreBoard.Controls.BackButton:SetPos(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y)
FAdmin.ScoreBoard.Controls.BackButton:SetText("")
FAdmin.ScoreBoard.Controls.BackButton:SetTooltip("Click me to go back!")
FAdmin.ScoreBoard.Controls.BackButton:SetCursor("hand")
FAdmin.ScoreBoard.Controls.BackButton:SetSize(100,90)
FAdmin.ScoreBoard.Controls.BackButton:SetZPos(999)
function FAdmin.ScoreBoard.Controls.BackButton:DoClick()
FAdmin.ScoreBoard.ChangeView("Main")
end
FAdmin.ScoreBoard.Controls.BackButton.Paint = function() end
end
--"fadmin/back", gui/gmod_tool
local GmodLogo, TempGmodLogo, GmodLogoColor = surface.GetTextureID("gui/gmod_logo"), surface.GetTextureID("gui/gmod_logo"), color_white
function FAdmin.ScoreBoard.ChangeGmodLogo(new)
if surface.GetTextureID(new) == TempGmodLogo then return end
TempGmodLogo = surface.GetTextureID(new)
for i = 0, 0.5, 0.01 do
timer.Simple(i, function() GmodLogoColor = Color(255,255,255,GmodLogoColor.a-5.1) end)
end
timer.Simple(0.5, function() GmodLogo = surface.GetTextureID(new) end)
for i = 0.5, 1, 0.01 do
timer.Simple(i, function()
GmodLogoColor = Color(255, 255, 255, GmodLogoColor.a + 5.1)
end)
end
end
function FAdmin.ScoreBoard.Background()
surface.SetDrawColor(0,0,0,200)
surface.DrawRect(FAdmin.ScoreBoard.X, FAdmin.ScoreBoard.Y, FAdmin.ScoreBoard.Width, FAdmin.ScoreBoard.Height)
surface.SetTexture(GmodLogo)
surface.SetDrawColor(255,255,255,GmodLogoColor.a)
surface.DrawTexturedRect(FAdmin.ScoreBoard.X - 20, FAdmin.ScoreBoard.Y, 128, 128)
end
function FAdmin.ScoreBoard.DrawScoreBoard()
if (input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)) and not FAdmin.ScoreBoard.DontGoBack then
FAdmin.ScoreBoard.ChangeView("Main")
elseif FAdmin.ScoreBoard.DontGoBack then
FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)
end
FAdmin.ScoreBoard.Background()
end
function FAdmin.ScoreBoard.ShowScoreBoard()
FAdmin.ScoreBoard.Visible = true
FAdmin.ScoreBoard.DontGoBack = input.IsMouseDown(MOUSE_4) or input.IsKeyDown(KEY_BACKSPACE)
FAdmin.ScoreBoard.Controls.Hostname = FAdmin.ScoreBoard.Controls.Hostname or vgui.Create("DLabel")
FAdmin.ScoreBoard.Controls.Hostname:SetText(DarkRP.deLocalise(GetHostName()))
FAdmin.ScoreBoard.Controls.Hostname:SetFont("ScoreboardHeader")
FAdmin.ScoreBoard.Controls.Hostname:SetColor(Color(200,200,200,200))
FAdmin.ScoreBoard.Controls.Hostname:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 20)
FAdmin.ScoreBoard.Controls.Hostname:SizeToContents()
FAdmin.ScoreBoard.Controls.Hostname:SetVisible(true)
FAdmin.ScoreBoard.Controls.Description = FAdmin.ScoreBoard.Controls.Description or vgui.Create("DLabel")
FAdmin.ScoreBoard.Controls.Description:SetText(string.format("%s\n%s", GAMEMODE.Name, GAMEMODE.Author))
FAdmin.ScoreBoard.Controls.Description:SetFont("ScoreboardSubtitle")
FAdmin.ScoreBoard.Controls.Description:SetColor(Color(200,200,200,200))
FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50)
FAdmin.ScoreBoard.Controls.Description:SizeToContents()
if FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Width / 9.5 + FAdmin.ScoreBoard.Controls.Description:GetWide() > FAdmin.ScoreBoard.Width - 150 then
FAdmin.ScoreBoard.Controls.Description:SetFont("Trebuchet18")
FAdmin.ScoreBoard.Controls.Description:SetPos(FAdmin.ScoreBoard.X + 90, FAdmin.ScoreBoard.Y + 50)
end
FAdmin.ScoreBoard.Controls.Description:SetVisible(true)
FAdmin.ScoreBoard.Controls.ServerSettingsLabel = FAdmin.ScoreBoard.Controls.ServerSettingsLabel or vgui.Create("DLabel")
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetFont("ScoreboardSubtitle")
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetText("Server settings")
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetColor(Color(200,200,200,200))
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SizeToContents()
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetPos(FAdmin.ScoreBoard.Width-150, FAdmin.ScoreBoard.Y + 68)
FAdmin.ScoreBoard.Controls.ServerSettingsLabel:SetVisible(true)
FAdmin.ScoreBoard.Controls.ServerSettings = FAdmin.ScoreBoard.Controls.ServerSettings or vgui.Create("DImageButton")
FAdmin.ScoreBoard.Controls.ServerSettings:SetMaterial("vgui/gmod_tool")
FAdmin.ScoreBoard.Controls.ServerSettings:SetPos(FAdmin.ScoreBoard.Width-200, FAdmin.ScoreBoard.Y - 20)
FAdmin.ScoreBoard.Controls.ServerSettings:SizeToContents()
FAdmin.ScoreBoard.Controls.ServerSettings:SetVisible(true)
function FAdmin.ScoreBoard.Controls.ServerSettings:DoClick()
FAdmin.ScoreBoard.ChangeView("Server")
end
if FAdmin.ScoreBoard.Controls.BackButton then FAdmin.ScoreBoard.Controls.BackButton:SetVisible(true) end
FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Show()
gui.EnableScreenClicker(true)
hook.Add("HUDPaint", "FAdmin_ScoreBoard", FAdmin.ScoreBoard.DrawScoreBoard)
hook.Call("FAdmin_ShowFAdminMenu")
return true
end
concommand.Add("+FAdmin_menu", FAdmin.ScoreBoard.ShowScoreBoard)
hook.Add("ScoreboardShow", "FAdmin_scoreboard", function()
if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server
return FAdmin.ScoreBoard.ShowScoreBoard()
end
end)
function FAdmin.ScoreBoard.HideScoreBoard()
if not FAdmin.GlobalSetting.FAdmin then return end
FAdmin.ScoreBoard.Visible = false
CloseDermaMenus()
gui.EnableScreenClicker(false)
hook.Remove("HUDPaint", "FAdmin_ScoreBoard")
for _, v in pairs(FAdmin.ScoreBoard[FAdmin.ScoreBoard.CurrentView].Controls) do
v:SetVisible(false)
end
for _, v in pairs(FAdmin.ScoreBoard.Controls) do
v:SetVisible(false)
end
return true
end
concommand.Add("-FAdmin_menu", FAdmin.ScoreBoard.HideScoreBoard)
hook.Add("ScoreboardHide", "FAdmin_scoreboard", function()
if FAdmin.GlobalSetting.FAdmin or OverrideScoreboard:GetBool() then -- Don't show scoreboard when FAdmin is not installed on server
return FAdmin.ScoreBoard.HideScoreBoard()
end
end)

View File

@@ -0,0 +1,110 @@
local Sorted, SortDown = CreateClientConVar("FAdmin_SortPlayerList", "Team", true), CreateClientConVar("FAdmin_SortPlayerListDown", 1, true)
local allowedSorts = {
["Name"] = true,
["Team"] = true,
["Frags"] = true,
["Deaths"] = true,
["Ping"] = true
}
function FAdmin.ScoreBoard.Main.Show()
local Sort = {}
local ScreenWidth, ScreenHeight = ScrW(), ScrH()
FAdmin.ScoreBoard.X = ScreenWidth * 0.05
FAdmin.ScoreBoard.Y = ScreenHeight * 0.025
FAdmin.ScoreBoard.Width = ScreenWidth * 0.9
FAdmin.ScoreBoard.Height = ScreenHeight * 0.95
FAdmin.ScoreBoard.ChangeView("Main")
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList = FAdmin.ScoreBoard.Main.Controls.FAdminPanelList or vgui.Create("DPanelList")
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetVisible(true)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList.Padding = 3
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:EnableVerticalScrollbar(true)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30 + 20)
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height - 90 - 30 - 20 - 20)
Sort.Name = Sort.Name or vgui.Create("DLabel")
Sort.Name:SetText("Sort by: Name")
Sort.Name:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 90 + 30)
Sort.Name.Type = "Name"
Sort.Name:SetVisible(true)
Sort.Team = Sort.Team or vgui.Create("DLabel")
Sort.Team:SetText("Team")
Sort.Team:SetPos(ScreenWidth * 0.5 - 30, FAdmin.ScoreBoard.Y + 90 + 30)
Sort.Team.Type = "Team"
Sort.Team:SetVisible(true)
Sort.Frags = Sort.Frags or vgui.Create("DLabel")
Sort.Frags:SetText("Kills")
Sort.Frags:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 200, FAdmin.ScoreBoard.Y + 90 + 30)
Sort.Frags.Type = "Frags"
Sort.Frags:SetVisible(true)
Sort.Deaths = Sort.Deaths or vgui.Create("DLabel")
Sort.Deaths:SetText("Deaths")
Sort.Deaths:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 140, FAdmin.ScoreBoard.Y + 90 + 30)
Sort.Deaths.Type = "Deaths"
Sort.Deaths:SetVisible(true)
Sort.Ping = Sort.Ping or vgui.Create("DLabel")
Sort.Ping:SetText("Ping")
Sort.Ping:SetPos(FAdmin.ScoreBoard.X + FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:GetWide() - 50, FAdmin.ScoreBoard.Y + 90 + 30)
Sort.Ping.Type = "Ping"
Sort.Ping:SetVisible(true)
local sortBy = Sorted:GetString()
sortBy = allowedSorts[sortBy] and sortBy or "Team"
FAdmin.ScoreBoard.Main.PlayerListView(sortBy, SortDown:GetBool())
for _, v in pairs(Sort) do
v:SetFont("Trebuchet20")
v:SizeToContents()
local X, Y = v:GetPos()
v.BtnSort = vgui.Create("DButton")
v.BtnSort:SetText("")
v.BtnSort.Type = "Down"
v.BtnSort.Paint = function(panel, w, h) derma.SkinHook("Paint", "ButtonDown", panel, w, h) end
v.BtnSort:SetSkin(GAMEMODE.Config.DarkRPSkin)
if Sorted:GetString() == v.Type then
v.BtnSort.Depressed = true
v.BtnSort.Type = (SortDown:GetBool() and "Down") or "Up"
end
v.BtnSort:SetSize(16, 16)
v.BtnSort:SetPos(X + v:GetWide() + 5, Y + 4)
function v.BtnSort.DoClick()
for _, b in pairs(Sort) do
b.BtnSort.Depressed = b.BtnSort == v.BtnSort
end
v.BtnSort.Type = (v.BtnSort.Type == "Down" and "Up") or "Down"
v.BtnSort.Paint = function(panel, w, h)
derma.SkinHook("Paint", "Button" .. v.BtnSort.Type, panel, w, h)
end
RunConsoleCommand("FAdmin_SortPlayerList", v.Type)
RunConsoleCommand("FAdmin_SortPlayerListDown", (v.BtnSort.Type == "Down" and "1") or "0")
FAdmin.ScoreBoard.Main.Controls.FAdminPanelList:Clear(true)
FAdmin.ScoreBoard.Main.PlayerListView(v.Type, v.BtnSort.Type == "Down")
end
table.insert(FAdmin.ScoreBoard.Main.Controls, v) -- Add them to the table so they get removed when you close the scoreboard
table.insert(FAdmin.ScoreBoard.Main.Controls, v.BtnSort)
end
end
function FAdmin.ScoreBoard.Main.AddPlayerRightClick(Name, func)
FAdmin.PlayerIcon.RightClickOptions[Name] = func
end
FAdmin.StartHooks["CopySteamID"] = function()
FAdmin.ScoreBoard.Main.AddPlayerRightClick("Copy SteamID", function(ply) SetClipboardText(ply:SteamID()) end)
end

View File

@@ -0,0 +1,181 @@
FAdmin.ScoreBoard.Player.Information = {}
FAdmin.ScoreBoard.Player.ActionButtons = {}
function FAdmin.ScoreBoard.Player.Show(ply)
ply = ply or FAdmin.ScoreBoard.Player.Player
FAdmin.ScoreBoard.Player.Player = ply
if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) then FAdmin.ScoreBoard.ChangeView("Main") return end
local ScreenHeight = ScrH()
FAdmin.ScoreBoard.Player.Controls.AvatarBackground = vgui.Create("AvatarImage")
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100)
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetSize(184, 184)
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetPlayer(ply, 184)
FAdmin.ScoreBoard.Player.Controls.AvatarBackground:SetVisible(true)
FAdmin.ScoreBoard.Player.InfoPanels = FAdmin.ScoreBoard.Player.InfoPanels or {}
for k, v in pairs(FAdmin.ScoreBoard.Player.InfoPanels) do
if IsValid(v) then
v:Remove()
FAdmin.ScoreBoard.Player.InfoPanels[k] = nil
end
end
if IsValid(FAdmin.ScoreBoard.Player.Controls.InfoPanel1) then
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Remove()
end
FAdmin.ScoreBoard.Player.Controls.InfoPanel1 = vgui.Create("DListLayout")
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + 184 + 5 --[[ + Avatar size]])
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetSize(184, ScreenHeight * 0.1 + 2)
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:SetVisible(true)
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Clear(true)
FAdmin.ScoreBoard.Player.Controls.InfoPanel2 = FAdmin.ScoreBoard.Player.Controls.InfoPanel2 or vgui.Create("FAdminPanelList")
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetPos(FAdmin.ScoreBoard.X + 25 + 184 --[[+ Avatar]], FAdmin.ScoreBoard.Y + 100)
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetSize(FAdmin.ScoreBoard.Width - 184 - 30 - 10, 184 + 5 + ScreenHeight * 0.1 + 2)
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:SetVisible(true)
FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Clear(true)
local function AddInfoPanel()
local pan = FAdmin.ScoreBoard.Player.Controls.InfoPanel2:Add("DListLayout")
pan:SetSize(1, FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall())
table.insert(FAdmin.ScoreBoard.Player.InfoPanels, pan)
return pan
end
local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in
for k, v in pairs(FAdmin.ScoreBoard.Player.Information) do
SelectedPanel:Dock(LEFT)
local Value = v.func(FAdmin.ScoreBoard.Player.Player)
--if not Value or Value == "" then return --[[ Value = "N/A" ]] end
if Value and Value ~= "" then
local Text = vgui.Create("DLabel")
Text:Dock(LEFT)
Text:SetFont("TabLarge")
Text:SetText(v.name .. ": " .. Value)
Text:SizeToContents()
Text:SetColor(Color(200,200,200,200))
Text:SetTooltip("Click to copy " .. v.name .. " to clipboard")
Text:SetMouseInputEnabled(true)
function Text:OnMousePressed(mcode)
self:SetTooltip(v.name .. " copied to clipboard!")
ChangeTooltip(self)
SetClipboardText(Value)
self:SetTooltip("Click to copy " .. v.name .. " to clipboard")
end
timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function()
if not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player) or not IsValid(Text) then
timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name)
if FAdmin.ScoreBoard.Visible and (not IsValid(ply) or not IsValid(FAdmin.ScoreBoard.Player.Player)) then FAdmin.ScoreBoard.ChangeView("Main") end
return
end
Value = v.func(FAdmin.ScoreBoard.Player.Player)
if not Value or Value == "" then Value = "N/A" end
Text:SetText(v.name .. ": " .. Value)
end)
if (#FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetChildren() * 17 + 17) <= FAdmin.ScoreBoard.Player.Controls.InfoPanel1:GetTall() and not v.NewPanel then
FAdmin.ScoreBoard.Player.Controls.InfoPanel1:Add(Text)
else
if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then
SelectedPanel = AddInfoPanel() -- Add new panel if the last one is full
end
SelectedPanel:Add(Text)
if Text:GetWide() > SelectedPanel:GetWide() then
SelectedPanel:SetWide(Text:GetWide() + 40)
end
end
end
end
local CatColor = team.GetColor(ply:Team())
if GAMEMODE.Name == "Sandbox" then
CatColor = Color(100, 150, 245, 255)
if ply:Team() == TEAM_CONNECTING then
CatColor = Color(200, 120, 50, 255)
elseif ply:IsAdmin() then
CatColor = Color(30, 200, 50, 255)
end
if ply:GetFriendStatus() == "friend" then
CatColor = Color(236, 181, 113, 255)
end
end
CatColor = hook.Run("FAdmin_PlayerRowColour", ply, CatColor) or CatColor
FAdmin.ScoreBoard.Player.Controls.ButtonCat = FAdmin.ScoreBoard.Player.Controls.ButtonCat or vgui.Create("FAdminPlayerCatagory")
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetLabel(" Player options!")
FAdmin.ScoreBoard.Player.Controls.ButtonCat.CatagoryColor = CatColor
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetSize(FAdmin.ScoreBoard.Width - 40, 100)
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5)
FAdmin.ScoreBoard.Player.Controls.ButtonCat:SetVisible(true)
function FAdmin.ScoreBoard.Player.Controls.ButtonCat:Toggle()
end
FAdmin.ScoreBoard.Player.Controls.ButtonPanel = FAdmin.ScoreBoard.Player.Controls.ButtonPanel or vgui.Create("FAdminPanelList", FAdmin.ScoreBoard.Player.Controls.ButtonCat)
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSpacing(5)
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableHorizontal(true)
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:EnableVerticalScrollbar(true)
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SizeToContents()
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetVisible(true)
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:SetSize(0, (ScreenHeight - FAdmin.ScoreBoard.Y - 40) - (FAdmin.ScoreBoard.Y + 100 + FAdmin.ScoreBoard.Player.Controls.InfoPanel2:GetTall() + 5))
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Clear()
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:DockMargin(5, 5, 5, 5)
for _, v in ipairs(FAdmin.ScoreBoard.Player.ActionButtons) do
if v.Visible == true or (isfunction(v.Visible) and v.Visible(FAdmin.ScoreBoard.Player.Player) == true) then
local ActionButton = vgui.Create("FAdminActionButton")
local imageType = TypeID(v.Image)
if imageType == TYPE_STRING then
ActionButton:SetImage(v.Image or "icon16/exclamation")
elseif imageType == TYPE_TABLE then
ActionButton:SetImage(v.Image[1])
if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end
elseif imageType == TYPE_FUNCTION then
local img1, img2 = v.Image(ply)
ActionButton:SetImage(img1)
if img2 then ActionButton:SetImage2(img2) end
else
ActionButton:SetImage("icon16/exclamation")
end
local name = v.Name
if isfunction(name) then name = name(FAdmin.ScoreBoard.Player.Player) end
ActionButton:SetText(DarkRP.deLocalise(name))
ActionButton:SetBorderColor(v.color)
function ActionButton:DoClick()
if not IsValid(FAdmin.ScoreBoard.Player.Player) then return end
return v.Action(FAdmin.ScoreBoard.Player.Player, self)
end
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:AddItem(ActionButton)
if v.OnButtonCreated then
v.OnButtonCreated(FAdmin.ScoreBoard.Player.Player, ActionButton)
end
end
end
FAdmin.ScoreBoard.Player.Controls.ButtonPanel:Dock(TOP)
end
function FAdmin.ScoreBoard.Player:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column
table.insert(FAdmin.ScoreBoard.Player.Information, {name = name, func = func, NewPanel = ForceNewPanel})
end
function FAdmin.ScoreBoard.Player:AddActionButton(Name, Image, color, Visible, Action, OnButtonCreated)
table.insert(FAdmin.ScoreBoard.Player.ActionButtons, {Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
end
FAdmin.ScoreBoard.Player:AddInformation("Name", function(ply) return ply:Nick() end)
FAdmin.ScoreBoard.Player:AddInformation("Kills", function(ply) return ply:Frags() end)
FAdmin.ScoreBoard.Player:AddInformation("Deaths", function(ply) return ply:Deaths() end)
FAdmin.ScoreBoard.Player:AddInformation("Health", function(ply) return ply:Health() end)
FAdmin.ScoreBoard.Player:AddInformation("Ping", function(ply) return ply:Ping() end)
FAdmin.ScoreBoard.Player:AddInformation("SteamID", function(ply) return ply:SteamID() end, true)

View File

@@ -0,0 +1,227 @@
FAdmin.ScoreBoard.Server.Information = {} -- Compatibility for autoreload
FAdmin.ScoreBoard.Server.ActionButtons = {} -- Refresh server buttons when reloading gamemode
local function MakeServerOptions()
local _, YPos, Width = 20, FAdmin.ScoreBoard.Y + 120 + FAdmin.ScoreBoard.Height / 5 + 20, (FAdmin.ScoreBoard.Width - 40) / 3
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat = FAdmin.ScoreBoard.Server.Controls.ServerActionsCat or vgui.Create("FAdminPlayerCatagory")
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetLabel(" Server Actions")
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat.CatagoryColor = Color(155, 0, 0, 255)
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20, YPos)
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetVisible(true)
function FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:Toggle()
end
FAdmin.ScoreBoard.Server.Controls.ServerActions = FAdmin.ScoreBoard.Server.Controls.ServerActions or vgui.Create("FAdminPanelList")
FAdmin.ScoreBoard.Server.Controls.ServerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerActions)
FAdmin.ScoreBoard.Server.Controls.ServerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerActions:GetChildren()) do
if k == 1 then continue end
v:Remove()
end
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat = FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat or vgui.Create("FAdminPlayerCatagory")
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetLabel(" Player Actions")
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat.CatagoryColor = Color(0, 155, 0, 255)
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width, YPos)
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetVisible(true)
function FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:Toggle()
end
FAdmin.ScoreBoard.Server.Controls.PlayerActions = FAdmin.ScoreBoard.Server.Controls.PlayerActions or vgui.Create("FAdminPanelList")
FAdmin.ScoreBoard.Server.Controls.PlayerActionsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.PlayerActions)
FAdmin.ScoreBoard.Server.Controls.PlayerActions:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.PlayerActions:GetChildren()) do
if k == 1 then continue end
v:Remove()
end
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat = FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat or vgui.Create("FAdminPlayerCatagory")
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetLabel(" Server Settings")
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat.CatagoryColor = Color(0, 0, 155, 255)
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetSize(Width-5, FAdmin.ScoreBoard.Height - 20 - YPos)
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetPos(FAdmin.ScoreBoard.X + 20 + Width * 2, YPos)
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetVisible(true)
function FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:Toggle()
end
FAdmin.ScoreBoard.Server.Controls.ServerSettings = FAdmin.ScoreBoard.Server.Controls.ServerSettings or vgui.Create("FAdminPanelList")
FAdmin.ScoreBoard.Server.Controls.ServerSettingsCat:SetContents(FAdmin.ScoreBoard.Server.Controls.ServerSettings)
FAdmin.ScoreBoard.Server.Controls.ServerSettings:SetTall(FAdmin.ScoreBoard.Height - 20 - YPos)
for k, v in ipairs(FAdmin.ScoreBoard.Server.Controls.ServerSettings:GetChildren()) do
if k == 1 then continue end
v:Remove()
end
for k, v in ipairs(FAdmin.ScoreBoard.Server.ActionButtons) do
local visible = v.Visible == true or (isfunction(v.Visible) and v.Visible(LocalPlayer()) == true)
local ActionButton = vgui.Create("FAdminActionButton")
local imageType = TypeID(v.Image)
if imageType == TYPE_STRING then
ActionButton:SetImage(v.Image or "icon16/exclamation")
elseif imageType == TYPE_TABLE then
ActionButton:SetImage(v.Image[1])
if v.Image[2] then ActionButton:SetImage2(v.Image[2]) end
elseif imageType == TYPE_FUNCTION then
local img1, img2 = v.Image()
ActionButton:SetImage(img1)
if img2 then ActionButton:SetImage2(img2) end
else
ActionButton:SetImage("icon16/exclamation")
end
local name = v.Name
if isfunction(name) then name = name() end
ActionButton:SetText(DarkRP.deLocalise(name))
ActionButton:SetBorderColor(visible and v.color or Color(120, 120, 120))
ActionButton:SetDisabled(not visible)
ActionButton:Dock(TOP)
function ActionButton:DoClick()
return v.Action(self)
end
FAdmin.ScoreBoard.Server.Controls[v.TYPE]:Add(ActionButton)
if v.OnButtonCreated then
v.OnButtonCreated(ActionButton)
end
end
end
function FAdmin.ScoreBoard.Server:AddServerAction(Name, Image, color, Visible, Action, OnButtonCreated)
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
end
function FAdmin.ScoreBoard.Server:AddPlayerAction(Name, Image, color, Visible, Action, OnButtonCreated)
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "PlayerActions", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
end
function FAdmin.ScoreBoard.Server:AddServerSetting(Name, Image, color, Visible, Action, OnButtonCreated)
table.insert(FAdmin.ScoreBoard.Server.ActionButtons, {TYPE = "ServerSettings", Name = Name, Image = Image, color = color, Visible = Visible, Action = Action, OnButtonCreated = OnButtonCreated})
end
function FAdmin.ScoreBoard.Server.Show(ply)
FAdmin.ScoreBoard.Server.InfoPanels = FAdmin.ScoreBoard.Server.InfoPanels or {}
for k, v in pairs(FAdmin.ScoreBoard.Server.InfoPanels) do
if IsValid(v) then
v:Remove()
FAdmin.ScoreBoard.Server.InfoPanels[k] = nil
end
end
if IsValid(FAdmin.ScoreBoard.Server.Controls.InfoPanel) then
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Remove()
end
FAdmin.ScoreBoard.Server.Controls.InfoPanel = vgui.Create("FAdminPanelList")
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetPos(FAdmin.ScoreBoard.X + 20, FAdmin.ScoreBoard.Y + 120)
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetSize(FAdmin.ScoreBoard.Width - 40, FAdmin.ScoreBoard.Height / 5)
FAdmin.ScoreBoard.Server.Controls.InfoPanel:SetVisible(true)
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Clear(true)
local function AddInfoPanel()
local pan = vgui.Create("FAdminPanelList")
pan:SetSize(1, FAdmin.ScoreBoard.Server.Controls.InfoPanel:GetTall())
pan:Dock(LEFT)
FAdmin.ScoreBoard.Server.Controls.InfoPanel:Add(pan)
table.insert(FAdmin.ScoreBoard.Server.InfoPanels, pan)
return pan
end
local SelectedPanel = AddInfoPanel() -- Make first panel to put the first things in
for _, v in pairs(FAdmin.ScoreBoard.Server.Information) do
local Text = vgui.Create("DLabel")
Text:SetFont("TabLarge")
Text:SetColor(Color(255,255,255,200))
Text:Dock(TOP)
Text.Func = v.Func
local EndText
local function RefreshText()
local Value = v.func()
if not Value or Value == "" then
Value = "N/A"
end
EndText = v.name .. ": " .. Value
local strLen = string.len(EndText)
if strLen > 40 then
local NewValue = string.sub(EndText, 1, 40)
for i = 40, strLen, 34 do
NewValue = NewValue .. "\n " .. string.sub(EndText, i + 1, i + 34)
end
EndText = NewValue
else
local MaxWidth = 240
surface.SetFont("TabLarge")
local TextWidth = surface.GetTextSize(v.name .. ": " .. Value)
if TextWidth <= MaxWidth then
local SpacesAmount = (MaxWidth - TextWidth) / 3
local Spaces = ""
for i = 1, SpacesAmount, 1 do
Spaces = Spaces .. " "
end
EndText = v.name .. ":" .. Spaces .. Value
end
end
Text:SetText(DarkRP.deLocalise(EndText))
Text:SizeToContents()
Text:SetTooltip("Click to copy " .. v.name .. " to clipboard")
Text:SetMouseInputEnabled(true)
end
RefreshText()
function Text:OnMousePressed(mcode)
self:SetTooltip(v.name .. " copied to clipboard!")
ChangeTooltip(self)
SetClipboardText(v.func() or "")
self:SetTooltip("Click to copy " .. v.name .. " to clipboard")
end
timer.Create("FAdmin_Scoreboard_text_update_" .. v.name, 1, 0, function()
if not IsValid(Text) then
timer.Remove("FAdmin_Scoreboard_text_update_" .. v.name)
FAdmin.ScoreBoard.ChangeView("Main")
return
end
RefreshText()
end)
if #SelectedPanel:GetChildren() * 17 + 17 >= SelectedPanel:GetTall() or v.NewPanel then
SelectedPanel = AddInfoPanel()
end
-- Add new panel if the last one is full
SelectedPanel:Add(Text)
if Text:GetWide() > SelectedPanel:GetWide() then
SelectedPanel:SetWide(Text:GetWide() + 40)
end
end
MakeServerOptions()
end
function FAdmin.ScoreBoard.Server:AddInformation(name, func, ForceNewPanel) -- ForeNewPanel is to start a new column
table.insert(FAdmin.ScoreBoard.Server.Information, {name = name, func = func, NewPanel = ForceNewPanel})
end
FAdmin.ScoreBoard.Server:AddInformation("Hostname", GetHostName)
FAdmin.ScoreBoard.Server:AddInformation("Gamemode", function() return GAMEMODE.Name end)
FAdmin.ScoreBoard.Server:AddInformation("Author", function() return GAMEMODE.Author end)
FAdmin.ScoreBoard.Server:AddInformation("Map", game.GetMap)
FAdmin.ScoreBoard.Server:AddInformation("Players", function() return player.GetCount() .. "/" .. game.MaxPlayers() end)
FAdmin.ScoreBoard.Server:AddInformation("Ping", function() return LocalPlayer():Ping() end)

View File

@@ -0,0 +1,24 @@
FAdmin.ScoreBoard = FAdmin.ScoreBoard or {}
local ScreenWidth, ScreenHeight = ScrW(), ScrH()
FAdmin.ScoreBoard.X = ScreenWidth * 0.05
FAdmin.ScoreBoard.Y = ScreenHeight * 0.025
FAdmin.ScoreBoard.Width = ScreenWidth * 0.9
FAdmin.ScoreBoard.Height = ScreenHeight * 0.95
FAdmin.ScoreBoard.Controls = FAdmin.ScoreBoard.Controls or {}
FAdmin.ScoreBoard.CurrentView = "Main"
FAdmin.ScoreBoard.Main = FAdmin.ScoreBoard.Main or {}
FAdmin.ScoreBoard.Main.Controls = FAdmin.ScoreBoard.Main.Controls or {}
FAdmin.ScoreBoard.Main.Logo = "gui/gmod_logo"
FAdmin.ScoreBoard.Player = FAdmin.ScoreBoard.Player or {}
FAdmin.ScoreBoard.Player.Controls = FAdmin.ScoreBoard.Player.Controls or {}
FAdmin.ScoreBoard.Player.Player = NULL
FAdmin.ScoreBoard.Player.Logo = "fadmin/back"
FAdmin.ScoreBoard.Server = FAdmin.ScoreBoard.Server or {}
FAdmin.ScoreBoard.Server.Controls = FAdmin.ScoreBoard.Server.Controls or {}
FAdmin.ScoreBoard.Server.Logo = "fadmin/back"

View File

@@ -0,0 +1,53 @@
--[[---------------------------------------------------------------------------
Common times for several punishment actions
---------------------------------------------------------------------------]]
FAdmin.PlayerActions.commonTimes = {}
FAdmin.PlayerActions.commonTimes[0] = "indefinitely"
FAdmin.PlayerActions.commonTimes[10] = "10 seconds"
FAdmin.PlayerActions.commonTimes[30] = "30 seconds"
FAdmin.PlayerActions.commonTimes[60] = "1 minute"
FAdmin.PlayerActions.commonTimes[300] = "5 minutes"
FAdmin.PlayerActions.commonTimes[600] = "10 minutes"
function FAdmin.PlayerActions.addTimeSubmenu(menu, submenuText, submenuClick, submenuItemClick)
local SubMenu = menu:AddSubMenu(submenuText, submenuClick)
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
SubMenu:AddPanel(Padding)
local SubMenuTitle = vgui.Create("DLabel")
SubMenuTitle:SetText(" Time:\n")
SubMenuTitle:SetFont("UiBold")
SubMenuTitle:SizeToContents()
SubMenuTitle:SetTextColor(color_black)
SubMenu:AddPanel(SubMenuTitle)
for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do
SubMenu:AddOption(Time, function() submenuItemClick(secs) end)
end
end
function FAdmin.PlayerActions.addTimeMenu(ItemClick)
local menu = DermaMenu()
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
menu:AddPanel(Padding)
local Title = vgui.Create("DLabel")
Title:SetText(" Time:\n")
Title:SetFont("UiBold")
Title:SizeToContents()
Title:SetTextColor(color_black)
menu:AddPanel(Title)
for secs, Time in SortedPairs(FAdmin.PlayerActions.commonTimes) do
menu:AddOption(Time, function() ItemClick(secs) end)
end
menu:Open()
end

View File

@@ -0,0 +1,22 @@
FAdmin.StartHooks["CleanUp"] = function()
FAdmin.Access.AddPrivilege("CleanUp", 2)
FAdmin.Commands.AddCommand("ClearDecals", nil)
FAdmin.Commands.AddCommand("StopSounds", nil)
FAdmin.Commands.AddCommand("CleanUp", nil)
FAdmin.ScoreBoard.Server:AddServerAction("Clear decals", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
RunConsoleCommand("_FAdmin", "ClearDecals")
end)
FAdmin.ScoreBoard.Server:AddServerAction("Stop all sounds", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
RunConsoleCommand("_FAdmin", "StopSounds")
end)
usermessage.Hook("FAdmin_StopSounds", function()
RunConsoleCommand("stopsound") -- bypass for ConCommand blocking it
end)
FAdmin.ScoreBoard.Server:AddServerAction("Clean up server", "fadmin/icons/cleanup", Color(155, 0, 0, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") end, function(ply, button)
RunConsoleCommand("_FAdmin", "CleanUp")
end)
end

View File

@@ -0,0 +1,44 @@
local function ClearDecals(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
for _, v in ipairs(player.GetAll()) do
v:ConCommand("r_cleardecals")
end
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have removed all decals. NOTE: this does NOT make the server ANY less laggy!", "All decals have been removed. NOTE: this does NOT make the server ANY less laggy!", "Removed all decals.")
return true
end
local function StopSounds(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
umsg.Start("FAdmin_StopSounds")
umsg.End()
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have stopped all sounds", "All sounds have been stopped", "Stopped all sounds")
return true
end
local function CleanUp(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "CleanUp") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
game.CleanUpMap()
FAdmin.Messages.ActionMessage(ply, player.GetAll(), "You have cleaned up the map", "The map has been cleaned up", "Cleaned up the map")
return true
end
FAdmin.StartHooks["CleanUp"] = function()
FAdmin.Commands.AddCommand("ClearDecals", ClearDecals)
FAdmin.Commands.AddCommand("StopSounds", StopSounds)
FAdmin.Commands.AddCommand("CleanUp", CleanUp)
local oldCleanup = concommand.GetTable()["gmod_admin_cleanup"]
concommand.Add("gmod_admin_cleanup", function(ply, cmd, args)
if args[1] then return oldCleanup(ply, cmd, args) end
return CleanUp(ply, cmd, args)
end)
FAdmin.Access.AddPrivilege("CleanUp", 2)
end

View File

@@ -0,0 +1,109 @@
local Options = {}
local targets
local colorBackground = Color(0, 0, 0, 200)
local colorHighlight = Color(255, 125, 0, 200)
hook.Add("ChatTextChanged", "FAdmin_Chat_autocomplete", function(text)
if not FAdmin.GlobalSetting.FAdmin then return end
Options = {}
local prefix = GetGlobalString("FAdmin_commandprefix")
prefix = prefix ~= '' and prefix or '/'
if string.sub(text, 1, 1) ~= prefix then targets = nil return end
local TExplode = string.Explode(" ", string.sub(text, 2))
if not TExplode[1] then return end
local Command = string.lower(TExplode[1])
local Args = table.Copy(TExplode)
Args[1] = nil
Args = table.ClearKeys(Args)
local optionsCount = 0
for k, v in pairs(FAdmin.Commands.List) do
if string.find(string.lower(k), Command, 1, true) ~= 1 then continue end
Options[prefix .. k] = table.Copy(v.ExtraArgs)
optionsCount = optionsCount + 1
end
local ChatBoxPosX, ChatBoxPosY = chat.GetChatBoxPos()
local ChatBoxWidth = chat.GetChatBoxSize() -- Don't need height
local DidMakeShorter = false
table.sort(Options)
local i = 1
for k in pairs(Options) do
local Pos = ChatBoxPosY + i * 24
if Pos + 24 > ScrH() then
Options[k] = nil
DidMakeShorter = true
optionsCount = optionsCount - 1
end
i = i + 1
end
-- Player arguments
local firstVal = table.GetFirstValue(Options)
if optionsCount == 1 and firstVal[#Args] and string.match(firstVal[#Args], ".Player.") then
local players = {}
for _, v in ipairs(FAdmin.FindPlayer(Args[#Args]) or {}) do
if not IsValid(v) then continue end
table.insert(players, v:Nick())
end
targets = table.concat(players, ", ")
end
local xPos = ChatBoxPosX + ChatBoxWidth + 2
hook.Add("HUDPaint", "FAdmin_Chat_autocomplete", function()
local j = 0
for option, args in pairs(Options) do
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, option, "UiBold", colorBackground, color_white)
for k, arg in pairs(args) do
draw.WordBox(4, xPos + k * 130, ChatBoxPosY + j * 24, arg, "UiBold", colorBackground, color_white)
end
j = j + 1
end
if targets then
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "Targets: " .. targets, "UiBold", colorHighlight, color_white)
end
if DidMakeShorter then
draw.WordBox(4, xPos, ChatBoxPosY + j * 24, "...", "UiBold", colorBackground, color_white)
end
end)
end)
hook.Add("FinishChat", "FAdmin_Chat_autocomplete", function() hook.Remove("HUDPaint", "FAdmin_Chat_autocomplete") end)
local plyIndex = 1
hook.Add("OnChatTab", "FAdmin_Chat_autocomplete", function(text)
if not FAdmin.GlobalSetting.FAdmin then return end
for command in pairs(Options) do
if string.find(text, " ") == nil then
return string.sub(command, 1, string.find(command, " "))
elseif string.find(text, " ") then
plyIndex = plyIndex + 1
if plyIndex > player.GetCount() then
plyIndex = 1
end
return string.sub(command, 1, string.find(command, " ")) .. " " .. string.sub(player.GetAll()[plyIndex]:Nick(), 1, string.find(player.GetAll()[plyIndex]:Nick(), " "))
end
end
end)
FAdmin.StartHooks["Chatcommands"] = function()
FAdmin.ScoreBoard.Server:AddServerSetting("Set FAdmin's chat command prefix", "fadmin/icons/message", Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") end, function()
local prefix = GetGlobalString("FAdmin_commandprefix")
prefix = prefix ~= '' and prefix or '/'
Derma_StringRequest("Set chat command prefix", "Make sure it's only one character!", prefix, fp{RunConsoleCommand, "_Fadmin", "CommandPrefix"})
end)
end

View File

@@ -0,0 +1,28 @@
local function AutoComplete(command, args)
local autocomplete = {}
args = string.Explode(" ", args)
table.remove(args, 1) --Remove the first space
if args[1] == "" then
for k in pairs(FAdmin.Commands.List) do
table.insert(autocomplete, command .. " " .. k)
end
elseif not args[2] or args[3] then
for k, v in pairs(FAdmin.Commands.List) do
if string.sub(k, 1, string.len(args[1])) == args[1] then
local ExtraArgs = table.concat(v.ExtraArgs, " ")
table.insert(autocomplete, command .. " " .. k .. " " .. ExtraArgs)
end
end
elseif not args[3] and FAdmin.Commands.List[string.lower(args[1])] and FAdmin.Commands.List[string.lower(args[1])].ExtraArgs[1] == "<Player>" then
for _, v in ipairs(player.GetAll()) do
if args[2] == "" or table.HasValue(FAdmin.FindPlayer(args[2]) or {}, v) then
table.insert(autocomplete, command .. " " .. args[1] .. " " .. v:Nick())
end
end
end
table.sort(autocomplete)
return autocomplete
end
concommand.Add("FAdmin", function(ply, cmd, args)
RunConsoleCommand("_" .. cmd, unpack(args))
end, AutoComplete)

View File

@@ -0,0 +1,6 @@
FAdmin.Commands = {}
FAdmin.Commands.List = {}
function FAdmin.Commands.AddCommand(name, callback, ...)
FAdmin.Commands.List[string.lower(name)] = {callback = callback, ExtraArgs = {...}}
end

View File

@@ -0,0 +1,52 @@
local convar = CreateConVar("FAdmin_commandprefix", "/", {FCVAR_SERVER_CAN_EXECUTE})
SetGlobalString("FAdmin_commandprefix", convar:GetString())
cvars.AddChangeCallback("FAdmin_commandprefix", function()
SetGlobalString("FAdmin_commandprefix", convar:GetString())
end)
hook.Add("PlayerSay", "FAdminChatCommands", function(ply, text, Team, dead)
local prefix = convar:GetString()
if string.sub(text, 1, 1) ~= prefix then return end
local TExplode = string.Explode(" ", string.sub(text, 2))
if not TExplode then return end
for k, v in ipairs(TExplode) do
if string.sub(v, -1) == "," and TExplode[k + 1] then
TExplode[k] = (TExplode[k] or "") .. (TExplode[k + 1] or "")
table.remove(TExplode, k + 1)
end
end
table.ClearKeys(TExplode, false)
local Command = string.lower(TExplode[1])
local Args = table.Copy(TExplode)
Args[1] = nil
Args = table.ClearKeys(Args)
if FAdmin.Commands.List[Command] then
local res = {FAdmin.Commands.List[Command].callback(ply, Command, Args)}
hook.Call("FAdmin_OnCommandExecuted", nil, ply, Command, Args, res)
return ""
end
end)
FAdmin.StartHooks["Chatcommands"] = function()
convar = convar or GetConVar("FAdmin_commandprefix")
FAdmin.Commands.AddCommand("CommandPrefix", function(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "ServerSetting") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not args[1] or string.len(args[1]) ~= 1 then return end
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " set FAdmin's chat command prefix to " .. args[1], "FAdmin's chat command prefix has been set to " .. args[1], "Chat command prefix set to" .. args[1])
RunConsoleCommand("FAdmin_commandprefix", args[1])
FAdmin.SaveSetting("FAdmin_commandprefix", args[1])
return true
end)
end

View File

@@ -0,0 +1,71 @@
local function concommand_executed(ply, cmd, args)
if not args[1] then return end
local name = string.lower(args[1])
if not name or not FAdmin.Commands.List[name] then
FAdmin.Messages.SendMessage(ply, 1, "Command does not exist!")
return
end
local args2 = args
table.remove(args2, 1)
for k, v in pairs(args2) do
if string.sub(v, -1) == "," and args2[k + 1] then
args2[k] = args2[k] .. args2[k + 1]
table.remove(args2, k + 1)
end
end
table.ClearKeys(args2)
local res = {FAdmin.Commands.List[name].callback(ply, name, args2)}
hook.Call("FAdmin_OnCommandExecuted", nil, ply, name, args2, res)
end
local function AutoComplete(command, ...)
local autocomplete = {}
local args = string.Explode(" ", ...)
table.remove(args, 1) --Remove the first space
if args[1] == "" then
for k in pairs(FAdmin.Commands.List) do
table.insert(autocomplete, command .. " " .. k)
end
elseif not args[2] then
for k in pairs(FAdmin.Commands.List) do
if string.sub(k, 1, string.len(args[1])) == args[1] then
table.insert(autocomplete, command .. " " .. k)
end
end
end
table.sort(autocomplete)
return autocomplete
end
concommand.Add("_FAdmin", concommand_executed, AutoComplete)
concommand.Add("FAdmin", concommand_executed, AutoComplete)
-- DO NOT EDIT THIS, NO MATTER HOW MUCH YOU'VE EDITED FADMIN IT DOESN'T GIVE YOU ANY RIGHT TO CHANGE CREDITS AND/OR REMOVE THE AUTHOR
FAdmin.Commands.AddCommand("FAdminCredits", function(ply, cmd, args)
if ply:SteamID() == "STEAM_0:0:8944068" and args[1] then
local targets = FAdmin.FindPlayer(args[1])
if not targets or (#targets == 1 and not IsValid(targets[1])) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
for _, target in ipairs(targets) do
if IsValid(target) then
concommand_executed(target, "FAdmin", {"FAdminCredits"})
end
end
FAdmin.Messages.SendMessage(ply, 4, "Credits sent!")
return true
end
FAdmin.Messages.SendMessage(ply, 2, "FAdmin by (FPtje) Falco, STEAM_0:0:8944068")
for _, v in ipairs(player.GetAll()) do
if v:SteamID() == "STEAM_0:0:8944068" then
FAdmin.Messages.SendMessage(ply, 4, "(FPtje) Falco is in the server at this moment")
return true
end
end
FAdmin.Messages.SendMessage(ply, 5, "(FPtje) Falco is NOT in the server at this moment")
return true
end)

View File

@@ -0,0 +1,17 @@
local logging = CreateConVar("FAdmin_logging", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
if SERVER then return end
FAdmin.StartHooks["Logging"] = function()
FAdmin.Access.AddPrivilege("Logging", 3)
FAdmin.Commands.AddCommand("Logging", nil)
FAdmin.ScoreBoard.Server:AddServerSetting(function() return (logging:GetBool() and "Disable" or "Enable") .. " Logging" end,
function() return "fadmin/icons/message", logging:GetBool() and "fadmin/icons/disable" end,
Color(0, 0, 155, 255), function(ply) return FAdmin.Access.PlayerHasPrivilege(ply, "Logging") end, function(button)
button:SetImage2((not logging:GetBool() and "fadmin/icons/disable") or "null")
button:SetText((not logging:GetBool() and "Disable" or "Enable") .. " Logging")
button:GetParent():InvalidateLayout()
RunConsoleCommand("_Fadmin", "Logging", logging:GetBool() and 0 or 1)
end)
end

View File

@@ -0,0 +1,161 @@
local logging
FAdmin.StartHooks["Logging"] = function()
FAdmin.Access.AddPrivilege("Logging", 3)
FAdmin.Commands.AddCommand("Logging", function(ply, cmd, args)
if not FAdmin.Access.PlayerHasPrivilege(ply, "Logging") then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not tonumber(args[1]) then return end
local OnOff = (tobool(tonumber(args[1])) and "on") or "off"
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned logging " .. OnOff, "Logging has been turned " .. OnOff, "Turned logging " .. OnOff)
RunConsoleCommand("FAdmin_logging", args[1])
FAdmin.SaveSetting("FAdmin_logging", args[1])
return true, OnOff
end)
logging = GetConVar("FAdmin_logging")
end
function FAdmin.Log(text)
if not text or text == "" then return end
if not logging or not logging:GetBool() then return end
ServerLog("[FAdmin] " .. text .. "\n")
end
hook.Add("PlayerGiveSWEP", "FAdmin_Log", function(ply, class)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Gave themself a " .. (class or "Unknown"))
end)
hook.Add("PlayerSpawnedSENT", "FAdmin_Log", function(ply, ent)
if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown"))
end)
hook.Add("PlayerSpawnSWEP", "FAdmin_Log", function(ply, class)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (class or "Unknown"))
end)
hook.Add("PlayerSpawnedProp", "FAdmin_Log", function(ply, model, ent)
if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end
for _, v in ipairs(player.GetAll()) do
if v:IsAdmin() then
v:PrintMessage(HUD_PRINTCONSOLE, ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown"))
end
end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown"))
end)
hook.Add("PlayerSpawnedNPC", "FAdmin_Log", function(ply, ent)
if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown"))
end)
hook.Add("PlayerSpawnedVehicle", "FAdmin_Log", function(ply, ent)
if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (ent:GetClass() or "Unknown"))
end)
hook.Add("PlayerSpawnedEffect", "FAdmin_Log", function(ply, model, ent)
if not IsValid(ply) or not ply:IsPlayer() or not model then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown"))
end)
hook.Add("PlayerSpawnedRagdoll", "FAdmin_Log", function(ply, model, ent)
if not IsValid(ply) or not ply:IsPlayer() or not IsValid(ent) then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned a " .. (model or "Unknown"))
end)
hook.Add("CanTool", "FAdmin_Log", function(ply, tr, toolclass)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to use tool " .. (toolclass or "Unknown"))
end)
hook.Add("PlayerLeaveVehicle", "FAdmin_Log", function(ply, vehicle)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") exited a " .. (IsValid(vehicle) and vehicle:GetClass() or "Unknown"))
end)
hook.Add("OnNPCKilled", "FAdmin_Log", function(NPC, Killer, Weapon)
if not IsValid(NPC) then return end
FAdmin.Log(NPC:GetClass() .. " was killed by " .. (IsValid(Killer) and (Killer:IsPlayer() and Killer:Nick() or Killer:GetClass()) or "Unknown") .. " with a " .. (IsValid(Weapon) and Weapon:GetClass() or "Unknown"))
end)
hook.Add("OnPlayerChangedTeam", "FAdmin_Log", function(ply, oldteam, newteam)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") changed from " .. team.GetName(oldteam) .. " to " .. team.GetName(newteam))
end)
hook.Add("WeaponEquip", "FAdmin_Log", function(weapon)
timer.Simple(0, function()
if not IsValid(weapon) then return end
local ply = weapon:GetOwner()
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to pick up a " .. weapon:GetClass())
end)
end)
hook.Add("PlayerDeath", "FAdmin_Log", function(ply, inflictor, Killer)
local Nick = IsValid(ply) and ply:Nick() or "N/A"
local SteamID = IsValid(ply) and ply:SteamID() or "N/A"
local KillerName = IsValid(Killer) and (Killer:IsPlayer() and Killer:Nick() or Killer:GetClass()) or "N/A"
local InflictorName = IsValid(inflictor) and inflictor:GetClass() or "N/A"
FAdmin.Log(Nick .. " (" .. SteamID .. ") Got killed by " .. KillerName .. " with a " .. InflictorName)
end)
hook.Add("PlayerSilentDeath", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Got killed silently")
end)
hook.Add("PlayerDisconnected", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Disconnected")
end)
hook.Add("PlayerInitialSpawn", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned for the first time")
end)
hook.Add("PlayerSpawn", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Spawned")
end)
hook.Add("PlayerSpray", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Sprayed his spray")
end)
hook.Add("PlayerEnteredVehicle", "FAdmin_Log", function(ply, vehicle)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Entered " .. (IsValid(vehicle) and vehicle:GetClass() or "Unknown"))
end)
hook.Add("EntityRemoved", "FAdmin_Log", function(ent)
if IsValid(ent) and ent:GetClass() == "prop_physics" then
FAdmin.Log(ent:GetClass() .. "(" .. (ent:GetModel() or "<no model>") .. ") Got removed")
end
end)
hook.Add("PlayerAuthed", "FAdmin_Log", function(ply, SteamID, _)
if not IsValid(ply) then return end
FAdmin.Log(ply:Nick() .. " (" .. (SteamID or "Unknown Steam ID") .. ") is Authed")
end)
hook.Add("PlayerNoClip", "FAdmin_Log", function(ply)
if not IsValid(ply) or not ply:IsPlayer() then return end
FAdmin.Log(ply:Nick() .. " (" .. ply:SteamID() .. ") Attempted to switch noclip")
end)
hook.Add("ShutDown", "FAdmin_Log", function()
FAdmin.shuttingDown = true
FAdmin.Log("Server succesfully shut down.")
end)

View File

@@ -0,0 +1,189 @@
local showChat = CreateClientConVar("FAdmin_ShowChatNotifications", 1, true, false)
local HUDNote_c = 0
local HUDNote_i = 1
local HUDNotes = {}
--Notify ripped off the Sandbox notify, changed to my likings
function FAdmin.Messages.AddMessage(MsgType, Message)
local tab = {}
tab.text = Message
tab.recv = SysTime()
tab.velx = 0
tab.vely = -5
surface.SetFont("GModNotify")
local w, _ = surface.GetTextSize(Message)
tab.x = ScrW() / 2 + w * 0.5 + (ScrW() / 20)
tab.y = ScrH()
tab.a = 255
local MsgTypeNames = {"ERROR", "NOTIFY", "QUESTION", "GOOD", "BAD"}
if not MsgTypeNames[MsgType] then return end
tab.col = FAdmin.Messages.MsgTypes[MsgTypeNames[MsgType]].COLOR
table.insert(HUDNotes, tab)
HUDNote_c = HUDNote_c + 1
HUDNote_i = HUDNote_i + 1
LocalPlayer():EmitSound("npc/turret_floor/click1.wav", 30, 100)
end
usermessage.Hook("FAdmin_SendMessage", function(u) FAdmin.Messages.AddMessage(u:ReadShort(), u:ReadString()) end)
local function DrawNotice(k, v, i)
local H = ScrH() / 1024
local x = v.x - 75 * H
local y = v.y - 27
surface.SetFont("GModNotify")
local w, h = surface.GetTextSize(v.text)
h = h + 16
local col = v.col
draw.RoundedBox(4, x - w - h + 24, y - 8, w + h - 16, h, col)
-- Draw Icon
surface.SetDrawColor(255, 255, 255, v.a)
draw.DrawNonParsedSimpleText(v.text, "GModNotify", x + 1, y + 1, Color(0, 0, 0, v.a * 0.8), TEXT_ALIGN_RIGHT)
draw.DrawNonParsedSimpleText(v.text, "GModNotify", x - 1, y - 1, Color(0, 0, 0, v.a * 0.5), TEXT_ALIGN_RIGHT)
draw.DrawNonParsedSimpleText(v.text, "GModNotify", x + 1, y - 1, Color(0, 0, 0, v.a * 0.6), TEXT_ALIGN_RIGHT)
draw.DrawNonParsedSimpleText(v.text, "GModNotify", x - 1, y + 1, Color(0, 0, 0, v.a * 0.6), TEXT_ALIGN_RIGHT)
draw.DrawNonParsedSimpleText(v.text, "GModNotify", x, y, Color(255, 255, 255, v.a), TEXT_ALIGN_RIGHT)
local ideal_y = ScrH() - (HUDNote_c - i) * h
local ideal_x = ScrW() / 2 + w * 0.5 + (ScrW() / 20)
local timeleft = 6 - (SysTime() - v.recv)
-- Cartoon style about to go thing
if (timeleft < 0.8) then
ideal_x = ScrW() / 2 + w * 0.5 + 200
end
-- Gone!
if (timeleft < 0.5) then
ideal_y = ScrH() + 50
end
local spd = RealFrameTime() * 15
v.y = v.y + v.vely * spd
v.x = v.x + v.velx * spd
local dist = ideal_y - v.y
v.vely = v.vely + dist * spd * 1
if (math.abs(dist) < 2 and math.abs(v.vely) < 0.1) then
v.vely = 0
end
dist = ideal_x - v.x
v.velx = v.velx + dist * spd * 1
if math.abs(dist) < 2 and math.abs(v.velx) < 0.1 then
v.velx = 0
end
-- Friction.. kind of FPS independant.
v.velx = v.velx * (0.95 - RealFrameTime() * 8)
v.vely = v.vely * (0.95 - RealFrameTime() * 8)
end
local function HUDPaint()
if not HUDNotes then return end
local i = 0
for k, v in ipairs(HUDNotes) do
if v ~= 0 then
i = i + 1
DrawNotice(k, v, i)
end
end
for k, v in ipairs(HUDNotes) do
if v ~= 0 and v.recv + 6 < SysTime() then
HUDNotes[k] = 0
HUDNote_c = HUDNote_c - 1
if HUDNote_c == 0 then
HUDNotes = {}
end
end
end
end
hook.Add("HUDPaint", "FAdmin_MessagePaint", HUDPaint)
local function ConsoleMessage(um)
MsgC(Color(255, 0, 0, 255), "(FAdmin) ", Color(200, 0, 200, 255), um:ReadString() .. "\n")
end
usermessage.Hook("FAdmin_ConsoleMessage", ConsoleMessage)
local red = Color(255, 0, 0)
local white = Color(190, 190, 190)
local brown = Color(102, 51, 0)
local blue = Color(102, 0, 255)
-- Inserts the instigator into a notification message
local function insertInstigator(res, instigator, _)
table.insert(res, brown)
table.insert(res, FAdmin.PlayerName(instigator))
end
-- Inserts the targets into the notification message
local function insertTargets(res, _, targets)
table.insert(res, blue)
table.insert(res, FAdmin.TargetsToString(targets))
end
local modMessage = {
instigator = insertInstigator,
you = function(res) table.insert(res, brown) table.insert(res, "you") end,
targets = insertTargets,
}
local function showNotification(notification, instigator, targets, extraInfo)
local res = {red, "[", white, "FAdmin", red, "] "}
for _, text in pairs(notification.message) do
if modMessage[text] then modMessage[text](res, instigator, targets) continue end
if string.sub(text, 1, 10) == "extraInfo." then
local id = tonumber(string.sub(text, 11))
table.insert(res, notification.extraInfoColors and notification.extraInfoColors[id] or white)
table.insert(res, extraInfo[id])
continue
end
table.insert(res, white)
table.insert(res, text)
end
if showChat:GetBool() then
chat.AddText(unpack(res))
else
local msgTbl = {}
for i = 8, #res, 2 do table.insert(msgTbl, res[i]) end
FAdmin.Messages.AddMessage(FAdmin.Messages.MsgTypesByName[notification.msgType], table.concat(msgTbl, ""))
MsgC(unpack(res))
Msg("\n")
end
end
local function receiveNotification()
local id = net.ReadUInt(16)
local notification = FAdmin.Notifications[id]
local instigator = net.ReadEntity()
local targets = {}
if notification.hasTarget then
local targetCount = net.ReadUInt(8)
for i = 1, targetCount do
table.insert(targets, net.ReadEntity())
end
end
local extraInfo = notification.readExtraInfo and notification.readExtraInfo()
showNotification(notification, instigator, targets, extraInfo)
end
net.Receive("FAdmin_Notification", receiveNotification)

View File

@@ -0,0 +1,135 @@
FAdmin.Messages = {}
FAdmin.Messages.MsgTypes = {
ERROR = {TEXTURE = "icon16/exclamation.png", COLOR = Color(255,180,0,80)},
NOTIFY = {TEXTURE = "vgui/notices/error", COLOR = Color(255,255,0,80)},
QUESTION = {TEXTURE = "vgui/notices/hint", COLOR = Color(0,0,255,80)},
GOOD = {TEXTURE = "icon16/tick.png", COLOR = Color(0,255,0,80)},
BAD = {TEXTURE = "icon16/cross.png", COLOR = Color(255,0,0,80)}
}
FAdmin.Messages.MsgTypesByName = {
ERROR = 1,
NOTIFY = 2,
QUESTION = 3,
GOOD = 4,
BAD = 5,
}
function FAdmin.PlayerName(ply)
if CLIENT and ply == LocalPlayer() then return "you" end
if isstring(ply) then return ply end
return isentity(ply) and (ply:EntIndex() == 0 and "Console" or ply:Nick()) or "unknown"
end
function FAdmin.TargetsToString(targets)
if not istable(targets) then
return FAdmin.PlayerName(targets)
end
local targetCount = #targets
if targetCount == 0 then
return "no one"
end
if targetCount == player.GetCount() and targetCount ~= 1 then
return "everyone"
end
targets = table.Copy(targets)
local names = fn.Map(FAdmin.PlayerName, targets)
if #names == 1 then
return names[1]
end
return table.concat(names, ", ", 1, #names - 1) .. " and " .. names[#names]
end
FAdmin.Notifications = {}
local validNotification = tc.checkTable{
-- A name to identify the notification by
name =
tc.addHint(
isstring,
"The name must be a string!"
),
-- Whether the notification applies to some kind of target
hasTarget =
tc.addHint(
tc.optional(isbool),
"hasTarget must either be true, false or nil!"
),
-- Who receives the notification. Can be either one of the list or a function that returns a table of players
receivers =
tc.addHint(
fn.FOr{tc.client, isfunction, tc.oneOf{"everyone", "admins", "superadmins", "self", "targets", "involved", "involved+admins", "involved+superadmins"}},
"receivers must either be a function returning a table of players or one of 'admins', 'superadmins', 'everyone', 'self', 'targets', 'involved', 'involved+admins', 'involved+superadmins'"
),
-- A table containing the message in parts. There are special strings
message =
tc.addHint(
tc.tableOf(isstring),
"The message field must be a table of strings! with special strings 'targets', 'you', 'instigator', 'extraInfo.#', with # a number."
),
-- The message type when chat notifications are disabled. NOTIFY by default
msgType =
tc.default(
"NOTIFY",
tc.addHint(
tc.oneOf{"ERROR", "NOTIFY", "QUESTION", "GOOD", "BAD"}, "msgType must be one of 'ERROR', 'NOTIFY', 'QUESTION', 'GOOD', 'BAD'"
)
),
-- A function that writes extra data in the net message
writeExtraInfo =
tc.addHint(
tc.optional(isfunction),
"writeExtraInfo must be a function"
),
-- A function that reads the written data, formats it and puts it in a table
readExtraInfo =
tc.addHint(
tc.optional(isfunction),
"writeExtraInfo must be a function"
),
-- When using extra information, this table contains the colours of the extraInfo messages
extraInfoColors =
tc.addHint(
tc.optional(tc.tableOf(tc.iscolor)),
"extraInfoColors must be a table of colours!"
),
-- Whether the notification is to be logged to console
logging =
tc.default(true,
tc.addHint(
isbool,
"logging must be a boolean!"
)
),
}
FAdmin.NotificationNames = {}
function FAdmin.Messages.RegisterNotification(tbl)
local correct, err = validNotification(tbl)
if not correct then
error(string.format("Incorrect notification format for notification '%s'!\n\n%s", istable(tbl) and tbl.name or "unknown", err), 2)
end
local key = table.insert(FAdmin.Notifications, tbl)
FAdmin.NotificationNames[tbl.name] = key
return key
end

View File

@@ -0,0 +1,137 @@
util.AddNetworkString("FAdmin_Notification")
function FAdmin.Messages.SendMessage(ply, MsgType, text)
if ply:EntIndex() == 0 then
ServerLog("FAdmin: " .. text .. "\n")
print("FAdmin: " .. text)
return
end
umsg.Start("FAdmin_SendMessage", ply)
umsg.Short(MsgType)
umsg.String(text)
umsg.End()
ply:PrintMessage(HUD_PRINTCONSOLE, text)
end
function FAdmin.Messages.SendMessageAll(text, MsgType)
FAdmin.Log("FAdmin message to everyone: " .. text)
umsg.Start("FAdmin_SendMessage")
umsg.Short(MsgType)
umsg.String(text)
umsg.End()
for _, ply in ipairs(player.GetAll()) do
ply:PrintMessage(HUD_PRINTCONSOLE, text)
end
end
function FAdmin.Messages.ConsoleNotify(ply, message)
umsg.Start("FAdmin_ConsoleMessage", ply)
umsg.String(message)
umsg.End()
end
function FAdmin.Messages.ActionMessage(ply, target, messageToPly, MessageToTarget, LogMSG)
if not target then return end
local Targets = (target.IsPlayer and target:IsPlayer() and target:Nick()) or ""
local plyNick = IsValid(ply) and ply:IsPlayer() and ply:Nick() or "Console"
local plySteamID = IsValid(ply) and ply:IsPlayer() and ply:SteamID() or "Console"
local bad = false
if ply ~= target then
if istable(target) then
if table.IsEmpty(target) then Targets = "no one" bad = true end
for k, v in ipairs(target) do
local suffix = ((k == #target-1) and " and ") or (k ~= #target and ", ") or ""
local Name = (v == ply and "yourself") or v:Nick()
if v ~= ply then FAdmin.Messages.SendMessage(v, 2, string.format(MessageToTarget, plyNick)) end
Targets = Targets .. Name .. suffix
end
else
FAdmin.Messages.SendMessage(target, 2, string.format(MessageToTarget, plyNick))
end
FAdmin.Messages.SendMessage(ply, bad and 1 or 4, string.format(messageToPly, Targets))
else
FAdmin.Messages.SendMessage(ply, bad and 1 or 4, string.format(messageToPly, "yourself"))
end
local action = plyNick .. " (" .. plySteamID .. ") " .. string.format(LogMSG, Targets:gsub("yourself", "themselves"))
FAdmin.Log("FAdmin Action: " .. action)
local haspriv = fn.Partial(fn.Flip(FAdmin.Access.PlayerHasPrivilege), "SeeAdmins")
local plys = fn.Filter(haspriv, player.GetAll())
if table.IsEmpty(plys) then return end
FAdmin.Messages.ConsoleNotify(plys, action)
end
local function logNotification(notification, instigator, targets, extraInfo)
local msgs = table.Copy(notification.message)
local function replace(val)
if val == "instigator" then return FAdmin.PlayerName(instigator) end
if val == "targets" then return FAdmin.TargetsToString(targets) end
if string.sub(val, 1, 10) == "extraInfo." then return tostring(extraInfo[tonumber(string.sub(val, 11))]) end
return val
end
fn.Map(replace, msgs)
FAdmin.Log(table.concat(msgs))
end
local receiversToPlayers -- allows usage of variable inside
receiversToPlayers = {
everyone = player.GetAll,
admins = function() return table.ClearKeys(fn.Filter(tc.player.IsAdmin, player.GetAll())) end,
superadmins = function() return table.ClearKeys(fn.Filter(tc.player.IsSuperAdmin, player.GetAll())) end,
self = fn.Id,
targets = function(_, t) return t end,
involved = function(i, t) local res = table.Copy(istable(t) and t or {t}) table.insert(res, i) return res end,
["involved+admins"] = function(i, t) return table.Add(receiversToPlayers.admins(i, t), receiversToPlayers.involved(i, t)) end,
["involved+superadmins"] = function(i, t) return table.Add(receiversToPlayers.superadmins(i, t), receiversToPlayers.involved(i, t)) end,
}
function FAdmin.Messages.FireNotification(name, instigator, targets, extraInfo)
local notId = FAdmin.NotificationNames[name]
if not notId then
error(string.format("Notification '%s' does not exist!", name), 2)
end
local notification = FAdmin.Notifications[notId]
local receivers = receiversToPlayers[notification.receivers]
receivers = receivers and receivers(instigator, targets) or notification.receivers(instigator, targets)
local targetCount = istable(targets) and #targets or not IsValid(targets) and 0 or 1
net.Start("FAdmin_Notification")
net.WriteUInt(notId, 16)
net.WriteEntity(instigator)
if notification.hasTarget then
net.WriteUInt(targetCount, 8)
if istable(targets) then
for _, t in ipairs(targets) do
net.WriteEntity(t)
end
else
net.WriteEntity(targets)
end
end
if notification.writeExtraInfo then notification.writeExtraInfo(extraInfo) end
net.Send(receivers)
if notification.logging then
logNotification(notification, instigator, targets, extraInfo)
end
end

View File

@@ -0,0 +1,85 @@
local MOTDPage = CreateConVar("_FAdmin_MOTDPage", "default", {FCVAR_REPLICATED, FCVAR_ARCHIVE, FCVAR_SERVER_CAN_EXECUTE})
if CLIENT then -- I can't be bothered to make a cl_init when there's a shared file with just one line in it.
FAdmin.StartHooks["MOTD"] = function()
FAdmin.ScoreBoard.Server:AddServerAction("Place MOTD", "fadmin/icons/motd", Color(155, 0, 0, 255), function(ply) return ply:IsSuperAdmin() end, function()
RunConsoleCommand("_FAdmin", "CreateMOTD")
end)
FAdmin.ScoreBoard.Server:AddServerSetting("Set MOTD page", "fadmin/icons/motd", Color(0, 0, 155, 255), function(ply) return ply:IsSuperAdmin() end, function()
local Window = vgui.Create("DFrame")
Window:SetTitle("Set MOTD page")
Window:SetDraggable(false)
Window:ShowCloseButton(false)
Window:SetBackgroundBlur(true)
Window:SetDrawOnTop(true)
local InnerPanel = vgui.Create("DPanel", Window)
InnerPanel:SetPaintBackground(false) -- clear background
local Text = vgui.Create("DLabel", InnerPanel)
Text:SetText("Set the MOTD page. Click default to reset the MOTD to default.")
Text:SizeToContents()
Text:SetContentAlignment(5)
Text:SetTextColor(color_white)
local TextEntry = vgui.Create("DTextEntry", InnerPanel)
TextEntry:SetText(MOTDPage:GetString())
TextEntry.OnEnter = function() Window:Close() RunConsoleCommand("_FAdmin", "motdpage", TextEntry:GetValue()) end
function TextEntry:OnFocusChanged(changed)
self:RequestFocus()
self:SelectAllText(true)
end
local ButtonPanel = vgui.Create("DPanel", Window)
ButtonPanel:SetPaintBackground(false) -- clear background
ButtonPanel:SetTall(30)
local Button = vgui.Create("DButton", ButtonPanel)
Button:SetText("OK")
Button:SizeToContents()
Button:SetTall(20)
Button:SetWide(Button:GetWide() + 20)
Button:SetPos(5, 5)
Button.DoClick = function()
Window:Close()
RunConsoleCommand("_FAdmin", "motdpage", TextEntry:GetValue())
end
local ButtonDefault = vgui.Create("DButton", ButtonPanel)
ButtonDefault:SetText("Default")
ButtonDefault:SizeToContents()
ButtonDefault:SetTall(20)
ButtonDefault:SetWide(Button:GetWide() + 20)
ButtonDefault:SetPos(5, 5)
ButtonDefault.DoClick = function() Window:Close() RunConsoleCommand("_FAdmin", "motdpage", "default") end
ButtonDefault:MoveRightOf(Button, 5)
local ButtonCancel = vgui.Create("DButton", ButtonPanel)
ButtonCancel:SetText("Cancel")
ButtonCancel:SizeToContents()
ButtonCancel:SetTall(20)
ButtonCancel:SetWide(Button:GetWide() + 20)
ButtonCancel:SetPos(5, 5)
ButtonCancel.DoClick = function() Window:Close() end
ButtonCancel:MoveRightOf(ButtonDefault, 5)
ButtonPanel:SetWide(Button:GetWide() + 5 + ButtonCancel:GetWide() + 10 + ButtonDefault:GetWide() + 5)
local w, h = Text:GetSize()
w = math.max(w, 400)
Window:SetSize(w + 50, h + 25 + 75 + 10)
Window:Center()
InnerPanel:StretchToParent(5, 25, 5, 45)
Text:StretchToParent(5, 5, 5, 35)
TextEntry:StretchToParent(5, nil, 5, nil)
TextEntry:AlignBottom(5)
TextEntry:RequestFocus()
ButtonPanel:CenterHorizontal()
ButtonPanel:AlignBottom(8)
Window:MakePopup()
Window:DoModal()
end)
end
end

View File

@@ -0,0 +1,91 @@
FAdmin.MOTD = {}
local MOTDPage
sql.Query([[CREATE TABLE IF NOT EXISTS FADMIN_MOTD(
'map' TEXT NOT NULL PRIMARY KEY,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
pitch INTEGER NOT NULL,
yaw INTEGER NOT NULL,
roll INTEGER NOT NULL
);]])
local MOTD = sql.Query("SELECT * FROM FADMIN_MOTD WHERE LOWER(map) = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";")
hook.Add("InitPostEntity", "PlaceMOTD", function()
if not MOTD or (not MOTD[1] and not MOTD["1"]) then return end
MOTD = MOTD[1] or MOTD["1"]
local ent = ents.Create("fadmin_motd")
ent:SetPos(Vector(MOTD.x, MOTD.y, MOTD.z))
ent:SetAngles(Angle(MOTD.pitch % 360, MOTD.yaw % 360, MOTD.roll % 360))
ent:Spawn()
ent:Activate()
if file.Exists("FAdmin/CurMOTDPage.txt", "DATA") and file.Read("FAdmin/CurMOTDPage.txt", "DATA") ~= "" then
game.ConsoleCommand("_FAdmin_MOTDPage \"" .. file.Read("FAdmin/CurMOTDPage.txt", "DATA") .. "\"\n")
end
end)
function FAdmin.MOTD.SaveMOTD(ent, ply)
local pos = ent:GetPos()
local ang = ent:GetAngles()
local map, x, y, z, pitch, yaw, roll =
string.lower(game.GetMap()),
pos.x, pos.y, pos.z,
ang.p, ang.y, ang.r
if MOTD then
sql.Query([[UPDATE FADMIN_MOTD SET ]]
.. "x = " .. MySQLite.SQLStr(x) .. ", "
.. "y = " .. MySQLite.SQLStr(y) .. ", "
.. "z = " .. MySQLite.SQLStr(z) .. ", "
.. "pitch = " .. MySQLite.SQLStr(pitch) .. ", "
.. "yaw = " .. MySQLite.SQLStr(yaw) .. ", "
.. "roll = " .. MySQLite.SQLStr(roll)
.. " WHERE map = " .. MySQLite.SQLStr(map) .. ";")
else
sql.Query([[INSERT INTO FADMIN_MOTD VALUES(]]
.. MySQLite.SQLStr(map) .. ", "
.. MySQLite.SQLStr(x) .. ", "
.. MySQLite.SQLStr(y) .. ", "
.. MySQLite.SQLStr(z) .. ", "
.. MySQLite.SQLStr(pitch) .. ", "
.. MySQLite.SQLStr(yaw) .. ", "
.. MySQLite.SQLStr(roll)
.. ");")
end
FAdmin.Messages.SendMessage(ply, 4, "MOTD position saved!")
end
function FAdmin.MOTD.RemoveMOTD(ent, ply)
sql.Query("DELETE FROM FADMIN_MOTD WHERE map = " .. MySQLite.SQLStr(string.lower(game.GetMap())) .. ";")
FAdmin.Messages.SendMessage(ply, 4, "MOTD removed!")
end
function FAdmin.MOTD.SetMOTDPage(ply, cmd, args)
if not args[1] then
FAdmin.Messages.SendMessage(ply, 4, "MOTD is set to: " .. MOTDPage:GetString())
return false
end
if ply:EntIndex() ~= 0 and (not ply.IsSuperAdmin or not ply:IsSuperAdmin()) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
RunConsoleCommand("_FAdmin_MOTDPage", args[1])
file.Write("FAdmin/CurMOTDPage.txt", args[1])
return true, args[1]
end
local function CreateMOTD(ply)
if IsValid(ply) and not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local MOTDEnt = ents.Create("fadmin_motd")
MOTDEnt:SpawnFunction(ply, ply:GetEyeTrace())
return true, MOTDEnt
end
FAdmin.StartHooks["MOTD"] = function()
MOTDPage = GetConVar("_FAdmin_MOTDPage")
FAdmin.Commands.AddCommand("MOTDPage", FAdmin.MOTD.SetMOTDPage)
FAdmin.Commands.AddCommand("CreateMOTD", CreateMOTD)
end

View File

@@ -0,0 +1,21 @@
local AdminsCanPickUpPlayers = CreateConVar("AdminsCanPickUpPlayers", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
local PlayersCanPickUpPlayers = CreateConVar("PlayersCanPickUpPlayers", 0, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
FAdmin.StartHooks["PickUpPlayers"] = function()
FAdmin.Access.AddPrivilege("PickUpPlayers", 2)
FAdmin.ScoreBoard.Server:AddPlayerAction(function() return (AdminsCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Admin>Player pickup" end,
function() return "fadmin/icons/pickup", AdminsCanPickUpPlayers:GetBool() and "fadmin/icons/disable" end, Color(0, 155, 0, 255), function(ply) return ply:IsSuperAdmin() end, function(button)
button:SetImage2((not AdminsCanPickUpPlayers:GetBool() and "fadmin/icons/disable") or "null")
button:SetText((not AdminsCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Admin>Player pickup")
button:GetParent():InvalidateLayout()
RunConsoleCommand("_FAdmin", "AdminsCanPickUpPlayers", AdminsCanPickUpPlayers:GetBool() and "0" or "1")
end)
FAdmin.ScoreBoard.Server:AddPlayerAction(function() return (PlayersCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Player>Player pickup" end,
function() return "fadmin/icons/pickup", PlayersCanPickUpPlayers:GetBool() and "fadmin/icons/disable" end, Color(0, 155, 0, 255), function(ply) return ply:IsSuperAdmin() end, function(button)
button:SetImage2((not PlayersCanPickUpPlayers:GetBool() and "fadmin/icons/disable") or "null")
button:SetText((not PlayersCanPickUpPlayers:GetBool() and "Disable" or "Enable") .. " Player>Player pickup")
button:GetParent():InvalidateLayout()
RunConsoleCommand("_FAdmin", "PlayersCanPickUpPlayers", PlayersCanPickUpPlayers:GetBool() and "0" or "1")
end)
end

View File

@@ -0,0 +1,58 @@
local AdminsCanPickUpPlayers = CreateConVar("AdminsCanPickUpPlayers", 1, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
local PlayersCanPickUpPlayers = CreateConVar("PlayersCanPickUpPlayers", 0, {FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE})
hook.Add("PhysgunPickup", "FAdmin_PickUpPlayers", function(ply, ent)
if not IsValid(ent) or not ent:IsPlayer() then return end
if PlayersCanPickUpPlayers:GetBool() or AdminsCanPickUpPlayers:GetBool() and
FAdmin.Access.PlayerHasPrivilege(ply, "PickUpPlayers", ent) and tobool(ply:GetInfo("cl_pickupplayers")) then
ent:SetMoveType(MOVETYPE_NONE)
ent:Freeze(true)
return true
end
end)
hook.Add("PhysgunDrop", "FAdmin_PickUpPlayers", function(ply, ent)
if IsValid(ent) and ent:IsPlayer() then
ent:SetMoveType(MOVETYPE_WALK)
ent:Freeze(false)
end
end)
local function ChangeAdmin(ply, cmd, args)
if not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not args[1] then return false end
local Value = tonumber(args[1])
if Value ~= 1 and Value ~= 0 then return false end
RunConsoleCommand("AdminsCanPickUpPlayers", Value)
FAdmin.SaveSetting("AdminsCanPickUpPlayers", Value)
local OnOff = (tobool(Value) and "on") or "off"
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned Admin>Player pickup " .. OnOff, "Admin>Player pickup has been turned " .. OnOff, "Turned Admin>Player pickup " .. OnOff)
return true, OnOff
end
local function ChangeUser(ply, cmd, args)
if not ply:IsSuperAdmin() then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if not args[1] then return false end
local Value = tonumber(args[1])
if Value ~= 1 and Value ~= 0 then return false end
RunConsoleCommand("PlayersCanPickUpPlayers", Value)
FAdmin.SaveSetting("PlayersCanPickUpPlayers", Value)
local OnOff = (tobool(Value) and "on") or "off"
FAdmin.Messages.ActionMessage(ply, player.GetAll(), ply:Nick() .. " turned Player>Player pickup " .. OnOff, "Player>Player pickup has been turned " .. OnOff, "Turned Player>Player pickup " .. OnOff)
return true, OnOff
end
FAdmin.StartHooks["PickUpPlayers"] = function()
FAdmin.Access.AddPrivilege("PickUpPlayers", 2)
FAdmin.Commands.AddCommand("AdminsCanPickUpPlayers", ChangeAdmin)
FAdmin.Commands.AddCommand("PlayersCanPickUpPlayers", ChangeUser)
end

View File

@@ -0,0 +1,37 @@
FAdmin.StartHooks["zzSetTeam"] = function()
FAdmin.Messages.RegisterNotification{
name = "setteam",
hasTarget = true,
message = {"instigator", " set the team of ", "targets", " to ", "extraInfo.1"},
readExtraInfo = function()
return {team.GetName(net.ReadUInt(16))}
end,
extraInfoColors = {Color(255, 102, 0)}
}
FAdmin.Access.AddPrivilege("SetTeam", 2)
FAdmin.Commands.AddCommand("SetTeam", nil, "<Player>", "<Team>")
FAdmin.ScoreBoard.Player:AddActionButton("Set team", "fadmin/icons/changeteam", Color(0, 200, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "SetTeam", ply) end, function(ply, button)
local menu = DermaMenu()
local Padding = vgui.Create("DPanel")
Padding:SetPaintBackgroundEnabled(false)
Padding:SetSize(1,5)
menu:AddPanel(Padding)
local Title = vgui.Create("DLabel")
Title:SetText(" Teams:\n")
Title:SetFont("UiBold")
Title:SizeToContents()
Title:SetTextColor(color_black)
menu:AddPanel(Title)
for k, v in SortedPairsByMemberValue(team.GetAllTeams(), "Name") do
local uid = ply:UserID()
menu:AddOption(v.Name, function() RunConsoleCommand("_FAdmin", "setteam", uid, k) end)
end
menu:Open()
end)
end

View File

@@ -0,0 +1,67 @@
local function checkDarkRP(ply, target, t)
if not DarkRP then return true end
local TEAM = RPExtraTeams[t]
if not TEAM then return true end
if TEAM.customCheck then
local ret = TEAM.customCheck(target)
if ret ~= nil and not (ply:IsAdmin() and GAMEMODE.Config.adminBypassJobRestrictions) then return ret end
end
local hookValue = hook.Call("playerCanChangeTeam", nil, target, t, true)
if hookValue == false then return false end
local a = TEAM.admin
if a > 0 and not target:IsAdmin()
or a > 1 and not target:IsSuperAdmin()
then return false end
return true
end
local function SetTeam(ply, cmd, args)
local targets = FAdmin.FindPlayer(args[1])
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
local targetsSet = {}
for k, v in pairs(team.GetAllTeams()) do
if k == tonumber(args[2]) or string.lower(v.Name) == string.lower(args[2] or "") then
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "SetTeam", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
local setTeam = target.changeTeam or target.SetTeam -- DarkRP compatibility
if IsValid(target) and checkDarkRP(ply, target, k) then
setTeam(target, k, true)
table.insert(targetsSet, target)
end
end
if not table.IsEmpty(targetsSet) then
FAdmin.Messages.FireNotification("setteam", ply, targetsSet, {k})
else
FAdmin.Messages.SendMessage(ply, 1, "Could not set team")
end
break
end
end
return true, targets
end
FAdmin.StartHooks["zzSetTeam"] = function()
FAdmin.Messages.RegisterNotification{
name = "setteam",
hasTarget = true,
receivers = "everyone",
writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end,
message = {"instigator", " set the team of ", "targets", " to ", "extraInfo.1"},
}
FAdmin.Commands.AddCommand("SetTeam", SetTeam)
FAdmin.Access.AddPrivilege("SetTeam", 2)
end

View File

@@ -0,0 +1,47 @@
FAdmin.StartHooks["Chatmute"] = function()
FAdmin.Messages.RegisterNotification{
name = "chatmute",
hasTarget = true,
message = {"instigator", " chat muted ", "targets", " ", "extraInfo.1"},
readExtraInfo = function()
local time = net.ReadUInt(16)
return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))}
end
}
FAdmin.Messages.RegisterNotification{
name = "chatunmute",
hasTarget = true,
message = {"instigator", " chat unmuted ", "targets"},
}
FAdmin.Access.AddPrivilege("Chatmute", 2)
FAdmin.Commands.AddCommand("Chatmute", nil, "<Player>")
FAdmin.Commands.AddCommand("UnChatmute", nil, "<Player>")
FAdmin.ScoreBoard.Player:AddActionButton(function(ply)
if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "Unmute chat" end
return "Mute chat"
end, function(ply)
if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "fadmin/icons/chatmute" end
return "fadmin/icons/chatmute", "fadmin/icons/disable"
end, Color(255, 130, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Chatmute", ply) end, function(ply, button)
if not ply:FAdmin_GetGlobal("FAdmin_chatmuted") then
FAdmin.PlayerActions.addTimeMenu(function(secs)
RunConsoleCommand("_FAdmin", "chatmute", ply:UserID(), secs)
button:SetImage2("null")
button:SetText("Unmute chat")
button:GetParent():InvalidateLayout()
end)
else
RunConsoleCommand("_FAdmin", "UnChatmute", ply:UserID())
end
button:SetImage2("fadmin/icons/disable")
button:SetText("Mute chat")
button:GetParent():InvalidateLayout()
end)
end

View File

@@ -0,0 +1,75 @@
local function MuteChat(ply, cmd, args)
if not args[1] then return false end
local targets = FAdmin.FindPlayer(args[1]) or {}
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
local time = tonumber(args[2] or 0)
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Chatmute", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_chatmuted") then
target:FAdmin_SetGlobal("FAdmin_chatmuted", true)
if time == 0 then continue end
timer.Simple(time, function()
if not IsValid(target) or not target:FAdmin_GetGlobal("FAdmin_chatmuted") then return false end
target:FAdmin_SetGlobal("FAdmin_chatmuted", false)
end)
end
end
FAdmin.Messages.FireNotification("chatmute", ply, targets, {time})
return true, targets, time
end
local function UnMuteChat(ply, cmd, args)
if not args[1] then return false end
local targets = FAdmin.FindPlayer(args[1])
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Chatmute", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_chatmuted") then
target:FAdmin_SetGlobal("FAdmin_chatmuted", false)
end
end
FAdmin.Messages.FireNotification("chatunmute", ply, targets)
return true, targets
end
FAdmin.StartHooks["Chatmute"] = function()
FAdmin.Messages.RegisterNotification{
name = "chatmute",
hasTarget = true,
receivers = "involved+admins",
writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end,
message = {"instigator", " chat muted ", "targets", " ", "extraInfo.1"},
}
FAdmin.Messages.RegisterNotification{
name = "chatunmute",
hasTarget = true,
receivers = "involved+admins",
message = {"instigator", " chat unmuted ", "targets"},
}
FAdmin.Commands.AddCommand("Chatmute", MuteChat)
FAdmin.Commands.AddCommand("UnChatmute", UnMuteChat)
FAdmin.Access.AddPrivilege("Chatmute", 2)
end
hook.Add("PlayerSay", "FAdmin_Chatmute", function(ply, text, Team, dead)
if ply:FAdmin_GetGlobal("FAdmin_chatmuted") then return "" end
end)

View File

@@ -0,0 +1,26 @@
FAdmin.StartHooks["zz_Cloak"] = function()
FAdmin.Access.AddPrivilege("Cloak", 2)
FAdmin.Commands.AddCommand("Cloak", nil, "<Player>")
FAdmin.Commands.AddCommand("Uncloak", nil, "<Player>")
FAdmin.ScoreBoard.Player:AddActionButton(function(ply)
if ply:FAdmin_GetGlobal("FAdmin_cloaked") then return "Uncloak" end
return "Cloak"
end, function(ply)
if ply:FAdmin_GetGlobal("FAdmin_cloaked") then return "fadmin/icons/cloak", "fadmin/icons/disable" end
return "fadmin/icons/cloak"
end, Color(0, 200, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Cloak", ply) end, function(ply, button)
if not ply:FAdmin_GetGlobal("FAdmin_cloaked") then
RunConsoleCommand("_FAdmin", "Cloak", ply:UserID())
else
RunConsoleCommand("_FAdmin", "Uncloak", ply:UserID())
end
if not ply:FAdmin_GetGlobal("FAdmin_cloaked") then button:SetImage2("fadmin/icons/disable") button:SetText("Uncloak") button:GetParent():InvalidateLayout() return end
button:SetImage2("null")
button:SetText("Cloak")
button:GetParent():InvalidateLayout()
end)
end

View File

@@ -0,0 +1,89 @@
local CloakThink
local function Cloak(ply, cmd, args)
local targets = FAdmin.FindPlayer(args[1]) or {ply}
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Cloak", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_cloaked") then
target:FAdmin_SetGlobal("FAdmin_cloaked", true)
target:SetNoDraw(true)
for _, v in ipairs(target:GetWeapons()) do
v:SetNoDraw(true)
end
for _, v in ipairs(ents.FindByClass("physgun_beam")) do
if v:GetParent() == target then
v:SetNoDraw(true)
end
end
hook.Add("Think", "FAdmin_Cloak", CloakThink)
end
end
FAdmin.Messages.ActionMessage(ply, targets, "You have cloaked %s", "You were cloaked by %s", "Cloaked %s")
return true, targets
end
local function UnCloak(ply, cmd, args)
local targets = FAdmin.FindPlayer(args[1]) or {ply}
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Cloak", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_cloaked") then
target:FAdmin_SetGlobal("FAdmin_cloaked", false)
target:SetNoDraw(false)
for _, v in ipairs(target:GetWeapons()) do
v:SetNoDraw(false)
end
for _, v in ipairs(ents.FindByClass("physgun_beam")) do
if v:GetParent() == target then
v:SetNoDraw(false)
end
end
target.FAdmin_CloakWeapon = nil
local RemoveThink = true
for _, v in ipairs(player.GetAll()) do
if v:FAdmin_GetGlobal("FAdmin_cloaked") then
RemoveThink = false
break
end
end
if RemoveThink then hook.Remove("Think", "FAdmin_Cloak") end
end
end
FAdmin.Messages.ActionMessage(ply, targets, "You have uncloaked %s", "You were uncloaked by %s", "Uncloaked %s")
return true, targets
end
FAdmin.StartHooks["Cloak"] = function()
FAdmin.Commands.AddCommand("Cloak", Cloak)
FAdmin.Commands.AddCommand("Uncloak", UnCloak)
FAdmin.Access.AddPrivilege("Cloak", 2)
end
function CloakThink()
for _, v in ipairs(player.GetAll()) do
local ActiveWeapon = v:GetActiveWeapon()
if v:FAdmin_GetGlobal("FAdmin_cloaked") and ActiveWeapon:IsValid() and ActiveWeapon ~= v.FAdmin_CloakWeapon then
v.FAdmin_CloakWeapon = ActiveWeapon
ActiveWeapon:SetNoDraw(true)
if ActiveWeapon:GetClass() == "weapon_physgun" then
for a,b in ipairs(ents.FindByClass("physgun_beam")) do
if b:GetParent() == v then
b:SetNoDraw(true)
end
end
end
end
end
end

View File

@@ -0,0 +1,48 @@
FAdmin.StartHooks["Freeze"] = function()
FAdmin.Messages.RegisterNotification{
name = "freeze",
hasTarget = true,
message = {"instigator", " froze ", "targets", " ", "extraInfo.1"},
readExtraInfo = function()
local time = net.ReadUInt(16)
return {time == 0 and FAdmin.PlayerActions.commonTimes[time] or string.format("for %s", FAdmin.PlayerActions.commonTimes[time] or (time .. " seconds"))}
end
}
FAdmin.Messages.RegisterNotification{
name = "unfreeze",
hasTarget = true,
message = {"instigator", " unfroze ", "targets"},
}
FAdmin.Access.AddPrivilege("Freeze", 2)
FAdmin.Commands.AddCommand("freeze", nil, "<Player>")
FAdmin.Commands.AddCommand("unfreeze", nil, "<Player>")
FAdmin.ScoreBoard.Player:AddActionButton(function(ply)
if ply:FAdmin_GetGlobal("FAdmin_frozen") then return "Unfreeze" end
return "Freeze"
end, function(ply)
if ply:FAdmin_GetGlobal("FAdmin_frozen") then return "fadmin/icons/freeze", "fadmin/icons/disable" end
return "fadmin/icons/freeze"
end, Color(255, 130, 0, 255),
function(ply) return FAdmin.Access.PlayerHasPrivilege(LocalPlayer(), "Freeze", ply) end, function(ply, button)
if not ply:FAdmin_GetGlobal("FAdmin_frozen") then
FAdmin.PlayerActions.addTimeMenu(function(secs)
RunConsoleCommand("_FAdmin", "freeze", ply:UserID(), secs)
button:SetImage2("fadmin/icons/disable")
button:SetText("Unfreeze")
button:GetParent():InvalidateLayout()
end)
else
RunConsoleCommand("_FAdmin", "unfreeze", ply:UserID())
end
button:SetImage2("null")
button:SetText("Freeze")
button:GetParent():InvalidateLayout()
end)
end

View File

@@ -0,0 +1,79 @@
local function Freeze(ply, cmd, args)
if not args[1] then return false end
local targets = FAdmin.FindPlayer(args[1])
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
local time = tonumber(args[2]) or 0
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Freeze", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and not target:FAdmin_GetGlobal("FAdmin_frozen") then
target:FAdmin_SetGlobal("FAdmin_frozen", true)
target:Lock()
if time == 0 then continue end
timer.Simple(time, function()
if not IsValid(target) or not target:FAdmin_GetGlobal("FAdmin_frozen") then return end
target:FAdmin_SetGlobal("FAdmin_frozen", false)
target:UnLock()
end)
end
end
FAdmin.Messages.FireNotification("freeze", ply, targets, {time})
return true, targets, time
end
local function Unfreeze(ply, cmd, args)
if not args[1] then return false end
local targets = FAdmin.FindPlayer(args[1])
if not targets or #targets == 1 and not IsValid(targets[1]) then
FAdmin.Messages.SendMessage(ply, 1, "Player not found")
return false
end
for _, target in ipairs(targets) do
if not FAdmin.Access.PlayerHasPrivilege(ply, "Freeze", target) then FAdmin.Messages.SendMessage(ply, 5, "No access!") return false end
if IsValid(target) and target:FAdmin_GetGlobal("FAdmin_frozen") then
target:FAdmin_SetGlobal("FAdmin_frozen", false)
target:UnLock()
end
end
FAdmin.Messages.FireNotification("unfreeze", ply, targets)
return true, targets
end
FAdmin.StartHooks["Freeze"] = function()
FAdmin.Messages.RegisterNotification{
name = "freeze",
hasTarget = true,
receivers = "involved+admins",
writeExtraInfo = function(info) net.WriteUInt(info[1], 16) end,
message = {"instigator", " froze ", "targets", " ", "extraInfo.1"},
}
FAdmin.Messages.RegisterNotification{
name = "unfreeze",
hasTarget = true,
receivers = "involved+admins",
message = {"instigator", " unfroze ", "targets"},
}
FAdmin.Commands.AddCommand("freeze", Freeze)
FAdmin.Commands.AddCommand("unfreeze", Unfreeze)
FAdmin.Access.AddPrivilege("Freeze", 2)
end
local disallow = function(ply) if ply:FAdmin_GetGlobal("FAdmin_frozen") then return false end end
hook.Add("PlayerSpawnObject", "FAdmin_jail", disallow)
hook.Add("CanPlayerSuicide", "FAdmin_jail", disallow)

View File

@@ -0,0 +1,164 @@
-- Controls for the give weapons menu. These are litterally copied and edited from the garry's mod code.
-- Remaking them in case the gamemode is not derived from sandbox
-- Copying from garry's mod code because I'm lazy and because it looks good.
-- Weapon icon:
local PANEL = {}
function PANEL:Init()
self:SetSize(83, 83)
self.Label = vgui.Create("DLabel", self)
self:SetKeepAspect(true)
self:SetDrawBorder(true)
self.m_Image:SetPaintedManually(true)
end
function PANEL:PerformLayout()
self.Label:SizeToContents()
self.Label:SetFont("TabLarge")
self.Label:SetTextColor(color_white)
self.Label:SetContentAlignment(5)
self.Label:SetWide(self:GetWide())
self.Label:AlignBottom(2)
DImageButton.PerformLayout(self)
if self.imgAdmin then
self.imgAdmin:SizeToContents()
self.imgAdmin:AlignTop(4)
self.imgAdmin:AlignRight(4)
end
end
function PANEL:CreateAdminIcon()
self.imgAdmin = vgui.Create("DImage", self)
self.imgAdmin:SetImage("icon16/shield.png") -- SilkIcons are now merged into GMOD as materials/icon16
self.imgAdmin:SetTooltip("#Admin Only")
end
function PANEL:Paint()
local w, h = self:GetSize()
self.m_Image:Paint()
surface.SetDrawColor(30, 30, 30, 200)
surface.DrawRect(0, h - 16, w, 16)
end
function PANEL:Setup(NiceName, SpawnName, IconMaterial, AdminOnly, Parent, IsAmmo)
self.Label:SetText(DarkRP.deLocalise(NiceName))
self.DoClick = function() Parent:DoGiveWeapon(SpawnName, IsAmmo) end
self.DoRightClick = function() end
if not IconMaterial then
IconMaterial = "VGUI/entities/" .. SpawnName
end
self:SetOnViewMaterial(IconMaterial, "vgui/swepicon")
if AdminOnly then self:CreateAdminIcon() end
self:InvalidateLayout()
end
local WeaponIcon = vgui.RegisterTable(PANEL, "DImageButton")
-- Full panel:
local PANEL2 = {}
function PANEL2:Init()
self.PanelList = vgui.Create("DPanelList", self)
self.PanelList:SetPadding(4)
self.PanelList:SetSpacing(2)
self.PanelList:EnableVerticalScrollbar(true)
end
function PANEL2:BuildList()
self.PanelList:Clear()
if not self.HideAmmo then
local AmmoCat = vgui.Create("DCollapsibleCategory", self)
self.PanelList:AddItem(AmmoCat)
AmmoCat:SetLabel("Give ammo")
local AmmoPan = vgui.Create("DPanelList")
AmmoCat:SetContents(AmmoPan)
AmmoPan:EnableHorizontal(true)
AmmoPan:SetPaintBackground(false)
AmmoPan:SetSpacing(2)
AmmoPan:SetPadding(2)
AmmoPan:SetAutoSize(true)
for k in SortedPairs(FAdmin.AmmoTypes) do
local Icon = vgui.CreateFromTable(WeaponIcon, self)
Icon:Setup(k, k, "spawnicons/models/items/boxmrounds60x60.png", false, self, true) -- Gets created clientside by GMOD when someone is after that model, or trying to buy ammo.
AmmoPan:AddItem(Icon)
end
end
local Weapons = weapons.GetList()
local Categorised = {}
Categorised["Half-life 2"] = {}
for k, weapon in pairs(FAdmin.HL2Guns) do
table.insert(Categorised["Half-life 2"], {PrintName = k, ClassName = weapon, Spawnable = true,
Author = "Half-life 2",
Contact = "gaben@valvesoftware.com",
Instructions = "Shoot!"})
end
for k, weapon in pairs(Weapons) do
weapon = weapons.Get(weapon.ClassName)
Weapons[k] = weapon
weapon.Category = weapon.Category or "Other"
if not weapon.Spawnable and not weapon.AdminSpawnable then
Weapons[k] = nil
else
Categorised[weapon.Category] = Categorised[weapon.Category] or {}
table.insert(Categorised[weapon.Category], weapon)
Weapons[k] = nil
end
end
Weapons = nil
for CategoryName, v in SortedPairs(Categorised) do
local Category = vgui.Create("DCollapsibleCategory", self)
self.PanelList:AddItem(Category)
Category:SetLabel(CategoryName)
Category:SetCookieName("WeaponSpawn." .. CategoryName)
local Content = vgui.Create("DPanelList")
Category:SetContents(Content)
Content:EnableHorizontal(true)
Content:SetPaintBackground(false)
Content:SetSpacing(2)
Content:SetPadding(2)
Content:SetAutoSize(true)
for _, WeaponTable in SortedPairsByMemberValue(v, "PrintName") do
local Icon = vgui.CreateFromTable(WeaponIcon, self)
Icon:Setup(WeaponTable.PrintName or WeaponTable.ClassName, WeaponTable.ClassName, WeaponTable.SpawnMenuIcon, WeaponTable.AdminSpawnable and not WeaponTable.Spawnable, self)
local Tooltip = Format("Name: %s", WeaponTable.PrintName)
if WeaponTable.Author ~= "" then Tooltip = Format("%s\nAuthor: %s", Tooltip, WeaponTable.Author) end
if WeaponTable.Contact ~= "" then Tooltip = Format("%s\nContact: %s", Tooltip, WeaponTable.Contact) end
if WeaponTable.Instructions ~= "" then Tooltip = Format("%s\n\n%s", Tooltip, WeaponTable.Instructions) end
Icon:SetTooltip(Tooltip)
Content:AddItem(Icon)
end
end
self.PanelList:InvalidateLayout()
end
function PANEL2:PerformLayout()
self.PanelList:StretchToParent(0, 0, 0, 0)
end
derma.DefineControl("FAdmin_weaponPanel", "Weapon panel for giving weapons in FAdmin", PANEL2, "Panel")

Some files were not shown because too many files have changed in this diff Show More