Initial commit

This commit is contained in:
2026-03-15 14:54:49 +03:00
commit 64f8029c06
4027 changed files with 254888 additions and 0 deletions

View File

@@ -0,0 +1,629 @@
APG_panels = APG_panels or {}
local pull = include("cl_utils.lua")
local utils = pull.utils or {}
local menu = pull.menu or {}
local function showNotice(notifyLevel, notifyMessage)
if string.Trim(notifyMessage) == "" then return end
icon = notifyLevel == 0 and NOTIFY_GENERIC or notifyLevel == 1 and NOTIFY_CLEANUP or notifyLevel == 2 and NOTIFY_ERROR
notification.AddLegacy(notifyMessage, icon, 3 + (notifyLevel * 3))
if APG.cfg[ "notifySounds" ].value then
surface.PlaySound(notifyLevel == 1 and "buttons/button10.wav" or notifyLevel == 2 and "ambient/alarms/klaxon1.wav" or "buttons/lightswitch2.wav") -- Maybe let the player choose the sound?
end
MsgC( notifyLevel == 0 and Color( 0, 255, 0 ) or Color( 255, 191, 0 ), "[APG] ", Color( 255, 255, 255 ), notifyMessage,"\n")
end
net.Receive( "apg_notice_s2c", function()
local notifyLevel = net.ReadUInt( 3 )
local notifyMessage = net.ReadString()
showNotice(notifyLevel, notifyMessage)
end)
local function APGBuildHomePanel()
local panel = APG_panels[ "home" ]
panel.Paint = function( i, w, h ) end
local github = "https://github.com/NanoAi/gm_apg"
menu:initPanel( panel, 0, 40, 0, 35 )
menu:switch( 568, 20, "Welcome to APG! ( https://git.io/fjCQK )")
menu:switch( 568, 20, "Remember to check the github for updates! (Click to Copy)", function()
SetClipboardText( github )
showNotice(0, "Github URL copied to clipboard!")
end)
menu:switch( 568, 20, "<-- Select a Module to Configure!")
menu:switch( 568, 20, "To see this menu again just say \"!apg\"")
menu:switch( 568, 20, "For more help the wiki is available on the Github! (Click to Copy)", function()
SetClipboardText( github .. "/wiki" )
showNotice(0, "Github URL copied to clipboard!")
end)
menu:switch( 568, 20, "Sorry for the bad home page, this is hopefully a placeholder. : )")
menu:panelDone()
end
local function APGBuildStackPanel()
local panel = APG_panels[ "stack_detection" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:numSlider( 568, 20, "Maximum stacked ents", "stackMax", 3, 50, 0 )
menu:numSlider( 568, 20, "Stack distance (gmod units)", "stackArea", 5, 50, 0 )
menu:numSlider( 568, 20, "Maximum stacked fading doors", "fadingDoorStackMax", 5, 50, 0 )
menu:switch( 568, 20, "Notify player when their fading door is removed.", "fadingDoorStackNotify" )
menu:panelDone()
end
local function APGBuildToolsPanel()
local panel = APG_panels[ "tools" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:switch( 568, 20, "Should tools be blocked on APG_CantPickup", "checkCanTool" )
menu:switch( 568, 20, "Block players from spamming the toolgun", "blockToolSpam" )
menu:numSlider( 568, 20, "Max click's per second(s)", "blockToolRate", 1, 15, 0 ) -- It's really hard to click more then 15 times a second.
menu:numSlider( 568, 20, "The aforementioned second(s)", "blockToolDelay", 1, 5, 0 )
menu:switch( 568, 20, "Prevent using the toolgun on the world", "blockToolWorld" )
menu:switch( 568, 20, "Prevent the toolgun from unfreezing props", "blockToolUnfreeze" )
menu:switch( 568, 20, "Block the Creator Tool? (Requires OSS)", "blockCreatorTool" )
menu:switch( 568, 20, "Review entities near tool use", "checkTooledEnts" )
menu:panelDone()
end
local function APGBuildMiscPanel()
local panel = APG_panels[ "misc" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:switch( 568, 20, "Override Server Settings? (OSS)", "touchServerSettings" )
menu:switch( 568, 20, "Auto freeze over time", "autoFreeze" )
menu:numSlider( 568, 20, "Auto freeze delay(seconds)", "autoFreezeTime", 5, 600, 0 )
menu:switch( 568, 20, "Disable vehicle damages", "vehDamage" )
menu:switch( 568, 20, "Disable vehicle collisions (with players)", "vehNoCollide" )
menu:numSlider(575, 20, "Physgun maxrange (how far they can reach in gmod units)", "physGunMaxRange", 128, 8192, 0)
menu:switch( 568, 20, "Block GravGun throwing", "blockGravGunThrow" )
menu:switch( 568, 20, "Block Physgun Reload", "blockPhysgunReload" )
menu:switch( 568, 20, "Block players from moving contraptions", "blockContraptionMove" )
menu:switch( 568, 20, "Inject custom hooks into Fading Doors", "fadingDoorHook" )
menu:switch( 568, 20, "Activate FRZR9K (Sleepy Physics)", "sleepyPhys" )
menu:switch( 568, 20, "Hook FRZR9K into collision (Experimental)", "sleepyPhysHook" )
menu:switch( 568, 20, "Allow prop killing", "allowPK" )
menu:switch( 568, 20, "Activate Turbo Physics (Requires OSS)", "setTurboPhysics" )
menu:panelDone()
end
local function APGBuildLagPanel()
local panel = APG_panels[ "lag_detection" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:numSlider( 568, 20, "Lag threshold (%)", "lagTrigger", 5, 200, 0 )
menu:numSlider( 568, 20, "Frames lost", "lagsCount", 1, 20, 0 )
menu:numSlider( 568, 20, "Heavy lag trigger (seconds)", "bigLag", 1, 5, 1 )
menu:comboBox( 568, 20, "Lag fix function", "lagFunc", APG_lagFuncs )
menu:numSlider( 568, 20, "Lag func. delay (seconds)", "lagFuncTime", 1, 300, 0 )
menu:panelDone()
end
local function APGBuildNotificationPanel()
local panel = APG_panels[ "notification" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:switch( 568, 20, "Notification Sounds", "notifySounds" )
menu:comboBox( 568, 20, "Notification Level", "notifyLevel", APG_notifyLevels )
menu:switch( 570, 20, "Do you want to show what lag function ran?", "notifyLagFunc" )
menu:switch( 568, 20, "Developer logs (shows a notification, is spammy)", "developerDebug" )
menu:panelDone()
end
local function APGBuildLogsPanel()
local panel = APG_panels[ "logs" ]
panel.Paint = function( i, w, h ) end
menu:initPanel( panel, 0, 40, 0, 35 )
menu:switch( 568, 20, "Should we log when there is lag detected?", "logLagDetected" )
menu:switch( 568, 20, "Should we log when a player attempts to crash the server", "logStackCrashAttempt" )
menu:panelDone()
end
local function APGBuildGhostPanel()
local panel = APG_panels[ "ghosting" ]
panel.Paint = function( i, w, h)
draw.RoundedBox( 0, 0, 37, 170, 135, Color( 38, 38, 38, 255 ) )
draw.DrawText( "Ghosting color:", "APG_element_font", 5, 37, Color( 189, 189, 189 ), 3 )
--draw.RoundedBox(cornerRadius, x, y, width, height, color)
draw.RoundedBox( 0, 175, 37, 500, 300, Color( 38, 38, 38, 255) )
draw.DrawText( "Bad entities:", "APG_element_font", 180, 37, Color( 189, 189, 189), 3 )
draw.DrawText( "(Right-Click to Toggle)", "APG_title2_font", 280, 38, Color( 189, 189, 189), 3 )
--draw.DrawText(text, font="DermaDefault", x=0, y=0, color=Color(255,255,255,255), xAlign=TEXT_ALIGN_LEFT)
draw.DrawText( "Good entities:", "APG_element_font", 180, 230, Color( 189, 189, 189), 3 )
draw.DrawText( "(Right-Click to Toggle)", "APG_title2_font", 285, 232, Color( 189, 189, 189), 3 )
end
menu:initPanel( panel, 0, 180, 0, 35 )
menu:switch( 170, 20, "Always frozen", "alwaysFrozen" )
menu:switch( 170, 20, "Fading Doors", "fadingDoorGhosting" )
menu:switch( 170, 20, "Ignore Vehicles", "vehAntiGhost" )
menu:switch( 170, 20, "Enable Color", "ghostColorToggle")
local offsets = menu:panelDone()
local Mixer = vgui.Create( "CtrlColor", panel )
Mixer:SetPos( 5, 55 )
Mixer:SetSize( 160, 110 )
Mixer.Mixer.ValueChanged = function( self, color )
APG.cfg[ "ghostColor" ].value = Color( color.r, color.g, color.b, color.a)
end
local badList = vgui.Create( "DListView", panel )
badList:Clear()
badList:SetPos( 180, 55 )
badList:SetSize( panel:GetWide() - 185, panel:GetTall() / 2.5 )
badList:SetMultiSelect( false )
badList:SetHideHeaders( false )
badList:AddColumn( "Class" )
badList:AddColumn( "Exact" )
function badList:OnRowRightClick( id, line )
local key = line:GetColumnText(1)
local value = not tobool(line:GetColumnText(2))
line:SetColumnText( 2, value )
APG.cfg[ "badEnts" ].value[key] = value
end
local goodList = vgui.Create( "DListView", panel )
goodList:Clear()
goodList:SetPos( 180, 250 )
goodList:SetSize( panel:GetWide() - 185, panel:GetTall() / 2.5 )
goodList:SetMultiSelect( false )
goodList:SetHideHeaders( false )
goodList:AddColumn( "Class" )
goodList:AddColumn( "Exact" )
function goodList:OnRowRightClick( id, line )
local key = line:GetColumnText(1)
local value = not tobool(line:GetColumnText(2))
line:SetColumnText( 2, value )
APG.cfg[ "unGhostingWhitelist" ].value[key] = value
end
local function updateTab()
badList:Clear()
for class,complete in pairs(APG.cfg[ "badEnts" ].value) do
badList:AddLine(class, complete)
end
goodList:Clear()
for class,complete in pairs(APG.cfg[ "unGhostingWhitelist" ].value) do
goodList:AddLine(class, complete)
end
end
updateTab()
badList.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 150, 150, 150, 255 ) )
end
badList.VBar.Paint = function(i,w,h)
surface.SetDrawColor( 88, 110, 110, 240 )
surface.DrawRect( 0, 0, w, h )
end
badList.VBar.btnGrip.Paint = function(i,w,h)
surface.SetDrawColor( 255, 83, 13, 50 )
surface.DrawRect( 0, 0, w, h )
draw.RoundedBox( 0, 1, 1, w - 2, h / 2, Color( 72, 89, 89, 255 ) )
end
badList.VBar.btnUp.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) )
end
badList.VBar.btnDown.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) )
end
goodList.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 150, 150, 150, 255 ) )
end
goodList.VBar.Paint = function(i,w,h)
surface.SetDrawColor( 88, 110, 110, 240 )
surface.DrawRect( 0, 0, w, h )
end
goodList.VBar.btnGrip.Paint = function(i,w,h)
surface.SetDrawColor( 255, 83, 13, 50 )
surface.DrawRect( 0, 0, w, h )
draw.RoundedBox( 0, 1, 1, w - 2, h / 2, Color( 72, 89, 89, 255 ) )
end
goodList.VBar.btnUp.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) )
end
goodList.VBar.btnDown.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 72, 89, 89, 240 ) )
end
local BadTextEntry = vgui.Create( "DTextEntry", panel )
BadTextEntry:SetPos( offsets.x, panel:GetTall() - 100 )
BadTextEntry:SetSize( 100, 20 )
BadTextEntry:SetText( "Bad Entity class" )
BadTextEntry.OnEnter = function( self )
chat.AddText( self:GetValue() )
end
local BadAdd = vgui.Create( "DButton" , panel)
BadAdd:SetPos( offsets.x + 100, panel:GetTall() - 100 )
BadAdd:SetSize( 75,20 )
BadAdd:SetText( "Add" )
BadAdd.DoClick = function()
if BadTextEntry:GetValue() == "Bad Entity class" then return end
utils.addBadEntity( BadTextEntry:GetValue() )
updateTab()
end
BadAdd:SetTextColor( Color(255, 255, 255) )
BadAdd.Paint = function( i, w, h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 44, 55, 55, 255 ) )
draw.RoundedBox( 0, 1, 1, w-2, h-2, Color( 58, 58, 58, 255 ) )
end
local BadRemove = vgui.Create( "DButton" , panel)
BadRemove:SetPos( offsets.x, panel:GetTall() - 80 )
BadRemove:SetSize( 175, 20 )
BadRemove:SetText( "Remove selected" )
BadRemove.DoClick = function()
for k, v in pairs(badList:GetSelected()) do
local key = v:GetValue(1)
APG.cfg[ "badEnts" ].value[key] = nil
updateTab()
end
end
BadRemove:SetTextColor( Color( 255, 255, 255 ) )
BadRemove.Paint = function( i, w, h )
draw.RoundedBox( 0, 0, 0, w, h, Color( 58, 58, 58, 255 ) )
draw.RoundedBox( 0, 0, 0, w, 1, Color( 30, 30, 30, 125 ) )
end
local GoodTextEntry = vgui.Create( "DTextEntry", panel )
GoodTextEntry:SetPos( offsets.x, panel:GetTall() - 45 )
GoodTextEntry:SetSize( 100, 20 )
GoodTextEntry:SetText( "Good Entity class" )
GoodTextEntry.OnEnter = function( self )
chat.AddText( self:GetValue() )
end
local GoodAdd = vgui.Create( "DButton" , panel)
GoodAdd:SetPos( offsets.x + 100, panel:GetTall() - 45 )
GoodAdd:SetSize( 75,20 )
GoodAdd:SetText( "Add" )
GoodAdd.DoClick = function()
if GoodTextEntry:GetValue() == "Good Entity class" then return end
utils.addGoodEntity( GoodTextEntry:GetValue() )
updateTab()
end
local GoodRemove = vgui.Create( "DButton" , panel)
GoodRemove:SetPos( offsets.x, panel:GetTall() - 25 )
GoodRemove:SetSize( 175, 20 )
GoodRemove:SetText( "Remove selected" )
GoodRemove.DoClick = function()
for k, v in pairs(goodList:GetSelected()) do
local key = v:GetValue(1)
APG.cfg[ "unGhostingWhitelist" ].value[key] = nil
updateTab()
end
end
GoodRemove:SetTextColor( Color( 255, 255, 255 ) )
GoodRemove.Paint = function( i, w, h )
draw.RoundedBox( 0, 0, 0, w, h, Color( 58, 58, 58, 255 ) )
draw.RoundedBox( 0, 0, 0, w, 1, Color( 30, 30, 30, 125 ) )
end
end
local main_color = Color( 32, 255, 0, 255 )
local main_color_red = Color( 96, 0, 0, 255 )
local main_color_darker = Color( 51, 91, 51, 255 )
local function setScrollerTheme( scroller )
scroller:SetSize(1, 0)
scroller:SetHideButtons(true)
function scroller:Paint( w, h )
draw.RoundedBox( 0, 0, 0, 1, h, main_color_darker )
end
function scroller.btnGrip:Paint( w, h )
draw.RoundedBox( 0, 0, 0, 1, h, main_color )
end
end
local function openMenu( len )
len = net.ReadUInt( 32 )
if len == 0 then return end
local settings = net.ReadData( len )
settings = util.Decompress( settings )
settings = util.JSONToTable( settings )
APG.cfg = settings.cfg
table.Merge(APG, settings)
local APG_Main = vgui.Create( "DFrame" )
APG_Main:SetSize( 800, 450 )
APG_Main:SetPos( 800 - APG_Main:GetWide() / 2, 450 - APG_Main:GetTall() / 2)
APG_Main:SetTitle( "" )
APG_Main:SetVisible( true )
APG_Main:SetDraggable( true )
APG_Main:MakePopup()
APG_Main:ShowCloseButton( false )
APG_Main.Paint = function(i,w,h)
draw.RoundedBox(4,0,0,w,h,Color(34, 34, 34, 255))
draw.RoundedBox(0,0,23,w,1,main_color)
local name = "A.P.G. - Anti Prop Griefing Solution"
draw.DrawText( name, "APG_title_font",8, 5, Color( 204, 204, 204, 255 ), 3 )
end
local closeButton = vgui.Create( "DButton",APG_Main )
closeButton:SetPos( APG_Main:GetWide() - 20, 4 )
closeButton:SetSize( 18, 18 )
closeButton:SetText(" ")
closeButton.DoClick = function()
APG_Main:Hide()
APG_Main:Remove()
end
closeButton.Paint = function(i,w,h)
draw.RoundedBox( 0,0,0,w,h, Color( 91, 51, 51, 255 ) )
draw.DrawText( "", "APG_sideBar_font", 1, -1, Color( 204, 204, 204, 255 ), TEXT_ALIGN_TOP )
end
local saveButton = vgui.Create( "DButton", APG_Main )
saveButton:SetPos( APG_Main:GetWide() - 117, 4 )
saveButton:SetSize( 77, 18 )
saveButton:SetText(" ")
saveButton.DoClick = function()
if not LocalPlayer():IsSuperAdmin() then return end
local settings = APG
settings = util.TableToJSON( settings )
settings = util.Compress( settings )
net.Start( "apg_settings_c2s")
net.WriteUInt( settings:len(), 32 ) -- Write the length of the data (up to {{ user_id | 76561197972967270 }})
net.WriteData( settings, settings:len() ) -- Write the data
net.SendToServer()
showNotice(1, "APG Settings saved!")
end
saveButton.Paint = function(i,w,h)
draw.RoundedBox( 0, 0, 0, w, h, Color( 51, 91, 51, 255 ) )
draw.DrawText( "Save Settings", "APG_title2_font",w/2, 1, Color( 204, 204, 204, 255 ), 1 )
end
-- Side bar
local sidebar = vgui.Create( "DScrollPanel", APG_Main )
setScrollerTheme( sidebar:GetVBar() )
sidebar:SetSize( APG_Main:GetWide() / 4 , APG_Main:GetTall() - 35)
sidebar:SetPos( 0, 30 )
sidebar.Paint = function( i, w, h )
draw.RoundedBox( 0, 0, 0, w, h, Color( 33, 33, 33, 255 ) )
draw.RoundedBox( 0, w-1, 0, 1, h, main_color_darker)
end
local x, y = (APG_Main:GetWide() - sidebar:GetWide()) - 19, APG_Main:GetTall() - 35
local px, py = sidebar:GetWide() + 15, 30
local first = true
local modules = APG.modules
-- Attempt to force essential modules to be enabled.
modules["home"] = true
modules["canphysgun"] = true
for k, v in next, APG.modules do
if k == "canphysgun" then continue end -- This module doesn't have UI, so it doesn't need a UI button.
local panel = vgui.Create( "DScrollPanel", APG_Main )
setScrollerTheme( panel:GetVBar() )
panel:SetSize( x, y )
panel:SetPos( px, py )
panel:SetVisible( first )
panel.Paint = function() end
APG_panels[k] = panel
first = false
local button = vgui.Create( "DButton", panel )
button:SetPos( 0, 0 )
button:SetSize( panel:GetWide(), 35 )
button:SetText("")
button.UpdateColours = function( label, skin )
label:SetTextStyleColor( Color( 189, 189, 189 ) )
end
button.Paint = function( slf, w, h )
local enabled = APG.modules[k]
draw.RoundedBox( 0, 0, h * 0.85, w-5, 1, enabled and main_color or main_color_red )
local text = utils.getNiceName(k) .. " module "
draw.DrawText( text, "APG_mainPanel_font", 5, 8, Color( 189, 189, 189 ), 3 )
menu:mainSwitch( w * 0.90, (h * 0.5) - 16, enabled )
end
button.DoClick = function()
APG.modules[k] = not APG.modules[k]
end
end
local i = 0
local height = ( sidebar:GetTall()/5 )
for k, v in next, APG.modules do
if k == "canphysgun" then continue end
local button = sidebar:Add( "DButton" )
button:SetPos( 5, (height + 5) * i)
button:SetSize( sidebar:GetWide() - 10 , height )
button:SetText("")
button.DoClick = function()
for l,m in next, APG_panels do
if k ~= l then
APG_panels[l]:SetVisible( false )
else
APG_panels[l]:SetVisible( true )
end
end
end
local size = sidebar:GetWide()
button.Paint = function( _, w, h )
local name = utils.getNiceName( k )
if button.Hovered then
draw.RoundedBox( 5, 0, 0, w, h, Color( 48, 48, 48, 255 ) )
draw.RoundedBox( 0, 2, 2, w - 4, h - 4, Color( 36, 36, 36, 255 ) )
end
if APG_panels[k]:IsVisible() then
draw.RoundedBox( 0, 0, 0, w, h, Color( 51, 51, 51, 255 ) )
draw.RoundedBox( 0, w * 0.10, h * 0.60, w * 0.8, 2, main_color_darker )
end
draw.DrawText( name, "APG_sideBar_font", ( size - name:len() ) / 2, h * 0.35, Color( 189, 189, 189 ), 1)
end
if k == "home" then
button:DoClick()
end
i = i + 1
end
-- Build all the expected panels.
APGBuildHomePanel()
APGBuildMiscPanel()
APGBuildToolsPanel()
APGBuildGhostPanel()
APGBuildLagPanel()
APGBuildStackPanel()
APGBuildLogsPanel()
APGBuildNotificationPanel()
end
net.Receive( "apg_menu_s2c", openMenu )
properties.Add( "apgoptions", {
MenuLabel = "APG Options", -- Name to display on the context menu
Order = 9999, -- The order to display this property relative to other properties
MenuIcon = "icon16/fire.png", -- The icon to display next to the property
Filter = function( self, ent, ply ) -- A function that determines whether an entity is valid for this property
if not ply:IsSuperAdmin() then return false end
if not IsValid(ent) then return false end
if not ent:GetClass() then return false end
if ent:EntIndex() < 0 then return false end
return true
end,
MenuOpen = function( self, option, ent, tr )
local submenu = option:AddSubMenu()
local function addoption( str, data )
local menu = submenu:AddOption( str, data.callback )
if data.icon then
menu:SetImage( data.icon )
end
return menu
end
addoption( "Sleep entities of this Class", {
icon = "icon16/clock.png",
callback = function() self:APGcmd( ent, "sleepclass" ) end,
})
addoption( "Freeze entities of this Class", {
icon = "icon16/bell_delete.png",
callback = function() self:APGcmd( ent, "freezeclass" ) end,
})
submenu:AddSpacer()
addoption( "Cleanup Owner - Unfrozens", {
icon = "icon16/cog_delete.png",
callback = function() self:APGcmd( ent, "clearunfrozen" ) end,
})
addoption( "Cleanup Owner", {
icon = "icon16/bin_closed.png",
callback = function() self:APGcmd( ent, "clearowner" ) end,
})
submenu:AddSpacer()
addoption( "Get Owner SteamID", {
icon = "icon16/user.png",
callback = function() self:APGcmd( ent, "getownerid" ) end,
})
addoption( "Get Owner Entity Count", {
icon = "icon16/brick.png",
callback = function() self:APGcmd( ent, "getownercount" ) end,
})
submenu:AddSpacer()
addoption( "Add this entity class to the Ghosting List", {
icon = "icon16/cross.png",
callback = function() self:APGcmd( ent, "addghost" ) end,
})
addoption( "Remove this entity class from the Ghosting List", {
icon = "icon16/tick.png",
callback = function() self:APGcmd( ent, "remghost" ) end,
})
submenu:AddSpacer()
addoption( "Ghost this entity", {
icon = "icon16/tick.png",
callback = function() self:APGcmd( ent, "ghost" ) end,
})
end,
Action = function( self, ent ) end,
APGcmd = function( self, ent, cmd )
if cmd == "getownerid" then
local owner, _ = ent:CPPIGetOwner()
if IsValid( owner ) then
local id = tostring( owner:SteamID() )
local name = tostring( owner:Nick() )
SetClipboardText( id )
showNotice(0, name .. " [ " .. id .. " ]" .. " has been copied to your clipboard.")
else
showNotice(0, "\nOops, that's not a Player!")
end
elseif cmd == "getentname" then
showNotice(0, ent:GetClass())
elseif IsValid( ent ) and ent.EntIndex() then
net.Start( "apg_context_c2s" )
net.WriteString( cmd )
net.WriteEntity( ent )
net.SendToServer()
end
end,
})

