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

22
addons/apg/.travis.yml Normal file
View File

@@ -0,0 +1,22 @@
sudo: false
language: c
addons:
apt:
packages:
- libc6:i386
- libstdc++6:i386
before_install:
# Download the lua
- wget https://github.com/Metastruct/gtravis/releases/download/travisbins/gluac.tar.xz
- tar -xf gluac.tar.xz
- export LD_LIBRARY_PATH=`pwd`/gluac${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH:-}
# Set the $PATH so gluac can be executed
- export PATH=$PATH:`pwd`/gluac
- echo $PWD
script: find lua/ -iname '*.lua' -print0 | xargs -0 -- gluac -p --

View File

@@ -0,0 +1,62 @@
# Commit Rules
- [Commit Rules](#commit-rules)
- [Squash](#squash)
- [Here's what you do](#heres-what-you-do)
- [Oh no I messed up](#oh-no-i-messed-up)
- [Be Descriptive](#be-descriptive)
- [Test and Check](#test-and-check)
- [Be Considerate](#be-considerate)
## Squash
[Back to Top](#commit-rules)
If you have lots of commits like "Oops", "Reverted X", "Fixed bug", squash them.
### Here's what you do
1. Backup your current repo by copying it into another folder
1. Be sure to remove the `.git` folder as you don't need that in your backup.
2. Go back to the working folder, aka the one with the `.git` folder.
3. `git log --pretty=oneline --abbrev-commit`
1. The displayed list will be shown top (newest) to bottom (oldest).
2. Find the main commit that you made (before the small fixes).
3. Copy the code under it, it will look something like this... `742c3ac`
4. `git rebase -i <abbreviated commit code>`
1. Git should guide you through this.
2. If you need to fix conflicts your files will be edited, and you will get an error.
3. [Don't panic](https://help.github.com/en/articles/resolving-a-merge-conflict-using-the-command-line)
5. Review your changes!
6. Make sure to **review your changes** sometimes conflict resolution can get undesirable code back into your repo.
7. When you're ready use `git push --force` to push your rebase.
### Oh no I messed up
1. Don't panic! You made a backup.
2. Delete everything besides the `.git` folder.
3. Copy and paste everything from your backup.
4. Now just push the change as a new commit.
- Be sure to be very descriptive in your commit message/comment.
---
You may also want to look into [this stackoverflow question](https://stackoverflow.com/q/134882) and [the answer](https://stackoverflow.com/a/135614) provided.
## Be Descriptive
[Back to Top](#commit-rules)
You don't have to write a paragraph about all your changes, but describe what you're trying to do in a clear way.
## Test and Check
[Back to Top](#commit-rules)
Always test and check your code, and don't just test once. Test multiple times in multiple environements. If your code isn't tested don't commit.
## Be Considerate
[Back to Top](#commit-rules)
This addon will be runnning with other addons, please make sure it plays nice. Specify any detours, try not to [error](https://wiki.garrysmod.com/page/Global/error), and be sure to make all your codes behaviour expected.

68
addons/apg/README.md Normal file
View File

@@ -0,0 +1,68 @@
Due to seemingly consistent issues with the code base of APG I've made PUG.
However I doubt PUG will get any updates because I simply no longer play Garry's mod,
but if you'd like to check my alternative to APG, check out [__PUG__ on Gitlab](https://gitlab.com/NanoAi/gm_pug) or [__PUG__ Github Mirror](https://github.com/NanoAi/gm_pug)
------------------
This will no longer be updated as I can no longer support it, if you are looking for a good alternative please check out PUG linked above. It's also free, and can do almost everything APG can do.
Also please for the love of god, don't pay for stuff like this.
------------------
# APG - Anti Prop Griefing & Crash Protection
![- APG - Light Weight, Easy to Use, Stops Crashes](https://i.imgur.com/DrbZOgk.png "APG - Light Weight, Easy to Use, Stops Crashes")
We are dedicated and focused on providing servers with the best answer to prop
griefers, killers, and exploiters. We saw all the currently available addons and
felt that neither one of them alone could really do everything that we wanted it
to do, so naturally we made our own and decided to share it with you.
## This does still require a prop protection addon
### Compatible prop protection addons (with CPPI)
* [Falco's Prop Protection (FPP)](https://github.com/FPtje/Falcos-Prop-protection/)
* [PatchProtect](https://github.com/Patcher56/PatchProtect)
* [Simple Prop Protection (SPP)](https://github.com/Donkie/SimplePropProtection)
### ⚠ Don't use more then one prop protection/anti prop griefing addon at a time!
### ( You have been warned! )
## Features & Notes
* ✅ Easy install and configuration ( Just say !apg )
* ✅ Customizable blacklist of entities to protect ( props, wire, etc )
* ✅ Props ghosting/unghosting on physgun
* ✅ Disables prop damage to players
* ✅ Controls prop pushing against players
* ✅ Controls prop pushing vehicles
* ✅ Controls prop surfing
* ✅ Blocks many types of exploits
* ✅ Blocks stacker exploit
* ✅ Blocks fading door exploit
* ✅ Blocks Advanced Duplicator exploit
* ✅ Blocks tool gun spamming
* ✅ Allows to toggle the use of the toolgun on the world
* ✅ Ability to check entities around the prop for stack's
* ✅ Ability to block vehicles damages against players
* ✅ Ability to make vehicles not collide with players
* ✅ Allows to block physgun reload
* ✅ Allows to block moving contraptions (props that are welded together)
* ✅ Supports anti-trapping for fading doors.
* ✅ Send a message to admins when a large stack of props is detected
* ⚠ Detours [PhysgunPickup](https://wiki.garrysmod.com/page/GM/PhysgunPickup) for better confirmations
* ⚠ Detours [SetColor](https://wiki.garrysmod.com/page/Entity/SetColor) to prevent stuff from turning purple/pink unexpectedly
* ⚠ Detours [SetCollisionGroup](https://wiki.garrysmod.com/page/Entity/SetCollisionGroup) to prevent overrides
Lag triggers are based on fancy algorithms and timers, if you are getting false positives try messing around with the values.
If you find any issue, exploit, possible improvement, suggestions, feel free to make an issue!
Credits:
* This project is currently updated and maintained by [NanoAi](http://steamcommunity.com/profiles/76561198096713277)
* This addon was originally created by [WhileTrue](http://steamcommunity.com/profiles/76561197972967270)
* Special thanks to [AvarianKnight](http://steamcommunity.com/profiles/76561198174460202) for reviving this project!

17
addons/apg/addon.json Normal file
View File

@@ -0,0 +1,17 @@
{
"title": "APG - Anti Prop Griefing & Crash Detection",
"type": "ServerContent",
"tags": [
"roleplay",
"build"
],
"ignore": [
".git",
".travis.yml",
"*.psd",
"*.vcproj",
"*.svn*",
"*.git*",
"*.md"
]
}

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.