Initial Commit

This commit is contained in:
NanoAi 2019-05-21 21:05:00 -04:00
commit 70986191dc
11 changed files with 648 additions and 0 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"Lua.diagnostics.disable": [
"undefined-global"
]
}

View File

@ -0,0 +1,5 @@
hook.Add("InitPostEntity", "PUG_Startup", function()
timer.Simple(0, function()
include("pug/sv_pug.lua")
end)
end)

View File

@ -0,0 +1,200 @@
local PUG = PUG
local hook, timer = hook, timer
local u = PUG.util
hook.Add("PUG_SetCollisionGroup", "PUGCollision", function( ent, group )
local isGroupNone = ( group == COLLISION_GROUP_NONE )
local checkEnt = ( ent.PUGBadEnt and not PUG:isGoodEnt( ent ) )
if isGroupNone and checkEnt and ( not ent.PUGFrozen ) then
return COLLISION_GROUP_INTERACTIVE
end
end)
hook.Add("PUG_EnableMotion", "PUGCollision", function( ent, _, bool )
if bool and ent.PUGBadEnt then
ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE )
end
end)
local function isTrap( ent )
local check = false
local isVehicle = u.isVehicle( ent )
local center = ent:LocalToWorld( ent:OBBCenter() )
local bRadius = ent:BoundingRadius()
for _,v in next, ents.FindInSphere( center, bRadius ) do
local isLivingPlayer = ( v:IsPlayer() and v:Alive() )
if isVehicle then
if isLivingPlayer then
-- Check if the distance between the sphere centers is less
-- than the sum of their radius.
local vCenter = v:LocalToWorld( v:OBBCenter() )
if center:Distance( vCenter ) < v:BoundingRadius() then
check = v
end
end
else
if isLivingPlayer then
local pos = v:GetPos()
local trace = { start = pos, endpos = pos, filter = v }
local tr = util.TraceEntity( trace, v )
if tr.Entity == ent then
check = v
end
end
end
if check then break end
end
return check and true or false
end
function PUG:Ghost( ent, ghost )
if not ghost then
self:UnGhost( ent )
return
end
if ent.jailWall then return end
if not ent.PUGBadEnt then return end
ent.FPPAntiSpamIsGhosted = nil -- Override FPP Ghosting.
ent.PUGGhost = ent.PUGGhost or {}
ent.PUGGhost.collision = ent:GetCollisionGroup()
-- If and old collision group was set get it.
if ent.OldCollisionGroup then -- FPP Compatibility
ent.PUGGhost.collision = ent.OldCollisionGroup
end
if ent.DPP_oldCollision then -- DPP Compatibility
ent.PUGGhost.collision = ent.DPP_oldCollision
end
ent.OldCollisionGroup = nil
ent.DPP_oldCollision = nil
ent.PUGGhosted = true
timer.Simple(0, function()
if not IsValid( ent ) then return end
if not ent.PUGGhost.colour then
ent.PUGGhost.colour = ent:GetColor()
-- Compatibility with other Ghosting
if ent.OldColor then
ent.PUGGhost.colour = ent.OldColor
end
if ent.__DPPColor then
ent.PUGGhost.colour = ent.__DPPColor
end
ent.OldColor = nil
ent.__DPPColor = nil
end
ent:SetColor( Color(4, 20, 36, 250) )
end)
ent.PUGGhost.render = ent:GetRenderMode()
ent:SetRenderMode( RENDERMODE_TRANSALPHA )
ent:DrawShadow( false )
if noCollide then
ent:SetCollisionGroup( COLLISION_GROUP_WORLD )
else
ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER )
end
do -- Fix magic surfing
local phys = ent:GetPhysicsObject()
if IsValid(phys) then
phys:EnableCollisions( false )
timer.Simple(0, function()
if IsValid(phys) then
phys:EnableCollisions( true )
end
end)
end
end
ent:CollisionRulesChanged()
end
function PUG:UnGhost( ent )
if not ent.PUGGhosted then return end
local trap = isTrap(ent)
local moving = u.entityIsMoving(ent)
if not ( trap or moving ) then
u.entityForceDrop( ent )
ent.APG_Ghosted = false
ent:DrawShadow( true )
ent:SetRenderMode( ent.APG_oldRenderMode or RENDERMODE_NORMAL )
ent:SetColor( ent.APG_oldColor or Color( 255, 255, 255, 255) )
ent.APG_oldColor = false
local newCollisionGroup = COLLISION_GROUP_INTERACTIVE
if PUG:isGoodEnt( ent ) then
newCollisionGroup = ent.PUGGhost.collision
else
if ent.PUGGhost.collision == COLLISION_GROUP_WORLD then
newCollisionGroup = COLLISION_GROUP_WORLD
else
if ent.PUGFrozen then
newCollisionGroup = COLLISION_GROUP_NONE
end
end
end
ent:SetCollisionGroup( newCollisionGroup )
ent:CollisionRulesChanged()
return true
else
--FIXME: Add notification here!
print("A prop has tried to unghost, but couldn't!")
return false
end
end
hook.Add("PUG_PostPhysgunPickup", "PUGGhosting", function(_, ent, canPickup)
u.addJob(function()
if not canPickup then return end
if IsValid( ent ) then
PUG:Ghost( ent, true )
end
end)
end)
hook.Add("PhysgunDrop", "PUGGhosting", function(_, ent)
timer.Simple(0.5, function()
u.addJob(function()
if u.isEntityHeld(ent) then return end
if IsValid( ent ) then
PUG:Ghost( ent, false )
end
end)
end)
end)
return {
hooks = {
["PUG_SetCollisionGroup"] = "PUGCollision",
["PUG_EnableMotion"] = "PUGCollision",
["PUG_PostPhysgunPickup"] = "PUGGhosting",
["PhysgunDrop"] = "PUGGhosting",
},
settings = {
["GhostColour"] = "4 20 36 250",
["GhostsNoCollide"] = "0",
}
}

