Initial commit

This commit is contained in:
Mista-Tea 2014-05-27 18:48:11 -06:00
commit 4b472ef194
4 changed files with 1006 additions and 0 deletions

22
.gitattributes vendored Normal file
View File

@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

215
.gitignore vendored Normal file
View File

@ -0,0 +1,215 @@
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#############
## Windows detritus
#############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac crap
.DS_Store
#############
## Python
#############
*.py[co]
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg

48
addon.txt Normal file
View File

@ -0,0 +1,48 @@
"AddonInfo"
{
"name" "Improved Stacker"
"author_name" "Original - OverloadUT, Edited by - Marii, Edited by - Mista Tea"
"info" "Mista Tea's version (this one)
Cleaned, organized, and optimized code.
Fixes:
- Prevents crash from players using very high X/Y/Z offset values.
- Prevents crash from players using very high P/Y/R rotate values.
- Fixed the halo option for ghosted props not working.
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 console variables for server operators to limit various parts of stacker.
> stacker_max_count
> stacker_max_offsetx
> stacker_max_offsety
> stacker_max_offsetz
- Added console commands for server admins to control the console variables that limit stacker.
> stacker_set_maxcount #
> stacker_set_maxoffset #
> stacker_set_maxoffsetx #
> stacker_set_maxoffsety #
> stacker_set_maxoffsetz #
Marii's version (the one this is based on): http://www.steamcommunity.com/sharedfiles/filedetails/?id=104479831
Fixed for GM13, with other minor tweaks, by me. Not originally my work.
Fixes:
- Fixes ghosting errors.
- Fix InWorld() check.
- Fix bug with checking if prop is valid.
- Fix check to see if count is less then or zero.
Tweaks:
- Allows mass unfreezing of stacked props with physgun.
- Allows full ghosting of props (Shows entire stack).
- Ghosts can have halos!
- Tweak menu values and sliders to be ALOT more useful.
- Add Ghost All option.
Todo:
Clean ALL of the code!
GM12 Version (reupped by: sYcore, original author: OverlordUT): http://www.garrysmod.org/downloads/?a=view&id=64848"
}

View File

