588 lines
21 KiB
Lua
588 lines
21 KiB
Lua
FPP = FPP or {}
|
|
local plyMeta = FindMetaTable("Player")
|
|
local entMeta = FindMetaTable("Entity")
|
|
|
|
local bit_bor = bit.bor
|
|
local bit_band = bit.band
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Entity data explanation.
|
|
Every ent has a field FPPCanTouch. This is a table with one entry per player.
|
|
Every bit in FPPCanTouch represents a type.
|
|
The first bit says whether the player can physgun the ent
|
|
The second bit whether the player can use gravun on the ent
|
|
etc.
|
|
|
|
Then there is the FPPCanTouchWhy var This var follows the same idea as FPPCanTouch
|
|
except there are five bits for each type. These 4 bits represent a number that shows the reason
|
|
why a player can or cannot touch a prop.
|
|
---------------------------------------------------------------------------]]
|
|
|
|
local touchTypes = {
|
|
Physgun = 1,
|
|
Gravgun = 2,
|
|
Toolgun = 4,
|
|
PlayerUse = 8,
|
|
EntityDamage = 16
|
|
}
|
|
|
|
local touchTypeNumbers = {
|
|
[1] = "Physgun",
|
|
[2] = "Gravgun",
|
|
[4] = "Toolgun",
|
|
[8] = "PlayerUse",
|
|
[16] = "EntityDamage"
|
|
}
|
|
|
|
local reasonSize = 4 -- bits
|
|
|
|
local reasonNumbers = {
|
|
["owner"] = 1,
|
|
["world"] = 2,
|
|
["disconnected"] = 3,
|
|
["blocked"] = 4,
|
|
["constrained"] = 5,
|
|
["buddy"] = 6,
|
|
["shared"] = 7,
|
|
["player"] = 8,
|
|
}
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Utility functions
|
|
---------------------------------------------------------------------------]]
|
|
local function getPlySetting(ply, settingName)
|
|
return (ply[settingName] or ply:GetInfo(settingName)) == "1"
|
|
end
|
|
|
|
local function getSetting(touchType)
|
|
return touchType .. "1", string.upper("FPP_" .. touchType .. "1")
|
|
end
|
|
|
|
local constraints = { -- These little buggers think they're not constraints, but they are
|
|
["phys_spring"] = true,
|
|
}
|
|
local function isConstraint(ent)
|
|
return ent:IsConstraint() or constraints[ent:GetClass()] or false
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Touch calculations
|
|
---------------------------------------------------------------------------]]
|
|
local hardWhiteListed = { -- things that mess up when not allowed
|
|
["worldspawn"] = true, -- constraints with the world
|
|
["gmod_anchor"] = true, -- used in slider constraints with world
|
|
["gmod_wire_hologram"] = true, -- created by Expression2, has no physics, but can be constrained to other entities
|
|
["phys_spring"] = true, -- https://developer.valvesoftware.com/wiki/Phys_spring
|
|
["env_projectedtexture"] = true, -- https://developer.valvesoftware.com/wiki/Env_projectedtexture
|
|
["keyframe_rope"] = true, -- https://developer.valvesoftware.com/wiki/Keyframe_rope
|
|
["env_skypaint"] = true, -- https://developer.valvesoftware.com/wiki/Env_skypaint
|
|
["env_fog_controller"] = true -- https://developer.valvesoftware.com/wiki/Env_fog_controller
|
|
}
|
|
local function calculateCanTouchForType(ply, ent, touchType)
|
|
if not IsValid(ent) then return false, 0 end
|
|
|
|
ply.FPP_Privileges = ply.FPP_Privileges or {}
|
|
local class = ent:GetClass()
|
|
local setting, tablename = getSetting(touchType)
|
|
local FPPSettings = FPP.Settings[tablename]
|
|
|
|
-- hard white list
|
|
if hardWhiteListed[class] then return true, reasonNumbers.world end
|
|
|
|
-- picking up players
|
|
if touchType == "Physgun" and ent:IsPlayer() and not getPlySetting(ply, "cl_pickupplayers") then
|
|
return false, reasonNumbers.player
|
|
end
|
|
|
|
-- blocked entity
|
|
local whitelist = FPPSettings.iswhitelist ~= 0
|
|
local isInList = FPP.Blocked[setting][string.lower(class)] or false
|
|
|
|
local isAdmin = ply.FPP_Privileges.FPP_TouchOtherPlayersProps
|
|
local isBlocked = whitelist ~= isInList -- XOR
|
|
|
|
if isBlocked then
|
|
local adminsCanTouchBlocked = FPPSettings.admincanblocked ~= 0
|
|
local playersCanBlocked = FPPSettings.canblocked ~= 0
|
|
|
|
local canPhysgunBlocked = (playersCanBlocked or isAdmin and adminsCanTouchBlocked) and not getPlySetting(ply, "FPP_PrivateSettings_BlockedProps")
|
|
|
|
if not canPhysgunBlocked then
|
|
return canPhysgunBlocked, reasonNumbers.blocked
|
|
end
|
|
end
|
|
|
|
-- touch own props
|
|
local owner = ent.FPPOwner -- Circumvent CPPI for micro-optimisation
|
|
if owner == ply then
|
|
return not getPlySetting(ply, "FPP_PrivateSettings_OwnProps"),
|
|
reasonNumbers.owner
|
|
end
|
|
|
|
local noTouchOtherPlayerProps = getPlySetting(ply, "FPP_PrivateSettings_OtherPlayerProps")
|
|
|
|
-- Shared entity
|
|
if ent.AllowedPlayers and table.HasValue(ent.AllowedPlayers, ply) then
|
|
return not noTouchOtherPlayerProps, reasonNumbers.shared
|
|
end
|
|
if ent["Share" .. setting] then return not noTouchOtherPlayerProps, reasonNumbers.shared end
|
|
|
|
if IsValid(owner) then
|
|
-- Player is buddies with the owner of the entity
|
|
if owner.Buddies and owner.Buddies[ply] and owner.Buddies[ply][touchType] then return not noTouchOtherPlayerProps, reasonNumbers.buddy end
|
|
|
|
-- Someone else's prop
|
|
local adminProps = FPPSettings.adminall ~= 0
|
|
return isAdmin and adminProps and not noTouchOtherPlayerProps, reasonNumbers.owner
|
|
end
|
|
|
|
-- World props and disconnected players' props
|
|
local adminWorldProps = FPPSettings.adminworldprops ~= 0
|
|
local peopleWorldProps = FPPSettings.worldprops ~= 0
|
|
local restrictWorld = getPlySetting(ply, "FPP_PrivateSettings_WorldProps")
|
|
|
|
return not restrictWorld and (peopleWorldProps or (isAdmin and adminWorldProps)),
|
|
owner == nil and reasonNumbers.world or reasonNumbers.disconnected
|
|
end
|
|
|
|
local blockedEnts = {
|
|
["ai_network"] = true,
|
|
["network"] = true, -- alternative name for ai_network
|
|
["ambient_generic"] = true,
|
|
["beam"] = true,
|
|
["bodyque"] = true,
|
|
["env_soundscape"] = true,
|
|
["env_sprite"] = true,
|
|
["env_sun"] = true,
|
|
["env_tonemap_controller"] = true,
|
|
["func_useableladder"] = true,
|
|
["gmod_hands"] = true,
|
|
["info_ladder_dismount"] = true,
|
|
["info_player_start"] = true,
|
|
["info_player_terrorist"] = true,
|
|
["light_environment"] = true,
|
|
["light_spot"] = true,
|
|
["physgun_beam"] = true,
|
|
["player_manager"] = true,
|
|
["point_spotlight"] = true,
|
|
["predicted_viewmodel"] = true,
|
|
["scene_manager"] = true,
|
|
["shadow_control"] = true,
|
|
["soundent"] = true,
|
|
["spotlight_end"] = true,
|
|
["water_lod_control"] = true,
|
|
["gmod_gamerules"] = true,
|
|
["bodyqueue"] = true,
|
|
["phys_bone_follower"] = true,
|
|
}
|
|
function FPP.calculateCanTouch(ply, ent)
|
|
local canTouch = 0
|
|
|
|
local reasons = 0
|
|
local i = 0
|
|
|
|
for Bit, touchType in pairs(touchTypeNumbers) do
|
|
local canTouchType, why = calculateCanTouchForType(ply, ent, touchType)
|
|
if canTouchType then
|
|
canTouch = bit.bor(canTouch, Bit)
|
|
end
|
|
|
|
reasons = bit.bor(reasons, bit.lshift(why, i * reasonSize))
|
|
|
|
i = i + 1
|
|
end
|
|
|
|
ent.FPPCanTouch = ent.FPPCanTouch or {}
|
|
ent.FPPCanTouchWhy = ent.FPPCanTouchWhy or {}
|
|
|
|
local changed = ent.FPPCanTouch[ply] ~= canTouch or ent.FPPCanTouchWhy[ply] ~= reasons or ent.FPPOwnerChanged
|
|
ent.FPPCanTouch[ply] = canTouch
|
|
ent.FPPCanTouchWhy[ply] = reasons
|
|
|
|
return changed
|
|
end
|
|
|
|
-- try not to call this with both player.GetAll() and ents.GetAll()
|
|
local function recalculateCanTouch(players, entities)
|
|
for k, v in pairs(entities) do
|
|
if not IsValid(v) then entities[k] = nil continue end
|
|
if v:IsEFlagSet(EFL_SERVER_ONLY) then entities[k] = nil continue end
|
|
if blockedEnts[v:GetClass()] then entities[k] = nil continue end
|
|
if v:IsWeapon() and IsValid(v:GetOwner()) then entities[k] = nil continue end
|
|
end
|
|
|
|
for _, ply in pairs(players) do
|
|
if not IsValid(ply) then continue end
|
|
-- optimisations
|
|
ply.FPPIsAdmin = ply.FPP_Privileges.FPP_TouchOtherPlayersProps
|
|
ply.FPP_PrivateSettings_OtherPlayerProps = ply:GetInfo("FPP_PrivateSettings_OtherPlayerProps")
|
|
ply.cl_pickupplayers = ply:GetInfo("cl_pickupplayers")
|
|
ply.FPP_PrivateSettings_BlockedProps = ply:GetInfo("FPP_PrivateSettings_BlockedProps")
|
|
ply.FPP_PrivateSettings_OwnProps = ply:GetInfo("FPP_PrivateSettings_OwnProps")
|
|
ply.FPP_PrivateSettings_WorldProps = ply:GetInfo("FPP_PrivateSettings_WorldProps")
|
|
local changed = {}
|
|
|
|
for _, ent in pairs(entities) do
|
|
local hasChanged = FPP.calculateCanTouch(ply, ent)
|
|
if hasChanged then table.insert(changed, ent) end
|
|
end
|
|
|
|
FPP.plySendTouchData(ply, changed)
|
|
|
|
-- end optimisations
|
|
ply.FPP_PrivateSettings_OtherPlayerProps = nil
|
|
ply.cl_pickupplayers = nil
|
|
ply.FPP_PrivateSettings_BlockedProps = nil
|
|
ply.FPP_PrivateSettings_OwnProps = nil
|
|
ply.FPP_PrivateSettings_WorldProps = nil
|
|
ply.FPPIsAdmin = nil
|
|
end
|
|
end
|
|
|
|
function FPP.recalculateCanTouch(plys, ens)
|
|
FPP.calculatePlayerPrivilege("FPP_TouchOtherPlayersProps", function() recalculateCanTouch(plys, ens) end)
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Touch interface
|
|
---------------------------------------------------------------------------]]
|
|
function FPP.plyCanTouchEnt(ply, ent, touchType)
|
|
local entTable = ent:GetTable()
|
|
local entCanTouch = entTable.FPPCanTouch
|
|
if not entCanTouch then
|
|
entCanTouch = {}
|
|
entTable.FPPCanTouch = entCanTouch
|
|
end
|
|
|
|
entCanTouch[ply] = entCanTouch[ply] or 0
|
|
|
|
local allowedPlayers = entTable.AllowedPlayers
|
|
if not allowedPlayers then
|
|
entTable.AllowedPlayers = {}
|
|
end
|
|
|
|
local canTouch = entCanTouch[ply]
|
|
-- if an entity is constrained, return the least of the rights
|
|
local entRestrictConstraint = entTable.FPPRestrictConstraint
|
|
if entRestrictConstraint and entRestrictConstraint[ply] then
|
|
canTouch = bit_band(entRestrictConstraint[ply], entCanTouch[ply])
|
|
end
|
|
|
|
-- return the answer for every touch type if parameter is empty
|
|
if not touchType then
|
|
return canTouch
|
|
end
|
|
|
|
return bit_bor(canTouch, touchTypes[touchType]) == canTouch
|
|
end
|
|
|
|
local GetTable = entMeta.GetTable
|
|
|
|
function FPP.entGetOwner(ent)
|
|
return GetTable(ent).FPPOwner
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Networking
|
|
---------------------------------------------------------------------------]]
|
|
util.AddNetworkString("FPP_TouchabilityData")
|
|
-- Sends 13 + 8 + 5 + 20 = 46 bits of ownership data per entity
|
|
local function netWriteEntData(ply, ent)
|
|
-- EntIndex for when it's out of the PVS of the player
|
|
net.WriteUInt(ent:EntIndex(), 13)
|
|
|
|
local owner = ent:CPPIGetOwner()
|
|
net.WriteUInt(IsValid(owner) and owner:EntIndex() or 255, 8)
|
|
|
|
local entTable = ent:GetTable()
|
|
net.WriteUInt(entTable.FPPRestrictConstraint and entTable.FPPRestrictConstraint[ply] or entTable.FPPCanTouch[ply], 5) -- touchability information
|
|
net.WriteUInt(entTable.FPPConstraintReasons and entTable.FPPConstraintReasons[ply] or entTable.FPPCanTouchWhy[ply], 20) -- reasons
|
|
end
|
|
|
|
-- The net message gets too big with 5826 or more entities. In that case,
|
|
-- break up the input into chunks and recurse per chunk.
|
|
--
|
|
-- Note: There is still a limit on the entity count! If there are too many
|
|
-- entities, the client will disconnect with "Disconnect: Client 0
|
|
-- overflowed reliable channel..". That limit is above the entity limit of
|
|
-- 16384, though, so no further work is done to lift that limit.
|
|
local count_limit = 5825
|
|
|
|
function FPP.plySendTouchData(ply, ents)
|
|
local count = #ents
|
|
if count == 0 then return end
|
|
|
|
if count > count_limit then
|
|
local accumulator = {}
|
|
|
|
for i = 1, count do
|
|
table.insert(accumulator, ents[i])
|
|
|
|
if i % count_limit == 0 then
|
|
FPP.plySendTouchData(ply, accumulator)
|
|
accumulator = {}
|
|
end
|
|
end
|
|
|
|
if #accumulator > 0 then
|
|
FPP.plySendTouchData(ply, accumulator)
|
|
end
|
|
|
|
return
|
|
end
|
|
|
|
net.Start("FPP_TouchabilityData")
|
|
for i = 1, count do
|
|
netWriteEntData(ply, ents[i])
|
|
net.WriteBit(i == count)
|
|
end
|
|
net.Send(ply)
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Events that trigger recalculation
|
|
---------------------------------------------------------------------------]]
|
|
local function handleConstraintCreation(ent)
|
|
local ent1, ent2 = ent:GetConstrainedEntities()
|
|
ent1, ent2 = ent1 or ent.Ent1, ent2 or ent.Ent2
|
|
|
|
if not ent1 or not ent2 or not ent1.FPPCanTouch or not ent2.FPPCanTouch then return end
|
|
local reason = 0
|
|
local i = 0
|
|
for Bit, touchType in pairs(touchTypeNumbers) do
|
|
reason = bit.bor(reason, bit.lshift(reasonNumbers.constrained, i * reasonSize))
|
|
i = i + 1
|
|
end
|
|
|
|
for _, ply in ipairs(player.GetAll()) do
|
|
local touch1, touch2 = FPP.plyCanTouchEnt(ply, ent1), FPP.plyCanTouchEnt(ply, ent2)
|
|
|
|
-- The constrained entities have the same touching rights.
|
|
if touch1 == touch2 then continue end
|
|
|
|
local restrictedAccess = bit.band(touch1, touch2)
|
|
|
|
local send = {}
|
|
for _, e in pairs(constraint.GetAllConstrainedEntities(ent1) or {}) do
|
|
if not IsValid(e) then continue end
|
|
if FPP.plyCanTouchEnt(ply, e) == restrictedAccess then continue end
|
|
|
|
e.FPPRestrictConstraint = e.FPPRestrictConstraint or {}
|
|
e.FPPConstraintReasons = e.FPPConstraintReasons or {}
|
|
e.FPPRestrictConstraint[ply] = restrictedAccess
|
|
e.FPPConstraintReasons[ply] = reason
|
|
|
|
table.insert(send, e)
|
|
end
|
|
|
|
FPP.plySendTouchData(ply, send)
|
|
end
|
|
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
On entity created
|
|
---------------------------------------------------------------------------]]
|
|
local function onEntitiesCreated(ents)
|
|
-- Table from player to list of entities that need to be networked
|
|
local sendToPlayers = {}
|
|
|
|
for _, ent in ipairs(ents) do
|
|
if not ent:IsValid() then continue end
|
|
|
|
if isConstraint(ent) then
|
|
handleConstraintCreation(ent)
|
|
continue
|
|
end
|
|
|
|
-- Don't send information about server only entities to the clients
|
|
if ent:GetSolid() == 0 or ent:IsEFlagSet(EFL_SERVER_ONLY) then
|
|
continue
|
|
end
|
|
|
|
if blockedEnts[ent:GetClass()] then continue end
|
|
|
|
for _, ply in ipairs(player.GetAll()) do
|
|
local changed = FPP.calculateCanTouch(ply, ent)
|
|
-- Only send data that has been changed
|
|
if not changed then continue end
|
|
sendToPlayers[ply] = sendToPlayers[ply] or {}
|
|
table.insert(sendToPlayers[ply], ent)
|
|
end
|
|
end
|
|
|
|
for ply, entsToSend in pairs(sendToPlayers) do
|
|
FPP.plySendTouchData(ply, entsToSend)
|
|
end
|
|
end
|
|
|
|
-- Make a queue of entities created per frame, so the server will send out a maximum-
|
|
-- of one message per player per frame
|
|
local entQueue = {}
|
|
local timerFunc = function()
|
|
onEntitiesCreated(entQueue)
|
|
entQueue = {}
|
|
timer.Remove("FPP_OnEntityCreatedTimer")
|
|
end
|
|
hook.Add("OnEntityCreated", "FPP_EntityCreated", function(ent)
|
|
table.insert(entQueue, ent)
|
|
|
|
if timer.Exists("FPP_OnEntityCreatedTimer") then return end
|
|
timer.Create("FPP_OnEntityCreatedTimer", 0, 1, timerFunc)
|
|
end)
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
On entity removed
|
|
---------------------------------------------------------------------------]]
|
|
-- Recalculates touchability information for constrained entities
|
|
-- Note: Assumes normal touchability information is up to date!
|
|
-- Update constraints, O(players * (entities + constraints))
|
|
function FPP.RecalculateConstrainedEntities(players, entities)
|
|
for i, ent in pairs(entities) do
|
|
if not IsValid(ent) then entities[i] = nil continue end
|
|
if ent:IsEFlagSet(EFL_SERVER_ONLY) then entities[i] = nil continue end
|
|
if blockedEnts[ent:GetClass()] then entities[i] = nil continue end
|
|
|
|
ent.FPPRestrictConstraint = ent.FPPRestrictConstraint or {}
|
|
ent.FPPConstraintReasons = ent.FPPConstraintReasons or {}
|
|
end
|
|
|
|
-- constrained entities form a graph.
|
|
-- and graphs are things you can traverse with BFS
|
|
for _, ply in pairs(players) do
|
|
local discovered = {}
|
|
-- BFS vars
|
|
local BFSQueue = {}
|
|
local black, gray = {}, {} -- black = fully discovered, gray = seen, but discovery from this point is needed
|
|
local value -- used as key and value of the BFSQueue
|
|
|
|
for _, ent in pairs(entities) do
|
|
if discovered[ent] then continue end -- We've seen this ent in a graph
|
|
ent.FPPCanTouch = ent.FPPCanTouch or {}
|
|
ent.FPPCanTouch[ply] = ent.FPPCanTouch[ply] or 0
|
|
|
|
local left, right = 1, 2
|
|
BFSQueue[left] = ent
|
|
|
|
local FPP_CanTouch = ent.FPPCanTouch[ply] -- Find out the canTouch state
|
|
while BFSQueue[left] do
|
|
value = BFSQueue[left]
|
|
BFSQueue[left] = nil
|
|
left = left + 1
|
|
|
|
for _, constr in pairs(value.Constraints or {}) do
|
|
local otherEnt = constr.Ent1 == value and constr.Ent2 or constr.Ent1
|
|
|
|
if not IsValid(otherEnt) or gray[otherEnt] or black[otherEnt] then continue end
|
|
|
|
gray[otherEnt] = true
|
|
BFSQueue[right] = otherEnt
|
|
right = right + 1
|
|
end
|
|
|
|
black[value] = true
|
|
discovered[value] = true
|
|
|
|
-- The entity doesn't necessarily have CanTouch data at this point
|
|
value.FPPCanTouch = value.FPPCanTouch or {}
|
|
value.FPPCanTouch[ply] = value.FPPCanTouch[ply] or 0
|
|
FPP_CanTouch = bit.band(FPP_CanTouch or 0, value.FPPCanTouch[ply])
|
|
end
|
|
|
|
-- now update the ents to the client
|
|
local updated = {}
|
|
for e in pairs(black) do
|
|
if FPP.plyCanTouchEnt(ply, e) ~= FPP_CanTouch then
|
|
e.FPPRestrictConstraint = e.FPPRestrictConstraint or {}
|
|
e.FPPRestrictConstraint[ply] = e.FPPCanTouch[ply] ~= FPP_CanTouch and FPP_CanTouch or nil
|
|
table.insert(updated, e)
|
|
end
|
|
end
|
|
FPP.plySendTouchData(ply, updated)
|
|
|
|
-- reset BFS information for next BFS round
|
|
black = {}
|
|
gray = {}
|
|
end
|
|
end
|
|
end
|
|
|
|
local entMem = {}
|
|
local function constraintRemovedTimer(ent1, ent2)
|
|
if not IsValid(ent1) and not IsValid(ent2) then return end
|
|
entMem = {}
|
|
|
|
local allConstrainedEnts = {}
|
|
table.Add(allConstrainedEnts, constraint.GetAllConstrainedEntities(ent1))
|
|
table.Add(allConstrainedEnts, constraint.GetAllConstrainedEntities(ent2))
|
|
FPP.RecalculateConstrainedEntities(player.GetAll(), allConstrainedEnts)
|
|
end
|
|
|
|
local function handleConstraintRemoved(ent)
|
|
local ent1, ent2 = ent:GetConstrainedEntities()
|
|
ent1, ent2 = ent1 or ent.Ent1, ent2 or ent.Ent2
|
|
|
|
if not IsValid(ent1) or not IsValid(ent2) then return end
|
|
-- prevent the function from being called too often when many constraints are removed at once
|
|
if entMem[ent1] or entMem[ent2] then return end
|
|
entMem[ent1] = true
|
|
entMem[ent2] = true
|
|
|
|
timer.Create("FPP_ConstraintRemovedTimer", 0, 1, function() constraintRemovedTimer(ent1, ent2) end)
|
|
end
|
|
|
|
local function onEntityRemoved(ent)
|
|
if isConstraint(ent) then handleConstraintRemoved(ent) end
|
|
end
|
|
|
|
hook.Add("EntityRemoved", "FPP_OnEntityRemoved", onEntityRemoved)
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Player disconnected
|
|
---------------------------------------------------------------------------]]
|
|
local function playerDisconnected(ply)
|
|
local ownedEnts = {}
|
|
for _, ent in ipairs(ents.GetAll()) do
|
|
if ent:CPPIGetOwner() == ply then
|
|
table.insert(ownedEnts, ent)
|
|
end
|
|
end
|
|
|
|
timer.Simple(0, function() FPP.recalculateCanTouch(player.GetAll(), ownedEnts) end)
|
|
end
|
|
hook.Add("PlayerDisconnected", "FPP_PlayerDisconnected", playerDisconnected)
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Usergroup changed
|
|
---------------------------------------------------------------------------]]
|
|
local function userGroupRecalculate(ply)
|
|
if not IsValid(ply) or not ply:IsPlayer() then return end
|
|
|
|
timer.Create("FPP_recalculate_cantouch_" .. ply:UserID(), 0, 1, function()
|
|
FPP.recalculateCanTouch({ply}, ents.GetAll())
|
|
end)
|
|
end
|
|
|
|
FPP.oldSetUserGroup = FPP.oldSetUserGroup or plyMeta.SetUserGroup
|
|
function plyMeta:SetUserGroup(group)
|
|
userGroupRecalculate(self)
|
|
|
|
return FPP.oldSetUserGroup(self, group)
|
|
end
|
|
|
|
FPP.oldSetNWString = FPP.oldSetNWString or entMeta.SetNWString
|
|
function entMeta:SetNWString(str, val)
|
|
if str ~= "usergroup" then return FPP.oldSetNWString(self, str, val) end
|
|
|
|
userGroupRecalculate(self)
|
|
return FPP.oldSetNWString(self, str, val)
|
|
end
|
|
|
|
FPP.oldSetNetworkedString = FPP.oldSetNetworkedString or entMeta.SetNetworkedString
|
|
function entMeta:SetNetworkedString(str, val)
|
|
if str ~= "usergroup" then return FPP.oldSetNetworkedString(self, str, val) end
|
|
|
|
userGroupRecalculate(self)
|
|
return FPP.oldSetNetworkedString(self, str, val)
|
|
end
|