View File

View File

View File

View File

View File

234
lua/pug/sv_pug.lua Normal file
View File

@ -0,0 +1,234 @@
local hook, timer = hook, timer
PUG = PUG or {}
PUG.util = include("pug/sv_pug_util.lua")
local u = PUG.util
-- TODO: Make badEnts & goodEnts use a Database.
local badEnts = {
["prop_physics"] = true,
["prop_ragdoll"] = true,
["sent_deployableballoons"] = true,
["sent_streamradio"] = true,
["gmod_button"] = true,
["gmod_hoverball"] = true,
["gmod_thruster"] = true,
["gmod_wheel"] = true,
["gmod_poly"] = true,
["keypad"] = true,
}
local goodEnts = {
["gmod_hands"] = true,
}
function PUG:isBadEnt( ent )
if type(ent) ~= "Entity" then return false end
if not IsValid(ent) then
return false
end
if ent.jailWall then
return false
end
if ent:GetPersistent() then
return false
end
if ent == Entity(0) or ent:IsWorld() then
return false
end
if badEnts[ ent:GetClass() ] == true then
return true
end
return false
end
function PUG:isGoodEnt( ent )
if type(ent) ~= "Entity" then return false end
if goodEnts[ ent:GetClass() ] == true then
return true
end
return false
end
function PUG:addBadEnt( class )
local typeResult = type(class)
assert(typeResult == "string", "string expected got " .. typeResult)
badEnts[class] = true
end
function PUG:addGoodEnt( class )
local typeResult = type(class)
assert(typeResult == "string", "string expected got " .. typeResult)
goodEnts[class] = true
end
do
local GM = GM or GAMEMODE
PUG._PhysgunPickup = PUG._PhysgunPickup or GM.PhysgunPickup
function GM:PhysgunPickup(ply, ent)
local canPickup = PUG._PhysgunPickup(self, ply, ent)
hook.Run( "PUG_PostPhysgunPickup", ply, ent, canPickup )
return canPickup
end
end
do
local ENT = FindMetaTable("Entity")
PUG._SetCollisionGroup = PUG._SetCollisionGroup or ENT.SetCollisionGroup
PUG._SetModelScale = PUG._SetModelScale or ENT.SetModelScale
PUG._SetColor = PUG._SetColor or ENT.SetColor
PUG._SetPos = PUG._SetPos or ENT.SetPos
function ENT:SetCollisionGroup( group )
local getHook = hook.Run("PUG_SetCollisionGroup", self, group)
group = getHook or group
if getHook ~= true then
PUG._SetCollisionGroup( self, group )
end
end
--NOTE: Fix SetColor behaving unpredictably + legacy support.
function ENT:SetColor( color, ... )
local r, g, b, a
if type(color) == "number" then
r = color
g = select(1, ...) or 255
b = select(2, ...) or 255
a = select(3, ...) or 255
color = Color(r, g, b, a)
elseif type(color) == "table" and not IsColor(color) then
r = color.r or 255
g = color.g or 255
b = color.b or 255
a = color.a or 255
color = Color(r, g, b, a)
end
if not IsColor(color) then
local emsg = "Invalid color passed to SetColor! This error "
emsg = emsg .. "prevents stuff from turning purple/pink."
ErrorNoHalt( emsg )
else
PUG._SetColor( self, color )
end
end
function ENT:SetPos( pos )
PUG._SetPos( self, pos )
timer.Simple(0, function()
hook.Run( "PUG_PostSetPos", self, pos )
end)
end
--NOTE: Fix engine crash resulting from entities being resized.
--REF: https://github.com/Facepunch/garrysmod-issues/issues/3547
function ENT:SetModelScale( scale, deltaTime )
PUG._SetModelScale( self, scale, deltaTime )
local min, max = self:OBBMins(), self:OBBMaxs()
if min:Distance(max) > 12000 then
PUG._SetModelScale( self, 1, 0 )
FixInvalidPhysicsObject( self )
end
end
end
do
local PhysObj = FindMetaTable( "PhysObj" )
PUG._EnableMotion = PUG._EnableMotion or PhysObj.EnableMotion
function PhysObj:EnableMotion( bool )
local ent = self:GetEntity()
local hookRun = { hook.Run( "PUG_EnableMotion", ent, self, bool ) }
if hookRun[1] == true then return end
bool = hookRun[2] or bool
ent.PUGFrozen = (not bool)
return PUG._EnableMotion( self, bool )
end
end
local function getBadEnt( ent )
if PUG:isBadEnt( ent ) then
ent.PUGBadEnt = true
return true
else
if ent.PUGBadEnt then
ent.PUGBadEnt = nil
return false
end
end
end
hook.Add("OnEntityCreated", "PUG_EntityCreated", function( ent )
getBadEnt( ent )
timer.Simple(0, function()
hook.Run( "PUG_isBadEnt", ent, getBadEnt(ent) )
end)
end)
hook.Add("PUG_PostPhysgunPickup", "main", function( ply, ent, canPickup )
if not canPickup then return end
u.entityForceDrop( ent )
u.addEntityHolder( ent, ply )
ent.PUGPicked = true
end)
hook.Add("PhysgunDrop", "PUG_PhysgunDrop", function( ply, ent )
ent.PUGPicked = false
u.removeEntityHolder( ent, ply )
end)
local function applyPlayerHack( ply )
timer.Simple(0, function()
local phys = ply:GetPhysicsObject()
if IsValid(phys) then
phys:EnableMotion(false)
phys:Sleep()
end
end)
end
--FIXME: Check if "PlayerInitialSpawn" is also needed.
hook.Add("PlayerInitialSpawn", "PUG_PlayerSpawn", applyPlayerHack)
hook.Add("PlayerSpawn", "PUG_PlayerSpawn", applyPlayerHack)
hook.Add("EntityTakeDamage", "PUG_DamageControl", function(target, dmg)
if type(target) ~= "Player" then
return
end
local ent = dmg:GetInflictor()
local damageType = dmg:GetDamageType()
if ent.PUGBadEnt then
return true
else
if IsValid( ent ) then
if PUG:isGoodEnt( ent ) or ent:IsWeapon() then
return
end
end
end
if damageType == DMG_CRUSH or damageType == DMG_VEHICLE then
return true
end
end)
-- NOTE: Now that the base is setup, load the modules!
include("pug/sv_pug_loader.lua")

