1147 lines
35 KiB
Lua
1147 lines
35 KiB
Lua
local entMeta = FindMetaTable("Entity")
|
|
|
|
-- Maintains entities that are to be removed after disconnect
|
|
local queuedForRemoval = {}
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
DarkRP hooks
|
|
---------------------------------------------------------------------------]]
|
|
function GM:Initialize()
|
|
self.Sandbox.Initialize(self)
|
|
end
|
|
|
|
function GM:playerBuyDoor(ply, ent)
|
|
if ply:getJobTable().hobo then
|
|
return false, DarkRP.getPhrase("door_hobo_unable")
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function GM:getDoorCost(ply, ent)
|
|
return GAMEMODE.Config.doorcost ~= 0 and GAMEMODE.Config.doorcost or 30
|
|
end
|
|
|
|
function GM:getVehicleCost(ply, ent)
|
|
return GAMEMODE.Config.vehiclecost ~= 0 and GAMEMODE.Config.vehiclecost or 40
|
|
end
|
|
|
|
local disallowedNames = {["ooc"] = true, ["shared"] = true, ["world"] = true, ["world prop"] = true}
|
|
function GM:CanChangeRPName(ply, RPname)
|
|
if disallowedNames[string.lower(RPname)] then return false, DarkRP.getPhrase("forbidden_name") end
|
|
if not string.match(RPname, "^[a-zA-ZЀ-џ0-9 ]+$") then return false, DarkRP.getPhrase("illegal_characters") end
|
|
|
|
local len = string.len(RPname)
|
|
if len > 30 then return false, DarkRP.getPhrase("too_long") end
|
|
if len < 3 then return false, DarkRP.getPhrase("too_short") end
|
|
end
|
|
|
|
function GM:canDemote(ply, target, reason)
|
|
|
|
end
|
|
|
|
function GM:canVote(ply, vote)
|
|
|
|
end
|
|
|
|
function GM:playerWalletChanged(ply, amount)
|
|
|
|
end
|
|
|
|
function GM:playerGetSalary(ply, amount)
|
|
|
|
end
|
|
|
|
function GM:DarkRPVarChanged(ply, var, oldvar, newvalue)
|
|
|
|
end
|
|
|
|
function GM:playerBoughtVehicle(ply, ent, cost)
|
|
|
|
end
|
|
|
|
function GM:playerBoughtDoor(ply, ent, cost)
|
|
|
|
end
|
|
|
|
function GM:canDropWeapon(ply, weapon)
|
|
if not IsValid(weapon) then return false end
|
|
local class = string.lower(weapon:GetClass())
|
|
|
|
if not GAMEMODE.Config.dropspawnedweapons then
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.weapons and table.HasValue(jobTable.weapons, class) then return false end
|
|
end
|
|
|
|
if self.Config.DisallowDrop[class] then return false end
|
|
|
|
if not GAMEMODE.Config.restrictdrop then return true end
|
|
|
|
for _, v in pairs(CustomShipments) do
|
|
if v.entity ~= class then continue end
|
|
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function GM:DatabaseInitialized()
|
|
DarkRP.initDatabase()
|
|
end
|
|
|
|
function GM:canSeeLogMessage(ply, message, colour)
|
|
return true
|
|
end
|
|
|
|
function GM:canEarnNPCKillPay(ply, npc)
|
|
return GAMEMODE.Config.npckillpay > 0
|
|
end
|
|
|
|
function GM:calculateNPCKillPay(ply, npc)
|
|
-- A NPC spawned by an addon might be worth more money than the default
|
|
if npc.KillValue then
|
|
return npc.KillValue
|
|
end
|
|
return GAMEMODE.Config.npckillpay
|
|
end
|
|
|
|
--[[---------------------------------------------------------
|
|
Gamemode functions
|
|
---------------------------------------------------------]]
|
|
|
|
function GM:PlayerSpawnProp(ply, model)
|
|
-- No prop spawning means no prop spawning.
|
|
local allowed = GAMEMODE.Config.propspawning
|
|
|
|
if not allowed then return false end
|
|
if ply:isArrested() then return false end
|
|
|
|
model = string.gsub(tostring(model), "\\", "/")
|
|
model = string.gsub(tostring(model), "//", "/")
|
|
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.PlayerSpawnProp then
|
|
jobTable.PlayerSpawnProp(ply, model)
|
|
end
|
|
|
|
return self.Sandbox.PlayerSpawnProp(self, ply, model)
|
|
end
|
|
|
|
function GM:PlayerSpawnedProp(ply, model, ent)
|
|
self.Sandbox.PlayerSpawnedProp(self, ply, model, ent)
|
|
ent.SID = ply.SID
|
|
ent:CPPISetOwner(ply)
|
|
|
|
local phys = ent:GetPhysicsObject()
|
|
if IsValid(phys) then
|
|
ent.RPOriginalMass = phys:GetMass()
|
|
end
|
|
|
|
if GAMEMODE.Config.proppaying then
|
|
if ply:canAfford(GAMEMODE.Config.propcost) then
|
|
DarkRP.notify(ply, 0, 4, DarkRP.getPhrase("deducted_money", DarkRP.formatMoney(GAMEMODE.Config.propcost)))
|
|
ply:addMoney(-GAMEMODE.Config.propcost)
|
|
else
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("need_money", DarkRP.formatMoney(GAMEMODE.Config.propcost)))
|
|
SafeRemoveEntity(ent)
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
local function checkAdminSpawn(ply, configVar, errorStr)
|
|
local config = GAMEMODE.Config[configVar]
|
|
|
|
if (config == true or config == 1) and ply:EntIndex() ~= 0 and not ply:IsAdmin() then
|
|
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("need_admin", DarkRP.getPhrase(errorStr) or errorStr))
|
|
return false
|
|
elseif config == 2 and ply:EntIndex() ~= 0 and not ply:IsSuperAdmin() then
|
|
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("need_sadmin", DarkRP.getPhrase(errorStr) or errorStr))
|
|
return false
|
|
elseif config == 3 and ply:EntIndex() ~= 0 then
|
|
DarkRP.notify(ply, 1, 5, DarkRP.getPhrase("disabled", DarkRP.getPhrase(errorStr) or errorStr, DarkRP.getPhrase("see_settings")))
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function GM:PlayerSpawnSENT(ply, class)
|
|
return checkAdminSpawn(ply, "adminsents", "gm_spawnsent") and self.Sandbox.PlayerSpawnSENT(self, ply, class) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnedSENT(ply, ent)
|
|
self.Sandbox.PlayerSpawnedSENT(self, ply, ent)
|
|
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned SENT " .. ent:GetClass(), Color(255, 255, 0))
|
|
end
|
|
|
|
local function canSpawnWeapon(ply)
|
|
if (GAMEMODE.Config.adminweapons == 0 and ply:IsAdmin()) or
|
|
(GAMEMODE.Config.adminweapons == 1 and ply:IsSuperAdmin()) or
|
|
-- Can't use 2 to maintain compatibility
|
|
(GAMEMODE.Config.adminweapons == 3) then
|
|
return true
|
|
end
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("cant_spawn_weapons"))
|
|
|
|
return false
|
|
end
|
|
|
|
function GM:PlayerSpawnSWEP(ply, class, info)
|
|
return canSpawnWeapon(ply) and self.Sandbox.PlayerSpawnSWEP(self, ply, class, info) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerGiveSWEP(ply, class, info)
|
|
return canSpawnWeapon(ply) and self.Sandbox.PlayerGiveSWEP(self, ply, class, info) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnEffect(ply, model)
|
|
return self.Sandbox.PlayerSpawnEffect(self, ply, model) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnVehicle(ply, model, class, info)
|
|
return checkAdminSpawn(ply, "adminvehicles", "gm_spawnvehicle") and self.Sandbox.PlayerSpawnVehicle(self, ply, model, class, info) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnedVehicle(ply, ent)
|
|
self.Sandbox.PlayerSpawnedVehicle(self, ply, ent)
|
|
local vehicleClass = ent.GetVehicleClass and " (" .. ent:GetVehicleClass() .. ")" or ""
|
|
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned Vehicle " .. ent:GetClass() .. vehicleClass, Color(255, 255, 0))
|
|
end
|
|
|
|
function GM:PlayerSpawnNPC(ply, type, weapon)
|
|
return checkAdminSpawn(ply, "adminnpcs", "gm_spawnnpc") and self.Sandbox.PlayerSpawnNPC(self, ply, type, weapon) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnedNPC(ply, ent)
|
|
self.Sandbox.PlayerSpawnedNPC(self, ply, ent)
|
|
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned NPC " .. ent:GetClass(), Color(255, 255, 0))
|
|
end
|
|
|
|
function GM:PlayerSpawnRagdoll(ply, model)
|
|
return self.Sandbox.PlayerSpawnRagdoll(self, ply, model) and not ply:isArrested()
|
|
end
|
|
|
|
function GM:PlayerSpawnedRagdoll(ply, model, ent)
|
|
self.Sandbox.PlayerSpawnedRagdoll(self, ply, model, ent)
|
|
ent.SID = ply.SID
|
|
end
|
|
|
|
function GM:EntityRemoved(ent)
|
|
self.Sandbox.EntityRemoved(self, ent)
|
|
if ent:IsVehicle() then
|
|
local found = ent:CPPIGetOwner()
|
|
if IsValid(found) then
|
|
found.Vehicles = found.Vehicles or 1
|
|
found.Vehicles = found.Vehicles - 1
|
|
end
|
|
end
|
|
|
|
local owner = ent.Getowning_ent and ent:Getowning_ent() or Player(ent.SID or 0)
|
|
if ent.DarkRPItem and IsValid(owner) and not ent.IsPocketing then owner:removeCustomEntity(ent.DarkRPItem) end
|
|
if ent.isKeysOwnable and ent:isKeysOwnable() then ent:removeDoorData() end
|
|
|
|
-- Quick workaround for the fact that we don't have a hook ordering system
|
|
-- built into gmod.
|
|
if self.DarkRPPostEntityRemoved then
|
|
self.DarkRPPostEntityRemoved(self, ent)
|
|
end
|
|
end
|
|
|
|
function GM:ShowSpare1(ply)
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.ShowSpare1 then
|
|
return jobTable.ShowSpare1(ply)
|
|
end
|
|
end
|
|
|
|
function GM:ShowSpare2(ply)
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.ShowSpare2 then
|
|
return jobTable.ShowSpare2(ply)
|
|
end
|
|
end
|
|
|
|
function GM:ShowTeam(ply)
|
|
end
|
|
|
|
function GM:ShowHelp(ply)
|
|
end
|
|
|
|
function GM:OnNPCKilled(victim, ent, weapon)
|
|
-- If something killed the npc
|
|
if not ent then return end
|
|
|
|
if ent:IsVehicle() and ent:GetDriver():IsPlayer() then ent = ent:GetDriver() end
|
|
|
|
-- If it wasn't a player directly, find out who owns the prop that did the killing
|
|
if not ent:IsPlayer() then
|
|
ent = Player(tonumber(ent.SID) or 0)
|
|
end
|
|
|
|
-- If we know by now who killed the NPC, pay them.
|
|
if IsValid(ent) and hook.Call("canEarnNPCKillPay", GAMEMODE, ent, victim) then
|
|
local amount = hook.Call("calculateNPCKillPay", GAMEMODE, ent, victim)
|
|
ent:addMoney(amount)
|
|
DarkRP.notify(ent, 0, 4, DarkRP.getPhrase("npc_killpay", DarkRP.formatMoney(amount)))
|
|
end
|
|
end
|
|
|
|
function GM:KeyPress(ply, code)
|
|
self.Sandbox.KeyPress(self, ply, code)
|
|
end
|
|
|
|
-- IsInRoom function to see if the player is in the same room.
|
|
local roomTrResult = {}
|
|
local roomTr = {output = roomTrResult}
|
|
local function IsInRoom(listenerShootPos, talkerShootPos, talker)
|
|
roomTr.start = talkerShootPos
|
|
roomTr.endpos = listenerShootPos
|
|
-- Listener needs not be ignored as that's the end of the trace
|
|
roomTr.filter = talker
|
|
roomTr.collisiongroup = COLLISION_GROUP_WORLD
|
|
roomTr.mask = MASK_SOLID_BRUSHONLY
|
|
util.TraceLine(roomTr)
|
|
|
|
return not roomTrResult.HitWorld
|
|
end
|
|
|
|
local threed = GM.Config.voice3D
|
|
local vrad = GM.Config.voiceradius
|
|
local dynv = GM.Config.dynamicvoice
|
|
local deadv = GM.Config.deadvoice
|
|
local voiceDistance = GM.Config.voiceDistance * GM.Config.voiceDistance
|
|
local DrpCanHear = {}
|
|
|
|
-- Recreate DrpCanHear after Lua Refresh
|
|
-- This prevents an indexing nil error in PlayerCanHearPlayersVoice
|
|
for _, ply in ipairs(player.GetAll()) do
|
|
DrpCanHear[ply] = {}
|
|
end
|
|
|
|
local gridSize = GM.Config.voiceDistance -- Grid cell size is equal to the size of the radius of player talking
|
|
local floor = math.floor -- Caching floor as we will need to use it a lot
|
|
|
|
-- Grid based position check
|
|
local grid
|
|
-- Translate player to grid coordinates. The first table maps players to x
|
|
-- coordinates, the second table maps players to y coordinates.
|
|
local plyToGrid = {
|
|
{},
|
|
{}
|
|
}
|
|
|
|
-- Set DarkRP.voiceCheckTimeDelay before DarkRP is loaded to set the time
|
|
-- between player voice radius checks.
|
|
DarkRP.voiceCheckTimeDelay = DarkRP.voiceCheckTimeDelay or 0.3
|
|
timer.Create("DarkRPCanHearPlayersVoice", DarkRP.voiceCheckTimeDelay, 0, function()
|
|
-- Voiceradius is off, everyone can hear everyone
|
|
if not vrad then
|
|
return
|
|
end
|
|
|
|
local players = player.GetHumans()
|
|
|
|
-- Clear old values
|
|
plyToGrid[1] = {}
|
|
plyToGrid[2] = {}
|
|
grid = {}
|
|
|
|
local plyPos = {}
|
|
local eyePos = {}
|
|
|
|
-- Get the grid position of every player O(N)
|
|
for _, ply in ipairs(players) do
|
|
local pos = ply:GetPos()
|
|
plyPos[ply] = pos
|
|
eyePos[ply] = ply:EyePos()
|
|
local x = floor(pos.x / gridSize)
|
|
local y = floor(pos.y / gridSize)
|
|
|
|
local row = grid[x] or {}
|
|
local cell = row[y] or {}
|
|
|
|
table.insert(cell, ply)
|
|
row[y] = cell
|
|
grid[x] = row
|
|
|
|
plyToGrid[1][ply] = x
|
|
plyToGrid[2][ply] = y
|
|
|
|
DrpCanHear[ply] = {} -- Initialize output variable
|
|
end
|
|
|
|
-- Check all neighbouring cells for every player.
|
|
-- We are only checking in 1 direction to avoid duplicate check of cells
|
|
for _, ply1 in ipairs(players) do
|
|
local gridX = plyToGrid[1][ply1]
|
|
local gridY = plyToGrid[2][ply1]
|
|
local ply1Pos = plyPos[ply1]
|
|
local ply1EyePos = eyePos[ply1]
|
|
|
|
for i = 0, 3 do
|
|
local vOffset = 1 - ((i >= 3) and 1 or 0)
|
|
local hOffset = -(i % 3-1)
|
|
local x = gridX + hOffset
|
|
local y = gridY + vOffset
|
|
|
|
local row = grid[x]
|
|
if not row then continue end
|
|
|
|
local cell = row[y]
|
|
if not cell then continue end
|
|
|
|
for _, ply2 in ipairs(cell) do
|
|
local canTalk =
|
|
ply1Pos:DistToSqr(plyPos[ply2]) < voiceDistance and -- voiceradius is on and the two are within hearing distance
|
|
(not dynv or IsInRoom(ply1EyePos, eyePos[ply2], ply2)) -- Dynamic voice is on and players are in the same room
|
|
|
|
DrpCanHear[ply1][ply2] = canTalk and (deadv or ply2:Alive())
|
|
DrpCanHear[ply2][ply1] = canTalk and (deadv or ply1:Alive()) -- Take advantage of the symmetry
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Doing a pass-through inside every cell to compute the interactions inside of the cells.
|
|
-- Each grid check is O(N(N+1)/2) where N is the number of players inside the cell.
|
|
for _, row in pairs(grid) do
|
|
for _, cell in pairs(row) do
|
|
local count = #cell
|
|
for i = 1, count do
|
|
local ply1 = cell[i]
|
|
for j = i + 1, count do
|
|
local ply2 = cell[j]
|
|
local canTalk =
|
|
plyPos[ply1]:DistToSqr(plyPos[ply2]) < voiceDistance and -- voiceradius is on and the two are within hearing distance
|
|
(not dynv or IsInRoom(eyePos[ply1], eyePos[ply2], ply2)) -- Dynamic voice is on and players are in the same room
|
|
|
|
DrpCanHear[ply1][ply2] = canTalk and (deadv or ply2:Alive())
|
|
DrpCanHear[ply2][ply1] = canTalk and (deadv or ply1:Alive()) -- Take advantage of the symmetry
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
hook.Add("PlayerDisconnected", "DarkRPCanHear", function(ply)
|
|
DrpCanHear[ply] = nil -- Clear to avoid memory leaks
|
|
end)
|
|
|
|
function GM:PlayerCanHearPlayersVoice(listener, talker)
|
|
if not deadv and not talker:Alive() then return false end
|
|
|
|
return not vrad or DrpCanHear[listener][talker] == true, threed
|
|
end
|
|
|
|
function GM:CanTool(ply, trace, mode)
|
|
if not self.Sandbox.CanTool(self, ply, trace, mode) then return false end
|
|
|
|
local ent = trace.Entity
|
|
if IsValid(ent) then
|
|
if ent.onlyremover then
|
|
if mode == "remover" then
|
|
return ply:IsAdmin() or ply:IsSuperAdmin()
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
if ent.nodupe and (mode == "weld" or
|
|
mode == "weld_ez" or
|
|
mode == "spawner" or
|
|
mode == "duplicator" or
|
|
mode == "adv_duplicator") then
|
|
return false
|
|
end
|
|
|
|
if ent:IsVehicle() and mode == "nocollide" and not GAMEMODE.Config.allowvnocollide then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function GM:CanPlayerSuicide(ply)
|
|
if ply.IsSleeping then
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", ""))
|
|
return false
|
|
end
|
|
if ply:isArrested() then
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", ""))
|
|
return false
|
|
end
|
|
if GAMEMODE.Config.wantedsuicide and ply:getDarkRPVar("wanted") then
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("unable", "suicide", ""))
|
|
return false
|
|
end
|
|
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.CanPlayerSuicide then
|
|
return jobTable.CanPlayerSuicide(ply)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function GM:CanDrive(ply, ent)
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("drive_disabled"))
|
|
return false -- Disabled until people can't minge with it anymore
|
|
end
|
|
|
|
function GM:CanProperty(ply, property, ent)
|
|
if self.Config.allowedProperties[property] and ent:CPPICanTool(ply, "remover") then
|
|
return true
|
|
end
|
|
|
|
if property == "persist" and ply:IsSuperAdmin() then
|
|
return true
|
|
end
|
|
DarkRP.notify(ply, 1, 4, DarkRP.getPhrase("property_disabled"))
|
|
return false -- Disabled until antiminge measure is found
|
|
end
|
|
|
|
function GM:PlayerShouldTaunt(ply, actid)
|
|
return GAMEMODE.Config.allowActs
|
|
end
|
|
|
|
function GM:DoPlayerDeath(ply, attacker, dmginfo, ...)
|
|
local weapon = ply:GetActiveWeapon()
|
|
local canDrop = hook.Call("canDropWeapon", self, ply, weapon)
|
|
|
|
if (GAMEMODE.Config.dropweapondeath or ply.dropWeaponOnDeath) and weapon:IsValid() and canDrop then
|
|
ply:dropDRPWeapon(weapon)
|
|
end
|
|
self.Sandbox.DoPlayerDeath(self, ply, attacker, dmginfo, ...)
|
|
end
|
|
|
|
function GM:PlayerDeath(ply, weapon, killer)
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.PlayerDeath then
|
|
jobTable.PlayerDeath(ply, weapon, killer)
|
|
end
|
|
|
|
if GAMEMODE.Config.deathblack and not ply.blackScreen then
|
|
ply.blackScreen = true
|
|
SendUserMessage("blackScreen", ply, true)
|
|
end
|
|
|
|
if weapon:IsVehicle() and weapon:GetDriver():IsPlayer() then killer = weapon:GetDriver() end
|
|
|
|
if GAMEMODE.Config.showdeaths then
|
|
self.Sandbox.PlayerDeath(self, ply, weapon, killer)
|
|
end
|
|
|
|
ply:Extinguish()
|
|
|
|
ply:ExitVehicle()
|
|
|
|
if ply:isArrested() and not GAMEMODE.Config.respawninjail then
|
|
-- If the player died in jail, make sure they can't respawn until their jail sentence is over
|
|
-- NextSpawnTime is set to CurTime() on unarrest
|
|
ply.NextSpawnTime = math.huge
|
|
DarkRP.printMessageAll(HUD_PRINTCENTER, DarkRP.getPhrase("died_in_jail", ply:Nick()))
|
|
DarkRP.notify(ply, 4, 4, DarkRP.getPhrase("dead_in_jail"))
|
|
else
|
|
-- Normal death, respawning.
|
|
ply.NextSpawnTime = CurTime() + math.Clamp(GAMEMODE.Config.respawntime, 0, 10)
|
|
end
|
|
ply.DeathPos = ply:GetPos()
|
|
|
|
if GAMEMODE.Config.dropmoneyondeath then
|
|
local amount = GAMEMODE.Config.deathfee
|
|
if not ply:canAfford(GAMEMODE.Config.deathfee) then
|
|
amount = ply:getDarkRPVar("money")
|
|
end
|
|
|
|
if amount > 0 then
|
|
ply:addMoney(-amount)
|
|
DarkRP.createMoneyBag(ply:GetPos(), amount)
|
|
end
|
|
end
|
|
|
|
if IsValid(ply) and (ply ~= killer or ply.Slayed) and not ply:isArrested() then
|
|
if not GAMEMODE.Config.wantedrespawn then
|
|
ply:setDarkRPVar("wanted", nil)
|
|
end
|
|
ply.DeathPos = nil
|
|
ply.Slayed = false
|
|
end
|
|
|
|
ply.ConfiscatedWeapons = nil
|
|
|
|
local KillerName = (killer:IsPlayer() and killer:Nick()) or tostring(killer)
|
|
local WeaponName = IsValid(weapon) and ((weapon:IsPlayer() and weapon:GetActiveWeapon():IsValid() and weapon:GetActiveWeapon():GetClass()) or weapon:GetClass()) or "unknown"
|
|
|
|
if killer == ply then
|
|
KillerName = "Themself"
|
|
WeaponName = "suicide trick"
|
|
end
|
|
|
|
DarkRP.log(ply:Nick() .. " was killed by " .. KillerName .. " with a " .. WeaponName, Color(255, 190, 0))
|
|
end
|
|
|
|
local adminCopWeapons = {
|
|
["door_ram"] = true,
|
|
["arrest_stick"] = true,
|
|
["unarrest_stick"] = true,
|
|
["stunstick"] = true,
|
|
["weaponchecker"] = true,
|
|
}
|
|
function GM:PlayerCanPickupWeapon(ply, weapon)
|
|
if ply:isArrested() then return false end
|
|
if weapon.PlayerUse == false then return false end
|
|
local weaponClass = weapon:GetClass()
|
|
if ply:IsAdmin() and GAMEMODE.Config.AdminsCopWeapons and adminCopWeapons[weaponClass] then return true end
|
|
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.PlayerCanPickupWeapon then
|
|
local val = jobTable.PlayerCanPickupWeapon(ply, weapon)
|
|
|
|
return val == nil or val
|
|
end
|
|
|
|
if GAMEMODE.Config.license and not ply:getDarkRPVar("HasGunlicense") and not ply.RPLicenseSpawn then
|
|
if GAMEMODE.NoLicense[string.lower(weaponClass)] or not weapon:IsWeapon() then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
function GM:PlayerSetModel(ply)
|
|
local jobTable = ply:getJobTable()
|
|
|
|
-- Invalid job, return to Sandbox behaviour
|
|
if not jobTable then return self.Sandbox.PlayerSetModel(ply) end
|
|
|
|
if jobTable.PlayerSetModel then
|
|
local model = jobTable.PlayerSetModel(ply)
|
|
if model then ply:SetModel(model) return end
|
|
end
|
|
|
|
local EndModel = ""
|
|
if GAMEMODE.Config.enforceplayermodel then
|
|
if istable(jobTable.model) then
|
|
local ChosenModel = string.lower(ply:getPreferredModel(ply:Team()) or "")
|
|
|
|
local found
|
|
for _, Models in pairs(jobTable.model) do
|
|
if ChosenModel == string.lower(Models) then
|
|
EndModel = Models
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not found then
|
|
EndModel = jobTable.model[math.random(#jobTable.model)]
|
|
end
|
|
else
|
|
EndModel = jobTable.model
|
|
end
|
|
|
|
ply:SetModel(EndModel)
|
|
else
|
|
local cl_playermodel = ply:GetInfo("cl_playermodel")
|
|
local modelname = player_manager.TranslatePlayerModel(cl_playermodel)
|
|
ply:SetModel(ply:getPreferredModel(ply:Team()) or modelname)
|
|
end
|
|
|
|
self.Sandbox.PlayerSetModel(self, ply)
|
|
|
|
ply:SetupHands()
|
|
end
|
|
|
|
local function initPlayer(ply)
|
|
timer.Simple(5, function()
|
|
if not IsValid(ply) then return end
|
|
|
|
if GetGlobalBool("DarkRP_Lockdown") then
|
|
SetGlobalBool("DarkRP_Lockdown", true) -- so new players who join know there's a lockdown, is this bug still there?
|
|
end
|
|
end)
|
|
|
|
ply:initiateTax()
|
|
|
|
ply:updateJob(team.GetName(GAMEMODE.DefaultTeam))
|
|
ply:setSelfDarkRPVar("salary", DarkRP.retrieveSalary(ply))
|
|
ply.LastJob = nil -- so players don't have to wait to get a job after joining
|
|
|
|
ply.Ownedz = {}
|
|
|
|
ply.LastLetterMade = CurTime() - 61
|
|
ply.LastVoteCop = CurTime() - 61
|
|
|
|
ply:SetTeam(GAMEMODE.DefaultTeam)
|
|
ply.DarkRPInitialised = true
|
|
|
|
-- Whether or not a player is being prevented from joining
|
|
-- a specific team for a certain length of time
|
|
if GAMEMODE.Config.restrictallteams then
|
|
for i = 1, #RPExtraTeams do
|
|
ply:teamBan(i, 0)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function restoreReconnectedEnts(ply)
|
|
local sid = ply:SteamID64()
|
|
if not queuedForRemoval[sid] then return end
|
|
|
|
timer.Remove("DarkRP_removeDisconnected_" .. sid)
|
|
|
|
for _, e in pairs(queuedForRemoval[sid]) do
|
|
if not IsValid(e) then continue end
|
|
|
|
e.SID = ply.SID
|
|
|
|
if e.Setowning_ent then
|
|
e:Setowning_ent(ply)
|
|
end
|
|
|
|
-- Some entities (e.g. vehicles) have an SID, but do not have a DarkRPItem
|
|
if e.DarkRPItem then
|
|
ply:addCustomEntity(e.DarkRPItem)
|
|
end
|
|
end
|
|
|
|
queuedForRemoval[sid] = nil
|
|
end
|
|
|
|
function GM:PlayerInitialSpawn(ply)
|
|
self.Sandbox.PlayerInitialSpawn(self, ply)
|
|
-- Initialize DrpCanHear for player (used for voice radius check)
|
|
DrpCanHear[ply] = {}
|
|
|
|
local sid = ply:SteamID()
|
|
DarkRP.log(ply:Nick() .. " (" .. sid .. ") has joined the game", Color(0, 130, 255))
|
|
ply:setDarkRPVarsAttribute()
|
|
ply:restorePlayerData()
|
|
initPlayer(ply)
|
|
ply.SID = ply:UserID()
|
|
|
|
timer.Simple(1, function()
|
|
if not IsValid(ply) then return end
|
|
local group = GAMEMODE.Config.DefaultPlayerGroups[sid]
|
|
if group then
|
|
ply:SetUserGroup(group)
|
|
end
|
|
end)
|
|
|
|
restoreReconnectedEnts(ply)
|
|
end
|
|
|
|
function GM:PlayerSelectSpawn(ply)
|
|
local spawn = self.Sandbox.PlayerSelectSpawn(self, ply)
|
|
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.PlayerSelectSpawn then
|
|
jobTable.PlayerSelectSpawn(ply, spawn)
|
|
end
|
|
|
|
local POS
|
|
if spawn and spawn.GetPos then
|
|
POS = spawn:GetPos()
|
|
else
|
|
POS = ply:GetPos()
|
|
end
|
|
|
|
local CustomSpawnPos = DarkRP.retrieveTeamSpawnPos(ply:Team())
|
|
if GAMEMODE.Config.customspawns and not ply:isArrested() and CustomSpawnPos and next(CustomSpawnPos) ~= nil then
|
|
POS = CustomSpawnPos[math.random(1, #CustomSpawnPos)]
|
|
end
|
|
|
|
-- Spawn where died in certain cases
|
|
if GAMEMODE.Config.strictsuicide and ply.DeathPos then
|
|
POS = ply.DeathPos
|
|
end
|
|
|
|
if ply:isArrested() then
|
|
POS = DarkRP.retrieveJailPos() or ply.DeathPos -- If we can't find a jail pos then we'll use where they died as a last resort
|
|
end
|
|
|
|
-- Make sure the player doesn't get stuck in something
|
|
|
|
local _, hull = ply:GetHull()
|
|
|
|
POS = DarkRP.findEmptyPos(POS, {ply}, 600, 30, hull)
|
|
|
|
return spawn, POS
|
|
end
|
|
|
|
local oldPlyColor
|
|
local function disableBabyGod(ply)
|
|
if not IsValid(ply) or not ply.Babygod then return end
|
|
|
|
ply.Babygod = nil
|
|
ply:SetRenderMode(RENDERMODE_NORMAL)
|
|
ply:GodDisable()
|
|
|
|
-- Don't reinstate the SetColor function
|
|
-- if there are still players who are babygodded
|
|
local reinstateOldColor = true
|
|
|
|
for _, p in ipairs(player.GetAll()) do
|
|
reinstateOldColor = reinstateOldColor and p.Babygod == nil
|
|
end
|
|
|
|
if reinstateOldColor then
|
|
entMeta.SetColor = oldPlyColor
|
|
oldPlyColor = nil
|
|
end
|
|
|
|
ply:SetColor(ply.babyGodColor or color_white)
|
|
|
|
ply.babyGodColor = nil
|
|
end
|
|
|
|
local function enableBabyGod(ply)
|
|
timer.Remove(ply:EntIndex() .. "babygod")
|
|
|
|
ply.Babygod = true
|
|
ply:GodEnable()
|
|
ply.babyGodColor = ply:GetColor()
|
|
ply:SetRenderMode(RENDERMODE_TRANSALPHA)
|
|
|
|
if not oldPlyColor then
|
|
oldPlyColor = entMeta.SetColor
|
|
entMeta.SetColor = function(p, c, ...)
|
|
if not p.Babygod then return oldPlyColor(p, c, ...) end
|
|
|
|
p.babyGodColor = c
|
|
oldPlyColor(p, Color(c.r, c.g, c.b, 100))
|
|
end
|
|
end
|
|
|
|
ply:SetColor(ply.babyGodColor)
|
|
timer.Create(ply:EntIndex() .. "babygod", GAMEMODE.Config.babygodtime or 0, 1, fp{disableBabyGod, ply})
|
|
end
|
|
|
|
function GM:PlayerSpawn(ply)
|
|
if not ply.DarkRPInitialised then
|
|
DarkRP.errorNoHalt(
|
|
string.format("DarkRP was unable to introduce player \"%s\" to the game. Expect further errors and shit generally being fucked!",
|
|
IsValid(ply) and ply:Nick() or "unknown"),
|
|
1,
|
|
{
|
|
"This error most likely does not stand on its own, and previous serverside errors have a very good chance of telling you the cause.",
|
|
"Note that errors from another addon could cause this. Specifically when they're thrown during 'PlayerInitialSpawn'.",
|
|
"This error can also be caused by some other addon returning a value in 'PlayerInitialSpawn', though that is less likely.",
|
|
"Errors in your DarkRP configuration (jobs, shipments, etc.) could also cause this. Earlier errors should tell you when this is the case."
|
|
}
|
|
)
|
|
end
|
|
|
|
ply:CrosshairEnable()
|
|
ply:UnSpectate()
|
|
|
|
-- Kill any colormod
|
|
if ply.blackScreen then
|
|
ply.blackScreen = false
|
|
SendUserMessage("blackScreen", ply, false)
|
|
end
|
|
|
|
if GAMEMODE.Config.babygod and not ply.IsSleeping and not ply.Babygod then
|
|
enableBabyGod(ply)
|
|
end
|
|
ply.IsSleeping = false
|
|
|
|
ply:Extinguish()
|
|
|
|
for i = 0, 2 do
|
|
local vm = ply:GetViewModel(i)
|
|
|
|
if IsValid(vm) then
|
|
vm:Extinguish()
|
|
end
|
|
end
|
|
|
|
if ply.demotedWhileDead then
|
|
ply.demotedWhileDead = nil
|
|
|
|
local demoteTeam = hook.Call("demoteTeam", nil, ply) or GAMEMODE.DefaultTeam
|
|
ply:changeTeam(demoteTeam, true)
|
|
ply:setDarkRPVar("job", team.GetName(demoteTeam))
|
|
end
|
|
|
|
local jobTable = ply:getJobTable()
|
|
|
|
player_manager.SetPlayerClass(ply, jobTable.playerClass or "player_darkrp")
|
|
|
|
ply:applyPlayerClassVars(true)
|
|
|
|
player_manager.RunClass(ply, "Spawn")
|
|
|
|
hook.Call("PlayerLoadout", self, ply)
|
|
hook.Call("PlayerSetModel", self, ply)
|
|
|
|
local ent, pos = hook.Call("PlayerSelectSpawn", self, ply)
|
|
ply:SetPos(pos or ent:GetPos())
|
|
|
|
if jobTable.PlayerSpawn then
|
|
jobTable.PlayerSpawn(ply)
|
|
end
|
|
|
|
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") spawned")
|
|
end
|
|
|
|
function GM:PlayerLoadout(ply)
|
|
self.Sandbox.PlayerLoadout(self, ply)
|
|
|
|
if ply:isArrested() then return end
|
|
|
|
ply.RPLicenseSpawn = true
|
|
timer.Simple(1, function()
|
|
if not IsValid(ply) then return end
|
|
ply.RPLicenseSpawn = false
|
|
end)
|
|
|
|
local jobTable = ply:getJobTable()
|
|
|
|
for _, v in pairs(jobTable.weapons or {}) do
|
|
ply:Give(v)
|
|
end
|
|
|
|
if jobTable.PlayerLoadout then
|
|
local val = jobTable.PlayerLoadout(ply)
|
|
if val == true then
|
|
ply:SwitchToDefaultWeapon()
|
|
return
|
|
end
|
|
end
|
|
|
|
if jobTable.ammo then
|
|
for k, v in pairs(jobTable.ammo) do
|
|
ply:SetAmmo(v, k)
|
|
end
|
|
end
|
|
|
|
for _, v in pairs(self.Config.DefaultWeapons) do
|
|
ply:Give(v)
|
|
end
|
|
|
|
CAMI.PlayerHasAccess(ply, "DarkRP_GetAdminWeapons", function(access)
|
|
if not access or not IsValid(ply) then return end
|
|
|
|
for _, v in pairs(GAMEMODE.Config.AdminWeapons) do
|
|
ply:Give(v)
|
|
end
|
|
|
|
if not GAMEMODE.Config.AdminsCopWeapons then return end
|
|
|
|
ply:Give("door_ram")
|
|
ply:Give("arrest_stick")
|
|
ply:Give("unarrest_stick")
|
|
ply:Give("stunstick")
|
|
ply:Give("weaponchecker")
|
|
end)
|
|
|
|
ply:SwitchToDefaultWeapon()
|
|
end
|
|
|
|
--[[---------------------------------------------------------------------------
|
|
Remove with a delay if the player doesn't rejoin before the timer has run out
|
|
---------------------------------------------------------------------------]]
|
|
local function removeDelayed(entList, ply)
|
|
local removedelay = GAMEMODE.Config.entremovedelay
|
|
|
|
if removedelay <= 0 then
|
|
for _, e in pairs(entList) do
|
|
SafeRemoveEntity(e)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
local sid = ply:SteamID64()
|
|
queuedForRemoval[sid] = entList
|
|
|
|
timer.Create("DarkRP_removeDisconnected_" .. sid, removedelay, 1, function()
|
|
for _, e in pairs(queuedForRemoval[sid] or {}) do
|
|
SafeRemoveEntity(e)
|
|
end
|
|
|
|
queuedForRemoval[sid] = nil
|
|
end)
|
|
end
|
|
|
|
-- Collect entities that are to be removed
|
|
local function collectRemoveEntities(ply)
|
|
if not GAMEMODE.Config.removeondisconnect then return {} end
|
|
|
|
local collect = {}
|
|
-- Get the classes of entities to remove
|
|
local remClasses = {}
|
|
for _, customEnt in pairs(DarkRPEntities) do
|
|
remClasses[string.lower(customEnt.ent)] = true
|
|
end
|
|
|
|
local sid = ply.SID
|
|
for _, v in ipairs(ents.GetAll()) do
|
|
if v.SID ~= sid or not v:IsVehicle() and not remClasses[string.lower(v:GetClass() or "")] then continue end
|
|
|
|
table.insert(collect, v)
|
|
end
|
|
|
|
if not ply:isMayor() then return collect end
|
|
|
|
for _, ent in pairs(ply.lawboards or {}) do
|
|
if not IsValid(ent) then continue end
|
|
table.insert(collect, ent)
|
|
end
|
|
|
|
return collect
|
|
end
|
|
|
|
function GM:PlayerDisconnected(ply)
|
|
self.Sandbox.PlayerDisconnected(self, ply)
|
|
timer.Remove(ply:SteamID64() .. "jobtimer")
|
|
timer.Remove(ply:SteamID64() .. "propertytax")
|
|
|
|
local isMayor = ply:isMayor()
|
|
|
|
local remList = collectRemoveEntities(ply)
|
|
removeDelayed(remList, ply)
|
|
|
|
DarkRP.destroyQuestionsWithEnt(ply)
|
|
DarkRP.destroyVotesWithEnt(ply)
|
|
|
|
if isMayor and GetGlobalBool("DarkRP_LockDown") then -- Stop the lockdown
|
|
DarkRP.unLockdown(ply)
|
|
end
|
|
|
|
if isMayor and GAMEMODE.Config.shouldResetLaws then
|
|
DarkRP.resetLaws()
|
|
end
|
|
|
|
if IsValid(ply.SleepRagdoll) then
|
|
ply.SleepRagdoll:Remove()
|
|
end
|
|
|
|
ply:keysUnOwnAll()
|
|
DarkRP.log(ply:Nick() .. " (" .. ply:SteamID() .. ") disconnected", Color(0, 130, 255))
|
|
|
|
local agenda = ply:getAgendaTable()
|
|
|
|
-- Clear agenda
|
|
if agenda and ply:Team() == agenda.Manager and team.NumPlayers(ply:Team()) <= 1 then
|
|
agenda.text = nil
|
|
for _, v in ipairs(player.GetAll()) do
|
|
if v:getAgendaTable() ~= agenda then continue end
|
|
v:setSelfDarkRPVar("agenda", agenda.text)
|
|
end
|
|
end
|
|
|
|
local jobTable = ply:getJobTable()
|
|
if jobTable.PlayerDisconnected then
|
|
jobTable.PlayerDisconnected(ply)
|
|
end
|
|
end
|
|
|
|
function GM:GetFallDamage(ply, flFallSpeed)
|
|
if GetConVar("mp_falldamage"):GetBool() or GAMEMODE.Config.realisticfalldamage then
|
|
if GAMEMODE.Config.falldamagedamper then return flFallSpeed / GAMEMODE.Config.falldamagedamper else return flFallSpeed / 15 end
|
|
else
|
|
if GAMEMODE.Config.falldamageamount then return GAMEMODE.Config.falldamageamount else return 10 end
|
|
end
|
|
end
|
|
|
|
local function fuckQAC()
|
|
local netRecs = {"Debug1", "Debug2", "checksaum", "gcontrol_vars", "control_vars", "QUACK_QUACK_MOTHER_FUCKER"}
|
|
for _, v in ipairs(netRecs) do
|
|
net.Receivers[v] = fn.Id
|
|
end
|
|
end
|
|
|
|
function GM:InitPostEntity()
|
|
self.InitPostEntityCalled = true
|
|
|
|
local physData = physenv.GetPerformanceSettings()
|
|
physData.MaxVelocity = 2000
|
|
physData.MaxAngularVelocity = 3636
|
|
|
|
physenv.SetPerformanceSettings(physData)
|
|
|
|
-- Scriptenforcer enabled by default? Fuck you, not gonna happen.
|
|
if not GAMEMODE.Config.disallowClientsideScripts then
|
|
game.ConsoleCommand("sv_allowcslua 1\n")
|
|
timer.Simple(1, fuckQAC) -- Also, fuck QAC which bans innocent people when allowcslua = 1
|
|
end
|
|
game.ConsoleCommand("physgun_DampingFactor 0.9\n")
|
|
game.ConsoleCommand("sv_sticktoground 0\n")
|
|
game.ConsoleCommand("sv_airaccelerate 1000\n")
|
|
-- sv_alltalk must be 0
|
|
-- Note, everyone will STILL hear everyone UNLESS GM.Config.voiceradius is set to true
|
|
-- This will fix the GM.Config.voiceradius not working
|
|
game.ConsoleCommand("sv_alltalk 0\n")
|
|
|
|
if GAMEMODE.Config.unlockdoorsonstart then
|
|
for _, v in ipairs(ents.GetAll()) do
|
|
if not v:isDoor() then continue end
|
|
v:Fire("unlock", "", 0)
|
|
end
|
|
end
|
|
end
|
|
timer.Simple(0.1, function()
|
|
if not GAMEMODE.InitPostEntityCalled then
|
|
GAMEMODE:InitPostEntity()
|
|
end
|
|
end)
|
|
|
|
function GM:loadCustomDarkRPItems()
|
|
-- Error when the default team isn't set
|
|
if not GAMEMODE.DefaultTeam or not RPExtraTeams[GAMEMODE.DefaultTeam] then
|
|
-- Re-set to first available team to hopefully prevent further errors.
|
|
-- Because this error is more important than any that follow because of it.
|
|
GAMEMODE.DefaultTeam = next(RPExtraTeams)
|
|
|
|
local hints = {
|
|
"This may happen when you disable the default citizen job. Make sure you update GAMEMODE.DefaultTeam to the new default team.",
|
|
"GAMEMODE.DefaultTeam may be set to a job that does not exist anymore. Did you remove the job you had set to default?",
|
|
"The error being in jobs.lua is a guess. This is usually right, but the problem might lie somewhere else."
|
|
}
|
|
|
|
-- Gotta be totally clear here
|
|
local stack = "\tjobs.lua, settings.lua, disabled_defaults.lua or any of your other custom files."
|
|
DarkRP.error("GAMEMODE.DefaultTeam is not set to an existing job.", 1, hints, "lua/darkrp_customthings/jobs.lua", -1, stack)
|
|
end
|
|
end
|
|
|
|
function GM:PlayerLeaveVehicle(ply, vehicle)
|
|
if GAMEMODE.Config.autovehiclelock and vehicle:isKeysOwnedBy(ply) then
|
|
vehicle:keysLock()
|
|
end
|
|
self.Sandbox.PlayerLeaveVehicle(self, ply, vehicle)
|
|
end
|
|
|
|
local function ClearDecals()
|
|
if GAMEMODE.Config.decalcleaner then
|
|
for _, p in ipairs(player.GetAll()) do
|
|
p:ConCommand("r_cleardecals")
|
|
end
|
|
end
|
|
end
|
|
timer.Create("RP_DecalCleaner", GM.Config.decaltimer, 0, ClearDecals)
|
|
|
|
function GM:PlayerSpray()
|
|
return not GAMEMODE.Config.allowsprays
|
|
end
|
|
|
|
function GM:GravGunOnPickedUp(ply, ent, ...)
|
|
self.Sandbox.GravGunOnPickedUp(self, ply, ent, ...)
|
|
-- Keeping track of who is holding an entity is done to make sure the entity
|
|
-- cannot be pocketed. This is because holding an entity with the gravgun
|
|
-- changes some properties. One such property is setting the mass to 1,
|
|
-- causing the mass check of the pocket to always succeed.
|
|
ent.DarkRPBeingGravGunHeldBy = ply
|
|
end
|
|
|
|
function GM:GravGunOnDropped(ply, ent, ...)
|
|
self.Sandbox.GravGunOnDropped(self, ply, ent, ...)
|
|
-- See comment at GravGunOnPickedUp
|
|
ent.DarkRPBeingGravGunHeldBy = nil
|
|
end
|