View File

@@ -0,0 +1,281 @@
surface.CreateFont( "APG_title_font", {
font = "Arial",
size = 14,
weight = 700,
} )
surface.CreateFont( "APG_title2_font", {
font = "Arial",
size = 13,
weight = 700,
} )
surface.CreateFont( "APG_sideBar_font", {
font = "Arial",
size = 18,
weight = 1500,
} )
surface.CreateFont( "APG_mainPanel_font", {
font = "Arial",
size = 19,
weight = 8500,
} )
surface.CreateFont( "APG_tick_font", {
font = "Arial",
size = 29,
weight = 1900,
} )
surface.CreateFont( "APG_element_font", {
font = "Arial",
size = 17,
weight = 1300,
} )
surface.CreateFont( "APG_element2_font", {
font = "Arial",
size = 17,
weight = 2900,
} )
local utils = {}
local menu = {}
function utils.addBadEntity( class )
local found = false
for k, v in pairs ( ents.GetAll() ) do
if class == v:GetClass() then
found = true
break
end
end
if not found then
for k in pairs (scripted_ents.GetList()) do
if class == k then
found = true
break
end
end
end
APG.cfg["badEnts"].value[ class ] = found
end
function utils.addGoodEntity( class )
local found = false
for k, v in pairs ( ents.GetAll() ) do
if class == v:GetClass() then
found = true
break
end
end
if not found then
for k in pairs (scripted_ents.GetList()) do
if class == k then
found = true
break
end
end
end
APG.cfg["unGhostingWhitelist"].value[ class ] = found
end
function utils.addInvalidWhitelist( model )
local found = false
for k, v in pairs ( ents.GetAll() ) do
if model == v:GetModel() then
found = true
break
end
end
APG.cfg["invalidPhysicsWhitelist"].value[ model ] = found
end
function utils.getNiceName( str )
local nName = string.gsub(str, "^%l", string.upper)
nName = string.gsub(nName, "_", " " )
return nName
end
function menu:mainSwitch( x, y, on )
draw.RoundedBox(10, x, y, 45, 18, Color( 58, 58, 58, 255))
if on then
draw.RoundedBox(10, x + 1, y + 1, 45 - 2, 18 - 2, Color( 11, 70, 30, 255))
draw.DrawText( "ON", "APG_title_font", x + 8, y + 2, Color( 189, 189, 189 ), 3 )
draw.RoundedBox(10, x + 27, y, 18, 18, Color( 88, 88, 88, 255))
else
--draw.RoundedBox(10, x, y, 45, 18, Color( 110, 28, 38, 255))
draw.RoundedBox(10, x + 1, y + 1, 43, 16, Color( 34, 34, 34, 255))
draw.DrawText( "OFF", "APG_title_font", x + 21, y + 2, Color( 189, 189, 189), 3 )
draw.RoundedBox(10, x, y, 18, 18, Color( 88, 88, 88, 255))
end
--draw.RoundedBox(0, x+20, y, 1, 18, Color( 88, 88, 88, 255))
end
function menu:initPanel( panel, x, y, ix, iy )
self.panel = panel
self.vars = {x = x, y = y, ix = ix, iy = iy}
end
function menu:panelDone()
local old = self.vars
self.panel = {}
self.vars = {}
return old
end
function menu:grabVars()
local v = self.vars
return self.panel, v.x, v.y, v.ix, v.iy
end
function menu:switch( w, h, text, var )
local panel, x, y, ix, iy = menu:grabVars()
local button = vgui.Create("DButton", panel)
local isKey = ( type(var) == "string" )
local isFunction = ( type(var) == "function" )
button:SetPos(x, y)
button:SetSize(w, h)
button:SetText("")
button.Paint = function(slf, w, h)
local enabled = isKey and APG.cfg[ var ].value or isFunction
draw.RoundedBox(0, 0, h * 0.95, w - 5, 1, Color(250, 250, 250, 1))
draw.DrawText( text, "APG_element2_font", 0, 0, Color( 189, 189, 189), 3 )
menu:mainSwitch( w-45, 0, enabled )
end
if isKey then
button.DoClick = function()
APG.cfg[ var ].value = not APG.cfg[ var ].value
end
else
if isFunction then
button.DoClick = var
else
button:SetEnabled( false )
end
end
self.vars.x = x + ix
self.vars.y = y + iy
end
function menu:numSlider( w, h, text, var, minSlider, maxSlider, decimal )
local panel, x, y, ix, iy = menu:grabVars()
local slider = panel:Add( "DNumSlider" )
slider:SetPos( x, y )
slider:SetSize( w, h )
slider:SetText( "" )
slider:SetMin( minSlider )
slider:SetMax( maxSlider )
slider:SetDecimals( decimal )
slider:SetValue( APG.cfg[ var ].value )
slider.OnValueChanged = function( self, newValue )
APG.cfg[ var ].value = newValue
end
slider.Paint = function(slf, w, h)
draw.RoundedBox( 0, 0, h * 0.97, w - 5, 1, Color(250, 250, 250, 1 ) )
draw.DrawText( text, "APG_element2_font", 0, 0, Color( 189, 189, 189), 3 )
end
slider.Slider.Paint = function( slf, w, h)
--draw.RoundedBox(cornerRadius, x, y, width, height, color)
draw.RoundedBox( 0, 8, 9 - 1, w - 16, 1 + 2, Color( 250, 250, 250, 1))
end
slider.Slider.Knob.Paint = function(slf, w, h)
draw.RoundedBox(6, 0, 4, 10, 10, Color( 11, 70, 30, 255))
end
slider.Slider:Dock( NODOCK )
slider.Slider:SetPos( panel:GetWide() - 110, 0 )
slider.Slider:SetWide( 100 )
slider.TextArea:Dock( NODOCK )
slider.TextArea:SetPos( panel:GetWide() - 145, - 3 )
slider.TextArea.m_colText = Color(189, 189, 189)
slider.TextArea.Paint = function( self, w, h)
draw.RoundedBox(10, 0, 1, w-15, h, Color( 58, 58, 58, 255))
derma.SkinHook( "Paint", "TextEntry", self, w, h )
end
self.vars.x = x + ix
self.vars.y = y + iy
end
function menu:textEntry( w, h, text, var )
local panel, x, y, ix, iy = menu:grabVars()
local label = panel:Add( "DLabel" )
label:SetPos( x, y )
label:SetSize( w, h )
label:SetText( text )
label:SetFont("APG_element2_font")
label:SetColor( Color( 189, 189, 189) )
label.Paint = function(self, w, h)
draw.RoundedBox(0, 0, h * 0.97, w, 1, Color(250, 250, 250, 1))
end
local txtEntry = vgui.Create( "DTextEntry", panel ) -- create the form as a child of frame
txtEntry:SetPos( panel:GetWide() - 110, y-1 )
txtEntry:SetSize( 125, 20 )
txtEntry:SetText( "custom" )
txtEntry.OnEnter = function( self )
end
self.vars.x = x + ix
self.vars.y = y + iy
end
function menu:comboBox( w, h, text, var, content )
local panel, x, y, ix, iy = menu:grabVars()
local label = panel:Add( "DLabel" )
label:SetPos( x, y )
label:SetSize( w, h )
label:SetText( text )
label:SetFont("APG_element2_font")
label:SetColor( Color( 189, 189, 189) )
label.Paint = function(self, w, h)
draw.RoundedBox(0, 0, h * 0.97, w, 1, Color(250, 250, 250, 1))
end
local comboBox = vgui.Create( "DComboBox", panel )
comboBox:SetPos( panel:GetWide() - 145, y-2 )
comboBox:SetSize( 125, 20 )
comboBox:SetValue( APG.cfg[var].value )
for k, v in pairs ( content ) do
comboBox:AddChoice(v)
end
comboBox.OnSelect = function( panel, index, value )
APG.cfg[var].value = value
end
comboBox.Paint = function(i, w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(58, 58, 58, 240))
end
comboBox:SetTextColor(Color( 189, 189, 189))
local o_OpenMenu = comboBox.OpenMenu
comboBox.OpenMenu = function( pControlOpener )
o_OpenMenu(pControlOpener)
comboBox.Menu.Paint = function (i, w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(58, 58, 58, 240))
end
end
self.vars.x = x + ix
self.vars.y = y + iy
end
return {utils = utils, menu = menu}