@ -0,0 +1,721 @@
--[[--------------------------------------------------------------------------
Stacker Tool
File name:
stacker.lua
Author:
Original - OverloadUT
Updated for GMod 13 - Marii
Cleaned and optimized - Mista Tea
Changelog:
- Edited May 27th, 2014
----------------------------------------------------------------------------]]
--[[--------------------------------------------------------------------------
-- Tool Settings
--------------------------------------------------------------------------]]--
TOOL.Category = "Construction"
TOOL.Name = "#Tool.stacker.name"
TOOL.Command = nil
TOOL.ConfigName = ""
TOOL.ClientConVar[ "mode" ] = "1"
TOOL.ClientConVar[ "dir" ] = "1"
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[ "color" ] = "1"
TOOL.ClientConVar[ "model" ] = ""
TOOL.ClientConVar[ "offsetx" ] = "0"
TOOL.ClientConVar[ "offsety" ] = "0"
TOOL.ClientConVar[ "offsetz" ] = "0"
TOOL.ClientConVar[ "rotp" ] = "0"
TOOL.ClientConVar[ "roty" ] = "0"
TOOL.ClientConVar[ "rotr" ] = "0"
TOOL.ClientConVar[ "recalc" ] = "0"
TOOL.ClientConVar[ "halo" ] = "0"
TOOL.ClientConVar[ "halo_r" ] = "0"
TOOL.ClientConVar[ "halo_g" ] = "200"
TOOL.ClientConVar[ "halo_b" ] = "190"
TOOL.ClientConVar[ "halo_a" ] = "255"
if ( CLIENT ) then
language.Add( "Tool.stacker.name", "Stacker" )
language.Add( "Tool.stacker.desc", "Allows you to easily stack props" )
language.Add( "Tool.stacker.0", "Click to stack the prop you're pointing at." )
language.Add( "Undone_stacker", "Undone stacked prop(s)" )
end
--[[--------------------------------------------------------------------------
-- Console Variables
--------------------------------------------------------------------------]]--
CreateConVar( "stacker_max_count", "30", bit.bor( FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE ) ) -- defines the max amount of props that can be stacked at a time
CreateConVar( "stacker_max_offsetx", "500", bit.bor( FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE ) ) -- defines the max distance on the x plane that stacked props can be offset (for individual control)
CreateConVar( "stacker_max_offsety", "500", bit.bor( FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE ) ) -- defines the max distance on the y plane that stacked props can be offset (for individual control)
CreateConVar( "stacker_max_offsetz", "500", bit.bor( FCVAR_NOTIFY, FCVAR_REPLICATED, FCVAR_SERVER_CAN_EXECUTE ) ) -- defines the max distance on the z plane that stacked props can be offset (for individual control)
--[[--------------------------------------------------------------------------
-- Console Commands
--------------------------------------------------------------------------]]--
if ( CLIENT ) then
local function ResetOffsets( ply, command, arguments )
-- Reset all of the offset options to 0
LocalPlayer():ConCommand( "stacker_offsetx 0\n" )
LocalPlayer():ConCommand( "stacker_offsety 0\n" )
LocalPlayer():ConCommand( "stacker_offsetz 0\n" )
LocalPlayer():ConCommand( "stacker_rotp 0\n" )
LocalPlayer():ConCommand( "stacker_roty 0\n" )
LocalPlayer():ConCommand( "stacker_rotr 0\n" )
LocalPlayer():ConCommand( "stacker_recalc 1\n" )
LocalPlayer():ConCommand( "stacker_ghostall 1\n" )
LocalPlayer():ConCommand( "stacker_material 1\n" )
LocalPlayer():ConCommand( "stacker_color 1\n" )
LocalPlayer():ConCommand( "stacker_halo 1\n" )
end
concommand.Add( "stacker_resetoffsets", ResetOffsets )
elseif ( SERVER ) then
concommand.Add( "stacker_set_maxcount", function( ply, cmd, args )
if ( IsValid( ply ) and !ply:IsAdmin() ) then return false end
local count = tonumber( args[1] )
if ( !count or count < 0 ) then return false end
RunConsoleCommand( "stacker_max_count", count )
end, nil, "Sets the max amount of props that can be stacked at one time", FCVAR_CLIENTCMD_CAN_EXECUTE )
concommand.Add( "stacker_set_maxoffset", function( ply, cmd, args )
if ( IsValid( ply ) and !ply:IsAdmin() ) then return false end
local count = tonumber( args[1] )
if ( !count or count < 0 ) then return false end
RunConsoleCommand( "stacker_max_offsetx", count )
RunConsoleCommand( "stacker_max_offsety", count )
RunConsoleCommand( "stacker_max_offsetz", count )
end, nil, "Sets the max distance that stacked props can be offset in the xyz planes", FCVAR_CLIENTCMD_CAN_EXECUTE )
concommand.Add( "stacker_set_maxoffsetx", function( ply, cmd, args )
if ( IsValid( ply ) and !ply:IsAdmin() ) then return false end
local count = tonumber( args[1] )
if ( !count or count < 0 ) then return false end
RunConsoleCommand( "stacker_max_offsetx", count )
end, nil, "Sets the max distance that stacked props can be offset on the x plane", FCVAR_CLIENTCMD_CAN_EXECUTE )
concommand.Add( "stacker_set_maxoffsety", function( ply, cmd, args )
if ( IsValid( ply ) and !ply:IsAdmin() ) then return false end
local count = tonumber( args[1] )
if ( !count or count < 0 ) then return false end
RunConsoleCommand( "stacker_max_offsety", count )
end, nil, "Sets the max distance that stacked props can be offset on the y plane", FCVAR_CLIENTCMD_CAN_EXECUTE )
concommand.Add( "stacker_set_maxoffsetz", function( ply, cmd, args )
if ( IsValid( ply ) and !ply:IsAdmin() ) then return false end
local count = tonumber( args[1] )
if ( !count or count < 0 ) then return false end
RunConsoleCommand( "stacker_max_offsetz", count )
end, nil, "Sets the max distance that stacked props can be offset on the z plane", FCVAR_CLIENTCMD_CAN_EXECUTE )
end
--[[--------------------------------------------------------------------------
-- Enumerations
--------------------------------------------------------------------------]]--
local MODE_WORLD = 1 -- stacking relative to the world
local MODE_PROP = 2 -- stacking relative to the prop
local DIRECTION_UP = 1
local DIRECTION_DOWN = 2
local DIRECTION_FRONT = 3
local DIRECTION_BEHIND = 4
local DIRECTION_RIGHT = 5
local DIRECTION_LEFT = 6
--[[--------------------------------------------------------------------------
-- Localized Functions & Variables
--------------------------------------------------------------------------]]--
-- localizing globals is an encouraged practice that inproves code efficiency,
-- accessing a local value is considerably faster than a global value
local util = util
local math = math
local undo = undo
local halo = halo
local game = game
local pairs = pairs
local table = table
local Angle = Angle
local Vector = Vector
local IsValid = IsValid
local language = language
local MOVETYPE_NONE = MOVETYPE_NONE
local SOLID_VPHYSICS = SOLID_VPHYSICS
local RENDERMODE_TRANSALPHA = RENDERMODE_TRANSALPHA
--[[--------------------------------------------------------------------------
-- Convenience Functions
--------------------------------------------------------------------------]]--
--[[--------------------------------------------------------------------------
-- TOOL:GetCount()
--
-- Gets the maximum amount of props that can be stacked at a time
--]]--
function TOOL:GetCount()
return math.Clamp( self:GetClientNumber( "count" ), 0, GetConVarNumber( "stacker_max_count" ) )
end
--[[--------------------------------------------------------------------------
-- TOOL:GetDirection()
--
-- Gets the direction to stack the props
--]]--
function TOOL:GetDirection() return self:GetClientNumber( "dir" ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetStackerMode()
--
-- Gets the stacker mode (1 = MODE_WORLD, 2 = MODE_PROP)
--]]--
function TOOL:GetStackerMode() return self:GetClientNumber( "mode" ) 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" ), - GetConVarNumber( "stacker_max_offsetx" ), GetConVarNumber( "stacker_max_offsetx" ) ) end
function TOOL:GetOffsetY() return math.Clamp( self:GetClientNumber( "offsety" ), - GetConVarNumber( "stacker_max_offsety" ), GetConVarNumber( "stacker_max_offsety" ) ) end
function TOOL:GetOffsetZ() return math.Clamp( self:GetClientNumber( "offsetz" ), - GetConVarNumber( "stacker_max_offsetz" ), GetConVarNumber( "stacker_max_offsetz" ) ) end
function TOOL:GetOffsetVector() return Vector( self:GetOffsetX(), self:GetOffsetY(), self:GetOffsetZ() ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetRotateP(), TOOL:GetRotateY(), TOOL:GetRotateR(), TOOL:GetRotateAngle()
--
-- 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( "rotp" ), -360, 360 ) end
function TOOL:GetRotateY() return math.Clamp( self:GetClientNumber( "roty" ), -360, 360 ) end
function TOOL:GetRotateR() return math.Clamp( self:GetClientNumber( "rotz" ), -360, 360 ) end
function TOOL:GetRotateAngle() return Angle( self:GetRotateP(), self:GetRotateY(), self:GetRotateR() ) end
--[[--------------------------------------------------------------------------
-- TOOL:GetGhostStack(), TOOL:SetGhostStack( table )
--
-- Gets and sets the table of ghosted props in the stack.
--]]--
function TOOL:GetGhostStack() return self.GhostStack end
function TOOL:SetGhostStack( tbl ) self.GhostStack = tbl end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldFreeze()
--
-- Returns true if the stacked props should be spawned frozen.
--]]--
function TOOL:ShouldFreeze() return self:GetClientNumber( "freeze" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldWeld()
--
-- Returns true if the stacked props should be welded together.
--]]--
function TOOL:ShouldWeld() return self:GetClientNumber( "weld" ) == 1 end
--[[--------------------------------------------------------------------------
-- TOOL:ShouldNoCollide()
--
-- Returns true if the stacked props should be nocollided with each other.
--]]--
function TOOL:ShouldNoCollide() return self:GetClientNumber( "nocollide" ) == 1 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( "recalc" ) == 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( "halo" ) == 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 Functions
--------------------------------------------------------------------------]]--
--[[--------------------------------------------------------------------------
--
-- TOOL:Holster()
--
-- Called when the player switches to a different weapon or tool.
--]]--
function TOOL:Holster()
self:ReleaseGhostStack()
end
--[[--------------------------------------------------------------------------
--
-- TOOL:Init()
--
-- Creates two net message receivers on the client that control the stack
-- of ghosted props.
--]]--
function TOOL:Init()
if ( SERVER ) then
util.AddNetworkString( "Stacker.StackGhost" )
util.AddNetworkString( "Stacker.UnstackGhost" )
elseif ( CLIENT ) then
net.Receive( "Stacker.StackGhost", function( bits )
self:SetGhostStack( net.ReadTable() )
end )
net.Receive( "Stacker.UnstackGhost", function( bits )
if ( !self:GetGhostStack() ) then return end
table.Empty( self:GetGhostStack() )
end )
end
end
--[[--------------------------------------------------------------------------
--
-- TOOL:LeftClick( table )
--
-- Attempts to create a stack of props relative to the entity being left clicked.
--]]--
function TOOL:LeftClick( trace )
if ( !IsValid( trace.Entity ) or trace.Entity:GetClass() ~= "prop_physics" ) then return false end
if ( CLIENT ) then return true end
local count = self:GetCount()
if ( count <= 0 ) then return false end
local dir = self:GetDirection()
local mode = self:GetStackerMode()
local offset = self:GetOffsetVector()
local rotate = self:GetRotateAngle()
local applyFreeze = self:ShouldFreeze()
local applyWeld = self:ShouldWeld()
local applyNoCollide = self:ShouldNoCollide()
local stackRelative = self:ShouldStackRelative()
local applyMaterial = self:ShouldApplyMaterial()
local applyColor = self:ShouldApplyColor()
local ply = self:GetOwner()
local ent = trace.Entity
local entPos = ent:GetPos()
local entAng = ent:GetAngles()
local entMat = ent:GetMaterial()
local entCol = ent:GetColor()
local lastEnt = ent
local newEnt
undo.Create( "stacker" )
for i = 1, count, 1 do
if ( !self:GetSWEP():CheckLimit( "props" ) ) then break end
if ( i == 1 or ( mode == MODE_PROP and stackRelative ) ) then
stackdir, height, thisoffset = self:StackerCalcPos( lastEnt, mode, dir, offset )
end
entPos = entPos + stackdir * height + thisoffset
entAng = entAng + rotate
newEnt = ents.Create( "prop_physics" )
newEnt["IsFromStacker"] = true -- this is for external prop protections or anti-spam addons
newEnt:SetModel( ent:GetModel() )
newEnt:SetPos( entPos )
newEnt:SetAngles( entAng )
if ( applyMaterial ) then newEnt:SetMaterial( entMat ) end
if ( applyColor ) then newEnt:SetColor( entCol ) end
newEnt:Spawn()
if ( applyFreeze ) then
--ply:AddFrozenPhysicsObject( newEnt, newEnt:GetPhysicsObject() ) -- fix so you can mass-unfreeze
newEnt:GetPhysicsObject():EnableMotion( false )
else
newEnt:GetPhysicsObject():Wake()
end
if ( applyWeld ) then undo.AddEntity( constraint.Weld( lastEnt, newEnt, 0, 0, 0 ) ) end
if ( applyNoCollide ) then undo.AddEntity( constraint.NoCollide( lastEnt, newEnt, 0, 0 ) ) end
lastEnt = newEnt
undo.AddEntity( newEnt )
ply:AddCount( "props", newEnt )
ply:AddCleanup( "props", newEnt )
end
undo.SetPlayer( ply )
undo.Finish()
return true
end
--[[--------------------------------------------------------------------------
--
-- TOOL:StackerCalcPos( entity, number, number, number )
--
-- Calculates the positions and angles of the entity being created in the stack.
-- This function uses a lookup table for added optimization as opposed to an if-else block.
--]]--
local CALC_POS = {
[MODE_WORLD] = {
[DIRECTION_UP] = function( forward, upper, lower ) return forward:Up(), math.abs( upper.z - lower.z ) end,
[DIRECTION_DOWN] = function( forward, upper, lower ) return forward:Up() * -1, math.abs( upper.z - lower.z ) end,
[DIRECTION_FRONT] = function( forward, upper, lower ) return forward:Forward(), math.abs( upper.x - lower.x ) end,
[DIRECTION_BEHIND] = function( forward, upper, lower ) return forward:Forward() * -1, math.abs( upper.x - lower.x ) end,
[DIRECTION_RIGHT] = function( forward, upper, lower ) return forward:Right(), math.abs( upper.y - lower.y ) end,
[DIRECTION_LEFT] = function( forward, upper, lower ) return forward:Right() * -1, math.abs( upper.y - lower.y ) end,
},
[MODE_PROP] = {
[DIRECTION_UP] = function( forward, offset, gupper, glower ) return forward:Up(), math.abs( gupper.z - glower.z ), forward:Up() * offset.X + forward:Forward() * -1 * offset.Z + forward:Right() * offset.Y end,
[DIRECTION_DOWN] = function( forward, offset, gupper, glower ) return forward:Up() * -1, math.abs( gupper.z - glower.z ), forward:Up() * -1 * offset.X + forward:Forward() * offset.Z + forward:Right() * offset.Y end,
[DIRECTION_FRONT] = function( forward, offset, gupper, glower ) return forward:Forward(), math.abs( gupper.x - glower.x ), forward:Forward() * offset.X + forward:Up() * offset.Z + forward:Right() * offset.Y end,
[DIRECTION_BEHIND] = function( forward, offset, gupper, glower ) return forward:Forward() * -1, math.abs( gupper.x - glower.x ), forward:Forward() * -1 * offset.X + forward:Up() * offset.Z + forward:Right() * -1 * offset.Y end,
[DIRECTION_RIGHT] = function( forward, offset, gupper, glower ) return forward:Right(), math.abs( gupper.y - glower.y ), forward:Right() * offset.X + forward:Up() * offset.Z + forward:Forward() * -1 * offset.Y end,
[DIRECTION_LEFT] = function( forward, offset, gupper, glower ) return forward:Right() * -1, math.abs( gupper.y - glower.y ), forward:Right() * -1 * offset.X + forward:Up() * offset.Z + forward:Forward() * offset.Y end,
},
}
local VECX = Vector( 1, 0, 0 )
local VECZ = Vector( 0, 0, 1 )
function TOOL:StackerCalcPos( ent, mode, dir, offset )
local forward = VECX:Angle()
local entAng = ent:GetAngles()
local lower = ent:WorldSpaceAABB()
local upper = ent:WorldSpaceAABB()
local glower = ent:OBBMins()
local gupper = ent:OBBMaxs()
local stackdir = VECZ
local height = math.abs( upper.z - lower.z )
if ( mode == MODE_WORLD ) then -- get the position relative to the world's directions
stackdir, height = CALC_POS[ mode ][ dir ]( forward, upper, lower )
elseif ( mode == MODE_PROP ) then -- get the position relative to the prop's directions
stackdir, height, offset = CALC_POS[ mode ][ dir ]( entAng, offset, gupper, glower )
end
return stackdir, height, offset
end
--[[--------------------------------------------------------------------------
--
-- TOOL:UpdateGhostStack( entity )
--
-- Attempts to update the positions and angles of all ghosted props in the stack.
--]]--
function TOOL:UpdateGhostStack( ent )
if ( !IsValid( ent ) or !self:CheckGhostStack() ) then return end
local count = self:GetCount()
local mode = self:GetStackerMode()
local dir = self:GetDirection()
local offset = self:GetOffsetVector()
local rotate = self:GetRotateAngle()
local recalc = self:ShouldStackRelative()
local newEnt = ent
local entPos = newEnt:GetPos()
local entAng = newEnt:GetAngles()
local stackdir, height, thisoffset
for k,v in pairs( self:GetGhostStack() ) do
if ( k == 1 or ( mode == MODE_PROP and recalc ) ) then
stackdir, height, thisoffset = self:StackerCalcPos( newEnt, mode, dir, offset )
end
entPos = entPos + stackdir * height + thisoffset
entAng = entAng + rotate
v:SetAngles( entAng )
v:SetPos( entPos )
v:SetNoDraw( false )
newEnt = v
end
end
--[[--------------------------------------------------------------------------
--
-- TOOL:CheckGhostStack()
--
-- Attempts to validate the status of the ghosted props in the stack.
--]]--
function TOOL:CheckGhostStack()
local ghoststack = self:GetGhostStack()
if ( !ghoststack ) then return false end
local count = self:GetCount()
local ghostAll = self:ShouldGhostAll()
for k, v in pairs( ghoststack ) do
if ( !IsValid( v ) ) then
return false
end
end
if ( #ghoststack ~= count and ghostAll ) then return false
elseif ( #ghoststack ~= 1 and !ghostAll ) then return false end
return true
end
--[[--------------------------------------------------------------------------
--
-- TOOL: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 TRANSPARENT = Color( 255, 255, 255, 150 )
function TOOL:CreateGhostStack( ent, pos, ang )
if ( self:GetGhostStack() ) then self:ReleaseGhostStack() end
local ghoststack = {}
if ( SERVER and !game.SinglePlayer() ) then return false; end
if ( CLIENT and game.SinglePlayer() ) then return false; end
local count = self:GetCount()
local addHalos = self:ShouldAddHalos()
local ghostAll = self:ShouldGhostAll()
local applyMaterial = self:ShouldApplyMaterial()
local applyColor = self:ShouldApplyColor()
local entMod = ent:GetModel()
local entMat = ent:GetMaterial() or ""
local entCol = ent:GetColor() or TRANSPARENT
entCol.a = 150
if ( !ghostAll and count ~= 0 ) then
count = 1
end
for i = 1, count, 1 do
local ghost
if ( CLIENT ) then ghost = ents.CreateClientProp( entMod )
else ghost = ents.Create( "prop_physics" ) end
if ( !IsValid( ghost ) ) then ghost = nil continue end
ghost:SetModel( entMod )
ghost:SetPos( pos )
ghost:SetAngles( ang )
ghost:Spawn()
ghost:SetSolid( SOLID_VPHYSICS )
ghost:SetMoveType( MOVETYPE_NONE )
ghost:SetRenderMode( RENDERMODE_TRANSALPHA )
ghost:SetNotSolid( true )
ghost:SetMaterial( ( applyMaterial and entMat ) or "" )
ghost:SetColor( ( applyColor and entCol ) or TRANSPARENT )
table.insert( ghoststack, ghost )
end
if ( SERVER and addHalos ) then
net.Start( "Stacker.StackGhost" )
net.WriteTable( ghoststack )
net.Send( self:GetOwner() )
end
self:SetGhostStack( ghoststack )
return true
end
--[[--------------------------------------------------------------------------
--
-- TOOL:ReleaseGhostStack()
--
-- Attempts to remove all ghosted props in the stack on the server (if singleplayer)
-- or on the client (in multiplayer). This occurs when the player stops looking at
-- a prop with the stacker tool equipped.
--]]--
function TOOL:ReleaseGhostStack()
local ghoststack = self:GetGhostStack()
if ( !ghoststack ) then return end
for k,v in pairs( ghoststack ) do
if ( !IsValid( v ) ) then continue end
v:Remove()
end
if ( SERVER ) then
net.Start( "Stacker.UnstackGhost" )
net.Send( self:GetOwner() )
end
table.Empty( ghoststack )
end
--[[--------------------------------------------------------------------------
--
-- TOOL:Think()
--
-- While the stacker tool is equipped, this function will check to see if
-- the player is looking at any props and attempt to create the stack of
-- ghosted props before the players actually left clicks.
--]]--
local VEC = Vector( 0, 0, 0 )
local ANG = Angle( 0, 0, 0 )
local HaloColor = Color( 181, 0, 217 )
function TOOL:Think()
local ply = self:GetOwner()
local trace = ply:GetEyeTrace()
local traceValid = IsValid( trace.Entity )
if ( traceValid and trace.Entity:GetClass() == "prop_physics" ) then
self.NewEnt = trace.Entity
if ( self.NewEnt ~= self.LastEnt ) then
if ( self:CreateGhostStack( self.NewEnt, VEC, ANG ) ) then self.LastEnt = self.NewEnt end
end
if ( !self:CheckGhostStack() ) then
self:ReleaseGhostStack()
self.LastEnt = nil
end
elseif ( ( traceValid and trace.Entity:GetClass() ~= "prop_physics" and self.LastEnt ~= nil ) or !traceValid ) then
self:ReleaseGhostStack()
self.LastEnt = nil
end
if ( IsValid( self.LastEnt ) ) then
self:UpdateGhostStack( self.LastEnt )
end
if ( CLIENT ) then
if ( !self:ShouldAddHalos() ) then return end
local ghoststack = self:GetGhostStack()
if ( !ghoststack or #ghoststack <= 0 ) then return end
halo.Add( ghoststack, self:GetHaloColor() )
end
end
--[[--------------------------------------------------------------------------
--
-- TOOL.BuildCPanel( panel )
--
-- Builds the control panel menu that can be seen when holding Q and accessing
-- the stacker menu.
--]]--
function TOOL.BuildCPanel( CPanel )
CPanel:AddControl( "Header", { Text = "#Tool.stacker.name", Description = "#Tool.stacker.desc" } )
CPanel:AddControl( "Checkbox", { Label = "Freeze Props", Command = "stacker_freeze" } )
CPanel:AddControl( "Checkbox", { Label = "Weld Props", Command = "stacker_weld" } )
CPanel:AddControl( "Checkbox", { Label = "No Collide Props", Command = "stacker_nocollide" } )
local params = { Label = "Relative To:", MenuButton = "0", Options = {} }
params.Options[ "World" ] = { stacker_mode = "1" }
params.Options[ "Prop" ] = { stacker_mode = "2" }
CPanel:AddControl( "ComboBox", params )
local params = { Label = "Stack Direction", MenuButton = "0", Options = {} }
params.Options[ "Up" ] = { stacker_dir = "1" }
params.Options[ "Down" ] = { stacker_dir = "2" }
params.Options[ "Front" ] = { stacker_dir = "3" }
params.Options[ "Behind" ] = { stacker_dir = "4" }
params.Options[ "Right" ] = { stacker_dir = "5" }
params.Options[ "Left" ] = { stacker_dir = "6" }
CPanel:AddControl( "ComboBox", params )
CPanel:AddControl( "Slider", { Label = "Count",Type = "Integer", Min = 1, Max = GetConVarNumber( "stacker_max_count" ), Command = "stacker_count", Description = "How many props to stack." } )
CPanel:AddControl( "Header", { Text = "Advanced Options", Description = "These options are for advanced users. Leave them all default ( 0 ) if you don't understand what they do." } )
CPanel:AddControl( "Button", { Label = "Reset Advanced Options", Command = "stacker_resetoffsets", Text = "Reset" } )
CPanel:AddControl( "Slider", { Label = "Offset X ( forward/back )", Type = "Float", Min = - GetConVarNumber( "stacker_max_offsetx" ), Max = GetConVarNumber( "stacker_max_offsetx" ), Value = 0, Command = "stacker_offsetx" } )
CPanel:AddControl( "Slider", { Label = "Offset Y ( right/left )", Type = "Float", Min = - GetConVarNumber( "stacker_max_offsety" ), Max = GetConVarNumber( "stacker_max_offsety" ), Value = 0, Command = "stacker_offsety" } )
CPanel:AddControl( "Slider", { Label = "Offset Z ( up/down )", Type = "Float", Min = - GetConVarNumber( "stacker_max_offsetz" ), Max = GetConVarNumber( "stacker_max_offsetz" ), Value = 0, Command = "stacker_offsetz" } )
CPanel:AddControl( "Slider", { Label = "Rotate Pitch", Type = "Float", Min = -360, Max = 360, Value = 0, Command = "stacker_rotp" } )
CPanel:AddControl( "Slider", { Label = "Rotate Yaw", Type = "Float", Min = -360, Max = 360, Value = 0, Command = "stacker_roty" } )
CPanel:AddControl( "Slider", { Label = "Rotate Roll", Type = "Float", Min = -360, Max = 360, Value = 0, Command = "stacker_rotr" } )
CPanel:AddControl( "Checkbox", { Label = "Stack props relative to new rotation", Command = "stacker_recalc", Description = "If this is checked, each item in the stack will be stacked relative to the previous item in the stack. This allows you to create curved stacks." } )
CPanel:AddControl( "Checkbox", { Label = "Ghost all of the props in the stack", Command = "stacker_ghostall", Description = "Creates every ghost prop in the stack instead of just the first ghost prop" } )
CPanel:AddControl( "Checkbox", { Label = "Apply material to the stacked props", Command = "stacker_material", Description = "Applies the material of the original prop to all stacked props" } )
CPanel:AddControl( "Checkbox", { Label = "Apply color to the stacked props", Command = "stacker_color", Description = "Applies the color of the original prop to all stacked props" } )
CPanel:AddControl( "Checkbox", { Label = "Add halos to the ghost props", Command = "stacker_halo", Description = "Give the ghost a halo" } )
CPanel:AddControl( "Color", { Label = "Halo color", Red = "stacker_halo_r", Green = "stacker_halo_g", Blue = "stacker_halo_b", Alpha = "" } )
end