102
lua/pug/sv_pug_loader.lua Normal file
View File

@ -0,0 +1,102 @@
PUG.modules = PUG.modules or {}
local file, hook, timer = file, hook, timer
local path = "pug/modules/"
local modules = file.Find( path .. "*.lua", "LUA" )
local u = PUG.util
for _, fileName in next, modules do
if fileName then
local niceName = string.gsub(fileName, "%.lua", "")
PUG.modules[ niceName ] = {
enabled = false,
path = path .. fileName,
data = {}
}
end
end
function PUG:load( moduleName )
local module = self.modules[ moduleName ]
if module then
module.data = include( module.path )
module.enabled = true
end
end
function PUG:unLoad( moduleName )
local module = self.modules[ moduleName ]
if module and next(module.data) == 1 then
local data = module.data
for _, hookID in next, data.hooks do
hook.Remove(hookID)
end
for _, timerID in next, data.timers do
timer.Remove(timerID)
end
module.enabled = false
else
local emsg = "The module " .. moduleName .. " doesn't exist or is "
emsg = emsg .. "invalid."
ErrorNoHalt( emsg )
end
end
local function writeData()
local json = util.TableToJSON( PUG.modules, true )
file.Write( "pugsettings.txt", json )
end
function PUG:saveConfig()
local readFile = file.Read( "pugsettings.txt", "DATA" )
if ( not readFile ) or ( readFile == "" ) then
writeData()
else
local data = util.JSONToTable( readFile )
if not data then
local emsg = "Your custom settings have not been loaded "
emsg = emsg .. "because you have a misconfigured settings file! "
emsg = emsg .. "The default settings were used instead!"
ErrorNoHalt( emsg )
return
end
-- Look for saved data that does not actually exist and remove it.
-- k is Key, v is Value, _ is Dropped/Unused.
for k, _ in next, data do
if not self.modules[ k ] then
data[ k ] = nil
end
end
-- Update data with new modules if new modules were added.
for k, v in next, self.modules do
if not data[ k ] then
data[ k ] = v
end
end
for k, v in next, data do
if v.enabled then
self:load(k)
print("[PUGLoader] ", k, " has been loaded!")
end
end
self.modules = data
timer.Simple(0, function()
writeData()
end)
end
end
PUG:saveConfig()
print("PUG has hopped onto your server! Your physics are safe with PUG.")

