Files
wnsrc/lua/weapons/gmod_tool/stools/stacker_improved.lua

1692 lines
75 KiB
Lua
Raw Normal View History

2024-08-04 22:55:00 +03:00
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[--------------------------------------------------------------------------
Improved Stacker Tool
Note:
Please DO NOT reupload this tool (verbatim or small tweaks) to the workshop or other public file-sharing websites.
I actively maintain this tool, so reuploading it may lead to people using outdated, buggy, or malicious copies.
If there is an issue with the tool, LET ME KNOW via one of the following pages:
- GitHub: https://github.com/Mista-Tea/improved-stacker
- Workshop: http://steamcommunity.com/sharedfiles/filedetails/?id=264467687
- Facepunch: https://facepunch.com/showthread.php?t=1399120
Author:
- Original :: OverloadUT (STEAM_0:1:5250809)
- Updated for GMod 13 :: Marii (STEAM_0:1:16015332)
- Rewritten :: Mista Tea (STEAM_0:0:27507323)
Changelog:
- May 27th, 2014 :: Added to GitHub
- May 28th, 2014 :: Added to Workshop
- Jun 5th, 2014 :: Massive overhaul
- Jul 24th, 2014 :: Large update
- Aug 12th, 2014 :: Optimizations
- Jun 30th, 2015 :: Bug fixes/features
- Jul 11th, 2015 :: Bug fixes
- Oct 26th, 2015 :: Bug fixes
- Aug 3rd, 2016 :: Bug fixes
- Aug 31st, 2016 :: Bug fixes
- Sep 2nd, 2016 :: Added Bulgarian language support
- Sep 26th, 2017 :: Added ability to toggle use of SHIFT key with LMB/RMB
- Oct 27th, 2017 :: Small client optimization, reverted nocollide implementation back to original
- Apr 14th, 2018 :: Added French language support
- Oct 12th, 2018 :: Added Polish language support
- Jul 21st, 2019 :: Added Russian language support
- Jul 23rd, 2019 :: Added Simplified Chinese language support
- May 10th, 2020 :: Fixed unstable clientside ghosts caused by ents.CreateClientProp changes
- Jun 18th, 2020 :: Clientside performance improvement when tool is not selected
- Apr 30th, 2021 :: Added German language support
Fixes:
- Prevented crash from players using very high X/Y/Z offset values.
- Prevented crash from players using very high P/Y/R rotate values.
- Prevented crash from very specific constraint settings.
- Fixed the halo option for ghosted props not working.
- Fixed massive FPS drop from halos being rendered in a Think hook instead of a PreDrawHalos hook.
- Fixed materials and color saving when duping stacked props.
- Fixed incorrect stack angles when trying to create a stack on an existing stack.
Tweaks:
- Added convenience functions to retrieve the client convars.
- Added option to enable/disable automatically applying materials to the stacked props.
- Added option to enable/disable automatically applying colors to the stacked props.
- Added option to enable/disable automatically applying physical properties (gravity, physics material, weight) to the stacked props.
- Added support for props with multiple skins.
- Added support for external prop protections/anti-spam addons with the StackerEntity hook.
- Modified NoCollide to actually no-collide each stacker prop with every other prop in the stack.
- Added console variables for server operators to limit various parts of stacker.
> stacker_improved_max_per_player <-inf/inf> (less than 0 == no limit)
> stacker_improved_max_per_stack <-inf/inf> (less than 0 == no limit)
> stacker_improved_max_offsetx <-inf/inf>
> stacker_improved_max_offsety <-inf/inf>
> stacker_improved_max_offsetz <-inf/inf>
> stacker_improved_force_stayinworld <0/1>
> stacker_improved_force_weld <0/1>
> stacker_improved_force_freeze <0/1>
> stacker_improved_force_nocollide <0/1>
> stacker_improved_force_nocollide_all <0/1>
> stacker_improved_delay <0/inf>
- Added console commands for server admins to control the console variables that limit stacker.
> stacker_improved_set_max_per_player <-inf/inf> (less than 0 == no limit)
> stacker_improved_set_max_per_stack <-inf/inf> (less than 0 == no limit)
> stacker_improved_set_maxoffset <-inf/inf>
> stacker_improved_set_maxoffsetx <-inf/inf>
> stacker_improved_set_maxoffsety <-inf/inf>
> stacker_improved_set_maxoffsetz <-inf/inf>
> stacker_improved_set_force_stayinworld <0/1>
> stacker_improved_set_weld <0/1>
> stacker_improved_set_freeze <0/1>
> stacker_improved_set_nocollide <0/1>
> stacker_improved_set_nocollide_all <0/1>
> stacker_improved_set_delay <0/inf>
----------------------------------------------------------------------------]]
local mode = TOOL.Mode -- defined by the name of this file (default should be stacker_improved)
--[[--------------------------------------------------------------------------
-- Modules & Dependencies
--------------------------------------------------------------------------]]--
-- needed for localization support (depends on GMod locale: "gmod_language")
include( "improvedstacker/localify.lua" )
localify.LoadSharedFile( "improvedstacker/localization.lua" ) -- loads the file containing localized phrases
local L = localify.Localize -- used for translating string tokens into localized phrases
local prefix = "#tool."..mode.."." -- prefix used for this tool's localization tokens
-- needed for various stacker functionality
include( "improvedstacker/improvedstacker.lua" )
improvedstacker.Initialize( mode )
--[[--------------------------------------------------------------------------
-- Localized Functions & Variables
--------------------------------------------------------------------------]]--
-- localizing global functions/tables is an encouraged practice that improves code efficiency,
-- since accessing a local value is considerably faster than a global value
local bit = bit
local cam = cam
local net = net
local util = util
local math = math
local undo = undo
local halo = halo
local game = game
local ents = ents
local draw = draw
local hook = hook
local list = list
local pairs = pairs
local table = table
local Angle = Angle
local Color = Color
local render = render
local Vector = Vector
local tobool = tobool
local CurTime = CurTime
local surface = surface
local IsValid = IsValid
local localify = localify
local language = language
local tonumber = tonumber
local GetConVar = GetConVar
local construct = construct
local duplicator = duplicator
local constraint = constraint
local concommand = concommand
local LocalPlayer = LocalPlayer
local CreateConVar = CreateConVar
local improvedstacker = improvedstacker
local GetConVarNumber = GetConVarNumber
local RunConsoleCommand = RunConsoleCommand
local IN_USE = IN_USE
local NOTIFY_ERROR = NOTIFY_ERROR or 1
local MOVETYPE_NONE = MOVETYPE_NONE
local SOLID_VPHYSICS = SOLID_VPHYSICS
local RENDERMODE_TRANSALPHA = RENDERMODE_TRANSALPHA
local TRANSPARENT = Color( 255, 255, 255, 150 )
local MIN_NOTIFY_BITS = 3 -- the minimum number of bits needed to send a NOTIFY enum
local NOTIFY_DURATION = 5 -- the number of seconds to display notifications
local MAX_ANGLE = 180
local showSettings = false
--[[--------------------------------------------------------------------------
-- Tool Settings
--------------------------------------------------------------------------]]--
TOOL.Category = "Construction"
TOOL.Name = L(prefix.."name")
TOOL.Information = {
"left",
"right",
{
name = "shift_left",
icon2 = "gui/e.png",
icon = "gui/lmb.png",
},
{
name = "shift_right",
icon2 = "gui/e.png",
icon = "gui/rmb.png",
},
"reload",
}
if ( CLIENT ) then
TOOL.ClientConVar[ "mode" ] = improvedstacker.MODE_PROP
TOOL.ClientConVar[ "direction" ] = improvedstacker.DIRECTION_UP
TOOL.ClientConVar[ "count" ] = "1"
TOOL.ClientConVar[ "freeze" ] = "1"
TOOL.ClientConVar[ "weld" ] = "1"
TOOL.ClientConVar[ "nocollide" ] = "1"
TOOL.ClientConVar[ "ghostall" ] = "1"
TOOL.ClientConVar[ "material" ] = "1"
TOOL.ClientConVar[ "physprop" ] = "1"
TOOL.ClientConVar[ "color" ] = "1"
TOOL.ClientConVar[ "offsetx" ] = "0"
TOOL.ClientConVar[ "offsety" ] = "0"
TOOL.ClientConVar[ "offsetz" ] = "0"
TOOL.ClientConVar[ "pitch" ] = "0"
TOOL.ClientConVar[ "yaw" ] = "0"
TOOL.ClientConVar[ "roll" ] = "0"
TOOL.ClientConVar[ "relative" ] = "1"
TOOL.ClientConVar[ "draw_halos" ] = "0"
TOOL.ClientConVar[ "halo_r" ] = "255"
TOOL.ClientConVar[ "halo_g" ] = "0"
TOOL.ClientConVar[ "halo_b" ] = "0"
TOOL.ClientConVar[ "halo_a" ] = "255"
TOOL.ClientConVar[ "draw_axis" ] = "1"
TOOL.ClientConVar[ "axis_labels" ] = "1"
TOOL.ClientConVar[ "axis_angles" ] = "0"
TOOL.ClientConVar[ "opacity" ] = "100"
TOOL.ClientConVar[ "use_shift_key" ] = "0"
--[[--------------------------------------------------------------------------
-- Language Settings
--------------------------------------------------------------------------]]--
language.Add( "tool."..mode..".name", L(prefix.."name") )
language.Add( "tool."..mode..".desc", L(prefix.."desc") )
--language.Add( "tool."..mode..".0", L(prefix.."0") )
language.Add( "tool."..mode..".left", L(prefix.."left") )
language.Add( "tool."..mode..".shift_left", L(prefix.."shift_left") )
language.Add( "tool."..mode..".right", L(prefix.."right") )
language.Add( "tool."..mode..".shift_right", L(prefix.."shift_right") )
language.Add( "tool."..mode..".reload", L(prefix.."reload") )
language.Add( "Undone_"..mode, L("Undone_"..mode) )
--[[--------------------------------------------------------------------------
-- Net Messages
--------------------------------------------------------------------------]]--
--[[--------------------------------------------------------------------------
-- Net :: <toolmode>_error( string )
--]]--
net.Receive( mode.."_error", function( bytes )
surface.PlaySound( "buttons/button10.wav" )
notification.AddLegacy( net.ReadString(), net.ReadUInt(MIN_NOTIFY_BITS), NOTIFY_DURATION )
end )
end
--[[--------------------------------------------------------------------------
-- Console Variables
--------------------------------------------------------------------------]]--
-- This is solely for backwards compatibility.
-- We're essentially copying everyone's old cvar values over since we're switching from 'stacker' to 'stacker_improved'.
-- If we didn't do this, we'd run the risk of ruining someone's custom setup
--[[local oldMaxTotal = GetConVar( "stacker_max_total" ) and GetConVar( "stacker_max_total" ):GetInt() or -1
local oldMaxCount = GetConVar( "stacker_max_count" ) and GetConVar( "stacker_max_count" ):GetInt() or 15
local oldMaxOffX = GetConVar( "stacker_max_offsetx" ) and GetConVar( "stacker_max_offsetx" ):GetFloat() or 200
local oldMaxOffY = GetConVar( "stacker_max_offsety" ) and GetConVar( "stacker_max_offsety" ):GetFloat() or 200
local oldMaxOffZ = GetConVar( "stacker_max_offsetz" ) and GetConVar( "stacker_max_offsetz" ):GetFloat() or 200
local oldStayInWorld = GetConVar( "stacker_stayinworld" ) and GetConVar( "stacker_stayinworld" ):GetInt() or 1
local oldFreeze = GetConVar( "stacker_force_freeze" ) and GetConVar( "stacker_force_freeze" ):GetInt() or 0
local oldWeld = GetConVar( "stacker_force_weld" ) and GetConVar( "stacker_force_weld" ):GetInt() or 0
local oldNoCollide = GetConVar( "stacker_force_nocollide" ) and GetConVar( "stacker_force_nocollide" ):GetInt() or 0
local oldDelay = GetConVar( "stacker_delay" ) and GetConVar( "stacker_delay" ):GetFloat() or 0.25
]]
local cvarFlags, cvarFlagsNotify
if ( SERVER ) then
cvarFlags = bit.bor( FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE )
cvarFlagsNotif = bit.bor( cvarFlags, FCVAR_NOTIFY )
elseif ( CLIENT ) then
cvarFlags = bit.bor( FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE, FCVAR_ARCHIVE )
cvarFlagsNotif = bit.bor( cvarFlags, FCVAR_NOTIFY )
end
local oldMaxTotal = CreateConVar( "stacker_max_total", -1, cvarFlagsNotif, "Defines the max amount of props that a player can have spawned from stacker" )
local oldMaxCount = CreateConVar( "stacker_max_count", 15, cvarFlagsNotif, "Defines the max amount of props that can be stacked at a time" )
local oldDelay = CreateConVar( "stacker_delay", 0.5, cvarFlagsNotif, "Determines the amount of time that must pass before a player can use stacker again" )
local oldMaxOffX = CreateConVar( "stacker_max_offsetx", 200, cvarFlagsNotif, "Defines the max distance on the x plane that stacked props can be offset (for individual control)" )
local oldMaxOffY = CreateConVar( "stacker_max_offsety", 200, cvarFlagsNotif, "Defines the max distance on the y plane that stacked props can be offset (for individual control)" )
local oldMaxOffZ = CreateConVar( "stacker_max_offsetz", 200, cvarFlagsNotif, "Defines the max distance on the z plane that stacked props can be offset (for individual control)" )
local oldFreeze = CreateConVar( "stacker_force_freeze", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn frozen or not" )
local oldWeld = CreateConVar( "stacker_force_weld", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn welded or not" )
local oldNoCollide = CreateConVar( "stacker_force_nocollide", 0, cvarFlagsNotif, "Determines whether props should be forced to spawn nocollided or not" )
local oldStayInWorld = CreateConVar( "stacker_stayinworld", 1, cvarFlagsNotif, "Determines whether props should be restricted to spawning inside the world or not (addresses possible crashes)" )
local cvarMaxPerPlayer = CreateConVar( mode.."_max_per_player", oldMaxTotal:GetInt(), cvarFlags, "Defines the max amount of props that a player can have spawned from stacker" )
local cvarMaxPerStack = CreateConVar( mode.."_max_per_stack", oldMaxCount:GetInt(), cvarFlags, "Defines the max amount of props that can be stacked at a time" )
local cvarDelay = CreateConVar( mode.."_delay", oldDelay:GetFloat(), cvarFlags, "Determines the amount of time that must pass before a player can use stacker again" )
local cvarMaxOffX = CreateConVar( mode.."_max_offsetx", oldMaxOffX:GetFloat(), cvarFlags, "Defines the max distance on the x plane that stacked props can be offset (for individual control)" )
local cvarMaxOffY = CreateConVar( mode.."_max_offsety", oldMaxOffY:GetFloat(), cvarFlags, "Defines the max distance on the y plane that stacked props can be offset (for individual control)" )
local cvarMaxOffZ = CreateConVar( mode.."_max_offsetz", oldMaxOffZ:GetFloat(), cvarFlags, "Defines the max distance on the z plane that stacked props can be offset (for individual control)" )
local cvarFreeze = CreateConVar( mode.."_force_freeze", oldFreeze:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn frozen or not" )
local cvarWeld = CreateConVar( mode.."_force_weld", oldWeld:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn welded or not" )
local cvarNoCollide = CreateConVar( mode.."_force_nocollide", oldNoCollide:GetInt(), cvarFlagsNotif, "Determines whether props should be forced to spawn nocollided or not" )
local cvarNoCollideAll = CreateConVar( mode.."_force_nocollide_all", 0, cvarFlags, "(EXPERIMENTAL, DISABLED) Determines whether props should be nocollide with everything except players, vehicles, and npcs" )
local cvarStayInWorld = CreateConVar( mode.."_force_stayinworld", oldStayInWorld:GetInt(), cvarFlagsNotif, "Determines whether props should be restricted to spawning inside the world or not (addresses possible crashes)" )
--[[--------------------------------------------------------------------------
-- Console Commands
--------------------------------------------------------------------------]]--
if ( CLIENT ) then
concommand.Add( mode.."_reset_offsets", function( ply, cmd, args )
-- reset all of the offset values to 0
RunConsoleCommand( mode.."_offsetx", "0.00" )
RunConsoleCommand( mode.."_offsety", "0.00" )
RunConsoleCommand( mode.."_offsetz", "0.00" )
end )
concommand.Add( mode.."_reset_angles", function( ply, cmd, args )
-- reset all of the angle values to 0
RunConsoleCommand( mode.."_pitch", "0.00" )
RunConsoleCommand( mode.."_yaw", "0.00" )
RunConsoleCommand( mode.."_roll", "0.00" )
end )
concommand.Add( mode.."_reset_admin", function( ply, cmd, args )
for cmd, val in pairs( improvedstacker.SETTINGS_DEFAULT ) do
RunConsoleCommand( cmd, val )
end
end )
elseif ( SERVER ) then
local function validateCommand( ply, cmd, arg )
-- run our hook to see if the server is manually allowing/blocking this player from changing the cvar
-- true: allow
-- false: block
-- nil (default): fallback to a ply:IsAdmin() check
local result, reason = hook.Run( "StackerConVar", ply, cmd, arg )
-- if a player ran the command and the server didn't explicitly allow them to change the cvar
if ( IsValid( ply ) and result ~= true ) then
-- if the server blocked the change, send the player an error
if ( result == false ) then
ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_blocked_by_server", localify.GetLocale( ply )) .. (isstring(reason) and ": " .. reason or "") )
return false
end
-- if the server didn't give a response, fallback to a ply:IsAdmin() check
if ( result == nil and not ply:IsAdmin() ) then
ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_not_admin", localify.GetLocale( ply )) .. ": " .. cmd )
return false
end
end
-- lastly, ensure the argument is a valid number before returning true
if ( not tonumber( arg ) ) then
ply:PrintMessage( HUD_PRINTTALK, L(prefix.."error_invalid_argument", localify.GetLocale( ply )) )
return false
end
return true
end
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_per_player", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_per_player", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_per_player", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_per_stack", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_per_stack", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_per_stack", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_offset", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_offset", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_offsetx", args[1] )
RunConsoleCommand( mode.."_max_offsety", args[1] )
RunConsoleCommand( mode.."_max_offsetz", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_offsetx", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_offsetx", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_offsetx", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_offsety", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_offsety", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_offsety", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_max_offsetz", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_max_offsetz", args[1] ) ) then return false end
RunConsoleCommand( mode.."_max_offsetz", args[1] )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_force_stayinworld", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_force_stayinworld", args[1] ) ) then return false end
RunConsoleCommand( mode.."_force_stayinworld", tobool( args[1] ) and "1" or "0" )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_force_freeze", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_force_freeze", args[1] ) ) then return false end
RunConsoleCommand( mode.."_force_freeze", tobool( args[1] ) and "1" or "0" )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_force_weld", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_force_weld", args[1] ) ) then return false end
RunConsoleCommand( mode.."_force_weld", tobool( args[1] ) and "1" or "0" )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_force_nocollide", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_force_nocollide", args[1] ) ) then return false end
RunConsoleCommand( mode.."_force_nocollide", tobool( args[1] ) and "1" or "0" )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_force_nocollide_all", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_force_nocollide_all", args[1] ) ) then return false end
RunConsoleCommand( mode.."_force_nocollide_all", tobool( args[1] ) and "1" or "0" )
end )
--[[-------------------------------------------------------------]]--
concommand.Add( mode.."_set_delay", function( ply, cmd, args )
if ( not validateCommand( ply, mode.."_set_delay", args[1] ) ) then return false end
RunConsoleCommand( mode.."_delay", args[1] )
end )
util.AddNetworkString( mode.."_error" )
--[[--------------------------------------------------------------------------
-- TOOL:SendError( str )
-- Convenience function for sending an error to the tool owner.
--]]--
function TOOL:SendError( str )
net.Start( mode.."_error" )
net.WriteString( str )
net.WriteUInt( NOTIFY_ERROR, MIN_NOTIFY_BITS )
net.Send( self:GetOwner() )
end
end
--[[--------------------------------------------------------------------------
-- Convenience Functions
--------------------------------------------------------------------------]]--
--[[--------------------------------------------------------------------------
-- TOOL:GetMaxPerPlayer() and TOOL:GetNumberPlayerEnts()
--
-- The total number of props a player has spawned from the Stacker tool is recorded
-- on them via ply.TotalStackerEnts. When a player removes a prop that has been spawned
-- from Stacker, the total count is decreased by 1.
--
-- In combination with the stacker_max_per_player cvar, this function can prevent players
-- from crashing the server by stacking dozens of welded props and unfreezing them.
--
-- By default, the number of stacker props is -1 (infinite). This is done to not interfere
-- with servers that don't want to limit the number of Stacker props a player can spawn directly.
-- They may still hit cvars like sbox_maxprops before ever hitting stacker_max_per_player.
--
-- As an example case, if players are crashing your servers by spawning 50 welded chairs
-- and unfreezing them all at once, you can set stacker_max_per_player to 30 so that at any
-- given time they can only have 30 props created by Stacker. Trying to stack any more props
-- would give the player an error message.
--]]--
function TOOL:GetMaxPerPlayer() return cvarMaxPerPlayer:GetInt() end
function TOOL:GetNumberPlayerEnts() return improvedstacker.GetEntCount( self:GetOwner(), 0 ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetStackSize()
-- Gets the amount of props that the client wants to stack at once.
--]]--
function TOOL:GetStackSize() return self:GetClientNumber( "count" ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetMaxPerStack()
-- Gets the maximum amount of props that can be stacked at a time.
--]]--
function TOOL:GetMaxPerStack() return cvarMaxPerStack:GetInt() end
--[[--------------------------------------------------------------------------
-- TOOL:GetDirection()
-- Gets the direction to stack the props.
--]]--
function TOOL:GetDirection()
local direction = self:GetClientNumber( "direction" )
return improvedstacker.Directions[ direction ] and direction or improvedstacker.DIRECTION_FRONT
end
--[[--------------------------------------------------------------------------
-- TOOL:GetStackerMode()
-- Gets the stacker mode (1 = MODE_WORLD, 2 = MODE_PROP).
--]]--
function TOOL:GetStackerMode()
local stackMode = self:GetClientNumber( "mode" )
return improvedstacker.Modes[ stackMode ] and stackMode or improvedstacker.MODE_PROP
end
--[[--------------------------------------------------------------------------
-- TOOL:GetOffsetX(), TOOL:GetOffsetY(), TOOL:GetOffsetZ(), TOOL:GetOffsetVector()
-- Gets the distance to offset the position of the stacked props.
-- These values are clamped to prevent server crashes from players
-- using very high offset values.
--]]--
function TOOL:GetOffsetX() return math.Clamp( self:GetClientNumber( "offsetx" ), -cvarMaxOffX:GetFloat(), cvarMaxOffX:GetFloat() ) end
function TOOL:GetOffsetY() return math.Clamp( self:GetClientNumber( "offsety" ), -cvarMaxOffY:GetFloat(), cvarMaxOffY:GetFloat() ) end
function TOOL:GetOffsetZ() return math.Clamp( self:GetClientNumber( "offsetz" ), -cvarMaxOffZ:GetFloat(), cvarMaxOffZ:GetFloat() ) end
function TOOL:GetOffsetVector() return Vector( self:GetOffsetX(), self:GetOffsetY(), self:GetOffsetZ() ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetRotateP(), TOOL:GetRotateY(), TOOL:GetRotateR(), TOOL:GetRotationAngle()
-- Gets the value to rotate the angle of the stacked props.
-- These values are clamped to prevent server crashes from players
-- using very high rotation values.
--]]--
function TOOL:GetRotateP() return math.Clamp( self:GetClientNumber( "pitch" ), -MAX_ANGLE, MAX_ANGLE ) end
function TOOL:GetRotateY() return math.Clamp( self:GetClientNumber( "yaw" ), -MAX_ANGLE, MAX_ANGLE ) end
function TOOL:GetRotateR() return math.Clamp( self:GetClientNumber( "roll" ), -MAX_ANGLE, MAX_ANGLE ) end
function TOOL:GetRotationAngle() return Angle( self:GetRotateP(), self:GetRotateY(), self:GetRotateR() ) end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldFreeze()
-- Returns true if the stacked props should be spawned frozen.
--]]--
function TOOL:ShouldApplyFreeze() return self:GetClientNumber( "freeze" ) == 1 end
function TOOL:ShouldForceFreeze() return cvarFreeze:GetBool() end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldWeld()
-- Returns true if the stacked props should be welded together.
--]]--
function TOOL:ShouldApplyWeld() return self:GetClientNumber( "weld" ) == 1 end
function TOOL:ShouldForceWeld() return cvarWeld:GetBool() end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldNoCollide()
-- Returns true if the stacked props should be nocollided with each other.
--]]--
function TOOL:ShouldApplyNoCollide() return self:GetClientNumber( "nocollide" ) == 1 end
function TOOL:ShouldForceNoCollide() return cvarNoCollide:GetBool() end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldStackRelative()
-- Returns true if the stacked props should be stacked relative to the new rotation.
-- Using this setting will allow you to create curved structures out of props.
--]]--
function TOOL:ShouldStackRelative() return self:GetClientNumber( "relative" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldGhostAll()
-- Returns true if the stacked props should all be ghosted or if only the
-- first stacked prop should be ghosted.
--]]--
function TOOL:ShouldGhostAll() return self:GetClientNumber( "ghostall" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldAddHalos(), TOOL:GetHaloR(), TOOL:GetHaloG(), TOOL:GetHaloB() TOOL:GetHaloA() TOOL:GetHaloColor()
-- Returns true if the stacked props should have halos drawn on them for added visibility.
-- Gets the RGBA values of the halo color.
--]]--
function TOOL:ShouldAddHalos() return self:GetClientNumber( "draw_halos" ) == 1 end
function TOOL:GetHaloR() return math.Clamp( self:GetClientNumber( "halo_r" ), 0, 255 ) end
function TOOL:GetHaloG() return math.Clamp( self:GetClientNumber( "halo_g" ), 0, 255 ) end
function TOOL:GetHaloB() return math.Clamp( self:GetClientNumber( "halo_b" ), 0, 255 ) end
function TOOL:GetHaloA() return math.Clamp( self:GetClientNumber( "halo_a" ), 0, 255 ) end
function TOOL:GetHaloColor() return Color( self:GetHaloR(), self:GetHaloG(), self:GetHaloB(), self:GetHaloA() ) end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldApplyMaterial()
-- Returns true if the stacked props should have the original prop's material applied.
--]]--
function TOOL:ShouldApplyMaterial() return self:GetClientNumber( "material" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldApplyColor()
-- Returns true if the stacked props should have the original prop's color applied.
--]]--
function TOOL:ShouldApplyColor() return self:GetClientNumber( "color" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldApplyPhysicalProperties()
-- Returns true if the stacked props should have the original prop's physicsl properties
-- applied, including gravity, physics material, and weight.
--]]--
function TOOL:ShouldApplyPhysicalProperties() return self:GetClientNumber( "physprop" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:GetDelay()
-- Returns the time in seconds that must pass before a player can use stacker again.
-- For example, if stacker_delay is set to 3, a player must wait 3 seconds in between each
-- use of stacker's left click. A delay of <= 0 means stacks can be created instantly.
--]]--
function TOOL:GetDelay() return cvarDelay:GetFloat() end
--[[--------------------------------------------------------------------------
-- TOOL:GetOpacity()
-- Returns the alpha value (opacity) of the ghosted props seen on the client.
-- Should be between 0 (invisible) and 255 (fully visible).
--]]--
function TOOL:GetOpacity() return self:GetClientNumber( "opacity" ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetUseShiftKey()
-- Returns true if the client has enabled the alternate use of SHIFT in combination
-- with left and right clicking. If enable, holding SHIFT and pressing LMB/RMB will
-- have the same effect as holding E and pressing LMB/RMB.
--]]--
function TOOL:GetUseShiftKey() return self:GetClientNumber( "use_shift_key" ) == 1 end
--[[--------------------------------------------------------------------------
-- Tool Functions
--------------------------------------------------------------------------]]--
--[[--------------------------------------------------------------------------
--
-- TOOL:LeftClick( table, boolean = nil )
--
-- Attempts to create a stack of props relative to the entity being left clicked.
--]]--
function TOOL:LeftClick( tr, isRightClick )
local ply = self:GetOwner()
-- check if the player is holding E or SHIFT (as long as they've enabled it)
if ( ply:KeyDown( IN_USE ) or (self:GetUseShiftKey() and ply:KeyDown( IN_SPEED )) ) then
if ( CLIENT ) then return false end
-- increase their stack count by 1 (until it hits the stack max)
local newCount = self:GetStackSize() >= self:GetMaxPerStack() and self:GetMaxPerStack() or self:GetStackSize() + 1
ply:ConCommand( mode.."_count "..newCount )
return false
end
if ( not IsValid( tr.Entity ) or tr.Entity:GetClass() ~= "prop_physics" ) then return false end
if ( CLIENT ) then return true end
-- otherwise, stack 1 if right-clicking or get the client's stack size value
local count = (isRightClick and 1) or self:GetStackSize()
-- check if the server wants to control how many props the player can use in the stack
local maxCount = hook.Run( "StackerMaxPerStack", ply, count, isRightClick ) or self:GetMaxPerStack()
-- check if the player's stack size is higher than the server's max allowed size (but only if the server didn't explictly override it)
if ( maxCount >= 0 ) then
if ( count > maxCount ) then self:SendError( L(prefix.."error_max_per_stack", localify.GetLocale( self:GetOwner() )) .. maxCount ) end
count = math.Clamp( count, 0, maxCount )
end
-- get the player's last stacker usage time, defaulting to 0 if it hasn't been set
local lastStackTime = improvedstacker.GetLastStackTime( ply, 0 )
-- retrieve the time delay between stacker usage
-- we call StackerDelay to let external mods to set their own delays (less than or equal to 0 means no delay)
-- delay time is in seconds (e.g. 0.1 is a tenth of a second)
local delay = hook.Run( "StackerDelay", ply, lastStackTime ) or self:GetDelay()
-- check if the player is trying to use stacker too quickly
if ( lastStackTime + delay > CurTime() ) then self:SendError( L(prefix.."error_too_quick", localify.GetLocale( self:GetOwner() )) ) return false end
improvedstacker.SetLastStackTime( ply, CurTime() )
local stackDirection = self:GetDirection()
local stackMode = self:GetStackerMode()
local stackOffset = self:GetOffsetVector()
local stackRotation = self:GetRotationAngle()
local stackRelative = self:ShouldStackRelative()
-- determines whether the stacked props are allowed to be positioned outside of the world or not
local stayInWorld = cvarStayInWorld:GetBool()
-- store the properties of the original prop so we can apply them to the stacked props
local ent = tr.Entity
local entPos = ent:GetPos()
local entAng = ent:GetAngles()
local entMod = ent:GetModel()
local entSkin = ent:GetSkin()
local entMat = ent:GetMaterial()
local physMat = ent:GetPhysicsObject():GetMaterial()
local physGrav = ent:GetPhysicsObject():IsGravityEnabled()
-- setup a table to hold the original prop's color data so that we can apply it to the stacked props
local colorData = {
Color = ent:GetColor(),
RenderMode = ent:GetRenderMode(),
RenderFX = ent:GetRenderFX()
}
local newEnt
local newEnts = { ent }
local lastEnt = ent
local direction, offset
-- we only need to calculate the distance once based on the direction the user selected
local distance = improvedstacker.GetDistance( stackMode, stackDirection, ent )
-- setup a new undo block so the player can undo the whole stack at once
undo.Create( mode )
-- check if the server wants to control how many stacker entities this player can create
local maxPerPlayer = hook.Run( "StackerMaxPerPlayer", ply, self:GetNumberPlayerEnts() ) or self:GetMaxPerPlayer()
-- loop for every prop to create in the stack and allow external addons to dictate control over the new stacked entities
for i = 1, count do
-- check if the player has too many active stacker props spawned out already
local stackerEntsSpawned = self:GetNumberPlayerEnts()
if ( maxPerPlayer >= 0 and stackerEntsSpawned >= maxPerPlayer ) then self:SendError( ("%s (%s)"):format(L(prefix.."error_max_per_player", localify.GetLocale( self:GetOwner() )), maxPerPlayer) ) break end
-- check if the player has exceeded the sbox_maxprops cvar
if ( not self:GetSWEP():CheckLimit( "props" ) ) then break end
-- check if external admin mods are blocking this entity
if ( hook.Run( "PlayerSpawnProp", ply, entMod ) == false ) then break end
-- if we're positioning the first entity in the stack (regardless of relative to PROP or WORLD), or
-- if we're stacking relative to PROP and on the previous rotation, update the new direction and offset
if ( i == 1 or ( stackMode == improvedstacker.MODE_PROP and stackRelative ) ) then
direction = improvedstacker.GetDirection( stackMode, stackDirection, entAng )
offset = improvedstacker.GetOffset( stackMode, stackDirection, entAng, stackOffset )
end
-- calculate the next stacked entity's position
entPos = entPos + (direction * distance) + offset
-- rotate the next stacked entity's angle by the client's rotation values
improvedstacker.RotateAngle( stackMode, stackDirection, entAng, stackRotation )
-- check if the stacked props would be spawned outside of the world
if ( stayInWorld and not util.IsInWorld( entPos ) ) then self:SendError( L(prefix.."error_not_in_world", localify.GetLocale( self:GetOwner() )) ) break end
-- create the new stacked entity
newEnt = ents.Create( "prop_physics" )
newEnt:SetModel( entMod )
newEnt:SetPos( entPos )
newEnt:SetAngles( entAng )
newEnt:SetSkin( entSkin )
newEnt:Spawn()
-- this hook is for external prop protections and anti-spam addons.
-- it is called before undo, ply:AddCount, and ply:AddCleanup to allow developers to
-- remove or mark this entity so that those same functions (if overridden) can
-- detect that the entity came from Stacker
if ( not IsValid( newEnt ) or hook.Run( "StackerEntity", newEnt, ply ) ~= nil ) then break end
if ( not IsValid( newEnt ) or hook.Run( "PlayerSpawnedProp", ply, entMod, newEnt ) ~= nil ) then break end
-- disabling this for now due to problems with ShouldCollide
--improvedstacker.MarkEntity( self:GetOwner(), newEnt )
-- increase the total number of active stacker props spawned by the player by 1
improvedstacker.IncrementEntCount( ply )
-- decrement the total number of active stacker props spawned by the player by 1
-- when the prop gets removed in any way
newEnt:CallOnRemove( "UpdateStackerTotal", function( ent, ply )
-- if the player is no longer connected, there is nothing to do
if ( not IsValid( ply ) ) then return end
improvedstacker.DecrementEntCount( ply )
end, ply )
self:ApplyMaterial( newEnt, entMat )
self:ApplyColor( newEnt, colorData )
self:ApplyFreeze( ply, newEnt )
-- attempt to nocollide the new entity with the last, or break out of the loop if CBaseEntityList::AddNonNetworkableEntity fails
if ( not self:ApplyNoCollide( lastEnt, newEnt ) ) then
newEnt:Remove()
break
end
-- attempt to weld the new entity with the last, or break out of the loop if CBaseEntityList::AddNonNetworkableEntity fails
if ( not self:ApplyWeld( lastEnt, newEnt ) ) then
newEnt:Remove()
break
end
self:ApplyPhysicalProperties( ent, newEnt, tr.PhysicsBone, { GravityToggle = physGrav, Material = physMat } )
lastEnt = newEnt
table.insert( newEnts, newEnt )
undo.AddEntity( newEnt )
ply:AddCleanup( "props", newEnt )
end
newEnts = nil
undo.SetPlayer( ply )
undo.Finish()
-- disabling this for now due to problems with ShouldCollide
--improvedstacker.MarkEntity( self:GetOwner(), ent )
return true
end
--[[--------------------------------------------------------------------------
--
-- TOOL:RightClick( trace )
--
-- Performs a LeftClick operation but only creates a single stacked entity.
-- Alternatively, if the player is holding down their USE key, this will
-- decrease their stack count by 1.
--]]--
function TOOL:RightClick( tr )
local ply = self:GetOwner()
-- check if the player is holding E or SHIFT (as long as they've enabled it)
if ( ply:KeyDown( IN_USE ) or (self:GetUseShiftKey() and ply:KeyDown( IN_SPEED )) ) then
if ( CLIENT ) then return false end
-- decrease the player's stack count by 1 (until a minimum of 1)
local count = self:GetStackSize()
local newCount = (count <= 1 and 1) or count - 1
ply:ConCommand( mode.."_count " .. newCount )
return false
else
-- create a single entity in the stack
return self:LeftClick( tr, true )
end
end
--[[--------------------------------------------------------------------------
--
-- TOOL:Reload()
--
-- Switches the client's stack direction.
--]]--
function TOOL:Reload()
if ( CLIENT ) then return false end
local ply = self:GetOwner()
local direction = self:GetDirection()
-- if they were at the last numerical direction (6), wrap around to the first (1)
if ( direction == improvedstacker.DIRECTION_DOWN ) then
direction = improvedstacker.DIRECTION_FRONT
-- otherwise just increment to the next direction
else
direction = direction + 1
end
-- make the player update their client direction setting
ply:ConCommand( mode.."_direction " .. direction )
return false
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyMaterial( entity, string )
--
-- Applies the original entity's material onto the stacked props.
--]]--
function TOOL:ApplyMaterial( ent, material )
if ( not self:ShouldApplyMaterial() ) then ent:SetMaterial( "" ) return end
-- From: gamemodes/sandbox/entities/weapons/gmod_tool/stools/material.lua
-- "Make sure this is in the 'allowed' list in multiplayer - to stop people using exploits"
if ( not game.SinglePlayer() and not list.Contains( "OverrideMaterials", material ) and material ~= "" ) then return end
ent:SetMaterial( material )
duplicator.StoreEntityModifier( ent, "material", { MaterialOverride = material } )
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyColor( entity, color )
--
-- Applies the original entity's color onto the stacked props.
--]]--
function TOOL:ApplyColor( ent, data )
if ( not self:ShouldApplyColor() ) then return end
ent:SetColor( data.Color )
ent:SetRenderMode( data.RenderMode )
ent:SetRenderFX( data.RenderFX )
duplicator.StoreEntityModifier( ent, "colour", table.Copy( data ) )
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyFreeze( player, entity )
--
-- Attempts to freeze the stacked props in place.
--]]--
function TOOL:ApplyFreeze( ply, ent )
if ( self:ShouldForceFreeze() or self:ShouldApplyFreeze() ) then
ent:GetPhysicsObject():EnableMotion( false )
else
ent:GetPhysicsObject():Wake()
end
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyWeld( entity, entity )
--
-- Attempts to weld the new entity to the last entity.
--]]--
function TOOL:ApplyWeld( lastEnt, newEnt )
if ( not self:ShouldForceWeld() and not self:ShouldApplyWeld() ) then return true end
local forceLimit = 0
local isNocollided = self:ShouldForceNoCollide() or self:ShouldApplyNoCollide()
local deleteOnBreak = false
local ok, err = pcall( constraint.Weld, lastEnt, newEnt, 0, 0, forceLimit, isNocollided, deleteOnBreak )
if ( not ok ) then
print( mode .. ": " .. L(prefix.."error_max_constraints") .." (error: " .. err .. ")" )
self:SendError( mode .. ": " .. L(prefix.."error_max_constraints", localify.GetLocale( self:GetOwner() )) )
end
return ok
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyNoCollide( entity, entity )
--
-- Attempts to nocollide the new entity to the last entity.
--]]--
function TOOL:ApplyNoCollide( lastEnt, newEnt )
if ( not self:ShouldForceNoCollide() and not self:ShouldApplyNoCollide() ) then return true end
-- we can skip this function if the client is trying to weld -and- nocollide, because
-- constraint.Weld already has a nocollide parameter
if ( self:ShouldForceWeld() or self:ShouldApplyWeld() ) then return true end
local ok, err = pcall( constraint.NoCollide, lastEnt, newEnt, 0, 0 )
if ( not ok ) then
print( mode .. ": " .. L(prefix.."error_max_constraints") .." (error: " .. err .. ")" )
self:SendError( mode .. ": " .. L(prefix.."error_max_constraints", localify.GetLocale( self:GetOwner() )) )
end
return ok
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ApplyPhysicalProperties( entity, entity, number, table )
--
-- Attempts to apply the original entity's Gravity/Physics Material properties
-- and weight onto the stacked propa.
--
--]]--
function TOOL:ApplyPhysicalProperties( original, newEnt, boneID, properties )
if ( not self:ShouldApplyPhysicalProperties() ) then return end
if ( boneID ) then construct.SetPhysProp( nil, newEnt, boneID, nil, properties ) end
newEnt:GetPhysicsObject():SetMass( original:GetPhysicsObject():GetMass() )
end
if ( CLIENT ) then
-- get the cvars if they're valid (e.g., editing and auto-refreshing this file).
-- otherwise they won't be valid yet when first ran and we have to wait until
-- TOOL:Init() gets called (below) to set them up
local cvarTool = GetConVar( "gmod_toolmode" )
local cvarCount = GetConVar( mode.."_count" )
local cvarMode = GetConVar( mode.."_mode" )
local cvarDirection = GetConVar( mode.."_direction" )
local cvarOffsetX = GetConVar( mode.."_offsetx" )
local cvarOffsetY = GetConVar( mode.."_offsety" )
local cvarOffsetZ = GetConVar( mode.."_offsetz" )
local cvarPitch = GetConVar( mode.."_pitch" )
local cvarYaw = GetConVar( mode.."_yaw" )
local cvarRoll = GetConVar( mode.."_roll" )
local cvarRelative = GetConVar( mode.."_relative" )
local cvarMaterial = GetConVar( mode.."_material" )
local cvarColor = GetConVar( mode.."_color" )
local cvarGhostAll = GetConVar( mode.."_ghostall" )
local cvarOpacity = GetConVar( mode.."_opacity" )
local cvarHalo = GetConVar( mode.."_draw_halos" )
local cvarHaloR = GetConVar( mode.."_halo_r" )
local cvarHaloG = GetConVar( mode.."_halo_g" )
local cvarHaloB = GetConVar( mode.."_halo_b" )
local cvarHaloA = GetConVar( mode.."_halo_a" )
local cvarHalo = GetConVar( mode.."_draw_halos" )
local cvarAxis = GetConVar( mode.."_draw_axis" )
local cvarAxisLbl = GetConVar( mode.."_axis_labels" )
local cvarAxisAng = GetConVar( mode.."_axis_angles" )
-- offsets for drawing the axis arrows
local o1 = Vector( 0, 0, 0.05 )
local o2 = Vector( 0, 0, -0.05 )
local o3 = Vector( 0.05, 0, 0 )
local o4 = Vector( -0.05, 0, 0 )
local ao = 2.5
-- colors for the axis arrows
local RED = Color( 255, 50, 50 )
local GREEN = Color( 0, 255, 0 )
local BLUE = Color( 50, 150, 255 )
local BLACK = Color( 0, 0, 0 )
surface.CreateFont( mode.."_direction", {
font = "Arial",
size = 24,
weight = 700,
antialias = true
})
-- we're creating a bunch of local functions here using the cvars above so that we don't have to
-- rely on the TOOL object (which can be problematic when trying to use it inside a hook).
-- these should be pretty much identical to the TOOL functions created near the top of this file
local function getStackSize() return cvarCount:GetInt() end
local function getMaxPerStack() return cvarMaxPerStack:GetInt() end
local function getStackerMode() return cvarMode:GetInt() end
local function getDirection() return cvarDirection:GetInt() end
local function getOpacity() return cvarOpacity:GetInt() end
local function shouldGhostAll() return cvarGhostAll:GetBool() end
local function shouldStackRelative() return cvarRelative:GetBool() end
local function shouldApplyMaterial() return cvarMaterial:GetBool() end
local function shouldApplyColor() return cvarColor:GetBool() end
local function shouldAddHalos() return cvarHalo:GetBool() end
local function getOffsetVector()
return Vector( math.Clamp( cvarOffsetX:GetFloat(), -cvarMaxOffX:GetFloat(), cvarMaxOffX:GetFloat() ),
math.Clamp( cvarOffsetY:GetFloat(), -cvarMaxOffY:GetFloat(), cvarMaxOffY:GetFloat() ),
math.Clamp( cvarOffsetZ:GetFloat(), -cvarMaxOffZ:GetFloat(), cvarMaxOffZ:GetFloat() ) )
end
local function getRotationAngle()
return Angle( math.Clamp( cvarPitch:GetFloat(), -MAX_ANGLE, MAX_ANGLE ),
math.Clamp( cvarYaw:GetFloat(), -MAX_ANGLE, MAX_ANGLE ),
math.Clamp( cvarRoll:GetFloat(), -MAX_ANGLE, MAX_ANGLE ) )
end
local function getHaloColor()
return Color( cvarHaloR:GetInt(),
cvarHaloG:GetInt(),
cvarHaloB:GetInt(),
cvarHaloA:GetInt() )
end
--[[--------------------------------------------------------------------------
--
-- TOOL:Init()
--
--]]--
function TOOL:Init()
-- now the convars are truly valid, so reassign the upvalues
cvarTool = GetConVar( "gmod_toolmode" )
cvarCount = GetConVar( mode.."_count" )
cvarMode = GetConVar( mode.."_mode" )
cvarDirection = GetConVar( mode.."_direction" )
cvarOffsetX = GetConVar( mode.."_offsetx" )
cvarOffsetY = GetConVar( mode.."_offsety" )
cvarOffsetZ = GetConVar( mode.."_offsetz" )
cvarPitch = GetConVar( mode.."_pitch" )
cvarYaw = GetConVar( mode.."_yaw" )
cvarRoll = GetConVar( mode.."_roll" )
cvarRelative = GetConVar( mode.."_relative" )
cvarMaterial = GetConVar( mode.."_material" )
cvarColor = GetConVar( mode.."_color" )
cvarGhostAll = GetConVar( mode.."_ghostall" )
cvarOpacity = GetConVar( mode.."_opacity" )
cvarHalo = GetConVar( mode.."_draw_halos" )
cvarHaloR = GetConVar( mode.."_halo_r" )
cvarHaloG = GetConVar( mode.."_halo_g" )
cvarHaloB = GetConVar( mode.."_halo_b" )
cvarHaloA = GetConVar( mode.."_halo_a" )
cvarHalo = GetConVar( mode.."_draw_halos" )
cvarAxis = GetConVar( mode.."_draw_axis" )
cvarAxisLbl = GetConVar( mode.."_axis_labels" )
cvarAxisAng = GetConVar( mode.."_axis_angles" )
end
--[[--------------------------------------------------------------------------
--
-- createGhostStack( entity, vector, angle )
--
-- Attempts to create a stack of ghosted props on the prop the player is currently
-- looking at before they actually left click to create the stack. This acts
-- as a visual aid for the player so they can see the results without actually creating
-- the entities yet (if in multiplayer).
--]]--
local function createGhostStack( ent )
if ( improvedstacker.GetGhosts() ) then improvedstacker.ReleaseGhosts() end
-- truncate the stack size to the maximum allowed by the server
local count = getStackSize()
local maxCount = getMaxPerStack()
if ( not shouldGhostAll() and count ~= 0 ) then count = 1 end
if ( maxCount >= 0 and count > maxCount ) then count = maxCount end
local entMod = ent:GetModel()
local entSkin = ent:GetSkin()
local ghosts = {}
local ghost
-- loop for the total stack size and create a new ghost prop
for i = 1, count do
ghost = ClientsideModel( entMod )
if ( not IsValid( ghost ) ) then continue end
ghost:SetModel( entMod )
ghost:SetSkin( entSkin )
ghost:Spawn()
ghost:SetRenderMode( RENDERMODE_TRANSALPHA )
table.insert( ghosts, ghost )
end
-- store the ghost array for later use
improvedstacker.SetGhosts( ghosts )
return true
end
--[[--------------------------------------------------------------------------
--
-- validateGhostStack()
--
-- Attempts to validate the status of the ghosted props in the stack.
-- True: all good, ready to update
-- False: something is invalid or missing, clear it
--]]--
local function validateGhostStack()
-- check if the array of ghosts is valid
local ghosts = improvedstacker.GetGhosts()
if ( not ghosts ) then return false end
-- check if all the ghost entities are valid
for i = 1, #ghosts do
if ( not IsValid( ghosts[ i ] ) ) then return false end
end
-- clamp the client's ghost stack to the server's maximum allowed size
local count = getStackSize()
local maxCount = getMaxPerStack()
if ( maxCount >= 0 and count > maxCount ) then count = maxCount end
-- check if the number of ghosts in the stack matches the client's setting
if ( #ghosts ~= count and shouldGhostAll() ) then return false
-- number of ghosts matches client's setting, so check if we should only be ghosting one
elseif ( #ghosts ~= 1 and not shouldGhostAll() ) then return false end
return true
end
--[[--------------------------------------------------------------------------
--
-- updateGhostStack( entity )
--
-- Attempts to update the positions and angles of all ghosted props in the stack.
--]]--
local function updateGhostStack( ent )
local stackMode = getStackerMode()
local stackDirection = getDirection()
local stackOffset = getOffsetVector()
local stackRotation = getRotationAngle()
local stackRelative = shouldStackRelative()
local applyMat = shouldApplyMaterial()
local applyCol = shouldApplyColor()
local lastEnt = ent
local entPos = ent:GetPos()
local entAng = ent:GetAngles()
local entMat = ent:GetMaterial()
local entCol = ent:GetColor()
entCol.a = getOpacity()
local direction, offset
-- we only need to calculate the distance once based on the direction the user selected
local distance = improvedstacker.GetDistance( stackMode, stackDirection, ent )
local ghost
local ghosts = improvedstacker.GetGhosts()
for i = 1, #ghosts do
-- if we're positioning the first entity in the stack (regardless of relative to PROP or WORLD), or
-- if we're stacking relative to PROP and on the previous rotation, update the new direction and offset
if ( i == 1 or ( stackMode == improvedstacker.MODE_PROP and stackRelative ) ) then
direction = improvedstacker.GetDirection( stackMode, stackDirection, entAng )
offset = improvedstacker.GetOffset( stackMode, stackDirection, entAng, stackOffset )
end
-- calculate the next stacked entity's position
entPos = entPos + (direction * distance) + offset
-- rotate the next stacked entity's angle by the client's rotation values
improvedstacker.RotateAngle( stackMode, stackDirection, entAng, stackRotation )
local ghost = ghosts[ i ]
ghost:SetPos( entPos )
ghost:SetAngles( entAng )
ghost:SetMaterial( ( applyMat and entMat ) or "" )
ghost:SetColor( ( applyCol and entCol ) or TRANSPARENT )
ghost:SetNoDraw( false )
lastEnt = ghost
end
end
--[[--------------------------------------------------------------------------
--
-- Hook :: PreDrawHalos
--
-- Loads the hook that draws halos on the ghosted entities in the stack.
--
-- This is the appropriate hook to create halos, NOT TOOL:Think(). The latter
-- will be called way more than it needs to be and causes horrible FPS drops in singleplayer.
--]]--
hook.Add( "PreDrawHalos", mode.."_predrawhalos", function()
-- check if the player has fully initialized
local ply = LocalPlayer()
if ( not IsValid( ply ) ) then return end
-- check if they have the toolgun out and have stacker selected
local wep = ply:GetActiveWeapon()
if ( not ( IsValid( wep ) and wep:GetClass() == "gmod_tool" and cvarTool and cvarTool:GetString() == mode ) ) then
improvedstacker.ReleaseGhosts()
improvedstacker.SetLookedAt( nil )
return
end
-- check if we're looking at a valid entity
local lookingAt = ply:GetEyeTrace().Entity
if ( not ( IsValid( lookingAt ) and lookingAt:GetClass() == "prop_physics" ) ) then
improvedstacker.ReleaseGhosts()
improvedstacker.SetLookedAt( nil )
return
end
-- check if the current toolobject is valid before trying to use it --
-- commenting this out for now since I refactored these TOOL functions
-- into just local functions to ditch the need for the tool object
--[[local tool = wep.GetToolObject and wep:GetToolObject()
if ( not ( tool and tool.GetOwner and IsValid( tool:GetOwner() ) ) ) then
return
end]]
-- specify the entity that the client is currently looking at for future reference
improvedstacker.SetLookingAt( lookingAt )
-- get the entity that the client was last (successfully) looking at
local lookedAt = improvedstacker.GetLookedAt()
-- if we're still looking at the same entity from the previous frame
if ( lookingAt == lookedAt ) then
-- if the ghost stack is still valid (nothing got deleted, etc)
if ( validateGhostStack() ) then
-- reposition the stack to the client's most recent stack settings
updateGhostStack( lookingAt )
else
-- something is wrong in the stack, so remove the ghost entities
improvedstacker.ReleaseGhosts()
improvedstacker.SetLookedAt( nil )
return
end
-- we looked at something else since the last frame
else
-- try to initialize a new ghost stack
if ( createGhostStack( lookingAt ) ) then
-- ghost stack was successfully created
improvedstacker.SetLookedAt( lookingAt )
end
end
-- check if we want to add halos to the ghost stack
if ( not shouldAddHalos() ) then return end
-- check if there are any ghosts to add halos to at all
local ghosts = improvedstacker.GetGhosts()
if ( not ghosts or #ghosts <= 0 ) then return end
halo.Add( ghosts, getHaloColor() )
end )
--[[--------------------------------------------------------------------------
--
-- Hook :: PostDrawTranslucentRenderables
--
-- Draws the 2D x/y/z axis when looking at entities with the stacker tool.
--]]--
hook.Add( "PostDrawTranslucentRenderables", mode.."_directions", function( drawingDepth, drawingSky )
if ( drawingSky ) then return end
-- check if the player has fully initialized
local ply = LocalPlayer()
if ( not IsValid( ply ) ) then return end
-- check if we want to draw the axis at all
if ( not ( cvarAxis and cvarAxis:GetBool() ) ) then return end
-- check if they have the toolgun out and have stacker selected
local wep = ply:GetActiveWeapon()
if ( not ( IsValid( wep ) and wep:GetClass() == "gmod_tool" and cvarTool and cvarTool:GetString() == mode ) ) then
return
end
-- check if we're looking at a valid entity
local ent = ply:GetEyeTrace().Entity
if ( not IsValid( ent ) ) then
return
end
local pos = ent:GetPos()
local f = ent:GetForward()
local r = ent:GetRight()
local u = ent:GetUp()
-- draw the front arrow (red)
render.DrawLine( pos, pos + (f*50), RED, false )
render.DrawLine( pos + (f*50) - f*ao + Vector(0,0,ao), pos + (f*50), RED, false )
render.DrawLine( pos + (f*50) - f*ao - Vector(0,0,ao), pos + (f*50), RED, false )
render.DrawLine( pos+o1, pos + (f*50) + o1, RED, false )
render.DrawLine( pos+o2, pos + (f*50) + o2, RED, false )
-- draw the right arrow (green)
render.DrawLine( pos, pos + (r*50), GREEN, false )
render.DrawLine( pos + (r*50) - r*ao + f*ao, pos + (r*50), GREEN, false )
render.DrawLine( pos + (r*50) - r*ao - f*ao, pos + (r*50), GREEN, false )
render.DrawLine( pos+o1, pos + (r*50) + o1, GREEN, false )
render.DrawLine( pos+o2, pos + (r*50) + o2, GREEN, false )
-- draw the upward arrow (blue)
render.DrawLine( pos, pos + (u*50), BLUE, false )
render.DrawLine( pos + (u*50) - u*ao + r*ao, pos + (u*50), BLUE, false )
render.DrawLine( pos + (u*50) - u*ao - r*ao, pos + (u*50), BLUE, false )
render.DrawLine( pos+o3, pos + (u*50) + o3, BLUE, false )
render.DrawLine( pos+o4, pos + (u*50) + o4, BLUE, false )
-- check if we want to draw the axis labels
if ( not ( cvarAxisLbl and cvarAxisAng ) ) then return end
if ( not ( cvarAxisLbl:GetBool() or cvarAxisAng:GetBool() ) ) then return end
local fs = (pos + f*50 - u*5):ToScreen()
local rs = (pos + r*50 - u*5):ToScreen()
local us = (pos + u*55):ToScreen()
local ang = ent:GetAngles()
local front = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_front").." " or "", cvarAxisAng:GetBool() and "("..ang.x..")" or "" )
local right = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_right").." " or "", cvarAxisAng:GetBool() and "("..ang.y..")" or "" )
local upwrd = ("%s%s"):format( cvarAxisLbl:GetBool() and L(prefix.."hud_up").." " or "", cvarAxisAng:GetBool() and "("..ang.z..")" or "" )
cam.Start2D()
draw.SimpleTextOutlined( front, mode.."_direction", fs.x, fs.y, RED, 0, 0, 1, BLACK )
draw.SimpleTextOutlined( right, mode.."_direction", rs.x, rs.y, GREEN, 0, 0, 1, BLACK )
draw.SimpleTextOutlined( upwrd, mode.."_direction", us.x, us.y, BLUE, 1, 0, 1, BLACK )
cam.End2D()
end )
end
if ( CLIENT ) then
--[[--------------------------------------------------------------------------
--
-- TOOL.BuildCPanel( panel )
--
-- Builds the control panel menu that can be seen when holding Q and accessing
-- the stacker menu.
--]]--
local function buildCPanel( cpanel )
-- quick presets for default settings
local presets = {
Label = "Presets",
MenuButton = 1,
Folder = mode,
Options = {
[L(prefix.."combobox_default")] = {
[mode.."_mode"] = tostring(improvedstacker.MODE_PROP),
[mode.."_direction"] = tostring(improvedstacker.DIRECTION_UP),
[mode.."_count"] = "1",
[mode.."_freeze"] = "1",
[mode.."_weld"] = "1",
[mode.."_nocollide"] = "1",
[mode.."_ghostall"] = "1",
[mode.."_material"] = "1",
[mode.."_physprop"] = "1",
[mode.."_color"] = "1",
[mode.."_offsetx"] = "0",
[mode.."_offsety"] = "0",
[mode.."_offsetz"] = "0",
[mode.."_pitch"] = "0",
[mode.."_yaw"] = "0",
[mode.."_roll"] = "0",
[mode.."_relative"] = "1",
[mode.."_draw_halos"] = "0",
[mode.."_halo_r"] = "255",
[mode.."_halo_g"] = "0",
[mode.."_halo_b"] = "0",
[mode.."_halo_a"] = "255",
[mode.."_draw_axis"] = "1",
[mode.."_axis_labels"] = "1",
[mode.."_axis_angles"] = "0",
},
},
CVars = {
mode.."_mode",
mode.."_direction",
mode.."_count",
mode.."_freeze",
mode.."_weld",
mode.."_nocollide",
mode.."_ghostall",
mode.."_material",
mode.."_physprop",
mode.."_color",
mode.."_offsetx",
mode.."_offsety",
mode.."_offsetz",
mode.."_pitch",
mode.."_yaw",
mode.."_roll",
mode.."_relative",
mode.."_draw_halos",
mode.."_halo_r",
mode.."_halo_g",
mode.."_halo_b",
mode.."_halo_a",
mode.."_draw_axis",
mode.."_axis_labels",
mode.."_axis_angles",
}
}
local relativeOptions = {
[L(prefix.."combobox_world")] = { [mode.."_mode"] = improvedstacker.MODE_WORLD },
[L(prefix.."combobox_prop")] = { [mode.."_mode"] = improvedstacker.MODE_PROP },
}
local relative = { Label = L(prefix.."label_relative"), MenuButton = "0", Options = relativeOptions }
local directionOptions = {
["1 - "..L(prefix.."combobox_direction_front")] = { [mode.."_direction"] = improvedstacker.DIRECTION_FRONT },
["2 - "..L(prefix.."combobox_direction_back")] = { [mode.."_direction"] = improvedstacker.DIRECTION_BACK },
["3 - "..L(prefix.."combobox_direction_right")] = { [mode.."_direction"] = improvedstacker.DIRECTION_RIGHT },
["4 - "..L(prefix.."combobox_direction_left")] = { [mode.."_direction"] = improvedstacker.DIRECTION_LEFT },
["5 - "..L(prefix.."combobox_direction_up")] = { [mode.."_direction"] = improvedstacker.DIRECTION_UP },
["6 - "..L(prefix.."combobox_direction_down")] = { [mode.."_direction"] = improvedstacker.DIRECTION_DOWN },
}
local directions = { Label = L(prefix.."label_direction"), MenuButton = "0", Options = directionOptions }
-- populate the table of valid languages that clients can switch between
local languageOptions = {}
for code, tbl in pairs( localify.GetLocalizations() ) do
if ( not L(prefix.."language_"..code, code) ) then continue end
languageOptions[ L(prefix.."language_"..code, code) ] = { localify_language = code }
end
local languages = {
Label = L(prefix.."label_language"),
MenuButton = 0,
Options = languageOptions,
}
cpanel:AddControl( "ComboBox", languages )
cpanel:ControlHelp( "\n" .. L(prefix.."label_credits") )
cpanel:AddControl( "Label", { Text = L(prefix.."label_presets") } )
cpanel:AddControl( "ComboBox", presets )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_freeze"), Command = mode.."_freeze" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_weld"), Command = mode.."_weld" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_nocollide"), Command = mode.."_nocollide" } )
cpanel:AddControl( "ComboBox", relative )
cpanel:AddControl( "ComboBox", directions )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_count"), Min = 1, Max = cvarMaxPerStack:GetInt(), Command = mode.."_count", Description = "How many props to create in each stack" } )
cpanel:AddControl( "Button", { Label = L(prefix.."label_reset_offsets"), Command = mode.."_reset_offsets" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_x"), Type = "Float", Min = - cvarMaxOffX:GetInt(), Max = cvarMaxOffX:GetInt(), Value = 0, Command = mode.."_offsetx" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_y"), Type = "Float", Min = - cvarMaxOffY:GetInt(), Max = cvarMaxOffY:GetInt(), Value = 0, Command = mode.."_offsety" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_z"), Type = "Float", Min = - cvarMaxOffZ:GetInt(), Max = cvarMaxOffZ:GetInt(), Value = 0, Command = mode.."_offsetz" } )
cpanel:AddControl( "Button", { Label = L(prefix.."label_reset_angles"), Command = mode.."_reset_angles" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_pitch"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_pitch" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_yaw"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_yaw" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_roll"), Type = "Float", Min = -MAX_ANGLE, Max = MAX_ANGLE, Value = 0, Command = mode.."_roll" } )
cpanel:AddControl( "Button", { Label = L(prefix.."label_"..(showSettings and "hide" or "show").."_settings"), Command = mode.."_show_settings" } )
if ( showSettings ) then
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_use_shift_key"), Command = mode.."_use_shift_key", Description = "Toggles the ability to hold SHIFT and click the left and right mouse buttons to change stack size" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_relative"), Command = mode.."_relative", Description = "Stacks each prop relative to the prop right before it. This allows you to create curved stacks" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_material"), Command = mode.."_material", Description = "Applies the material of the original prop to all stacked props" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_color"), Command = mode.."_color", Description = "Applies the color of the original prop to all stacked props" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_physprop"), Command = mode.."_physprop", Description = "Applies the physical properties of the original prop to all stacked props" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_ghost"), Command = mode.."_ghostall", Description = "Creates every ghost prop in the stack instead of just the first ghost prop" } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis"), Command = mode.."_draw_axis", } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis_labels"), Command = mode.."_axis_labels", } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_axis_angles"), Command = mode.."_axis_angles", } )
cpanel:AddControl( "Checkbox", { Label = L(prefix.."checkbox_halo"), Command = mode.."_draw_halos", Description = "Gives halos to all of the props in to ghosted stack" } )
cpanel:AddControl( "Slider", { Label = L(prefix.."label_opacity"), Type = "Integer", Min = 0, Max = 255, Command = mode.."_opacity" } )
cpanel:AddControl( "Color", { Label = L(prefix.."checkbox_halo_color"), Red = mode.."_halo_r", Green = mode.."_halo_g", Blue = mode.."_halo_b", Alpha = mode.."_halo_a" } )
end
end
concommand.Add( mode.."_show_settings", function( ply, cmd, args )
local cpanel = controlpanel.Get( mode )
if ( not IsValid( cpanel ) ) then return end
showSettings = not showSettings
cpanel:ClearControls()
buildCPanel( cpanel )
end )
-- listen for changes to the localify language and reload the tool's menu to update the localizations
cvars.AddChangeCallback( "localify_language", function( name, old, new )
local cpanel = controlpanel.Get( mode )
if ( not IsValid( cpanel ) ) then return end
cpanel:ClearControls()
buildCPanel( cpanel )
end, "improvedstacker" )
TOOL.BuildCPanel = buildCPanel
--[[--------------------------------------------------------------------------
--
-- PopulateToolMenu
--
-- Builds the admin settings control panel in the utility menu. This allows server
-- operators to quickly and easily save/change Stacker server settings.
--]]--
hook.Add( "PopulateToolMenu", mode.."AdminUtilities", function()
spawnmenu.AddToolMenuOption( "Utilities", "Admin", mode.."_utils", L(prefix.."name"), "", "", function( cpanel )
-- quick presets for default settings
local presets = {
label = "Presets",
menubutton = 1,
folder = mode.."_admin",
options = {
[L(prefix.."combobox_default")] = improvedstacker.SETTINGS_DEFAULT,
[L(prefix.."combobox_sandbox")] = improvedstacker.SETTINGS_SANDBOX,
[L(prefix.."combobox_darkrp")] = improvedstacker.SETTINGS_DARKRP,
[L(prefix.."combobox_singleplayer")] = improvedstacker.SETTINGS_SINGLEPLAYER,
},
cvars = {
{ CVar = mode.."_max_per_player", CCmd = mode.."_set_max_per_player" },
{ CVar = mode.."_max_per_stack", CCmd = mode.."_set_max_per_stack" },
{ CVar = mode.."_delay", CCmd = mode.."_set_delay" },
{ CVar = mode.."_max_offsetx", CCmd = mode.."_set_max_offsetx" },
{ CVar = mode.."_max_offsety", CCmd = mode.."_set_max_offsety" },
{ CVar = mode.."_max_offsetz", CCmd = mode.."_set_max_offsetz" },
{ CVar = mode.."_force_freeze", CCmd = mode.."_set_force_freeze" },
{ CVar = mode.."_force_weld", CCmd = mode.."_set_force_weld" },
{ CVar = mode.."_force_nocollide", CCmd = mode.."_set_force_nocollide" },
{ CVar = mode.."_force_stayinworld", CCmd = mode.."_set_force_stayinworld" },
},
}
local ctrl = vgui.Create( "StackerControlPresets", cpanel )
ctrl:SetPreset( presets.folder )
for k, v in pairs( presets.options ) do
ctrl:AddOption( k, v )
end
for k, v in pairs( presets.cvars ) do
ctrl:AddConVar( v )
end
cpanel:AddItem( ctrl )
--cpanel:AddControl( "ComboBox", presets )
local bg = Color( 210, 210, 210 ) or Color( 179, 216, 255 )
local fg = Color( 240, 240, 240 ) or Color( 229, 242, 255 )
local sliders = {
{ String = "max_per_player", Min = -1, Max = 2048, Decimals = 0 },
{ String = "max_per_stack", Min = 1, Max = 100, Decimals = 0 },
{ String = "delay", Min = 0, Max = 5, },
{ String = "max_offsetx", Min = 0, Max = 10000, },
{ String = "max_offsety", Min = 0, Max = 10000, },
{ String = "max_offsetz", Min = 0, Max = 10000, },
}
local sliderlist = vgui.Create( "DListLayout", cpanel )
sliderlist:DockPadding( 3, 1, 3, 3 )
sliderlist:SetPaintBackground( true )
function sliderlist:Paint( w, h )
draw.RoundedBox( 0, 0, 0, w, h, bg )
end
cpanel:AddItem( sliderlist )
for k, data in pairs( sliders ) do
local list = vgui.Create( "DListLayout", sliderlist )
list:DockPadding( 5, 0, 5, 5 )
list:DockMargin( 0, 2, 0, 0 )
list:SetPaintBackground( true )
function list:Paint( w, h )
draw.RoundedBox( 0, 0, 0, w, h, fg )
end
local decimals = data.Decimals or 2
local slider = vgui.Create( "StackerDNumSlider", list )
slider:SetText( L(prefix.."label_"..data.String) )
slider.Label:SetFont( "DermaDefaultBold" )
slider:SetMinMax( data.Min, data.Max )
slider:SetDark( true )
slider:SizeToContents()
slider:SetDecimals( decimals )
slider:SetValue( decimals == 0 and GetConVar( mode.."_"..data.String ):GetInt() or GetConVar( mode.."_"..data.String ):GetFloat(), true )
local cmd = mode.."_set_"..data.String
function slider:OnValueChanged( value )
value = math.Round( value, decimals )
RunConsoleCommand( cmd, value )
end
if ( L(prefix.."help_"..data.String) ) then
local help = vgui.Create( "DLabel", list )
help:SetText( L(prefix.."help_"..data.String) )
help:DockMargin( 10, 0, 5, 0 )
help:SetWrap( true )
help:SetDark( true )
help:SetAutoStretchVertical( true )
help:SetFont( "DermaDefault" )
end
if ( L(prefix.."warning_"..data.String) ) then
local help = vgui.Create( "DLabel", list )
help:SetText( L(prefix.."warning_"..data.String) )
help:DockMargin( 10, 0, 5, 0 )
help:SetWrap( true )
help:SetDark( true )
help:SetAutoStretchVertical( true )
help:SetFont( "DermaDefault" )
help:SetTextColor( Color( 200, 0, 0 ) )
end
cvars.AddChangeCallback( mode.."_"..data.String, function( name, old, new )
if ( not IsValid( slider ) ) then return end
slider:SetValue( GetConVar( mode.."_"..data.String ):GetFloat(), true )
end, mode.."_"..data.String.."_utilities" )
end
local checkboxes = {
"freeze",
"weld",
"nocollide",
"nocollide_all",
"stayinworld",
}
local cblist = vgui.Create( "DListLayout", cpanel )
cblist:DockPadding( 3, 1, 3, 3 )
cblist:SetPaintBackground( true )
function cblist:Paint( w, h )
draw.RoundedBox( 0, 0, 0, w, h, bg )
end
cpanel:AddItem( cblist )
for k, data in pairs( checkboxes ) do
local list = vgui.Create( "DListLayout", cblist )
list:DockPadding( 5, 5, 5, 5 )
list:DockMargin( 0, 2, 0, 0 )
list:SetPaintBackground( true )
function list:Paint( w, h )
draw.RoundedBox( 0, 0, 0, w, h, fg )
end
local cb = vgui.Create( "DCheckBoxLabel", list )
cb:SetText( L(prefix.."checkbox_"..data) )
cb:SetChecked( GetConVar( mode.."_force_"..data ):GetBool() )
cb.Label:SetFont( "DermaDefaultBold" )
cb:SizeToContents()
cb:SetDark( true )
-- we don't want this value to be changed while the server is running, so disable the checkbox
if ( data == "nocollide_all" ) then
cb:SetDisabled( true )
end
function cb:OnChange( bool ) RunConsoleCommand( mode.."_set_force_"..data, bool and "1" or "0" ) end
cvars.AddChangeCallback( mode.."_force_"..data, function( name, old, new )
if ( not IsValid( cb ) ) then return end
cb:SetChecked( tobool( new ) )
end, mode.."_"..data.."_utilities" )
if ( L(prefix.."help_"..data) ) then
local help = vgui.Create( "DLabel", list )
help:SetText( L(prefix.."help_"..data) )
help:DockMargin( 25, 5, 5, 0 )
help:SetWrap( true )
help:SetDark( true )
help:SetAutoStretchVertical( true )
help:SetFont( "DermaDefault" )
end
if ( L(prefix.."warning_"..data) ) then
local help = vgui.Create( "DLabel", list )
help:SetText( L(prefix.."warning_"..data) )
help:DockMargin( 25, 5, 5, 0 )
help:SetWrap( true )
help:SetDark( true )
help:SetAutoStretchVertical( true )
help:SetFont( "DermaDefault" )
help:SetTextColor( Color( 200, 0, 0 ) )
end
end
end )
end )
end