View File

@@ -0,0 +1,97 @@
--[[------------------------------------------
============================
Physgun Permissions Module
============================
Developer informations :
---------------------------------
Used variables :
]]--------------------------------------------
local mod = "canphysgun"
--[[ Entity pickup part ]]
local GM = GM or GAMEMODE
APG._PhysgunPickup = APG._PhysgunPickup or GM.PhysgunPickup
APG.hookAdd(mod, "PhysgunPickup","APG_PhysgunPickup", function(ply, ent)
if not APG.isBadEnt( ent ) then return end
if not APG.canPhysGun( ent, ply ) then return false end
end)
function GM:PhysgunPickup( ply, ent )
local canPickup = APG._PhysgunPickup( self, ply, ent )
hook.Run("APG_PostPhysgunPickup", ply, ent, canPickup)
if not canPickup then return canPickup end -- Assumed as `false` but returning just incase.
ent.APG_HeldBy = ent.APG_HeldBy or {}
ent.APG_HeldBy.plys = ent.APG_HeldBy.plys or {}
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 = (#ent.APG_HeldBy.plys > 0)
local HeldByLast = ent.APG_HeldBy.last
if HasHolder then
if HeldByLast then
for _, ply in next, ent.APG_HeldBy.plys do
APG.ForcePlayerDrop(ply, ent)
end
else
return false
end
end
end
ent.APG_HeldBy.plys[ply:SteamID()] = ply
ent.APG_HeldBy.last = {ply = ply, id = ply:SteamID()}
ply.APG_CurrentlyHolding = ent
if APG.cfg["blockContraptionMove"].value then
local count = 0
local noFrozen = true
for _,v in next, constraint.GetAllConstrainedEntities(ent) do
count = count + 1
if v.APG_Frozen then
noFrozen = false
break
end
end
if noFrozen and ( count > 1 ) then
timer.Simple(0, function()
APG.freezeIt(ent, true)
end)
end
end
return canPickup -- Assumed as `true`
end
--[[ PhysGun Drop and Anti Throw Props ]]
APG.hookAdd(mod, "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)
--[[ Load hooks and timers ]]
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1,403 @@
--[[------------------------------------------
============================
GHOSTING/UNGHOSTING MODULE
============================
Developer informations :
---------------------------------
Used variables :
ghostColor = { value = Color(34, 34, 34, 220), desc = "Color set on ghosted props" }
badEnts = {
value = {
["prop_physics"] = true,
["wire_"] = false,
["gmod_"] = false },
desc = "Entities to ghost/control/secure"}
alwaysFrozen = { value = false, desc = "Set to true to auto freeze props on physgun drop" }
]]--------------------------------------------
local mod = "ghosting"
--[[ Override base functions ]]
local ENT = FindMetaTable( "Entity" )
APG._SetCollisionGroup = APG._SetCollisionGroup or ENT.SetCollisionGroup
function ENT:SetCollisionGroup( group )
local group = group
local isBadEnt = APG.isBadEnt( self )
local hasValidOwner = APG.getOwner( self )
local groupIsNone = group == COLLISION_GROUP_NONE
local isNotFrozen = not self.APG_Frozen
local isWhitelistedEnt = APG.isWhitelistedEnt(self)
local shouldMakeInteractable = isBadEnt and hasValidOwner and groupIsNone and isNotFrozen and not isWhitelistedEnt
if shouldMakeInteractable then
group = COLLISION_GROUP_INTERACTIVE
end
APG._SetCollisionGroup( self, group )
end
APG._SetColor = APG._SetColor or ENT.SetColor
function ENT:SetColor( color, ... )
local color = color
local r, g, b, a
if type(color) == "number" then
color = Color(color, select(1, ...) or 255, select(2, ...) or 255, select(3, ...) or 255)
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
ErrorNoHalt( "Invalid color passed to SetColor!\nThis error prevents stuff from turning purple/pink." )
else
APG._SetColor( self, color )
end
end
local PhysObj = FindMetaTable( "PhysObj" )
APG._EnableMotion = APG._EnableMotion or PhysObj.EnableMotion
function PhysObj:EnableMotion( bool )
local ent = self:GetEntity()
if APG.isBadEnt( ent ) and APG.getOwner( ent ) then
ent.APG_Frozen = not bool
if not ent.APG_Frozen then
ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE)
end
end
return APG._EnableMotion( self, bool )
end
function APG.isTrap( ent, fullscan )
local check = false
local center = ent:LocalToWorld( ent:OBBCenter() )
local bRadius = ent:BoundingRadius()
local cache = {}
for _,v in next, ents.FindInSphere( center, bRadius ) do
if v:IsPlayer() and v:Alive() then
local pos = v:GetPos()
local trace = { start = pos, endpos = pos, filter = v }
local tr = util.TraceEntity( trace, v )
if tr.Entity == ent then
if fullscan then
table.insert( cache, v )
else
check = v
end
end
elseif APG.IsVehicle(v) then
-- Check if the distance between the spheres 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
if check then break end
end
if fullscan and ( #cache > 0 ) then
return cache
else
return check or false
end
end
function APG.entGhost( ent, noCollide, enforce )
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
if APG.cfg["vehAntiGhost"].value and APG.IsVehicle( ent ) then return end
if ent.jailWall then return end
if not ent.APG_Ghosted then
ent.FPPAntiSpamIsGhosted = nil -- Override FPP Ghosting.
DropEntityIfHeld( ent )
ent:ForcePlayerDrop()
ent.APG_oldCollisionGroup = ent:GetCollisionGroup()
if not enforce then
-- If and old collision group was set get it.
if ent.OldCollisionGroup then ent.APG_oldCollisionGroup = ent.OldCollisionGroup end -- For FPP
if ent.DPP_oldCollision then ent.APG_oldCollisionGroup = ent.DPP_oldCollision end -- For DPP
ent.OldCollisionGroup = nil
ent.DPP_oldCollision = nil
end
ent.APG_Ghosted = true
if APG.cfg["ghostColorToggle"].value then
timer.Simple(0, function()
if not IsValid( ent ) then return end
if not ent.APG_oldColor then
ent.APG_oldColor = ent:GetColor()
if not enforce then
if ent.OldColor then ent.APG_oldColor = ent.OldColor end -- For FPP
if ent.__DPPColor then ent.APG_oldColor = ent.__DPPColor end -- For DPP
ent.OldColor = nil
ent.__DPPColor = nil
end
end
ent:SetColor( APG.cfg[ "ghostColor" ].value )
end)
end
ent.APG_oldRenderMode = 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()
phys:EnableCollisions( true )
end)
end
end
ent:CollisionRulesChanged()
end
end
function APG.entUnGhost( ent, ply, failmsg )
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
if ent.APG_Picked or (ent.APG_HeldBy and #ent.APG_HeldBy > 1) then return end
if ent.APG_Ghosted == true then
ent.APG_isTrap = APG.isTrap(ent)
if not ent.APG_isTrap then
ent.APG_Ghosted = false
ent:DrawShadow( true )
ent:SetRenderMode( ent.APG_oldRenderMode or RENDERMODE_NORMAL )
if APG.cfg["ghostColorToggle"].value then
ent:SetColor( ent.APG_oldColor or Color( 255, 255, 255, 255) )
end
ent.APG_oldColor = false
local newCollisionGroup = COLLISION_GROUP_INTERACTIVE
if APG.isWhitelistedEnt(ent) then
newCollisionGroup = ent.APG_spawnedCollisionGroup
elseif ent.APG_oldCollisionGroup == COLLISION_GROUP_WORLD then
newCollisionGroup = ent.APG_oldCollisionGroup
elseif ent.APG_Frozen then
newCollisionGroup = COLLISION_GROUP_NONE
end
ent:SetCollisionGroup( newCollisionGroup )
ent:CollisionRulesChanged()
return true
else
APG.notify( false, 1, ply, failmsg or "There is something in this prop!" )
ent:SetCollisionGroup( COLLISION_GROUP_WORLD )
ent:CollisionRulesChanged()
return false
end
end
end
function APG.ConstraintApply( ent, callback )
local constrained = constraint.GetAllConstrainedEntities(ent)
for _,v in next, constrained do
if IsValid( v ) and v ~= ent then
callback( v )
end
end
end
--[[------------------------------------------
Hooks/Timers
]]--------------------------------------------
APG.hookAdd( mod, "APG_PostPhysgunPickup", "APG_makeGhost", function( ply, ent, canPickup )
if not canPickup then return end
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
ent.APG_Picked = true
if not APG.cfg[ "allowPK" ].value then
APG.entGhost( ent )
APG.ConstraintApply( ent, function( _ent )
if not _ent.APG_Frozen then
_ent.APG_Picked = true
APG.entGhost( _ent )
end
end) -- Apply ghost to all constrained ents
end
end)
APG.hookAdd( mod, "PlayerUnfrozeObject", "APG_unFreezeInteract", function (ply, ent, pObj)
if not APG.canPhysGun( ent, ply, "APG_unFreezeInteract" ) then return end
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
if APG.cfg[ "alwaysFrozen" ].value then
return false
end -- Do not unfreeze if Always Frozen is enabled !
if ent:GetCollisionGroup( ) ~= COLLISION_GROUP_WORLD then
ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE )
end
end)
APG.dJobRegister( "unghost", 0.1, 50, function( ent )
if IsValid(ent) then
APG.entUnGhost( ent )
end
end)
APG.hookAdd( mod, "PhysgunDrop", "APG_pGunDropUnghost", function( ply, ent )
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
ent.APG_Picked = false
if APG.cfg[ "alwaysFrozen" ].value then
APG.freezeIt( ent )
end
APG.entUnGhost( ent, ply )
APG.ConstraintApply( ent, function( _ent )
_ent.APG_Picked = false
APG.startDJob( "unghost", _ent )
end) -- Apply unghost to all constrained ents
end)
local function SafeSetCollisionGroup( ent, colgroup, pObj )
-- If the entity is being held by a player or is ghosted abort.
if ent:IsPlayerHolding() then return end
if ent.APG_Ghosted then return end
if pObj then pObj:Sleep() end
ent:SetCollisionGroup(colgroup)
ent:CollisionRulesChanged()
end
APG.hookAdd( mod, "OnEntityCreated", "APG_noCollideOnCreate", function( ent )
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
if not IsValid( ent ) then return end
if ent:GetClass() == "gmod_hands" then return end -- Fix shadow glitch
ent.APG_spawnedCollisionGroup = ent:GetCollisionGroup() -- have to set it before it's ghosted
timer.Simple( 0, function()
if not ent then return end
if not ent:IsSolid() then return end -- Don't ghost ghosts.
local spawnedEnt = tostring(ent)
local owner = ""
if APG.getOwner(ent) ~= nil then -- incase getowner return's nil (like on reloading)
owner = APG.getOwner(ent):Nick()
else
owner = "console"
end
APG.entGhost( ent )
end)
timer.Simple( 0, function()
if not ent then return end
if not ent:IsSolid() then return end -- Don't ghost ghosts.
local owner = APG.getOwner( ent )
DropEntityIfHeld( ent )
ent:ForcePlayerDrop()
if IsValid( owner ) and owner:IsPlayer() then
local pObj = ent:GetPhysicsObject()
if IsValid(pObj) then
if APG.cfg[ "alwaysFrozen" ].value then
ent.APG_Frozen = true
pObj:EnableMotion( false )
elseif pObj:IsMoveable() then
ent.APG_Frozen = false
SafeSetCollisionGroup( ent, COLLISION_GROUP_INTERACTIVE )
end
end
end
APG.startDJob( "unghost", ent )
end)
end)
local BlockedProperties = { "collision", "persist", "editentity", "drive", "ignite", "statue" }
APG.hookAdd( mod, "CanProperty", "APG_canProperty", function(ply, property, ent)
local property = tostring( property )
if ( table.HasValue(BlockedProperties, property) and ent.APG_Ghosted ) then
APG.notify( false, 1, ply, "Cannot set", property, "properties on ghosted entities!" )
return false
end
end)
-- Custom Hooks --
local function checkDoor(ply, ent)
local isTrap = APG.isTrap(ent, true)
if isTrap and istable(isTrap) then
ent.APG_Ghosted = true
ent:SetCollisionGroup(COLLISION_GROUP_WORLD)
for _,v in next, isTrap do
if v:IsPlayer() then
local push = v:GetForward()
push = push * 1200
push.z = 60
v:SetVelocity(push)
end
end
timer.Simple(1, function()
if IsValid(ply) and IsValid(ent) then
ent.APG_Ghosted = false
ent:oldFadeDeactivate()
ent:SetCollisionGroup( COLLISION_GROUP_INTERACTIVE )
if IsValid(isTrap) then
APG.notify( false, 1, ply, "Unable to unstuck objects from fading door!" )
APG.entGhost(ent)
end
end
end)
end
end
APG.hookAdd(mod, "APG.FadingDoorToggle", "APG_FadingDoor", function(ent, isFading)
if APG.isBadEnt(ent) and APG.cfg["fadingDoorGhosting"].value then
local ply = APG.getOwner( ent )
if (IsValid(ply) and ply:IsPlayer() and not isFading) then
-- Delay slightly, this is needed to wait for other things happen before it works.
timer.Simple(0.001, function()
checkDoor(ply, ent)
end)
end
end
end)
--[[ Load hooks and timers ]]
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1 @@
-- This is a dummy file, for APGs home page! (As the home page is built like a module.)

View File

@@ -0,0 +1,184 @@
--[[------------------------------------------
============================
LAG DETECTION MODULE
============================
Developer informations :
---------------------------------
Used variables :
lagTrigger = { value = 75, desc = "% difference between current lag and average lag."}
lagsCount = { value = 8, desc = "Number of consectuives laggy frames in order to run a cleanup."}
bigLag = { value = 2, desc = "Time (seconds) between 2 frames to trigger a cleanup"}
lagFunc = { value = "cleanUp_unfrozen", desc = "Function ran on lag detected" }
lagFuncTime = { value = 20, desc = "Time (seconds) between 2 cleanup (avoid spam)"}
Ready to hook :
APG_lagDetected = Ran on lag detected by the server.
Example : hook.Add( "APG_lagDetected", "myLagDetectHook", function() print("[APG] Lag detected (printed from my very own hook)") end)
]]--------------------------------------------
local mod = "lag_detection"
local table = table
--[[ Lag fixing functions ]]
local lagFix = {
cleanup_all = function( ) APG.cleanUp( "all" ) end,
cleanup_unfrozen = function( ) APG.cleanUp( "unfrozen" ) end,
ghost_unfrozen = APG.ghostThemAll,
freeze_unfrozen = APG.freezeProps,
smart_cleanup = APG.smartCleanup,
custom_function = APG.customFunc,
}
--[[ Lag detection vars ]]
local lastTick = 0
local tickDelta = 0
local tickRate = 0
local lagCount = 0
local lagThreshold = math.huge
local processHault = false
local processFunc = false
local processExecs = 0
local sampleData = {}
local sampleCount = 0
local function addSample( data )
local index = (sampleCount%66)+1
local data = tonumber(data)
sampleCount = sampleCount + 1
if sampleCount >= 66 then
sampleCount = 0
end
table.insert(sampleData, index, data)
end
function APG.resetLag(dontResetData)
lastTick = 0
tickDelta = 0
lagCount = 0
lagThreshold = tickRate
processHault = false
processFunc = false
processExecs = 0
end
function APG.calculateLagAverage()
local count = 0
local total = 0
local sampleData = sampleData
for _, v in next, sampleData do
total = total + v
count = count + 1
end
if count < 12 then
return false -- Not enough data, yet.
end
return (total/count)
end
hook.Add("Think", "APG_initLagDetection", function()
tickRate = FrameTime()
lagThreshold = tickRate
hook.Remove("Think", "APG_initLagDetection")
end)
APG.timerAdd( mod, "APG_process", 3, 0, function()
if not APG.modules[ mod ] then return end
if sampleCount < 12 or tickDelta < lagThreshold then
addSample(tickDelta)
end
local average = APG.calculateLagAverage()
if average then
lagThreshold = average * ( 1 + ( APG.cfg[ "lagTrigger" ].value / 100 ) )
end
processExecs = 0
end)
APG.hookAdd( mod, "Tick", "APG_lagDetection", function()
if not APG.modules[ mod ] then return end
local sysTime = SysTime()
tickDelta = sysTime - lastTick
if lagThreshold > tickRate and tickDelta >= lagThreshold then
lagCount = lagCount + 1
if (lagCount >= APG.cfg[ "lagsCount" ].value) or ( tickDelta > APG.cfg[ "bigLag" ].value ) then
lagCount = 0
if ( not processHault ) and ( not processFunc ) then
processHault = true
timer.Simple(APG.cfg["lagFuncTime"].value, function()
processHault = false
end)
hook.Run( "APG_lagDetected" )
end
end
else
lagCount = lagCount - 0.5
if lagCount < 0 then
lagCount = 0
end
end
lastTick = sysTime
end)
--[[ Utils ]]
hook.Remove( "APG_lagDetected", "main") -- Sometimes, I dream about cheese.
hook.Add( "APG_lagDetected", "main", function()
if not APG then return end
APG.notify( true, 2, APG.cfg["notifyLevel"].value, "!WARNING LAG DETECTED!" )
local funcName = APG.cfg[ "lagFunc" ].value
local func = lagFix[ funcName ]
if not func then return end
hook.Run("APG_logsLagDetected") -- put it here so it doesn't spam
processFunc = true
func(false, function()
processFunc = false
processExecs = processExecs + 1
end)
if processExecs > 3 then
-- If the lag cleanup process runs more then 3 times in 3 seconds, then
-- reset our data.
APG.resetLag()
end
end)
--[[ Load hooks and timers ]]
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1,64 @@
local mod = "logs"
if GAS then
if APG.modules[ mod ] and GAS.Logging then
local MODULE = GAS.Logging:MODULE()
MODULE.Category = "APG"
MODULE.Name = "Lag Detection"
MODULE.Colour = Color(255,0,0)
MODULE:Setup(function()
MODULE:Hook("APG_logsLagDetected", "APG.logs.lagDetected", function()
if not APG.cfg["logLagDetected"].value then return end
MODULE:Log("Lag detected, running lag function {1} to prevent further lag.", GAS.Logging:Highlight(APG.cfg["lagFunc"].value))
end)
end)
GAS.Logging:AddModule(MODULE)
local MODULE = GAS.Logging:MODULE()
MODULE.Category = "APG"
MODULE.Name = "Crash Attempts"
MODULE.Colour = Color(255,0,0)
MODULE:Setup(function()
MODULE:Hook("APG_stackCrashAttempt", "APG.logs.stackCrashAttempt", function(ply, count)
if not APG.cfg["logStackCrashAttempt"].value then return end
MODULE:Log("{1} stacked {2} props and triggered a detection.", GAS.Logging:FormatPlayer(ply), GAS.Logging:Highlight(count))
end)
end)
GAS.Logging:AddModule(MODULE)
end
end
if plogs then
if APG.modules[ mod ] and plogs.Register then
plogs.Register("APG", true, Color(255,100,0))
plogs.AddHook("APG_logsLagDetected", function()
if not APG.cfg["logLagDetected"].value then return end
plogs.PlayerLog("console", "APG", "Lag detected, running lag fix function " .. APG.cfg["lagFunc"].value .. " to prevent further lag.")
end)
plogs.AddHook("APG_stackCrashAttempt", function(ply, count)
if not APG.cfg["logStackCrashAttempt"].value then return end
plogs.PlayerLog(ply, "APG", ply:NameID() .. " stacked " .. count .. " props and triggered a detection.", {
["Name"] = ply:Name(),
["SteamID"] = ply:SteamID()
})
end)
end
end

View File

@@ -0,0 +1,261 @@
--[[------------------------------------------
============================
MISCELLANEOUS MODULE
============================
Developer informations :
---------------------------------
Used variables :
vehDamage = { value = true, desc = "True to enable vehicles damages, false to disable." }
vehNoCollide = { value = false, desc = "True to disable collisions between vehicles and players"}
autoFreeze = { value = false, desc = "Freeze every unfrozen prop each X seconds" }
autoFreezeTime = { value = 120, desc = "Auto freeze timer (seconds)"}
]]--------------------------------------------
local mod = "misc"
--[[ Helper functions ]]
local timerSimple = timer.Simple
local function isVehDamage( dmg, atk, ent )
if not IsValid( ent ) then return false end
if dmg:GetDamageType() == DMG_VEHICLE or APG.IsVehicle( atk ) or APG.IsVehicle( ent ) then
return true
end
return false
end
local function getPhys(ent)
local phys = ent.GetPhysicsObject and ent:GetPhysicsObject() or false
return ( phys and IsValid(phys) ) and phys or false
end
local function wait(callback)
timerSimple(0.003, callback)
end
--[[ No Collide vehicles on spawn ]]
APG.hookAdd( mod,"OnEntityCreated", "APG_noCollideVeh", function( ent )
timerSimple(0.03, function()
if APG.cfg[ "vehNoCollide" ].value and APG.IsVehicle( ent ) then
ent:SetCollisionGroup( COLLISION_GROUP_WEAPON )
end
end)
end)
--[[ Disable prop damage ]]
APG.hookAdd( mod, "EntityTakeDamage","APG_noPropDmg", function( target, dmg )
if ( not APG.cfg[ "allowPK" ].value ) then -- Check if prop kill is allowed, before checking anything else.
local atk, ent = dmg:GetAttacker(), dmg:GetInflictor()
if APG.isBadEnt( ent ) or dmg:GetDamageType() == DMG_CRUSH or ( APG.cfg[ "vehDamage" ].value and isVehDamage( dmg, atk, ent ) ) then
dmg:SetDamage(0)
return true
-- ^ Returning true overrides and blocks all related damage, it also prevents the hook from running any further preventing unintentional damage from other addons.
end
end
end)
--[[ Remove Invalid Physics ]]
APG.hookAdd( mod, "OnEntityCreated", "APG_removeInvalidPhysics", function( ent )
if ( not APG.cfg[ "removeInvalidPhysics" ].value ) then return end
timerSimple(0, function()
if not IsValid( ent ) then return end
local model = ent:GetModel()
local owner = APG.getOwner( ent )
local phys = ent:GetPhysicsObject()
if IsValid( owner ) and owner:IsPlayer() then
if ( not model ) or string.lower(string.sub(model, 1, 6)) ~= "models" then
SafeRemoveEntity( ent )
return
end
if ( not IsValid( physObj ) ) and ( not APG.cfg["invalidPhysicsWhitelist"].value[model] ) then
SafeRemoveEntity( ent )
end
end
end)
end)
--[[ Block Physgun Reload ]]
APG.hookAdd( mod, "OnPhysgunReload", "APG_blockPhysgunReload", function( _, ply )
if APG.cfg[ "blockPhysgunReload" ].value then
return false
end
end)
--[[ Block Gravitygun Throwing ]]
APG.hookAdd( mod, "GravGunOnDropped", "APG_blockGravGunThrow", function(ply, ent)
if ( not APG.cfg["blockGravGunThrow"].value ) then return end
APG.killVelocity(ent, false, false, true)
end)
--[[ Auto prop freeze ]]
APG.timerAdd( mod, "APG_autoFreeze", APG.cfg[ "autoFreezeTime" ].value, 0, function()
if APG.cfg[ "autoFreeze" ].value then
APG.freezeProps()
end
end)
--[[ Fading door management ]]
APG.hookAdd(mod, "CanTool", "APG_fadingDoorTool", function(ply, tr, tool)
if IsValid(tr.Entity) and tr.Entity.APG_Ghosted then
APG.notify(false, 1, ply, "Cannot use tool on ghosted entity!")
return false
end
if APG.cfg["fadingDoorHook"].value and tool == "fading_door" then
timerSimple(0, function()
if IsValid(tr.Entity) and not tr.Entity:IsPlayer() then
local ent = tr.Entity
if not IsValid(ent) then return end
if not ent.isFadingDoor then return end
local state = ent.fadeActive
if state then
ent:fadeDeactivate()
end
ent.oldFadeActivate = ent.oldFadeActivate or ent.fadeActivate
ent.oldFadeDeactivate = ent.oldFadeDeactivate or ent.fadeDeactivate
function ent:fadeActivate()
if hook.Run("APG.FadingDoorToggle", self, true, ply) then return end
ent:oldFadeActivate()
end
function ent:fadeDeactivate()
if hook.Run("APG.FadingDoorToggle", self, false, ply) then return end
ent:oldFadeDeactivate()
ent:SetCollisionGroup(COLLISION_GROUP_INTERACTIVE)
end
if state then
ent:fadeActivate()
end
end
end)
end
end)
APG.hookAdd(mod, "APG.FadingDoorToggle", "init", function(ent, state, ply)
if ent.APG_Ghosted then
APG.entUnGhost(ent, ply, "Your fading door is ghosted! (" .. ( ent.GetModel and ent:GetModel() or "???" ) .. ")")
return true
end
ent:ForcePlayerDrop()
local phys = getPhys(ent)
if phys then
phys:EnableMotion(false)
end
end)
--[[ Set sv_turbophysics? ]]--
if APG.cfg[ "touchServerSettings" ].value then
if APG.cfg[ "setTurboPhysics" ].value then
RunConsoleCommand('sv_turbophysics', '1')
else
RunConsoleCommand('sv_turbophysics', '0')
end
end
--[[ FRZR9K ]]--
local zero = Vector(0,0,0)
local function sleepyPhys(phys)
if not phys:IsAsleep() then
local vel = phys:GetVelocity()
if vel:Distance(zero) <= 23 then
phys:Sleep()
end
end
end
APG.timerAdd(mod, "frzr9k-p1", 5, 0, function(ent)
if APG.cfg["sleepyPhys"].value then
for _, v in next, ents.GetAll() do
if v.APG_Frozen == false then
local phys = getPhys( v )
if APG.isBadEnt( v ) and phys then
sleepyPhys( phys )
end
end
end
end
end)
-- Collision Monitoring --
local function collcall(ent, data)
local hit = data.HitObject
local mep = data.PhysObject
if IsValid(ent) and IsValid(hit) and IsValid(mep) then
ent["frzr9k"] = ent["frzr9k"] or {}
local obj = ent["frzr9k"]
obj.Collisions = (obj.Collisions or 0) + 1
obj.CollisionTime = obj.CollisionTime or (CurTime() + 5)
obj.LastCollision = CurTime()
if obj.Collisions > 23 then
obj.Collisions = 0
for _,e in next, {mep, hit} do
e:SetVelocityInstantaneous(Vector(0,0,0))
e:Sleep()
end
end
if obj.CollisionTime < obj.LastCollision then
local subtract = 1
local mem = obj.CollisionTime
while true do
mem = mem + 5
subtract = subtract + 1
if mem >= obj.LastCollision then
break
end
end
obj.Collisions = (obj.Collisions - subtract)
obj.Collisions = (obj.Collisions > 1) and obj.Collisions or 1
obj.CollisionTime = (CurTime() + 5)
end
ent["frzr9k"] = obj
end
end
APG.hookAdd(mod, "OnEntityCreated", "frzr9k-p2", function(ent)
if APG.cfg["sleepyPhys"].value and APG.cfg["sleepyPhysHook"].value then
wait(function()
if APG.isBadEnt( ent ) and getPhys( ent ) then
ent:AddCallback("PhysicsCollide", collcall)
end
end)
end
end)
if APG.cfg[ "physGunMaxRange" ].value then
RunConsoleCommand("physgun_maxrange", APG.cfg["physGunMaxRange"].value) -- Can't run SetInt on a convar that wasn't made in lua
end
--[[------------------------------------------
Load hooks and timers
]]--------------------------------------------
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1,95 @@
--[[------------------------------------------
============================
NOTIFICATION MODULE
============================
]]--------------------------------------------
local mod = "notification"
function APG.notify(log, level, target, ... ) -- The most advanced notification function in the world.
local log = log -- Should the message be logged?
local level = level -- The level of the error.
local target = target -- Whos ist this message meant for?
local msg = {...} -- Pack the arguments in a table.
local outMsg = "" -- Concat the message into this variable.
local isConsole = ( target == "console" ) -- Is this message meant for the console
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 target then
if type(target) == "string" then
(({
["all"] = function()
target = player.GetHumans()
end,
["admin"] = function()
local data = player.GetHumans()
for k, v in next, data do
if not v:IsAdmin() then
data[k] = nil
end
end
target = data
end,
["superadmin"] = function()
local data = player.GetHumans()
for k, v in next, data do
if not v:IsSuperAdmin() then
data[k] = nil
end
end
target = data
end,
["console"] = function()
-- Just send it to the logs without actually giving out a message.
end,
})[target])()
else
if IsEntity( target ) and IsValid( target ) and target:IsPlayer() then
target = { target }
end
end
end
local outMsg = ""
for _, v in next, msg do
local data = v and tostring(v) or ""
if string.len( outMsg ) == 0 then
outMsg = data
else
outMsg = outMsg .. " " .. data
end
end
outMsg = string.Trim( outMsg )
if string.len( outMsg ) > 0 and ( log or isConsole ) then
ServerLog("[APG] " .. outMsg .. "\n")
if isConsole then
MsgC( Color( 72, 216, 41 ), "[APG]", Color( 255, 255, 255 ), outMsg)
return true
end
end
if type(target) ~= "table" then return false end
for _, v in next, target do
if not isentity(v) then continue end
if not IsValid(v) then continue end
net.Start("apg_notice_s2c")
net.WriteUInt(level, 3)
net.WriteString(outMsg)
net.Send(v)
end
return true
end

View File

@@ -0,0 +1,112 @@
--[[------------------------------------------
============================
STACK DETECTION MODULE
============================
Developer informations :
---------------------------------
Used variables :
stackMax = { value = 15, desc = "Max amount of entities stacked on a small area"}
stackArea = { value = 15, desc = "Sphere radius for stack detection (gmod units)"}
fading
]]--------------------------------------------
local mod = "stack_detection"
local SafeRemoveEntity = SafeRemoveEntity
function APG.checkStack( ent, pcount )
if not APG.isBadEnt( ent ) then return end
local efound = ents.FindInSphere(ent:GetPos(), APG.cfg["stackArea"].value )
local count = 0
local max_count = APG.cfg["stackMax"].value
for k, v in pairs (efound) do
if APG.isBadEnt( v ) and APG.getOwner( v ) then
count = count + 1
end
end
if count >= (pcount or max_count) then
local owner, _ = ent:CPPIGetOwner()
ent:Remove()
if not owner.APG_CantPickup then
APG.blockPickup( owner, 10 )
APG.notify( false, 2, owner, "You tried to unfreeze a stack of " .. count .. " props! >:(" )
hook.Run("APG_stackCrashAttempt", owner, count)
APG.notify( true, 2, APG.cfg["notifyLevel"].value, owner:Nick() .. " [" .. owner:SteamID() .. "]" .. " tried to unfreeze a stack of " .. count .. " props!" )
end
end
end
APG.hookAdd(mod, "PhysgunPickup", "APG_stackCheck",function(ply, ent)
if not APG.canPhysGun( ent, ply, "APG_stackCheck" ) then return end
if not APG.modules[ mod ] or not APG.isBadEnt( ent ) then return end
APG.checkStack( ent )
end)
-- Requires Fading Door Hooks --
local notify = false
local curTime = 0
local lastCall = 0
APG.hookAdd(mod, "APG.FadingDoorToggle", "APG_fadingDoorStackCheck", function(ent, faded)
curTime = CurTime()
if IsValid(ent) and faded then
local ply = APG.getOwner(ent)
local pos = ent:GetPos()
local doors = {}
local count = 1 -- Start at 1 to include the original fading door
for _,v in next, ents.FindInSphere(pos, APG.cfg["stackArea"].value) do
if v ~= ent and IsValid(v) and v.isFadingDoor and APG.getOwner(v) == ply then
table.insert(doors, v)
count = count + 1
end
end
if count >= APG.cfg["fadingDoorStackMax"].value then
notify = true
for _,v in next, doors do
SafeRemoveEntity(v)
end
end
if curTime > lastCall then
if notify and APG.cfg["fadingDoorStackNotify"].value then
notify = false
APG.notification(ply:Nick() .. " had a stack of " .. count .. " fading doors that were removed.", APG.cfg["notifyLevel"].value, 2)
APG.notification("Some of your fading doors were removed.", ply)
end
end
end
lastCall = curTime + 0.001
end)
--[[ Stacker Exploit Quick Fix ]]
hook.Add( "InitPostEntity", "APG_InitStackFix", function()
timer.Simple(60, function()
local TOOL = weapons.GetStored("gmod_tool")["Tool"][ "stacker" ] or weapons.GetStored("gmod_tool")["Tool"][ "stacker_v2" ]
if not TOOL then return end
-- Stacker improved (beta) fixed this by setting a maximum number of constraints
-- See : https://git.io/vPvJK
APG.dJobRegister( "weld", 0.3, 20, function( sents )
if not IsValid( sents[1] ) or not IsValid( sents[2]) then return end
constraint.Weld( sents[1], sents[2], 0, 0, 0 )
end)
function TOOL:ApplyWeld( lastEnt, newEnt )
if ( not self:ShouldForceWeld() and not self:ShouldApplyWeld() ) then return end
APG.startDJob( "weld", {lastEnt, newEnt} )
end
end)
end)
--[[ Load hooks and timers ]]
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1,128 @@
--[[------------------------------------------
============================
TOOLS MODULE
============================
Developer informations :
---------------------------------
Used variables :
]]--------------------------------------------
local mod = "tools"
function APG.canTool( ply, tool, ent )
if IsValid(ent) then
if ent.ToolDisabled == false then
return false
end
if ent.CPPICanTool then
return ent:CPPICanTool(ply, tool)
end -- Let CPPI handle things from here.
end
if APG.cfg[ "checkCanTool" ].value and ply.APG_CantPickup == true then-- If we can't pickup we can't tool either.
return false
end
return 0 -- return 0 so if all of the check's don't return anything then it doesn't default to disabling toolgun.
end
--[[ APG CanTool Check ]]
APG.hookAdd(mod, "CanTool", "APG_ToolMain", function(ply, tr, tool)
if not APG.canTool(ply, tool, tr.Entity) then
return false
end
end)
--[[ Tool Spam Control ]]
APG.hookAdd(mod, "CanTool", "APG_ToolSpamControl", function(ply)
if not APG.cfg[ "blockToolSpam" ].value then return end
ply.APG_ToolCTRL = ply.APG_ToolCTRL or {}
local ply = ply
local data = ply.APG_ToolCTRL
local delay = 0
local diff = 0
data.curTime = CurTime()
data.toolDelay = data.toolDelay or 0
data.toolUseTimes = data.toolUseTimes or 0
diff = data.curTime - data.toolDelay
delay = APG.cfg[ "blockToolDelay" ].value
if data.toolUseTimes <= 0 or diff > delay then
data.toolUseTimes = 0
data.toolDelay = 0
data.wasNotified = false
end
if diff > 0 then
data.toolUseTimes = math.max( data.toolUseTimes - 1, 0 )
else
data.toolUseTimes = math.min( data.toolUseTimes + 1, APG.cfg[ "blockToolRate" ].value )
if data.toolUseTimes >= APG.cfg[ "blockToolRate" ].value then
data.toolDelay = data.curTime + delay
if not data.wasNotified then
data.wasNotified = true
APG.notify( false, 1, ply, "You are using the toolgun too fast, slow down!" )
end
return false
end
end
if data.toolDelay == 0 then
data.toolDelay = data.curTime + delay
end
end)
--[[ Block Tool World ]]
APG.hookAdd(mod, "CanTool", "APG_ToolWorldControl", function(ply, tr)
if not APG.cfg[ "blockToolWorld" ].value then return end
if tr.HitWorld then
if not timer.Exists("APG-" .. ply:UniqueID() .. "-Notify") then
APG.notify( false, 1, ply, "You may not use the toolgun on the world." )
timer.Create("APG-" .. ply:UniqueID() .. "-Notify", 5, 1, function() end)
end
return false
end
end)
--[[ Block Tool Unfreeze ]]
APG.hookAdd(mod, "CanTool", "APG_ToolUnfreezeControl", function(ply, tr)
if not APG.cfg[ "blockToolUnfreeze" ].value then return end
timer.Simple(0.003, function()
local ent = tr.Entity
local phys = NULL
if IsValid(ent) then
phys = ent:GetPhysicsObject()
if IsValid(phys) and phys:IsMotionEnabled() then
phys:EnableMotion( false )
end
end
end)
end)
if APG.cfg[ "touchServerSettings" ].value then
local conVar = GetConVar("toolmode_allow_creator")
if conVar then
if APG.cfg[ "blockCreatorTool" ].value then
conVar:SetBool(false)
else
conVar:SetBool(true)
end
end
end
--[[ Load hooks and timers ]]
APG.updateHooks(mod)
APG.updateTimers(mod)

View File

@@ -0,0 +1,378 @@
--[[------------------------------------------
====================================================================================
/!\ READ ME /!\ /!\ READ ME /!\ /!\ READ ME /!\
====================================================================================
This file is the default config file.
If you want to configure APG to fit your server needs, you can edit the config
ingame using the chat command : !apg or apg in console.
]]--------------------------------------------
APG.cfg = APG.cfg or {}
APG.modules = APG.modules or {}
--[[----------
Your very own custom function
This function will run whenever lag is detected on your server!
]]------------
function APG.customFunc( notification )
-- Do something
end
--[[----------
Avalaible premade functions - THIS IS INFORMATIVE PURPOSE ONLY !
]]------------
if CLIENT then
APG_lagFuncs = { -- THIS IS INFORMATIVE PURPOSE ONLY !
"cleanup_all", -- Cleanup every props/ents protected by APG (not worldprops nor vehicles)
"cleanup_unfrozen", -- Cleanup only unfrozen stuff
"ghost_unfrozen", -- Ghost unfrozen stuff
"freeze_unfrozen", -- Freeze unfrozen stuff
"smart_cleanup", -- Cleanup unfrozen fading doors, freeze unfrozens, remove large stacks
"custom_function" -- Your custom function (see APG.customFunc)
} -- THIS IS INFORMATIVE PURPOSE ONLY !
APG_notifyLevels = {
"everyone",
"admin",
"superadmin"
}
end
--[[------------------------------------------
DEFAULT SETTINGS -- You CAN edit this part, but you SHOULDN'T
]]--------------------------------------------
local defaultSettings = {}
defaultSettings.modules = { -- Set to true to enable and false to disable module.
["home"] = true, -- Essential, do not disable.
["ghosting"] = true,
["stack_detection"] = true,
["lag_detection"] = true,
["notification"] = true,
["canphysgun"] = true, -- Essential, do not disable.
["misc"] = true,
["tools"] = true,
["logs"] = true,
}
defaultSettings.cfg = {
--[[----------
Ghosting module
]]------------
ghostColor = {
value = Color(34, 34, 34, 220),
desc = "Color set on ghosted props"
},
ghostColorToggle = {
value = true,
desc = "Toggle ghosting color."
},
badEnts = {
value = {
["prop_physics"] = true,
["wire_"] = false,
["gmod_"] = false,
["keypad"] = false,
},
desc = "Entities to ghost/control/secure (true if exact name, false if it is a pattern"
},
unGhostingWhitelist = {
value = {
["zmlab_"] = false,
},
desc = "Entities that should be set back to their original (spawned) collision group when frozen/dropped."
},
removeInvalidPhysics = {
value = false,
desc = "Should we attempt to detect and remove invalid physics objects? (Entities with bad or no physics objects/Physics objects without models.)"
},
invalidPhysicsWhitelist = {
value = {},
desc = "Entities that shouldn't be removed if they don't have proper physics"
},
alwaysFrozen = {
value = true,
desc = "Set to true to auto freeze props on physgun drop (aka APG_FreezeOnDrop)"
},
--[[----------
Stack detection module
]]------------
stackMax = {
value = 15,
desc = "Max amount of entities stacked in a small area"
},
stackArea = {
value = 15,
desc = "Sphere radius for stack detection (gmod units)"
},
fadingDoorStackMax = {
value = 5,
desc = "Maximum amount of fading doors that can be stacked in stackArea."
},
fadingDoorStackNotify = {
value = false,
desc = "Notify the players when their fading doors were removed"
},
--[[----------
Lag detection module
]]------------
lagTrigger = {
value = 75,
desc = "[Default: 75%] Differential threshold between current lag and average lag."
},
lagsCount = {
value = 8,
desc = "Number of consectuives laggy frames in order to run a cleanup."
},
bigLag = {
value = 2,
desc = "Maximum time (seconds) between 2 frames to trigger a cleanup"
},
lagFunc = {
value = "ghost_unfrozen",
desc = "Function ran on lag detected, see APG_lagFuncs."
},
lagFuncTime = {
value = 8,
desc = "Time (seconds) between 2 anti lag function (avoid spam)"
},
--[[ Notifications ]] --
notifySounds = {
value = false, -- Might make it where certain ones run sound
desc = "When notifications run do you want the sounds to play?"
},
notifyLevel = {
value = "admin",
desc = "Notification levels, refer to APG_notifyLevels"
},
notifyLagFunc = {
value = false,
desc = "Do you want the notifyLevel to see the lagFunc that ran? (refer to APG_lagFuncs)"
},
-- TODO: Make a ULX/ULIB module
-- notifyULibInheritance = {
-- value = true,
-- desc = "Do you want to use inheritance for notifyRanks? (only works with ULIB/ULX)"
-- },
-- notifyRanks = {
-- value = { "trialmod", "moderator", "admin", "superadmin", "owner" },
-- desc = "The ranks that you want to see the notification" -- If you have notifyULibInheritance you only need to do the lowest rank(s)
-- },
--[[ Override Server Settings? ]]
touchServerSettings = {
value = false,
desc = "Should we override Server Settings? (Used for setting ConVars)"
},
--[[ Vehicles ]]--
vehDamage = {
value = false,
desc = "True to disable vehicles damages, false to enable."
},
vehNoCollide = {
value = false,
desc = "True to disable collisions between vehicles and players"
},
vehIncludeWAC = {
value = true,
desc = "Check for WAC vehicles."
},
vehAntiGhost = {
value = false,
desc = "Toggle vehicle ghosting"
},
blockGravGunThrow = {
value = true,
desc = "Toggle GravGun throwing."
},
setTurboPhysics = {
value = false,
desc = "Toggle sv_turbophysics."
},
--[[ Tool Control ]]--
checkCanTool = {
value = true,
desc = "Should tools be blocked on APG_CantPickup?"
},
blockToolSpam = {
value = true,
desc = "Block players from spamming the toolgun."
},
blockToolRate = {
value = 5,
desc = "How fast can we use the toolgun before it gets blocked? (Clicks per second(s))"
},
blockToolDelay = {
value = 1,
desc = "How many seconds should we wait after we were stopped? (The aforementioned second(s))"
},
blockToolWorld = {
value = false,
desc = "Prevent using the toolgun on the world."
},
blockToolUnfreeze = {
value = true,
desc = "Prevent the toolgun from unfreezing props."
},
blockCreatorTool = {
value = true,
desc = "Should we block the creator tool?"
},
checkTooledEnts = {
value = true,
desc = "Review entities near tool use."
},
physGunMaxRange = {
value = 700,
desc = "Max range a physics gun can go"
},
--[[ Logs ]]--
logStackCrashAttempt = {
value = true,
desc = "Log when someone tries to lag/crash the server with stacker"
},
logLagDetected = {
value = true,
desc = "Log when the server lags"
},
--[[ Props related ]]--
blockPhysgunReload = {
value = true,
desc = "Block players from using physgun reload"
},
blockContraptionMove = {
value = true,
desc = "Block players from moving contraptions"
},
autoFreeze = {
value = false,
desc = "Freeze every unfrozen prop each X seconds"
},
autoFreezeTime = {
value = 120,
desc = "Auto freeze timer (seconds)"
},
fadingDoorHook = {
value = true,
desc = "Inject custom hooks into Fading Doors"
},
fadingDoorGhosting = {
value = true,
desc = "Activate fading door ghosting"
},
sleepyPhys = {
value = false,
desc = "Activate FRZR9K (Sleepy Physics)"
},
sleepyPhysHook = {
value = false,
desc = "Hook FRZR9K into collision (Experimental)"
},
allowPK = {
value = false,
desc = "Allow prop killing"
},
developerDebug = {
value = false,
desc = "Dev Logs (prints stuff)"
}
}
--[[------------------------------------------
LOADING SAVED SETTINGS -- DO NOT EDIT THIS PART
]]--------------------------------------------
if SERVER and file.Exists( "apg/settings.txt", "DATA" ) then
table.Merge( APG, defaultSettings ) -- Load the default settings first!
local settings = file.Read( "apg/settings.txt", "DATA" )
settings = util.JSONToTable( settings )
if not settings.modules or not settings.cfg then
ErrorNoHalt("Your custom settings have not been loaded because you have a misconfigured settings file! The default settings were used instead!")
return
end
local removedSetting = {}
for k, v in next, settings.modules do
if defaultSettings.modules[k] == nil then
settings.modules[k] = nil
table.insert(removedSetting, k)
end
end
for k, v in next, settings.cfg do
if defaultSettings.cfg[k] == nil then
settings.cfg[k] = nil
table.insert(removedSetting, k)
end
end
if next(removedSetting) then
print("[APG] Settings File Updated. (Conflicts Resolved)")
print("[APG] The Following Settings Have Been Removed: ")
for _,v in next, removedSetting do
print("\t> \"" .. tostring(v) .. "\" has been removed.")
end
removedSetting = nil
file.Write("apg/settings.txt", util.TableToJSON(settings))
end
table.Merge( APG, settings )
else
table.Merge( APG, defaultSettings )
end

View File

@@ -0,0 +1,420 @@
util.AddNetworkString("apg_notice_s2c")
APG = APG or {}
local IsValid = IsValid
local table = table
local isentity = isentity
--[[ ENTITY Related ]]
--[[
Check if the player can pick up the entity
@param {entity} ent
@param {player} ply
@returns {boolean}
]]
function APG.canPhysGun( ent, ply )
-- Predict if the player can pickup an entity.
if not IsValid(ent) then return false end -- The entity isn't valid, don't pickup.
if ent:GetPersistent() then
return false
end
if ent.PhysgunDisabled then
return false
end -- Check if the entity is physgun disabled.
ent.APG_HeldBy = ent.APG_HeldBy or {}
ent.APG_HeldBy.plys = ent.APG_HeldBy.plys or {}
if ply.APG_CantPickup == true or next( ent.APG_HeldBy.plys ) then
ply:ConCommand("-attack") -- Tell the player to stop physgunning.
return false
end -- Is APG blocking the pickup?
if ent.CPPICanPhysgun then
return ent:CPPICanPhysgun(ply)
end -- Let CPPI handle things from here.
return false -- If everything fails we probably shouldn't be picking this up.
end
function APG.isWhitelistedEnt( ent )
local class = ent:GetClass()
for k, v in pairs (APG.cfg["unGhostingWhitelist"].value) do
if ( v and k == class ) or (not v and string.find( class, k ) ) then
return true
end
end
return false
end
--[[
Check if the entity is a bad entity, as defined in badEnts
@param {entity} ent
@returns {boolean}
]]
function APG.isBadEnt( ent )
if ent and not ent.GetClass then return false end -- Ignore if we can't read the class.
if not IsValid(ent) then return false end -- Ignore invalid entities.
if ent.jailWall == true then return false end -- Ignore ULX jails.
if Entity(0) == ent or ent:IsWorld() then return false end -- Ignore worldspawn.
if ent:IsWeapon() then return false end -- Ignore weapons.
if ent:IsPlayer() then return false end -- Ignore players.
if ent:IsNPC() then return false end
if ent.ARCBank_MapEntity then return false end --Ignore ARCBank ents
local h = hook.Run("APGisBadEnt", ent)
if isbool(h) then return h end
local class = ent:GetClass()
for k, v in pairs (APG.cfg["badEnts"].value) do
if ( v and k == class ) or (not v and string.find( class, k ) ) then
return true
end
end
return false
end
--[[
Check the props owner
@return {player} or nil
]]
function APG.getOwner( ent )
local owner, _ = ent:CPPIGetOwner() or ent.FPPOwner or nil
return owner
end
--[[
Add's a timer to the module table
@param {string} module
@param {string} identifier
@param {number} delay
@param {number} repetitions
@param {function} function
@void
]]
local function killVel(phys, freeze)
local vec = Vector()
if not IsValid(phys) then return end
if freeze then phys:EnableMotion(false) return end
phys:SetVelocity(vec)
phys:SetVelocityInstantaneous(vec)
phys:AddAngleVelocity(phys:GetAngleVelocity() * -1)
phys:Sleep()
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
ent:SetVelocity(vec)
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
timer.Simple(0, function()
if not IsValid(ent) then return end
local phys = ent:GetPhysicsObject()
if IsValid(phys) then
phys:Wake()
end
end)
end
end
function APG.freezeIt( ent, extend )
local obj = ent:GetPhysicsObject()
if extend then
for _,v in next, constraint.GetAllConstrainedEntities(ent) do
killVel(v:GetPhysicsObject(), true)
v.APG_Frozen = true
end
else
if IsValid(obj) then
killVel(obj, true)
ent.APG_Frozen = true
end
end
end
function APG.FindWAC(ent)
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.IsVehicle(v, basic)
if not IsValid(v) then return false end
if v:IsVehicle() then return true end
if string.find(v:GetClass(), "vehicle") then return true end
if basic then return false end
if APG.FindWAC(v) then return true end
local parent = v:GetParent()
return APG.IsVehicle(parent, true)
end
function APG.cleanUp( mode, notify, specific )
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 APG.IsVehicle(v) then continue end
if mode == "unfrozen" and v.APG_Frozen then -- Whether to clean only not frozen ents or all ents
continue
else
v:Remove()
end
end
if notify or APG.cfg["notifyLagFunc"].value then
APG.notify( false, 2, "all", APG.cfg["notifyLevel"].value, "Cleaned up (mode: ", mode, ")" )
end
end
function APG.ghostThemAll( notify, callback )
if not APG.modules[ "ghosting" ] then
return APG.notify( false, 0, "admins", "[APG] Warning: Tried to ghost props but ghosting is disabled!" )
end
for _, v in next, ents.GetAll() do
if ( not APG.isBadEnt(v) ) or ( not APG.getOwner( v ) ) or APG.IsVehicle(v) or v.APG_Frozen then continue end
APG.entGhost( v, true, false )
end
if notify or APG.cfg["notifyLagFunc"].value then
APG.notify( false, APG.cfg["notifyLevel"].value, "admins", "Some unfrozen entities were ghosted!" )
end
if isfunction(callback) then
callback()
end
end
function APG.freezeProps( notify, callback )
for _, v in next, ents.GetAll() do
if not APG.isBadEnt(v) or not APG.getOwner( v ) then continue end
APG.freezeIt( v )
end
if notify or APG.cfg["notifyLagFunc"].value then
APG.notify(false, APG.cfg["notifyLevel"].value, "all", "Some entities were frozen!")
end
if isfunction(callback) then
callback()
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, callback )
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()
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")
if isfunction(callback) then
callback()
end
end)
end
function APG.ForcePlayerDrop(ply, ent)
if IsValid(ply) then
ply:ConCommand("-attack")
end
if IsValid(ent) then
DropEntityIfHeld( ent )
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
--[[--------------------
Set when a prop is unfrozen.
]]----------------------
hook.Add("PlayerUnfrozeObject", "APG_PlayerUnfrozeObject", function(ply, ent, object)
if not APG.isBadEnt( ent ) then return end
ent.APG_Frozen = false
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)
--[[--------------------
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("InitPostEntity", "APG_Load", function()
hook.Add("Think", "APG_Load", function()
APG.initialize()
hook.Remove("Think", "APG_Load")
end)
end)

View File

@@ -0,0 +1,150 @@
util.AddNetworkString("apg_settings_c2s")
util.AddNetworkString("apg_menu_s2c")
util.AddNetworkString("apg_context_c2s")
local function saveSettings( json )
if not file.Exists("apg", "DATA") then file.CreateDir( "apg" ) end
file.Write("apg/settings.txt", json)
end
local function recSettings( len, ply)
if not ply:IsSuperAdmin() then return end
len = net.ReadUInt( 32 )
if len == 0 then return end
local settings = net.ReadData( len )
settings = util.Decompress( settings )
saveSettings( settings )
settings = util.JSONToTable( settings )
APG.cfg = settings.cfg
table.Merge(APG, settings)
APG.reload()
end
net.Receive( "apg_settings_c2s", recSettings)
local function sendToClient( ply )
local settings = {}
settings.cfg = APG.cfg or {}
settings.modules = APG.modules or {}
settings = util.TableToJSON( settings )
settings = util.Compress( settings )
net.Start("apg_menu_s2c")
net.WriteUInt( settings:len(), 32 ) -- Write the length of the data
net.WriteData( settings, settings:len() ) -- Write the data
net.Send(ply)
end
hook.Add( "PlayerSay", "openAPGmenu", function( ply, text, public )
text = string.lower( text )
if ply:IsSuperAdmin() and text == "!apg" then
sendToClient( ply )
return ""
end
end)
local function checkOwner(owner, ply)
if ( IsValid(owner) and owner:IsPlayer() ) then
return true
else
APG.notification("The owner of this entity is NOT a Player. (Owner: " .. type(owner) .. ")", ply)
return false
end
end
-- TODO: Revamp this, really don't like how it looks, would rather have a function for each
-- it's too clustered.
local function contextCMD(_,ply)
if not ply:IsSuperAdmin() then return end
local cmd = net.ReadString()
local ent = net.ReadEntity()
ent = IsValid(ent) and ent or ply:GetEyeTraceNoCursor().Entity or nil
local class = IsValid(ent) and ent.GetClass and ent:GetClass() or nil
if not class then return end
local owner = APG.getOwner(ent)
if cmd == "addghost" then
if not APG.cfg.badEnts.value[class] then
APG.cfg.badEnts.value[class] = true
APG.notify( false, 0, ply, "\"", class, "\" added to Ghost List!" )
else
APG.notify( false, 0, ply, "This class is already listed!" )
end
elseif cmd == "remghost" then
APG.cfg.badEnts.value[class] = nil
APG.notify( false, 0, ply, "\"", class, "\" removed from Ghost List!" )
elseif cmd == "clearowner" then
if not checkOwner(owner, ply) then return end
cleanup.CC_Cleanup(owner,"gmod_cleanup",{})
elseif cmd == "clearunfrozen" then
if not checkOwner(owner, ply) then return end
local count = 0
for _,v in next, ents.GetAll() do
if not (IsValid(v) and APG.getOwner(v) == owner) then continue end
if not APG.isBadEnt(v) then continue end
if not v.APG_Frozen then
SafeRemoveEntity(v)
count = count + 1
end
end
APG.notify( false, 0, ply, count, "entities have been removed!" )
elseif cmd == "getownercount" then
if not checkOwner(owner, ply) then return end
local count = 0
for _,v in next, ents.GetAll() do
if IsValid(v) and APG.getOwner(v) == owner then
count = count + 1
end
end
APG.notify( false, 0, ply, owner:Nick(), "[", owner:SteamID(), "]", "has", count, (count == 1 and "entity." or "entities.") )
elseif cmd == "freezeclass" then
local count = 0
for _,v in next, ents.FindByClass(class) do
if IsValid(v) and not v.APG_Frozen then
count = count + 1
APG.killVelocity(v, false, true, false)
end
end
APG.notify( false, 0, ply, (count or 0), (count == 1 and "Entity" or "Entities"), "Frozen" )
elseif cmd == "sleepclass" then
local count = 0
for _,v in next, ents.FindByClass(class) do
if IsValid(v) and not v.APG_Frozen then
count = count + 1
APG.killVelocity(v, false, false, false)
end
end
APG.notify( false, 0, ply, (count or 0), (count == 1 and "Entity is" or "Entities are"), "now Sleeping!" )
elseif cmd == "ghost" then
APG.entGhost(ent)
APG.notify( false, 0, ply, ent, " was ghosted." )
end
if cmd == "addghost" or cmd == "remghost" then
local settings = {}
settings.cfg = APG.cfg or {}
settings.modules = APG.modules or {}
saveSettings( util.TableToJSON( settings ) )
APG.reload()
end
end
net.Receive("apg_context_c2s", contextCMD)

View File

@@ -0,0 +1,3 @@
APG = {}
include( "apg/sh_config.lua" )
include( "apg/cl_menu.lua" )

View File

@@ -0,0 +1,176 @@
--[[ INITIALIZE APG ]]
APG = {}
APG.modules = APG.modules or {}
--[[ Micro Optimization ]]
local timer = timer
local table = table
--[[ CLIENT related ]]
AddCSLuaFile("apg/sh_config.lua")
AddCSLuaFile("apg/cl_utils.lua")
AddCSLuaFile("apg/cl_menu.lua")
--[[ REGISTER Modules ]]
local modules, _ = file.Find("apg/modules/*.lua","LUA")
for _,v in next, modules do
if v then
niceName = string.gsub(tostring(v),"%.lua","")
APG.modules[ niceName ] = false
APG[ niceName ] = { hooks = {}, timers = {}}
end
end
--[[
Add's a hook to the module table
@param {string} module
@param {string} event
@param {string} identifier
@param {function} function
@void
]]
function APG.hookAdd( module, event, identifier, func )
table.insert( APG[ module ][ "hooks"], { event = event, identifier = identifier, func = func })
end
--[[
Adds all the hooks that the module needs
@param {string} module
@void
]]
function APG.updateHooks( module )
for k, v in next, APG[module]["hooks"] do
hook.Add( v.event, v.identifier, v.func )
end
end
--[[
Add's a timer to the module table
@param {string} module
@param {string} identifier
@param {number} delay
@param {number} repetitions
@param {function} function
@void
]]
function APG.timerAdd( module, identifier, delay, repetitions, func )
table.insert( APG[ module ][ "timers"], { identifier = identifier, delay = delay, repetitions = repetitions, func = func } )
end
--[[
Add's a the timers a module needs.
@param {string} module
@void
]]
function APG.updateTimers(module)
for k, v in next, APG[module]["timers"] do
timer.Create( v.identifier, v.delay, v.repetitions, v.func )
end
end
--[[
Load's a APG module
@param {string} module
@void
]]
function APG.load( module )
APG.modules[ module ] = true
include( "apg/modules/" .. module .. ".lua" )
print("[APG] " .. module .. " loaded.")
end
--[[
Unload's a APG module
@param {string} module
@void
]]
function APG.unLoad( module )
APG.modules[module] = false
if not (istable(APG[module]) and next(APG[module])) then return end
local hooks = APG[ module ]["hooks"]
for k, v in next, hooks do
hook.Remove(v.event, v.identifier)
end
local timers = APG[ module ]["timers"]
for k, v in next, timers do
timer.Remove(v.identifier)
end
print("[APG] " .. module .. " unloaded.")
end
function APG.reload()
for k, v in next, APG.modules do
if APG.modules[ k ] == true then
APG.unLoad( k )
APG.load( k )
else
APG.unLoad( k )
end
end
end
--[[ local settings = {}
function APG.sampleServerSettings()
end
function APG.getServerSettings()
end ]]
function APG.initialize()
for k, v in next, APG.modules do
if APG.modules[k] == true then
APG.load(k)
end
end
end
--[[ LOADING ]]
-- Loading config first
include( "apg/sh_config.lua" )
-- Loading APG main functions
include( "apg/sv_apg.lua") -- Modules loaded at the bottom
-- Loading APG menu
include( "apg/sv_menu.lua" )
--[[ CVars INIT ]]
concommand.Add("apg_set", function( ply, cmd, args, argStr )
if not ply:IsSuperAdmin() then return end
if args[1] == "module" then
local _module = APG.modules[ args[2] ]
if _module != nil then
if _module == true then
APG.unLoad( args[2] )
APG.notification( "[APG] Module " .. args[2] .. " disabled.", ply)
else
APG.load( args[2] )
APG.notification( "[APG] Module " .. args[2] .. " enabled.", ply)
end
else
APG.notification( "[APG] This module does not exist", ply)
end
elseif args[1] == "help" then
local cfg = APG.cfg[ args[2] ]
if cfg then
APG.notification( cfg.desc, ply)
else
APG.notification( "[APG] Help: This setting does not exist", ply)
end
else
APG.notification( ply, "Error: unknown setting")
end
end)

View File

@@ -0,0 +1,2 @@
-- ULX Admin Commands Coming Soon!
--- PLanned Commands for Prop Management, and Server Cleanup.