Initial commit
This commit is contained in:
587
gamemodes/darkrp/gamemode/modules/fpp/pp/server/ownability.lua
Normal file
587
gamemodes/darkrp/gamemode/modules/fpp/pp/server/ownability.lua
Normal file
@@ -0,0 +1,587 @@
|
||||
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
|
||||
Reference in New Issue
Block a user