483 lines
No EOL
18 KiB
Lua
483 lines
No EOL
18 KiB
Lua
--[[--------------------------------------------------------------------------
|
|
Improved Stacker Module
|
|
|
|
Author:
|
|
Mista-Tea ([IJWTB] Thomas)
|
|
|
|
License:
|
|
The MIT License (MIT)
|
|
|
|
Copyright (c) 2014-2016 Mista-Tea
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
Changelog:
|
|
----------------------------------------------------------------------------]]
|
|
|
|
local math = math
|
|
local hook = hook
|
|
local Angle = Angle
|
|
local Vector = Vector
|
|
local GetConVar = GetConVar
|
|
local duplicator = duplicator
|
|
local CreateConVar = CreateConVar
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Namespace Tables
|
|
--------------------------------------------------------------------------]]--
|
|
|
|
module( "improvedstacker", package.seeall )
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Localized Functions & Variables
|
|
--------------------------------------------------------------------------]]--
|
|
|
|
-- enums for determining stack relativity
|
|
MODE_WORLD = 1 -- stacking relative to the world
|
|
MODE_PROP = 2 -- stacking relative to the prop
|
|
|
|
-- lookup table for validating relative values
|
|
Modes = {
|
|
[MODE_WORLD] = true,
|
|
[MODE_PROP] = true,
|
|
}
|
|
|
|
-- enums for determining the direction to stack props
|
|
DIRECTION_FRONT = 1
|
|
DIRECTION_BACK = 2
|
|
DIRECTION_RIGHT = 3
|
|
DIRECTION_LEFT = 4
|
|
DIRECTION_UP = 5
|
|
DIRECTION_DOWN = 6
|
|
|
|
-- lookup table for validating direction values
|
|
Directions = {
|
|
[DIRECTION_FRONT] = true,
|
|
[DIRECTION_BACK] = true,
|
|
[DIRECTION_RIGHT] = true,
|
|
[DIRECTION_LEFT] = true,
|
|
[DIRECTION_UP] = true,
|
|
[DIRECTION_DOWN] = true,
|
|
}
|
|
|
|
-- constants used for when stacking relative to the World
|
|
ANGLE_ZERO = Angle( 0, 0, 0 )
|
|
VECTOR_FRONT = ANGLE_ZERO:Forward()
|
|
VECTOR_RIGHT = ANGLE_ZERO:Right()
|
|
VECTOR_UP = ANGLE_ZERO:Up()
|
|
VECTOR_BACK = -VECTOR_FRONT
|
|
VECTOR_LEFT = -VECTOR_RIGHT
|
|
VECTOR_DOWN = -VECTOR_UP
|
|
|
|
-- there has been a longstanding problem where stacked entities were an inch apart (figuratively), causing gaps everywhere.
|
|
-- as it turns out, fixing this issue is as easy as subtracting 0.5 from the forward component of the offset vector.
|
|
MAGIC_OFFSET = -0.5
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Namespace Functions
|
|
--------------------------------------------------------------------------]]--
|
|
|
|
if ( SERVER ) then
|
|
|
|
-- the tables below are used internally and should only generally be interfaced with
|
|
-- via the functions declared afterward.
|
|
-- basically treat them as private, since they are only public for auto-refresh compatibility
|
|
|
|
-- holds the current stacked entity count for every player
|
|
m_EntCount = m_EntCount or {}
|
|
-- holds the last stacker usage for every player
|
|
m_StackTime = m_StackTime or {}
|
|
-- holds every stacker entity created
|
|
m_Ents = m_Ents or {}
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetEntCount( player, number )
|
|
--]]--
|
|
function GetEntCount( ply, default )
|
|
return m_EntCount[ ply:SteamID() ] or default
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- SetEntCount( player, number )
|
|
--]]--
|
|
function SetEntCount( ply, num )
|
|
m_EntCount[ ply:SteamID() ] = num
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- IncrementEntCount( player, number )
|
|
--]]--
|
|
function IncrementEntCount( ply, num )
|
|
m_EntCount[ ply:SteamID() ] = GetEntCount( ply, 0 ) + (num or 1)
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- DecrementEntCount( player, number )
|
|
--]]--
|
|
function DecrementEntCount( ply, num )
|
|
m_EntCount[ ply:SteamID() ] = ( m_EntCount[ ply:SteamID() ] and m_EntCount[ ply:SteamID() ] - (num or 1) ) or 0
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- SetLastStackTime( player, number )
|
|
--]]--
|
|
function SetLastStackTime( ply, num )
|
|
m_StackTime[ ply:SteamID() ] = num
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetLastStackTime( player, number )
|
|
--]]--
|
|
function GetLastStackTime( ply, default )
|
|
return m_StackTime[ ply:SteamID() ] or default
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Initialize( string )
|
|
--
|
|
-- This should be called immediately after including this file so that the follow
|
|
-- variables/functions can use the stacker tool's mode (i.e., the name of the file itself
|
|
-- and what is subsequently used in all of the cvars).
|
|
--]]--
|
|
function Initialize( mode )
|
|
mode = mode or "stacker_improved"
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Hook :: PlayerInitialSpawn
|
|
|
|
-- Sets the newly connected player's total stacker ents to 0.
|
|
-- See TOOL:IsExceedingMax() for more details
|
|
--]]--
|
|
hook.Add( "PlayerInitialSpawn", mode.."_set_ent_count", function( ply )
|
|
m_EntCount[ ply:SteamID() ] = 0
|
|
end )
|
|
--[[--------------------------------------------------------------------------
|
|
-- Hook :: PlayerDisconnected
|
|
--
|
|
-- Removes the player from the table when they disconnect (for sanitation).
|
|
--]]--
|
|
hook.Add( "PlayerDisconnected", mode.."_remove_ent_count", function( ply )
|
|
m_EntCount[ ply:SteamID() ] = nil
|
|
end )
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- MarkEntity( player, entity, table )
|
|
--
|
|
-- Marks the entity as a stacker entity. This allows the entity to be
|
|
-- collision-checked in GM.ShouldCollide.
|
|
--]]--
|
|
function MarkEntity( ply, ent, data )
|
|
m_Ents[ ent ] = true
|
|
duplicator.StoreEntityModifier( ent, mode, { StackerEnt = true } )
|
|
ent:SetCustomCollisionCheck( true )
|
|
|
|
-- when the entity is removed, sanitize our internal m_Ents array
|
|
ent:CallOnRemove( mode, function( ent )
|
|
ClearEntity( ent )
|
|
end )
|
|
end
|
|
--duplicator.RegisterEntityModifier( mode, MarkEntity )
|
|
--[[--------------------------------------------------------------------------
|
|
-- ClearEntity( entity )
|
|
--
|
|
-- Removes the entity from the internal m_Ents array for sanitation purposes.
|
|
-- This is called when an entity is just about to be removed.
|
|
--]]--
|
|
function ClearEntity( ent )
|
|
if ( m_Ents[ ent ] ) then m_Ents[ ent ] = nil end
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- CanUnfreeze( player, entity, physObject )
|
|
--]]--
|
|
function CanUnfreeze( ply, ent, phys )
|
|
if ( m_Ents[ ent ] ) then print("nope") return false end
|
|
end
|
|
--hook.Add( "CanPlayerUnfreeze", mode, CanUnfreeze )
|
|
--hook.Add( "PhysgunPickup", mode, CanUnfreeze )
|
|
--hook.Remove( "CanPlayerUnfreeze", mode )
|
|
--hook.Remove( "PhysgunPickup", mode )
|
|
|
|
local cvarNoCollideAll
|
|
local cvarNoCollide
|
|
--[[--------------------------------------------------------------------------
|
|
-- ShouldCollide( entity, entity )
|
|
--]]--
|
|
function ShouldCollide( a, b )
|
|
if ( not cvarNoCollideAll ) then cvarNoCollideAll = GetConVar( mode.."_force_nocollide_all" ) end
|
|
if ( not cvarNoCollide ) then cvarNoCollide = GetConVar( mode.."_force_nocollide" ) end
|
|
|
|
if ( cvarNoCollideAll:GetBool() ) then
|
|
if ( m_Ents[ a ] ) then
|
|
if not ( b:IsPlayer() or b:IsWorld() or b:IsNPC() or b:IsVehicle() ) then return false end
|
|
elseif ( m_Ents[ b ] ) then
|
|
if not ( a:IsPlayer() or a:IsWorld() or b:IsNPC() or b:IsVehicle() ) then return false end
|
|
end
|
|
elseif ( cvarNoCollide:GetBool() ) then
|
|
if ( m_Ents[ a ] and m_Ents[ b ] ) then return false end
|
|
end
|
|
end
|
|
--hook.Add( "ShouldCollide", mode, ShouldCollide )
|
|
--hook.Remove( "ShouldCollide", mode )
|
|
end
|
|
|
|
elseif ( CLIENT ) then
|
|
|
|
-- the table below is used internally and should only generally be interfaced with
|
|
-- via the functions declared afterward.
|
|
-- basically treat it as private, since it is only public for auto-refresh compatibility
|
|
|
|
m_Ghosts = m_Ghosts or {}
|
|
m_LookingAt = m_LookingAt or nil
|
|
m_LookedAt = m_LookedAt or nil
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetGhosts()
|
|
--]]--
|
|
function GetGhosts()
|
|
return m_Ghosts
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- SetGhosts( table )
|
|
--]]--
|
|
function SetGhosts( tbl )
|
|
m_Ghosts = tbl
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetLookingAt()
|
|
--]]--
|
|
function GetLookingAt()
|
|
return m_LookingAt
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- SetLookingAt( entity )
|
|
--]]--
|
|
function SetLookingAt( ent )
|
|
m_LookingAt = ent
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetLookedAt()
|
|
--]]--
|
|
function GetLookedAt()
|
|
return m_LookedAt
|
|
end
|
|
--[[--------------------------------------------------------------------------
|
|
-- SetLookedAt( entity )
|
|
--]]--
|
|
function SetLookedAt( ent )
|
|
m_LookedAt = ent
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- ReleaseGhosts()
|
|
--
|
|
-- Attempts to remove all ghosted props in the stack.
|
|
-- This occurs when the player stops looking at a prop with the stacker tool equipped.
|
|
--]]--
|
|
function ReleaseGhosts()
|
|
if ( #m_Ghosts == 0 ) then return end
|
|
|
|
for i = 1, #m_Ghosts do
|
|
if ( not IsValid( m_Ghosts[ i ] ) ) then continue end
|
|
SafeRemoveEntityDelayed( m_Ghosts[ i ], 0 )
|
|
m_Ghosts[ i ] = nil
|
|
end
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- Initialize( string )
|
|
--
|
|
-- This should be called immediately after including this file so that the follow
|
|
-- variables/functions can use the stacker tool's mode (i.e., the name of the file itself
|
|
-- and what is subsequently used in all of the cvars).
|
|
--]]--
|
|
function Initialize( mode )
|
|
mode = mode or "stacker_improved"
|
|
|
|
SETTINGS_DEFAULT = {
|
|
[mode.."_set_max_per_player"] = "-1",
|
|
[mode.."_set_max_per_stack"] = "15",
|
|
[mode.."_set_delay"] = "0.5",
|
|
[mode.."_set_max_offsetx"] = "200",
|
|
[mode.."_set_max_offsety"] = "200",
|
|
[mode.."_set_max_offsetz"] = "200",
|
|
[mode.."_set_force_freeze"] = "0",
|
|
[mode.."_set_force_weld"] = "0",
|
|
[mode.."_set_force_nocollide"] = "0",
|
|
[mode.."_set_force_stayinworld"] = "1",
|
|
}
|
|
|
|
SETTINGS_SANDBOX = {
|
|
[mode.."_set_max_per_player"] = "-1",
|
|
[mode.."_set_max_per_stack"] = "30",
|
|
[mode.."_set_delay"] = "0.5",
|
|
[mode.."_set_max_offsetx"] = "1000",
|
|
[mode.."_set_max_offsety"] = "1000",
|
|
[mode.."_set_max_offsetz"] = "1000",
|
|
[mode.."_set_force_freeze"] = "0",
|
|
[mode.."_set_force_weld"] = "0",
|
|
[mode.."_set_force_nocollide"] = "0",
|
|
[mode.."_set_force_stayinworld"] = "0",
|
|
}
|
|
|
|
SETTINGS_DARKRP = {
|
|
[mode.."_set_max_per_player"] = "50",
|
|
[mode.."_set_max_per_stack"] = "5",
|
|
[mode.."_set_delay"] = "1",
|
|
[mode.."_set_max_offsetx"] = "200",
|
|
[mode.."_set_max_offsety"] = "200",
|
|
[mode.."_set_max_offsetz"] = "200",
|
|
[mode.."_set_force_freeze"] = "1",
|
|
[mode.."_set_force_weld"] = "0",
|
|
[mode.."_set_force_nocollide"] = "1",
|
|
[mode.."_set_force_stayinworld"] = "1",
|
|
}
|
|
|
|
SETTINGS_SINGLEPLAYER = {
|
|
[mode.."_set_max_per_player"] = "-1",
|
|
[mode.."_set_max_per_stack"] = "100",
|
|
[mode.."_set_delay"] = "0",
|
|
[mode.."_set_max_offsetx"] = "10000",
|
|
[mode.."_set_max_offsety"] = "10000",
|
|
[mode.."_set_max_offsetz"] = "10000",
|
|
[mode.."_set_force_freeze"] = "0",
|
|
[mode.."_set_force_weld"] = "0",
|
|
[mode.."_set_force_nocollide"] = "0",
|
|
[mode.."_set_force_stayinworld"] = "0",
|
|
}
|
|
end
|
|
|
|
end
|
|
|
|
--
|
|
-- The functions below are used both serverside and clientside for properly orienting
|
|
-- and spacing props in a stack
|
|
--
|
|
|
|
-- Lookup table that holds functions related to determining the direction of a stack
|
|
DirectionFunctions = {
|
|
[MODE_WORLD] = {
|
|
[DIRECTION_FRONT] = function() return VECTOR_FRONT end,
|
|
[DIRECTION_BACK] = function() return VECTOR_BACK end,
|
|
[DIRECTION_RIGHT] = function() return VECTOR_RIGHT end,
|
|
[DIRECTION_LEFT] = function() return VECTOR_LEFT end,
|
|
[DIRECTION_UP] = function() return VECTOR_UP end,
|
|
[DIRECTION_DOWN] = function() return VECTOR_DOWN end,
|
|
},
|
|
|
|
[MODE_PROP] = {
|
|
[DIRECTION_FRONT] = function( angle ) return angle:Forward() end,
|
|
[DIRECTION_BACK] = function( angle ) return -angle:Forward() end,
|
|
[DIRECTION_RIGHT] = function( angle ) return angle:Right() end,
|
|
[DIRECTION_LEFT] = function( angle ) return -angle:Right() end,
|
|
[DIRECTION_UP] = function( angle ) return angle:Up() end,
|
|
[DIRECTION_DOWN] = function( angle ) return -angle:Up() end,
|
|
}
|
|
}
|
|
|
|
-- Lookup table that holds functions related to determining the distance to offset each prop in a stack
|
|
-- before applying the client's actual x/y/z offset values
|
|
DistanceFunctions = {
|
|
[DIRECTION_FRONT] = function( min, max ) return math.abs(max.x - min.x) end,
|
|
[DIRECTION_BACK] = function( min, max ) return math.abs(max.x - min.x) end,
|
|
[DIRECTION_RIGHT] = function( min, max ) return math.abs(max.y - min.y) end,
|
|
[DIRECTION_LEFT] = function( min, max ) return math.abs(max.y - min.y) end,
|
|
[DIRECTION_UP] = function( min, max ) return math.abs(max.z - min.z) end,
|
|
[DIRECTION_DOWN] = function( min, max ) return math.abs(max.z - min.z) end,
|
|
}
|
|
|
|
-- Lookup table that holds functions related to determining the distance to offset each prop in a stack
|
|
-- based on the client's x/y/z offset values
|
|
OffsetFunctions = {
|
|
[DIRECTION_FRONT] = function( angle, offset ) return ( angle:Forward() * offset.x) + ( angle:Up() * offset.z) + ( angle:Right() * offset.y) end,
|
|
[DIRECTION_BACK] = function( angle, offset ) return (-angle:Forward() * offset.x) + ( angle:Up() * offset.z) + (-angle:Right() * offset.y) end,
|
|
[DIRECTION_RIGHT] = function( angle, offset ) return ( angle:Right() * offset.x) + ( angle:Up() * offset.z) + (-angle:Forward() * offset.y) end,
|
|
[DIRECTION_LEFT] = function( angle, offset ) return (-angle:Right() * offset.x) + ( angle:Up() * offset.z) + ( angle:Forward() * offset.y) end,
|
|
[DIRECTION_UP] = function( angle, offset ) return ( angle:Up() * offset.x) + (-angle:Forward() * offset.z) + ( angle:Right() * offset.y) end,
|
|
[DIRECTION_DOWN] = function( angle, offset ) return (-angle:Up() * offset.x) + ( angle:Forward() * offset.z) + ( angle:Right() * offset.y) end,
|
|
}
|
|
|
|
RotationFunctions = {
|
|
[DIRECTION_FRONT] = function( angle ) return angle:Right(), angle:Up(), angle:Forward() end,
|
|
[DIRECTION_BACK] = function( angle ) return -angle:Right(), angle:Up(), -angle:Forward() end,
|
|
[DIRECTION_RIGHT] = function( angle ) return -angle:Forward(), angle:Up(), angle:Right() end,
|
|
[DIRECTION_LEFT] = function( angle ) return angle:Forward(), angle:Up(), -angle:Right() end,
|
|
[DIRECTION_UP] = function( angle ) return -angle:Right(), angle:Forward(), angle:Up() end,
|
|
[DIRECTION_DOWN] = function( angle ) return angle:Right(), angle:Forward(), -angle:Up() end,
|
|
}
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetDirection( number, number, angle )
|
|
--
|
|
-- Calculates the direction to point the entity to by depending on whether the stack is
|
|
-- created relative to the world or the original prop, and the direction to stack in.
|
|
--]]--
|
|
function GetDirection( stackMode, stackDir, angle )
|
|
return DirectionFunctions[ stackMode ][ stackDir ]( angle )
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetDistance( number, number, entity )
|
|
--
|
|
-- Calculates the space occupied by the entity depending on the stack direction.
|
|
-- This represents the number of units to offset the stack entities so they appear
|
|
-- directly in front of the previous entity (depending on direction).
|
|
--]]--
|
|
function GetDistance( stackMode, stackDir, ent )
|
|
if ( stackMode == MODE_WORLD ) then
|
|
return DistanceFunctions[ stackDir ]( ent:WorldSpaceAABB() )
|
|
elseif ( stackMode == MODE_PROP ) then
|
|
return DistanceFunctions[ stackDir ]( ent:OBBMins(), ent:OBBMaxs() )
|
|
end
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- GetOffset( number, number, angle, vector )
|
|
--
|
|
-- Calculates a direction vector used for offsetting a stacked entity based on the facing angle of the previous entity.
|
|
-- This function uses a lookup table for added optimization as opposed to an if-else block.
|
|
--]]--
|
|
function GetOffset( stackMode, stackDir, angle, offset )
|
|
-- if stacking relative to the world, apply the magic offset fix to the correct direction
|
|
if ( stackMode == MODE_WORLD ) then
|
|
local direction = DirectionFunctions[ stackMode ][ stackDir ]()
|
|
direction = direction * MAGIC_OFFSET
|
|
return offset + direction
|
|
-- if stacking relative to the prop, apply the magic offset only to the forward (x) component of the vector
|
|
elseif ( stackMode == MODE_PROP ) then
|
|
local trueOffset = Vector()
|
|
trueOffset:Set( offset )
|
|
trueOffset.x = trueOffset.x + MAGIC_OFFSET
|
|
return OffsetFunctions[ stackDir ]( angle, trueOffset )
|
|
end
|
|
end
|
|
|
|
--[[--------------------------------------------------------------------------
|
|
-- RotateAngle( angle, angle )
|
|
--
|
|
-- Rotates the first angle by the second angle. This ensures proper rotation
|
|
-- along all three axes and prevents various problems related to simply adding
|
|
-- two angles together. The first angle is modified directly by refence, so this does not
|
|
-- return anything.
|
|
--]]--
|
|
function RotateAngle( stackMode, stackDir, angle, rotation )
|
|
local axisPitch, axisYaw, axisRoll = RotationFunctions[ stackDir ]( angle )
|
|
|
|
angle:RotateAroundAxis( axisPitch, rotation.p )
|
|
angle:RotateAroundAxis( axisYaw, -rotation.y )
|
|
angle:RotateAroundAxis( axisRoll, rotation.r )
|
|
end |