dobrograd-13-06-2022/garrysmod/addons/gmod-tools/lua/improvedstacker/improvedstacker.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

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