102
lua/pug/sv_pug_util.lua Normal file
View File

@ -0,0 +1,102 @@
local util = {}
function util.safeSetCollisionGroup( ent, group, pObj )
if ent:IsPlayerHolding() then return end
if ent.APG_Ghosted then return end
if pObj then pObj:Sleep() end
ent:SetCollisionGroup( group )
ent:CollisionRulesChanged()
end
function util.isVehicle( ent, basic )
if not IsValid( ent ) then return false end
if ent:IsVehicle() then return true end
if string.find( ent:GetClass(), "vehicle" ) then return true end
if basic then return false end
local parent = ent:GetParent()
return util.isVehicle( parent, true )
end
function util.callOnConstraints( ent, callback )
local constrained = constraint.GetAllConstrainedEntities( ent )
for _, child in next, constrained do
if IsValid( child ) and child ~= ent then
callback( child )
end
end
end
function util.entityForceDrop( ent )
DropEntityIfHeld( ent )
ent:ForcePlayerDrop()
if type(ent.PUGHolding) ~= "table" then return end
for _, ply in next, ent.PUGHolding do
if ply and IsValid(ply) then
ply:ConCommand("-attack")
end
end
end
function util.entityIsMoving( ent )
if type( ent ) ~= "Entity" then return end
if not IsValid( ent ) then return end
local zero = Vector(0,0,0)
local phys = ent:GetPhysicsObject()
if IsValid(phys) then
local vel = phys:GetVelocity():Distance(zero)
return ( vel > 0.15 ), vel
else
return false, nil
end
end
function util.isEntityHeld( ent )
if type(ent.PUGHolding) ~= "table" then
return false
end
return ( next( ent.PUGHolding ) ~= nil )
end
function util.addEntityHolder( ent, ply )
local steamID = ply:SteamID()
ent.PUGHolding = ent.PUGHolding or {}
ent.PUGHolding[steamID] = ply
end
function util.removeEntityHolder( ent, ply )
local steamID = ply:SteamID()
ent.PUGHolding = ent.PUGHolding or {}
ent.PUGHolding[steamID] = nil
end
do
local jobs = {}
function util.addJob( callback )
assert(type(callback) == "function", "The callback must be a function!")
local index = #jobs + 1
jobs[ index ] = callback
end
function util.removeJob( index )
jobs[ index ] = nil
end
hook.Add("Tick", "PUG_JobProcessor", function()
local index = #jobs
local job = jobs[ index ]
if job then
job()
util.removeJob( index )
end
end)
end
return util