From 03f088ff55d13f2ac9bdbadcf467e45810eda8fd Mon Sep 17 00:00:00 2001 From: Nebual Date: Tue, 4 Oct 2016 12:13:13 -0700 Subject: [PATCH] Initial Nadmod Prop Protection commit, v1.3.1 --- addon.json | 14 + lua/autorun/client/cl_nadmodpp.lua | 205 +++++++++++ lua/autorun/server/nadmod_pp.lua | 561 +++++++++++++++++++++++++++++ 3 files changed, 780 insertions(+) create mode 100644 addon.json create mode 100644 lua/autorun/client/cl_nadmodpp.lua create mode 100644 lua/autorun/server/nadmod_pp.lua diff --git a/addon.json b/addon.json new file mode 100644 index 0000000..4f7f31c --- /dev/null +++ b/addon.json @@ -0,0 +1,14 @@ +{ + "title" : "Nadmod Prop Protection", + "type" : "tool", + "tags" : [ "build" ], + "description": "NPP is a simple and efficient Prop Protection system. \n\n* Admins can touch all props (configurable)\n* All player spawned entities are protected by default from:\n- All Tools\n- Properties menu (right clicking props while holding C)\n- Gravity gun punt/pickup\n- Physgun pickup/reload\n* Has a friends system with cpanel that saves friends to file\n* Quick and easy buttons to cleanup either Disconnected Player's stuff or specific players, which also show the current # of entities that player owns\n* Autoclean Disconnected Player's props timer\n* Simple overlay shown on all entities shows owner, class, model, and entID\n* Freshly written for Gmod13, it includes blocking the new CanProperty (right click menu)\n* CPPI Compliant\n\nFor more information, see the Facepunch thread http://facepunch.com/showthread.php?t=1242185\nNote: Initially Protection is disabled serverwide, just tick on the Main Power Switch in the Admin CPanel and hit Apply.", + "ignore" : + [ + ".git/*", + "*.psd", + "*.vcproj", + "*.svn*", + "*.txt" + ] +} \ No newline at end of file diff --git a/lua/autorun/client/cl_nadmodpp.lua b/lua/autorun/client/cl_nadmodpp.lua new file mode 100644 index 0000000..9d6f137 --- /dev/null +++ b/lua/autorun/client/cl_nadmodpp.lua @@ -0,0 +1,205 @@ +-- ================================= +-- NADMOD PP - Prop Protection +-- By Nebual@nebtown.info 2012 +-- Menus designed after SpaceTech's Simple Prop Protection +-- ================================= +if !NADMOD then + NADMOD = {} + NADMOD.PropOwners = {} + NADMOD.PPConfig = {} + NADMOD.Friends = {} +end + +local Props = NADMOD.PropOwners +net.Receive("nadmod_propowners",function(len) + local num = net.ReadUInt(16) + for k=1,num do + local id,str = net.ReadUInt(16), net.ReadString() + if str == "-" then Props[id] = nil + elseif str == "W" then Props[id] = "World" + elseif str == "O" then Props[id] = "Ownerless" + else Props[id] = str + end + end +end) + +local font = "ChatFont" +hook.Add("HUDPaint", "NADMOD.HUDPaint", function() + local tr = LocalPlayer():GetEyeTrace() + if !tr.HitNonWorld then return end + local ent = tr.Entity + if ent:IsValid() && !ent:IsPlayer() then + local text = "Owner: " .. (Props[ent:EntIndex()] or "N/A") + local text2 = "'"..string.sub(table.remove(string.Explode("/", ent:GetModel() or "?")), 1,-5).."' ["..ent:EntIndex().."]" + local text3 = ent:GetClass() + surface.SetFont(font) + local Width, Height = surface.GetTextSize(text) + local w2,h2 = surface.GetTextSize(text2) + local w3,h3 = surface.GetTextSize(text3) + local boxHeight = Height + h2 + h3 + 16 + local boxWidth = math.Max(Width,w2,w3) + 25 + draw.RoundedBox(4, ScrW() - (boxWidth + 4), (ScrH()/2 - 200) - 16, boxWidth, boxHeight, Color(0, 0, 0, 150)) + draw.SimpleText(text, font, ScrW() - (Width / 2) - 20, ScrH()/2 - 200, Color(255, 255, 255, 255), 1, 1) + draw.SimpleText(text2, font, ScrW() - (w2 / 2) - 20, ScrH()/2 - 200 + Height, Color(255, 255, 255, 255), 1, 1) + draw.SimpleText(text3, font, ScrW() - (w3 / 2) - 20, ScrH()/2 - 200 + Height + h2, Color(255, 255, 255, 255), 1, 1) + end +end) + +function NADMOD.CleanCLRagdolls() + for k,v in pairs(ents.FindByClass("class C_ClientRagdoll")) do v:SetNoDraw(true) end + for k,v in pairs(ents.FindByClass("class C_BaseAnimating")) do v:SetNoDraw(true) end +end +net.Receive("nadmod_cleanclragdolls", NADMOD.CleanCLRagdolls) + +-- ============================= +-- NADMOD PP CPanels +-- ============================= +net.Receive("nadmod_ppconfig",function(len) + NADMOD.PPConfig = net.ReadTable() + for k,v in pairs(NADMOD.PPConfig) do + local val = v + if isbool(v) then val = v and "1" or "0" end + + CreateClientConVar("npp_"..k,val, false, false) + RunConsoleCommand("npp_"..k,val) + end + NADMOD.AdminPanel(NADMOD.AdminCPanel, true) +end) + +concommand.Add("npp_apply",function(ply,cmd,args) + for k,v in pairs(NADMOD.PPConfig) do + if isbool(v) then NADMOD.PPConfig[k] = GetConVar("npp_"..k):GetBool() + elseif isnumber(v) then NADMOD.PPConfig[k] = GetConVarNumber("npp_"..k) + else NADMOD.PPConfig[k] = GetConVarString("npp_"..k) + end + end + net.Start("nadmod_ppconfig") + net.WriteTable(NADMOD.PPConfig) + net.SendToServer() +end) + +function NADMOD.AdminPanel(Panel, runByNetReceive) + if Panel then + if !NADMOD.AdminCPanel then NADMOD.AdminCPanel = Panel end + end + if not runByNetReceive then + RunConsoleCommand("npp_refreshconfig") + timer.Create("NADMOD.AdminPanelCheckFail",0.75,1,function() + Panel:ClearControls() + Panel:Help("Waiting for the server to say you're an admin...") + end) + return + end + + timer.Remove("NADMOD.AdminPanelCheckFail") + Panel:ClearControls() + Panel:SetName("NADMOD PP Admin Panel") + + Panel:CheckBox( "Main PP Power Switch", "npp_toggle") + Panel:CheckBox( "Admins can touch anything", "npp_adminall") + local use_protection = Panel:CheckBox( "Use (E) Protection", "npp_use") + use_protection:SetToolTip("Stop nonfriends from entering vehicles, pushing buttons/doors") + + local txt = Panel:Help("Autoclean Disconnected Players?") + txt:SetAutoStretchVertical(false) + txt:SetContentAlignment( TEXT_ALIGN_CENTER ) + local autoclean_admins = Panel:CheckBox( "Autoclean Admins", "npp_autocdpadmins") + autoclean_admins:SetToolTip("Should Admin Props also be autocleaned?") + local autoclean_timer = Panel:NumSlider("Autoclean Timer", "npp_autocdp", 0, 1200, 0 ) + autoclean_timer:SetToolTip("0 disables autocleaning") + Panel:Button( "Apply Settings", "npp_apply") + + local txt = Panel:Help(" Cleanup Panel") + txt:SetContentAlignment( TEXT_ALIGN_CENTER ) + txt:SetFont("DermaDefaultBold") + txt:SetAutoStretchVertical(false) + + local counts = {} + for k,v in pairs(NADMOD.PropOwners) do + counts[v] = (counts[v] or 0) + 1 + end + local dccount = 0 + for k,v in pairs(counts) do + if k != "World" and k != "Ownerless" then dccount = dccount + v end + end + for k, ply in pairs(player.GetAll()) do + if IsValid(ply) then + Panel:Button( ply:Nick().." ("..(counts[ply:Nick()] or 0)..")", "nadmod_cleanupprops", ply:EntIndex() ) + dccount = dccount - (counts[ply:Nick()] or 0) + end + end + + Panel:Help(""):SetAutoStretchVertical(false) -- Spacer + Panel:Button("Cleanup Disconnected Players Props ("..dccount..")", "nadmod_cdp") + Panel:Button("Cleanup All NPCs", "nadmod_cleanclass", "npc_*") + Panel:Button("Cleanup All Ragdolls", "nadmod_cleanclass", "prop_ragdol*") + Panel:Button("Cleanup Clientside Ragdolls", "nadmod_cleanclragdolls") + Panel:Button("Cleanup World Ropes", "nadmod_cleanworldropes") +end + +net.Receive("nadmod_ppfriends",function(len) + NADMOD.Friends = net.ReadTable() + for _,tar in pairs(player.GetAll()) do + CreateClientConVar("npp_friend_"..tar:UniqueID(),NADMOD.Friends[tar:SteamID()] and "1" or "0", false, false) + RunConsoleCommand("npp_friend_"..tar:UniqueID(),NADMOD.Friends[tar:SteamID()] and "1" or "0") + end +end) + +concommand.Add("npp_applyfriends",function(ply,cmd,args) + for _,tar in pairs(player.GetAll()) do + NADMOD.Friends[tar:SteamID()] = GetConVar("npp_friend_"..tar:UniqueID()):GetBool() + end + net.Start("nadmod_ppfriends") + net.WriteTable(NADMOD.Friends) + net.SendToServer() +end) + +function NADMOD.ClientPanel(Panel) + RunConsoleCommand("npp_refreshfriends") + Panel:ClearControls() + if !NADMOD.ClientCPanel then NADMOD.ClientCPanel = Panel end + Panel:SetName("NADMOD - Client Panel") + + Panel:Button("Cleanup Props", "nadmod_cleanupprops") + Panel:Button("Clear Clientside Ragdolls", "nadmod_cleanclragdolls") + + local txt = Panel:Help(" Friends Panel") + txt:SetContentAlignment( TEXT_ALIGN_CENTER ) + txt:SetFont("DermaDefaultBold") + txt:SetAutoStretchVertical(false) + + local Players = player.GetAll() + if(table.Count(Players) == 1) then + Panel:Help("No Other Players Are Online") + else + for _, tar in pairs(Players) do + if(IsValid(tar) and tar != LocalPlayer()) then + Panel:CheckBox(tar:Nick(), "npp_friend_"..tar:UniqueID()) + end + end + Panel:Button("Apply Friends", "npp_applyfriends") + end +end + +function NADMOD.SpawnMenuOpen() + if NADMOD.AdminCPanel then + NADMOD.AdminPanel(NADMOD.AdminCPanel) + end + if NADMOD.ClientCPanel then + NADMOD.ClientPanel(NADMOD.ClientCPanel) + end +end +hook.Add("SpawnMenuOpen", "NADMOD.SpawnMenuOpen", NADMOD.SpawnMenuOpen) + +function NADMOD.PopulateToolMenu() + spawnmenu.AddToolMenuOption("Utilities", "NADMOD Prop Protection", "Admin", "Admin", "", "", NADMOD.AdminPanel) + spawnmenu.AddToolMenuOption("Utilities", "NADMOD Prop Protection", "Client", "Client", "", "", NADMOD.ClientPanel) +end +hook.Add("PopulateToolMenu", "NADMOD.PopulateToolMenu", NADMOD.PopulateToolMenu) + +net.Receive("nadmod_notify", function(len) + local text = net.ReadString() + notification.AddLegacy(text, NOTIFY_GENERIC, 5) + surface.PlaySound("ambient/water/drip"..math.random(1, 4)..".wav") + print(text) +end) diff --git a/lua/autorun/server/nadmod_pp.lua b/lua/autorun/server/nadmod_pp.lua new file mode 100644 index 0000000..760086f --- /dev/null +++ b/lua/autorun/server/nadmod_pp.lua @@ -0,0 +1,561 @@ +-- Nebual 2012 (nebual@nebtown.info) presents: +-- NADMOD Prop Protection +-- Inspired by Spacetech's Simple Prop Protection, <3's +-- Bugs? Feature requests? Email me or poke the Facepunch http://www.facepunch.com/showthread.php?t=1221183 + +if not NADMOD then + -- User is running without my Admin mod NADMOD, lets just copy some required initialization stuff over here + concommand.Add("nadmod_reload", function(ply,cmd,args) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + if args[1] == "full" then NADMOD = nil end + include("autorun/server/nadmod_pp.lua") + end) + NADMOD = util.JSONToTable(file.Read("nadmod_config.txt","DATA") or "") or {Users = {}, Groups = {}, Bans = {}, PPConfig = {}} + function NADMOD.Save() + file.Write("nadmod_config.txt", util.TableToJSON({Users = NADMOD.Users, Groups = NADMOD.Groups, Bans = NADMOD.Bans, PPConfig = NADMOD.PPConfig})) + end + hook.Add("Shutdown","NADMOD.Save",NADMOD.Save) + function NADMOD.FindPlayer(nick) + if not nick or nick == "" then return end + nick = string.lower(nick) + local num = tonumber(nick) + for _,v in pairs(player.GetAll()) do + if string.lower(v:Nick()) == nick then return v -- Exact name match + elseif v:UserID() == num then return v -- UserID match (from status) + end + end + -- If the above two exact searches fail, try doing a partial search + for _,v in pairs(player.GetAll()) do + if string.find(string.lower(v:Nick()), nick) then return v end + end + end +end +if not NADMOD.Props then + -- NADMOD PP Initialization + NADMOD.PPVersion = "1.3.1" + NADMOD.Props = {} // {entid = {Ent = ent, Owner = ply, SteamID = ply:SteamID(), Name = ply:Nick() or "W" or "O"}} + NADMOD.PropOwnersSmall = {} // A smaller buffer of PropOwner names to send to current players + NADMOD.AutoCDPTimers = {} + + local oldCPPI = CPPI + CPPI = {} + + -- Copy over default settings if they aren't present in the disk's PPConfig + for k,v in pairs({toggle=true,use=false,adminall=true,autocdp=0,autocdpadmins=false}) do + if NADMOD.PPConfig[k] == nil then NADMOD.PPConfig[k] = v end + end + + AddCSLuaFile("autorun/client/cl_nadmodpp.lua") + util.AddNetworkString("nadmod_propowners") + util.AddNetworkString("nadmod_ppfriends") + util.AddNetworkString("nadmod_ppconfig") + util.AddNetworkString("nadmod_cleanclragdolls") + util.AddNetworkString("nadmod_notify") + + timer.Create("WarnOtherPPs", 0, 1, function() + //Print a harmless error if we detect other PP's active, since this often leads to confusion. NPP'll still load fine + //Known CPPI plugins: FPP, SPP, NPP, ULX's UPS + if not oldCPPI or (oldCPPI.GetName and oldCPPI:GetName() == "Nadmod Prop Protection") then oldCPPI = CPPI end + if oldCPPI and (not oldCPPI.GetName or oldCPPI:GetName() ~= "Nadmod Prop Protection") then Error("NPP has detected "..(oldCPPI.GetName and oldCPPI:GetName() or "another CPPI PP").." is installed, you probably only want one PP active at a time!!\n") + elseif PP_Settings then Error("NPP has detected Evolve's PP plugin, you probably only want one PP active at a time!!\n") + end + end) +end +local metaply = FindMetaTable("Player") +local metaent = FindMetaTable("Entity") + +-- Does your admin mod not seem to work with Nadmod PP? Try overriding this function! +function NADMOD.IsPPAdmin(ply) + if NADMOD.HasPermission then + return NADMOD.HasPermission(ply, "PP_All") + else + -- If the admin mod NADMOD isn't present, just default to using IsAdmin + return ply:IsAdmin() + end +end + +function NADMOD.PPInitPlayer(ply) + local steamid = ply:SteamID() + for _,v in pairs(NADMOD.Props) do + if v.SteamID == steamid then + v.Owner = ply + v.Ent.SPPOwner = ply + if v.Ent.SetPlayer then v.Ent:SetPlayer(ply) end + end + end + net.Start("nadmod_propowners") + net.WriteUInt(table.Count(NADMOD.Props),16) + for k,v in pairs(NADMOD.Props) do + net.WriteUInt(k,16) + net.WriteString(v.Name) + end + net.Send(ply) +end +hook.Add("PlayerInitialSpawn", "NADMOD.PPInitPlayer", NADMOD.PPInitPlayer) + +function NADMOD.PPOwnWeapons(ply) + timer.Create("NADMOD.PPOwnWeapons", 0.2, 1, function() + if not IsValid(ply) then return end + for k,v in pairs(ply:GetWeapons()) do + if not IsValid(v) then continue end + NADMOD.SetOwnerWorld(v) + end + end) +end +hook.Add("PlayerSpawn", "NADMOD.PPOwnWeapons", NADMOD.PPOwnWeapons) + +function NADMOD.RefreshOwners() + if next(NADMOD.PropOwnersSmall) then + net.Start("nadmod_propowners") + net.WriteUInt(table.Count(NADMOD.PropOwnersSmall),16) + for k,v in pairs(NADMOD.PropOwnersSmall) do + net.WriteUInt(k,16) + net.WriteString(v) + end + //net.WriteTable(NADMOD.PropOwnersSmall) + net.Broadcast() + table.Empty(NADMOD.PropOwnersSmall) + end +end +timer.Create("NADMOD.RefreshOwners", 1, 0, NADMOD.RefreshOwners) + +function NADMOD.IsFriendProp(ply, ent) + if IsValid(ent) && IsValid(ply) && ply:IsPlayer() && NADMOD.Props[ent:EntIndex()] then + local ownerSteamID = NADMOD.Props[ent:EntIndex()].SteamID + if NADMOD.Users[ownerSteamID] then + local friends = NADMOD.Users[ownerSteamID].Friends + return friends && friends[ply:SteamID()] + end + end + return false +end + +function NADMOD.PlayerCanTouch(ply, ent) + -- If PP is off or the ent is worldspawn, let them touch it + if not tobool(NADMOD.PPConfig["toggle"]) or ent:IsWorld() then return true end + + if !IsValid(ent) or !IsValid(ply) or ent:IsPlayer() or !ply:IsPlayer() then return false end + + if not NADMOD.Props[ent:EntIndex()] then + local class = ent:GetClass() + if(string.find(class, "stone_") == 1 or string.find(class, "rock_") == 1 or string.find(class, "stargate_") == 0 or string.find(class, "dhd_") == 0 or class == "flag" or class == "item" or class == "predicted_viewmodel" or class == "gmod_hands" or class == "physgun_beam") then + NADMOD.SetOwnerWorld(ent) + elseif ent.GetPlayer and IsValid(ent:GetPlayer()) then + NADMOD.PlayerMakePropOwner(ent:GetPlayer(), ent) + elseif ent.GetOwner and (IsValid(ent:GetOwner()) or ent:GetOwner():IsWorld()) then + NADMOD.PlayerMakePropOwner(ent:GetOwner(), ent) + elseif ent:EntIndex() == 0 then + -- Players cannot take ownership of EntIndex 0 ents (constraints, func_'s, map lights) + return false + else + NADMOD.PlayerMakePropOwner(ply, ent) + NADMOD.Notify(ply, "You now own this " .. class .. " (" .. string.sub(table.remove(string.Explode("/", ent:GetModel() or "?")), 1,-5) .. ")" ) + return true + end + end + if !NADMOD.Props[ent:EntIndex()] then + -- To get here implies the ent has a 'valid' GetPlayer()/GetOwner(), but still couldn't get set properly + -- For example, if an NPC is sitting in jeep (??), the jeep's GetPlayer returns the driver? or something + ent:CPPISetOwnerless(true) + if !NADMOD.Props[ent:EntIndex()] then return end + end + -- Ownerless props can be touched by all + if NADMOD.Props[ent:EntIndex()].Name == "O" then return true end + -- Admins can touch anyones props + world + if NADMOD.PPConfig["adminall"] and NADMOD.IsPPAdmin(ply) then return true end + -- Players can touch their own props and friends + if NADMOD.Props[ent:EntIndex()].SteamID == ply:SteamID() or NADMOD.IsFriendProp(ply, ent) then return true end + + return false +end + +-- We could hook directly to PlayerCanTouch, but returning true stops other hooks from being called +function NADMOD.PlayerCanTouchSafe(ply, ent) + if !IsValid(ent) or ent:IsPlayer() then return end + if !NADMOD.PlayerCanTouch(ply,ent) then return false end +end +hook.Add("PhysgunPickup", "NADMOD.PhysgunPickup", NADMOD.PlayerCanTouchSafe) +hook.Add("CanProperty", "NADMOD.CanProperty", function(ply, mode, ent) return NADMOD.PlayerCanTouchSafe(ply, ent) end) +hook.Add("CanEditVariable", "NADMOD.CanEditVariable", function(ent, ply, key, val, editor) return NADMOD.PlayerCanTouchSafe(ply, ent) end) + +function NADMOD.OnPhysgunReload(weapon, ply) + local tr = util.TraceLine(util.GetPlayerTrace(ply)) + if not tr.HitNonWorld or !tr.Entity:IsValid() or tr.Entity:IsPlayer() then return end + if !NADMOD.PlayerCanTouch(ply, tr.Entity) then return false end +end +hook.Add("OnPhysgunReload", "NADMOD.OnPhysgunReload", NADMOD.OnPhysgunReload) + +-- Basically just PlayerCanTouchSafe, but world props are fine to gravgun +function NADMOD.GravGunPickup(ply, ent) + if !IsValid(ent) or ent:IsPlayer() then return end + if NADMOD.Props[ent:EntIndex()] and NADMOD.Props[ent:EntIndex()].Name == "W" then return end + if !NADMOD.PlayerCanTouch(ply,ent) then return false end +end +hook.Add("GravGunPunt", "NADMOD.GravGunPunt", NADMOD.GravGunPickup) +hook.Add("GravGunPickupAllowed", "NADMOD.GravGunPickupAllowed", NADMOD.GravGunPickup) + +NADMOD.PPWeirdTraces = {"wire_winch","wire_hydraulic","slider","hydraulic","winch","muscle"} +function NADMOD.CanTool(ply, tr, mode) + local ent = tr.Entity + if !ent:IsWorld() and (!ent:IsValid() or ent:IsPlayer()) then return false end + if !NADMOD.PlayerCanTouch(ply, ent) then + if not ((NADMOD.Props[ent:EntIndex()] or {}).Name == "W" and (mode == "wire_debugger" or mode == "wire_adv")) then + return false + end + elseif(mode == "nail") then + local Trace = {} + Trace.start = tr.HitPos + Trace.endpos = tr.HitPos + (ply:GetAimVector() * 16.0) + Trace.filter = {ply, tr.Entity} + local tr2 = util.TraceLine(Trace) + if(tr2.Hit and IsValid(tr2.Entity) and !tr2.Entity:IsPlayer()) then + if(!NADMOD.PlayerCanTouch(ply, tr2.Entity)) then + return false + end + end + elseif(table.HasValue(NADMOD.PPWeirdTraces, mode)) then + local Trace = {} + Trace.start = tr.HitPos + Trace.endpos = Trace.start + (tr.HitNormal * 16384) + Trace.filter = {ply} + local tr2 = util.TraceLine(Trace) + if(tr2.Hit and IsValid(tr2.Entity) and !tr2.Entity:IsPlayer()) then + if(!NADMOD.PlayerCanTouch(ply, tr2.Entity)) then + return false + end + end + elseif(mode == "remover") then + if(ply:KeyDown(IN_ATTACK2) or ply:KeyDownLast(IN_ATTACK2)) then + for k,v in pairs(constraint.GetAllConstrainedEntities(ent) or {}) do + if !NADMOD.PlayerCanTouch(ply, v) then + return false + end + end + end + end +end +hook.Add("CanTool", "NADMOD.CanTool", NADMOD.CanTool) + +function NADMOD.PlayerUse(ply, ent) + if !NADMOD.PPConfig["use"] or NADMOD.PlayerCanTouch(ply, ent) or (ent:IsValid() and NADMOD.Props[ent:EntIndex()].Name == "W") then + return + end + return false +end +hook.Add("PlayerUse", "NADMOD.PlayerUse", NADMOD.PlayerUse) + +--==========================================================-- +-- Ownership Setting Functions -- +--==========================================================-- + +function NADMOD.PlayerMakePropOwner(ply,ent) + if !IsValid(ent) or ent:IsPlayer() then return end + if ply:IsWorld() then return NADMOD.SetOwnerWorld(ent) end + if !IsValid(ply) or !ply:IsPlayer() then return end + NADMOD.Props[ent:EntIndex()] = { + Ent = ent, + Owner = ply, + SteamID = ply:SteamID(), + Name = ply:Nick() + } + NADMOD.PropOwnersSmall[ent:EntIndex()] = ply:Nick() + ent.SPPOwner = ply +end +-- Hook into the cleanup and sbox-limit adding functions to catch most props +if(cleanup) then + local backupcleanupAdd = cleanup.Add + function cleanup.Add(ply, enttype, ent) + if IsValid(ent) and ply:IsPlayer() then + NADMOD.PlayerMakePropOwner(ply, ent) + end + backupcleanupAdd(ply, enttype, ent) + end +end +if(metaply.AddCount) then + local backupAddCount = metaply.AddCount + function metaply:AddCount(enttype, ent) + NADMOD.PlayerMakePropOwner(self, ent) + backupAddCount(self, enttype, ent) + end +end +hook.Add("PlayerSpawnedSENT", "NADMOD.PlayerSpawnedSENT", NADMOD.PlayerMakePropOwner) +hook.Add("PlayerSpawnedVehicle", "NADMOD.PlayerSpawnedVehicle", NADMOD.PlayerMakePropOwner) +hook.Add("PlayerSpawnedSWEP", "NADMOD.PlayerSpawnedSWEP", NADMOD.PlayerMakePropOwner) + +function metaent:CPPISetOwnerless(bool) + if !IsValid(self) or self:IsPlayer() then return end + if(bool) then + NADMOD.Props[self:EntIndex()] = { + Ent = self, + Owner = game.GetWorld(), + SteamID = "O", + Name = "O" + } + NADMOD.PropOwnersSmall[self:EntIndex()] = "O" + self.SPPOwner = game.GetWorld() + else + NADMOD.EntityRemoved(self) + end +end + +function NADMOD.SetOwnerWorld(ent) + NADMOD.Props[ent:EntIndex()] = { + Ent = ent, + Owner = game.GetWorld(), + SteamID = "W", + Name = "W" + } + NADMOD.PropOwnersSmall[ent:EntIndex()] = "W" + ent.SPPOwner = game.GetWorld() +end + +-- Loop through all entities that exist when the map is loaded, these are all "world owned" entities +function NADMOD.WorldOwner() + local WorldEnts = 0 + for k,v in pairs(ents.FindByClass("*")) do + if(!v:IsPlayer() and (v:EntIndex() == 0 or !NADMOD.Props[v:EntIndex()])) and not IsValid(v.SPPOwner) then + if game.GetMap() == "gm_construct" and v:GetClass() == "func_brush" then + v:CPPISetOwnerless(true) + else + NADMOD.SetOwnerWorld(v) + end + WorldEnts = WorldEnts + 1 + end + end + print("Nadmod Prop Protection: "..WorldEnts.." props belong to world") +end +if CurTime() < 5 then timer.Create("NADMOD.PPFindWorldProps",7,1,NADMOD.WorldOwner) end + + +function NADMOD.EntityRemoved(ent) + NADMOD.Props[ent:EntIndex()] = nil + NADMOD.PropOwnersSmall[ent:EntIndex()] = "-" + if ent:IsValid() and ent:IsPlayer() and not ent:IsBot() then + -- This is more reliable than PlayerDisconnect + local steamid, nick = ent:SteamID(), ent:Nick() + if NADMOD.PPConfig.autocdp > 0 and (NADMOD.PPConfig.autocdpadmins or not NADMOD.IsPPAdmin(ent)) then + timer.Create("NADMOD.AutoCDP_"..steamid, NADMOD.PPConfig.autocdp, 1, function() + local count = NADMOD.CleanupPlayerProps(steamid) + if count > 0 then NADMOD.Notify(nick.."'s props ("..count..") have been autocleaned.") end + end) + NADMOD.AutoCDPTimers[nick] = steamid + end + end +end +hook.Add("EntityRemoved", "NADMOD.EntityRemoved", NADMOD.EntityRemoved) +-- AutoCDP timer removal +function NADMOD.ClearAutoCDP( ply, steamid, uniqueid ) + timer.Remove("NADMOD.AutoCDP_"..steamid) +end +hook.Add( "PlayerAuthed", "NADMOD.ClearAutoCDP", NADMOD.ClearAutoCDP ) -- This occurs at PlayerInitialSpawn, late +hook.Add( "PlayerConnect", "NADMOD.ClearAutoCDP", function(nick, address) -- This occurs early but is unreliable + NADMOD.ClearAutoCDP(nil, NADMOD.AutoCDPTimers[nick] or "") +end) + + + + +--==========================================================-- +-- Useful Concommands -- +--==========================================================-- +function NADMOD.CleanupPlayerProps(steamid) + local count = 0 + for k,v in pairs(NADMOD.Props) do + if(v.SteamID == steamid) then + if IsValid(v.Ent) then + if !v.Ent:GetPersistent() then + v.Ent:Remove() + count = count + 1 + end + else + NADMOD.EntityRemoved(v.Ent) + end + end + end + return count +end + +function NADMOD.CleanPlayer(tar) + if IsValid(tar) and tar:IsPlayer() then + local count = NADMOD.CleanupPlayerProps(tar:SteamID()) + NADMOD.Notify(tar:Nick().."'s props have been cleaned up ("..count..")") + end +end + +function NADMOD.CleanupProps(ply, cmd, args) + local EntIndex = args[1] + if not EntIndex or EntIndex == "" then + local count = NADMOD.CleanupPlayerProps(ply:SteamID()) + NADMOD.Notify(ply,"Your props have been cleaned up ("..count..")") + elseif !ply:IsValid() or NADMOD.IsPPAdmin(ply) then + NADMOD.CleanPlayer(Entity(EntIndex)) + end +end +concommand.Add("nadmod_cleanupprops", NADMOD.CleanupProps) + +function NADMOD.CleanPlayerConCommand(ply, cmd, args, fullstr) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + NADMOD.CleanPlayer(NADMOD.FindPlayer(fullstr)) +end +concommand.Add("nadmod_cleanplayer", NADMOD.CleanPlayerConCommand) + +-- Cleans all props whose owner's name contained arg1. nadmod_cleanplayer is better, but only works on online players +function NADMOD.CleanName(ply, cmd, args, fullstr) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + if fullstr == "" or fullstr == "W" then return end + local tarname = string.lower(fullstr) + local count = 0 + for k,v in pairs(NADMOD.Props) do + if IsValid(v.Ent) and string.find(string.lower(v.Name),tarname,1,true) then + v.Ent:Remove() + count = count + 1 + end + end + NADMOD.Notify(fullstr.."'s props ("..count..") have been cleaned up") +end +concommand.Add("nadmod_cleanname",NADMOD.CleanName) + +function NADMOD.CDP(ply, cmd, args) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + local count = 0 + for k,v in pairs(NADMOD.Props) do + if not v.Ent:IsValid() then + NADMOD.EntityRemoved(v.Ent) + elseif not IsValid(v.Owner) and (v.Name != "O" and v.Name != "W") and !v.Ent:GetPersistent() then + v.Ent:Remove() + count = count + 1 + end + end + NADMOD.Notify("Disconnected players props ("..count..") have been cleaned up") +end +concommand.Add("nadmod_cdp",NADMOD.CDP) + +function NADMOD.CleanClass(ply,cmd,args) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + if args[1] == "npc_*" then NADMOD.Notify("NPCs have been cleaned up") + elseif args[1] == "prop_ragdol*" then NADMOD.Notify("Ragdolls have been cleaned up") + else NADMOD.Notify(args[1].." have been cleaned up") + end + for _,v in ipairs(ents.FindByClass(args[1])) do v:Remove() end +end +concommand.Add("nadmod_cleanclass", NADMOD.CleanClass) + +function NADMOD.CleanCLRagdolls(ply,cmd,args) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + NADMOD.Notify("Clientside Ragdolls have been cleaned up") + net.Start("nadmod_cleanclragdolls") net.Broadcast() + game.RemoveRagdolls() +end +concommand.Add("nadmod_cleanclragdolls", NADMOD.CleanCLRagdolls) + +function NADMOD.CleanWorldRopes(ply,cmd,args) + if ply:IsValid() and not NADMOD.IsPPAdmin(ply) then return end + NADMOD.Notify("World ropes have been cleaned up") + for k,v in pairs(ents.FindByClass("keyframe_rope")) do + if v.Ent1 and v.Ent1:IsWorld() and v.Ent2 and v.Ent2:IsWorld() then v:Remove() end + end +end +concommand.Add("nadmod_cleanworldropes", NADMOD.CleanWorldRopes) + +-- Sends a Hint to the player specified +-- If only one argument, broadcast to all players +function NADMOD.Notify(ply, text) + net.Start("nadmod_notify") + if not text then + net.WriteString(ply) + net.Broadcast() + print("NADMOD: "..ply) + else + net.WriteString(text) + net.Send(ply) + end +end + +function NADMOD.DebugTotals(ply,cmd,args) + -- Prints out a list of how many props are owned by each player. + -- This information is available in the clientside admin panel, but this is nice for rcon + if IsValid(ply) then return end -- PrintTable only shows in serverconsole + local tab = {} + for k,v in pairs(NADMOD.Props) do + local name = v.Name + if name == "O" then name = "Ownerless" + elseif name == "W" then name = "World" + elseif not v.SPPOwner:IsValid() then name = "[Disconnected]"..name + end + tab[name] = (tab[name] or 0) + 1 + end + PrintTable(tab) +end +concommand.Add("nadmod_totals", NADMOD.DebugTotals) + + +--=========================================================-- +-- Clientside Callbacks for the Friends/Options panels -- +--=========================================================-- +concommand.Add("npp_refreshconfig",function(ply,cmd,args) + if not ply:IsValid() or not NADMOD.IsPPAdmin(ply) then return end + net.Start("nadmod_ppconfig") + net.WriteTable(NADMOD.PPConfig) + net.Send(ply) +end) +net.Receive("nadmod_ppconfig",function(len,ply) + if not ply:IsValid() or not NADMOD.IsPPAdmin(ply) then return end + NADMOD.PPConfig = net.ReadTable() + NADMOD.Save() + NADMOD.Notify(ply, "Settings received!") +end) +concommand.Add("npp_refreshfriends",function(ply,cmd,args) + if not ply:IsValid() then return end + local friends = {} + if NADMOD.Users[ply:SteamID()] then friends = table.Copy(NADMOD.Users[ply:SteamID()].Friends) or {} end + if NADMOD.PPConfig["adminall"] then + for _,v in pairs(player.GetAll()) do + if NADMOD.IsPPAdmin(v) then friends[v:SteamID()] = true end + end + end + net.Start("nadmod_ppfriends") + net.WriteTable(friends) + net.Send(ply) +end) +net.Receive("nadmod_ppfriends",function(len,ply) + if not ply:IsValid() then return end + if !NADMOD.Users[ply:SteamID()] then NADMOD.Users[ply:SteamID()] = {Rank = 1} end + NADMOD.Users[ply:SteamID()].Friends = NADMOD.Users[ply:SteamID()].Friends or {} + local outtab = NADMOD.Users[ply:SteamID()].Friends + + local players = {} + for _,v in pairs(player.GetAll()) do players[v:SteamID()] = v end + + for steamid,bool in pairs(net.ReadTable()) do + if players[steamid] and (not bool or not (NADMOD.IsPPAdmin(players[steamid]) and NADMOD.PPConfig["adminall"])) then -- Users may not add admins to their friends list + if bool then outtab[steamid] = true else outtab[steamid] = nil end -- Don't bother storing falses + end + end + NADMOD.Save() + NADMOD.Notify(ply, "Friends received!") +end) + +function CPPI:GetName() return "Nadmod Prop Protection" end +function CPPI:GetVersion() return NADMOD.PPVersion end +function metaply:CPPIGetFriends() + if not self:IsValid() then return {} end + local ret = {} + local friends = (NADMOD.Users[self:SteamID()] or {Friends={}}).Friends or {} + for _,v in pairs(player.GetAll()) do + if NADMOD.IsPPAdmin(v) or friends[v:SteamID()] then table.insert(ret,v) end + end + return ret +end +function metaent:CPPIGetOwner() return self.SPPOwner end +function metaent:CPPISetOwner(ply) return NADMOD.PlayerMakePropOwner(ply, self) end +function metaent:CPPICanTool(ply,mode) return NADMOD.CanTool(ply,{Entity=self},mode) != false end +function metaent:CPPICanPhysgun(ply) return NADMOD.PlayerCanTouch(ply,self) == true end +function metaent:CPPICanPickup(ply) return NADMOD.GravGunPickup(ply, self) != false end +function metaent:CPPICanPunt(ply) return NADMOD.GravGunPickup(ply, self) != false end +if E2Lib and E2Lib.replace_function then + E2Lib.replace_function("isOwner", function(ply, entity) + return NADMOD.PlayerCanTouch(ply,entity) + end) +end + +print("[NADMOD PP - NADMOD Prop Protection Module v"..NADMOD.PPVersion.." Loaded]")