452 lines
12 KiB
Lua
452 lines
12 KiB
Lua
--[[------------------------------------------
|
|
|
|
A.P.G. - a lightweight Anti Prop Griefing solution (v2.2.0)
|
|
Made by :
|
|
- While True (http://steamcommunity.com/id/76561197972967270)
|
|
- LuaTenshi (http://steamcommunity.com/id/76561198096713277)
|
|
|
|
Licensed to : http://steamcommunity.com/id/76561198136465722
|
|
|
|
]]--------------------------------------------
|
|
|
|
util.AddNetworkString("apg_notice_s2c")
|
|
APG = APG or {}
|
|
|
|
local IsValid = IsValid
|
|
local table = table
|
|
local isentity = isentity
|
|
|
|
--[[------------------------------------------
|
|
ENTITY Related
|
|
]]--------------------------------------------
|
|
|
|
function APG.canPhysGun( ent, ply )
|
|
if not IsValid(ent) then return false end -- The entity isn't valid, don't pickup.
|
|
if ply.APG_CantPickup then return false end -- Is APG blocking the pickup?
|
|
if ent.CPPICanPhysgun then return ent:CPPICanPhysgun(ply) end -- Let CPPI handle things from here.
|
|
|
|
return (not ent.PhysgunDisabled) -- By default everything can be picked up, unless it is PhysgunDisabled.
|
|
end
|
|
|
|
function APG.isBadEnt( ent )
|
|
if not IsValid(ent) then return false end
|
|
if ent.jailWall == true then return false end
|
|
if ent.IsWeapon and ent:IsWeapon() then return false end
|
|
|
|
local h = hook.Run("APGisBadEnt", ent)
|
|
if isbool(h) then return h end
|
|
|
|
local class = ent:GetClass()
|
|
for k, v in pairs (APG.cfg["bad_ents"].value) do
|
|
if ( v and k == class ) or (not v and string.find( class, k) ) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function APG.getOwner( ent )
|
|
local owner, _ = ent:CPPIGetOwner() or ent.FPPOwner or nil
|
|
return owner
|
|
end
|
|
|
|
function APG.killVelocity(ent, extend, freeze, wake_target)
|
|
local vec = Vector()
|
|
|
|
if ent.GetClass and ent:GetClass() == "player" then ent:SetVelocity(ent:GetVelocity()*-1) return end
|
|
if ent:IsWorld() then return end
|
|
|
|
ent:SetVelocity(vec)
|
|
|
|
local function killvel(phys, freeze)
|
|
if not IsValid(phys) then return end
|
|
if freeze then phys:EnableMotion(false) return end
|
|
|
|
local collision = phys:IsCollisionEnabled()
|
|
|
|
phys:EnableCollisions(false)
|
|
|
|
phys:SetVelocity(vec)
|
|
phys:SetVelocityInstantaneous(vec)
|
|
phys:AddAngleVelocity(phys:GetAngleVelocity()*-1)
|
|
|
|
phys:EnableCollisions(collision)
|
|
|
|
phys:Sleep()
|
|
phys:RecheckCollisionFilter()
|
|
end
|
|
|
|
for i = 0, ent:GetPhysicsObjectCount() do killvel(ent:GetPhysicsObjectNum(i), freeze) end -- Includes self?
|
|
|
|
if extend then
|
|
for _,v in next, constraint.GetAllConstrainedEntities(ent) do killvel(v:GetPhysicsObject(), freeze) end
|
|
end
|
|
|
|
if wake_target then
|
|
local phys = ent:GetPhysicsObject()
|
|
if IsValid(phys) then
|
|
phys:Wake()
|
|
end
|
|
end
|
|
|
|
ent:CollisionRulesChanged()
|
|
end
|
|
|
|
function APG.FindWAC(ent) -- Note: Add a config to disable this check.
|
|
if not IsValid(ent) then return false end
|
|
if not APG.cfg["vehIncludeWAC"].value then return false end
|
|
|
|
local e
|
|
local i = 0
|
|
if ent.wac_seatswitch or ent.wac_ignore then return true end
|
|
for _,v in next, constraint.GetAllConstrainedEntities(ent) do
|
|
if v.wac_seatswitch or v.wac_ignore then e = v break end
|
|
if i > 12 then break end -- Only check up to 12.
|
|
i = i + 1
|
|
end
|
|
|
|
return IsValid(e)
|
|
end
|
|
|
|
function APG.cleanUp( mode, notify, specific )
|
|
local mode = mode or "unfrozen"
|
|
for _, v in next, specific or ents.GetAll() do
|
|
APG.killVelocity(v,false)
|
|
if not APG.isBadEnt(v) or not APG.getOwner( v ) or v:GetParent():IsVehicle() or APG.FindWAC(v) then continue end
|
|
if mode == "unfrozen" and v.APG_Frozen then -- Wether to clean only not frozen ents or all ents
|
|
continue
|
|
else
|
|
v:Remove()
|
|
end
|
|
end
|
|
end
|
|
|
|
function APG.ghostThemAll( notify )
|
|
for _, v in next, ents.GetAll() do
|
|
if not APG.isBadEnt(v) or not APG.getOwner( v ) or v:GetParent():IsVehicle() or v.APG_Frozen then continue end
|
|
APG.entGhost( v, false, true )
|
|
end
|
|
end
|
|
|
|
function APG.freezeIt( ent )
|
|
local pObj = ent:GetPhysicsObject()
|
|
if IsValid(pObj) then
|
|
pObj:EnableMotion( false)
|
|
ent.APG_Frozen = true
|
|
end
|
|
end
|
|
|
|
function APG.freezeProps( notify )
|
|
for _, v in next, ents.GetAll() do
|
|
if not APG.isBadEnt(v) or not APG.getOwner( v ) then continue end
|
|
APG.freezeIt( v )
|
|
end
|
|
end
|
|
|
|
local function GetPhysenv()
|
|
local env = physenv.GetPerformanceSettings()
|
|
local con = {}
|
|
local vars = {
|
|
"phys_upimpactforcescale",
|
|
"phys_impactforcescale",
|
|
"phys_pushscale",
|
|
"sv_turbophysics",
|
|
}
|
|
|
|
for _,v in next, vars do
|
|
local var = GetConVar(v)
|
|
con[v] = var and var:GetString() or nil
|
|
end
|
|
|
|
return {con = con, env = env}
|
|
end
|
|
|
|
function APG.smartCleanup( notify )
|
|
local defaults = GetPhysenv()
|
|
local phys = table.Copy(defaults.env)
|
|
|
|
hook.Add("PlayerSpawnObject", "APG_smartCleanup", function() return false end)
|
|
|
|
RunConsoleCommand("phys_upimpactforcescale","0")
|
|
RunConsoleCommand("phys_impactforcescale", "0")
|
|
RunConsoleCommand("phys_pushscale", "0")
|
|
RunConsoleCommand("sv_turbophysics", "1")
|
|
|
|
phys.MaxCollisionChecksPerTimestep = 0
|
|
phys.MaxAngularVelocity = 0
|
|
phys.MaxVelocity = 0
|
|
physenv.SetPerformanceSettings(phys)
|
|
|
|
local sphere = ents.FindInSphere
|
|
local all = ents.GetAll()
|
|
local bad = {}
|
|
|
|
for _, v in next, all do
|
|
if IsValid(v) and v.GetPhysicsObject then
|
|
local phys = v:GetPhysicsObject()
|
|
if IsValid(phys) and phys:IsMotionEnabled() then
|
|
if v.isFadingDoor and APG.isBadEnt(ent) then
|
|
SafeRemoveEntity(v)
|
|
else
|
|
table.insert(bad, {ent = v, phys = phys})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
APG.freezeProps( notify )
|
|
|
|
for _, v in next, bad do
|
|
local count = 0
|
|
|
|
local owner = APG.getOwner(v.ent)
|
|
local space = sphere(v.ent:GetPos(), 7)
|
|
local cache = {}
|
|
|
|
for _, ent in next, space do
|
|
if owner == APG.getOwner(ent) then
|
|
count = count + 1
|
|
table.insert(cache, ent)
|
|
end
|
|
end
|
|
|
|
if count > 4 then
|
|
for _, ent in next, cache do
|
|
if APG.isBadEnt(ent) then
|
|
SafeRemoveEntity(ent)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
timer.Simple(1.5, function() -- Give a few seconds for the engine to catch up.
|
|
for k,v in next, defaults.con do
|
|
RunConsoleCommand(k, v)
|
|
end
|
|
physenv.SetPerformanceSettings(defaults.env)
|
|
hook.Remove("PlayerSpawnObject", "APG_smartCleanup")
|
|
end)
|
|
end
|
|
|
|
function APG.ForcePlayerDrop(ply, ent)
|
|
ply:ConCommand("-attack")
|
|
timer.Simple(0.1, function()
|
|
ent:ForcePlayerDrop()
|
|
end)
|
|
end
|
|
|
|
function APG.blockPickup( ply )
|
|
if not IsValid(ply) or ply.APG_CantPickup then return end
|
|
ply.APG_CantPickup = true
|
|
timer.Simple(10, function()
|
|
if IsValid(ply) then
|
|
ply.APG_CantPickup = false
|
|
end
|
|
end)
|
|
end
|
|
|
|
function APG.notify(msg, targets, level, log) -- The most advanced notify function in the world.
|
|
local logged = false
|
|
|
|
local msg = string.Trim(tostring(msg))
|
|
local level = level or 0
|
|
|
|
if type(level) == "string" then
|
|
level = string.lower(level)
|
|
level = level == "notice" and 0 or level == "warning" and 1 or level == "alert" and 2
|
|
end
|
|
|
|
if isentity(targets) and IsValid(targets) and targets:GetClass() == "player" then
|
|
targets = {targets}
|
|
elseif type(targets) ~= "table" then -- Convert to a table.
|
|
targets = string.lower(tostring(targets))
|
|
if targets == "1" or targets == "superadmins" then
|
|
local new_targets = {}
|
|
for _,v in next, player.GetHumans() do
|
|
if not IsValid(v) then continue end
|
|
if not (v:IsSuperAdmin()) then continue end
|
|
table.insert(new_targets,v)
|
|
end
|
|
targets = new_targets
|
|
elseif targets == "2" or targets == "admins" then
|
|
local new_targets = {}
|
|
for _,v in next, player.GetHumans() do
|
|
if not IsValid(v) then continue end
|
|
if not (v:IsAdmin() or v:IsSuperAdmin()) then continue end
|
|
table.insert(new_targets,v)
|
|
end
|
|
targets = new_targets
|
|
elseif targets == "0" or targets == "all" or targets == "everyone" then
|
|
targets = player.GetHumans()
|
|
end
|
|
end
|
|
|
|
msg = (string.Trim(msg or "") ~= "") and msg or nil
|
|
|
|
if msg and (log or level >= 2) then
|
|
ServerLog("[APG] ",msg.."\n")
|
|
end
|
|
|
|
if type(targets) ~= "table" then return false end
|
|
|
|
for _,v in next, targets do
|
|
if not IsValid(v) then continue end
|
|
net.Start("apg_notice_s2c")
|
|
net.WriteUInt(level,3)
|
|
net.WriteString(msg)
|
|
net.Send(v)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
--[[------------------------------------------
|
|
Entity pickup part
|
|
]]--------------------------------------------
|
|
|
|
hook.Add("PhysgunPickup","APG_PhysgunPickup", function(ply, ent)
|
|
if not APG.isBadEnt( ent ) then return end
|
|
if not APG.canPhysGun( ent, ply ) then return false end
|
|
|
|
ent.APG_Picked = true
|
|
ent.APG_Frozen = false
|
|
|
|
if ent.APG_HeldBy and ent.APG_HeldBy.plys and not ent.APG_HeldBy.plys[sid] then
|
|
local HasHolder = istable(ent.APG_HeldBy.plys) and (table.Count(ent.APG_HeldBy.plys) > 0)
|
|
local HeldByLast = ent.APG_HeldBy.last
|
|
|
|
if HasHolder then
|
|
if HeldByLast and (ply:IsAdmin() or ply:IsSuperAdmin()) then
|
|
ent:ForcePlayerDrop()
|
|
for _,v in next, ent.APG_HeldBy.plys do
|
|
APG.ForcePlayerDrop(v, ent)
|
|
end
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
|
|
ent.APG_HeldBy = (ent.APG_HeldBy and istable(ent.APG_HeldBy.plys)) and ent.APG_HeldBy or {plys={}}
|
|
ent.APG_HeldBy.plys[ply:SteamID()] = ply
|
|
ent.APG_HeldBy.last = {ply = ply, id = ply:SteamID()}
|
|
|
|
ply.APG_CurrentlyHolding = ent
|
|
end)
|
|
|
|
--[[--------------------
|
|
No Collide (between them) on props unfreezed
|
|
]]----------------------
|
|
hook.Add("PlayerUnfrozeObject", "APG_PlayerUnfrozeObject", function(ply, ent, object)
|
|
if not APG.isBadEnt( ent ) then return end
|
|
ent.APG_Frozen = false
|
|
end)
|
|
|
|
--[[------------------------------------------
|
|
Entity drop part
|
|
]]--------------------------------------------
|
|
|
|
--[[--------------------
|
|
PhysGun Drop and Anti Throw Props
|
|
]]----------------------
|
|
hook.Add( "PhysgunDrop", "APG_physGunDrop", function( ply, ent )
|
|
ent.APG_HeldBy = ent.APG_HeldBy or {}
|
|
|
|
if ent.APG_HeldBy.plys then
|
|
ent.APG_HeldBy.plys[ply:SteamID()] = nil -- Remove the holder.
|
|
end
|
|
|
|
ply.APG_CurrentlyHolding = nil
|
|
|
|
if #ent.APG_HeldBy > 0 then return end
|
|
ent.APG_Picked = false
|
|
|
|
if APG.isBadEnt( ent ) and not APG.cfg["AllowPK"].value then
|
|
APG.killVelocity(ent,true,false,true) -- Extend to constrained props, and wake target.
|
|
end
|
|
end)
|
|
|
|
--[[--------------------
|
|
Physgun Drop & Freeze
|
|
]]----------------------
|
|
hook.Add( "OnPhysgunFreeze", "APG_OnPhysgunFreeze", function( weap, phys, ent, ply )
|
|
if not APG.isBadEnt( ent ) then return end
|
|
ent.APG_Frozen = true
|
|
end)
|
|
|
|
|
|
--[[--------------------
|
|
Admin utility
|
|
]]----------------------
|
|
|
|
function APG.log(msg, ply)
|
|
if type(ply) ~= "string" and IsValid(ply) then
|
|
ply:PrintMessage(3, msg.."\n")
|
|
else
|
|
print(msg)
|
|
end
|
|
end
|
|
|
|
--[[--------------------
|
|
APG job manager
|
|
--]]----------------------
|
|
local toProcess = toProcess or {}
|
|
|
|
function APG.dJobRegister( job, delay, limit, func, onBegin, onEnd )
|
|
local tab = {
|
|
content = {},
|
|
delay = delay,
|
|
limit = limit,
|
|
func = func,
|
|
onBegin = onBegin or nil,
|
|
onEnd = onEnd or nil
|
|
}
|
|
toProcess[job] = tab
|
|
end
|
|
|
|
local function APG_delayedTick( job )
|
|
if toProcess[job].processing and toProcess[job].processing == true then return end
|
|
toProcess[job].processing = true
|
|
if toProcess[job].onBegin then toProcess[job].onBegin() end
|
|
local delay, pLimit = toProcess[job].delay, toProcess[job].limit
|
|
local total = #toProcess[job].content
|
|
local count = math.Clamp(total,0,pLimit)
|
|
for i = 1, count do
|
|
local cur = toProcess[job].content[1]
|
|
timer.Create( "delay_" .. job .. "_" .. i , ( i - 1 ) * delay , 1, function()
|
|
toProcess[job].func( cur )
|
|
end)
|
|
table.remove(toProcess[job].content, 1)
|
|
end
|
|
timer.Create("dJob_" .. job .. "_process", ( count * delay ) + 0.1 , 1, function() toProcess[job].processing = false
|
|
if #toProcess[job].content < 1 and toProcess[job].onEnd then toProcess[job].onEnd() end
|
|
end)
|
|
end
|
|
|
|
function APG.startDJob( job, content )
|
|
if not job or not isstring(job) or not content then return end
|
|
if not toProcess or not toProcess[job] then
|
|
ErrorNoHalt("[APG] No Process Found, Attempting Reload!\n---\nThis Shouldn't Happen Concider Restarting!\n")
|
|
APG.reload()
|
|
return
|
|
end
|
|
|
|
if table.HasValue(toProcess[job].content, content) then return end
|
|
|
|
-- Is it a problem if there is a same ent being unghosted twice ?
|
|
table.insert( toProcess[job].content, content )
|
|
hook.Add("Tick", "APG_delayed_" .. job, function()
|
|
if #toProcess[job].content > 0 then
|
|
APG_delayedTick( job )
|
|
else
|
|
hook.Remove("Tick", "APG_delayed_" .. job)
|
|
end
|
|
end)
|
|
end
|
|
|
|
hook.Add("PostGamemodeLoaded", "APG_Load", function()
|
|
timer.Simple(0, function() -- Make sure we load last!
|
|
APG.reload()
|
|
end)
|
|
end)
|