mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
Upload
This commit is contained in:
20
addons/niknaks/lua/autorun/niknaks_autorun.lua
Normal file
20
addons/niknaks/lua/autorun/niknaks_autorun.lua
Normal file
@@ -0,0 +1,20 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Post Init. This is to stop using traces and other functions, as they can cause a crash if used before.
|
||||
if not _NIKNAKS_POSTENTITY then
|
||||
hook.Add("InitPostEntity","NikNaks_InitPostEntity", function()
|
||||
_NIKNAKS_POSTENTITY = true
|
||||
if _MODULES["niknaks"] then
|
||||
hook.Run("NikNaks._LoadPathOptions")
|
||||
end
|
||||
hook.Remove("InitPostEntity","NikNaks_InitPostEntity")
|
||||
end)
|
||||
end
|
||||
163
addons/niknaks/lua/includes/modules/niknaks.lua
Normal file
163
addons/niknaks/lua/includes/modules/niknaks.lua
Normal file
@@ -0,0 +1,163 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
|
||||
AddCSLuaFile()
|
||||
-- Make sure to use the newest version of NikNaks.
|
||||
local version = 0.36
|
||||
if NikNaks and NikNaks.Version > version then return end
|
||||
|
||||
local file_Find, MsgC, unpack, rawget = file.Find, MsgC, unpack, rawget
|
||||
|
||||
NikNaks = {}
|
||||
NikNaks.net = {}
|
||||
NikNaks.Version = version
|
||||
NikNaks.Authors = "Nak"
|
||||
MsgN("Loading NikNaks: " .. NikNaks.Version)
|
||||
NikNaks.__metatables = {}
|
||||
|
||||
do
|
||||
---A simply Msg function for NikNaks
|
||||
function NikNaks.Msg( ... )
|
||||
local a = {...}
|
||||
if #a < 1 then return end
|
||||
MsgC(NikNaks.REALM_COLOR,"[NN] ", unpack(a), "\n")
|
||||
end
|
||||
end
|
||||
|
||||
---Auto includes, runs and AddCSLuaFile files using their prefix.
|
||||
function NikNaks.AutoInclude( str )
|
||||
local path = str
|
||||
if string.find(str,"/") then
|
||||
path = string.GetFileFromFilename(str)
|
||||
end
|
||||
local _type
|
||||
if path ~= "shared.lua" then
|
||||
_type = string.sub(path,0,3)
|
||||
else
|
||||
_type = "sh_"
|
||||
end
|
||||
if SERVER then
|
||||
if _type == "cl_" or _type == "sh_" then
|
||||
AddCSLuaFile(str)
|
||||
end
|
||||
if _type ~= "cl_" then
|
||||
return include(str)
|
||||
end
|
||||
elseif _type ~= "sv_" then
|
||||
return pcall(include, str)
|
||||
end
|
||||
end
|
||||
|
||||
---Autp includes, runs and AddCSLuaFile a folder by the files prefix.
|
||||
function NikNaks.AutoIncludeFolder( str )
|
||||
for _,fil in ipairs(file_Find(str .. "/*.lua","LUA")) do
|
||||
NikNaks.AutoInclude(str .. "/" .. fil)
|
||||
end
|
||||
end
|
||||
|
||||
-- A simple scope-script
|
||||
do
|
||||
local g = _G
|
||||
local envs = {}
|
||||
local env = {}
|
||||
local getfenv, setfenv, source = getfenv, setfenv, jit.util.funcinfo( NikNaks.AutoInclude )["source"]
|
||||
local NikNaks = NikNaks
|
||||
local function createEnv( tab, source )
|
||||
local t = {}
|
||||
setmetatable(t, { __index = function(k, v)
|
||||
return rawget(NikNaks, v) or tab[v]
|
||||
end,
|
||||
__newindex = function( t, k, v)
|
||||
rawset( _G, k, v )
|
||||
end})
|
||||
envs[ tab ] = t
|
||||
return t
|
||||
end
|
||||
|
||||
-- Patches any tables with names that share _G
|
||||
--NikNaks._source = source:lower():match("addons/(.-)/")
|
||||
NikNaks._source = "niknak"
|
||||
local function using()
|
||||
local _env = getfenv( 2 )
|
||||
if _env ~= _GEnv then -- Make sure it isn't our env
|
||||
-- Create new env and apply it
|
||||
setfenv(2, envs[_env] or createEnv( _env, NikNaks._source ))
|
||||
else
|
||||
-- Ignore for now.
|
||||
-- error("Can't apply enviroment to self")
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(NikNaks,{
|
||||
__call = function( _, ...) return using( ... ) end
|
||||
})
|
||||
end
|
||||
|
||||
--[[
|
||||
For safty reasons, we're won't use AutoInclude or AutoIncludeFolder. These should be hardcoded.
|
||||
]]
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = {}
|
||||
meta.__index = meta
|
||||
meta.__tostring = function( self ) return string.format( "BSP Map [ %s ]", self._mapfile ) end
|
||||
meta.MetaName = "BSP"
|
||||
NikNaks.__metatables["BSP"] = meta
|
||||
NikNaks._Source = "niknak"
|
||||
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_enums.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_util_extended.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_file_extended.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_timedelta.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_datetime.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_color_extended.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_model_extended.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bitbuffer.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_module.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_entities.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_faces.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_leafs.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_brushes.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_pvspas.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_bsp_staticprops.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_pathfind_module.lua")
|
||||
NikNaks.AutoInclude("niknaks/modules/sh_ain_module.lua")
|
||||
|
||||
NikNaks.AutoInclude("niknaks/framework/sh_localbsp.lua")
|
||||
NikNaks.AutoInclude("niknaks/framework/sh_epath.lua")
|
||||
|
||||
-- Patch table to ref _G
|
||||
do
|
||||
local g = _G
|
||||
for key, val in pairs( NikNaks ) do
|
||||
if not istable( val ) then continue end
|
||||
if not _G[key] then continue end
|
||||
--if not NikNaks._source:find("niknak") then continue end
|
||||
setmetatable(val, { __index = function(k, v)
|
||||
return rawget(k, v) or g[key][v]
|
||||
end})
|
||||
end
|
||||
end
|
||||
|
||||
-- Post Init. This is a safety option, as using traces and other functions before InitPostEntity can cause crash.
|
||||
if _NIKNAKS_POSTENTITY then
|
||||
NikNaks.PostInit = true
|
||||
timer.Simple(1, NikNaks._LoadPathOptions )
|
||||
else
|
||||
hook.Add("NikNaks._LoadPathOptions", "wait", function()
|
||||
NikNaks.PostInit = true
|
||||
NikNaks._LoadPathOptions()
|
||||
hook.Remove("NikNaks._LoadPathOptions", "wait")
|
||||
end)
|
||||
end
|
||||
-- return NikNaks -- Doesn't work for require :C
|
||||
188
addons/niknaks/lua/niknaks/framework/sh_epath.lua
Normal file
188
addons/niknaks/lua/niknaks/framework/sh_epath.lua
Normal file
@@ -0,0 +1,188 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
local NikNaks = NikNaks
|
||||
|
||||
local autoload_nodegraph = CreateConVar( "sv_nodegraph_autoload", 0, FCVAR_REPLICATED, "Auto loads NodeGraph." )
|
||||
local autoload_nodegraph_cl = CreateConVar( "cl_nodegraph_autoload", 0, FCVAR_REPLICATED, "Auto loads NodeGraph on clients." )
|
||||
|
||||
local show_nodegraph = CLIENT and CreateClientConVar( "cl_nodegraph_show", 0, FCVAR_CHEAT, "Renderes NodeGraph." )
|
||||
|
||||
local file_nodegraph = "data/graphs/" .. game.GetMap() .. ".dat"
|
||||
local file_ain = "maps/graphs/" .. game.GetMap() .. ".ain"
|
||||
|
||||
local _safe = NikNaks.PostInit or false
|
||||
|
||||
-- Loads the Nodegraph and returns it
|
||||
local function init_NodeGraph()
|
||||
local _NodeGraph
|
||||
|
||||
-- Make sure the data has priority, in case they get edited.
|
||||
if file.Exists( file_nodegraph, "GAME" ) then
|
||||
_NodeGraph = NikNaks.NodeGraph.LoadAin( file_nodegraph ) or false
|
||||
if _NodeGraph then
|
||||
hook.Run( "NodeGraph.Loaded" )
|
||||
return _NodeGraph
|
||||
end
|
||||
end
|
||||
|
||||
-- If something fails to load the data, try the AIN ( If there )
|
||||
if file.Exists( file_ain, "GAME" ) then
|
||||
_NodeGraph = NikNaks.NodeGraph.LoadAin( file_ain ) or false
|
||||
if _NodeGraph then
|
||||
hook.Run( "NodeGraph.Loaded" )
|
||||
return _NodeGraph
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Navigation options w autogen
|
||||
local navigation = {
|
||||
__index = function( k, v )
|
||||
if not _safe then return end -- Not safe to generate orl oad yet
|
||||
if v == "node" then
|
||||
local result = init_NodeGraph()
|
||||
rawset( k, v, result or false )
|
||||
return result
|
||||
end
|
||||
end }
|
||||
setmetatable( navigation, navigation )
|
||||
|
||||
-- Gets called once the map has been loaded.
|
||||
function NikNaks._LoadPathOptions()
|
||||
_safe = true -- Allow gen/load.
|
||||
if SERVER and autoload_nodegraph:GetBool() or CLIENT and autoload_nodegraph_cl:GetBool() then
|
||||
-- Try and load / generate niknav
|
||||
local _ = navigation.node
|
||||
end
|
||||
end
|
||||
|
||||
-- NodeGrapth Alias and logic
|
||||
do
|
||||
--- Returns the AIN version. Should be 37.
|
||||
--- @return number
|
||||
function NikNaks.NodeGraph.GetVersion()
|
||||
return navigation.node and navigation.node:GetVersion()
|
||||
end
|
||||
|
||||
--- Returns the AIN map-version.
|
||||
--- @return number
|
||||
function NikNaks.NodeGraph.GetMapVersion()
|
||||
return navigation.node and navigation.node:GetMapVersion()
|
||||
end
|
||||
|
||||
--- Returns the given ain_node at said ID.
|
||||
--- @param id number
|
||||
--- @return ain_node
|
||||
function NikNaks.NodeGraph.GetNode( id )
|
||||
return navigation.node and navigation.node:GetNode( id )
|
||||
end
|
||||
|
||||
--- Returns the nearest node
|
||||
--- @param position Vector
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param Zone? number
|
||||
--- @return ain_node
|
||||
function NikNaks.NodeGraph.FindNode( position, NODE_TYPE, Zone, HULL )
|
||||
return navigation.node and navigation.node:FindNode( position, NODE_TYPE, Zone, HULL )
|
||||
end
|
||||
|
||||
--- Returns the nearest node with a connection matching the hull.
|
||||
--- @param position Vector
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param HULL number
|
||||
--- @param Zone? number
|
||||
--- @return ain_node
|
||||
function NikNaks.NodeGraph.FindNodeWithHull( position, NODE_TYPE, Zone, HULL )
|
||||
return navigation.node and navigation.node:FindNodeWithHull( position, NODE_TYPE, Zone, HULL )
|
||||
end
|
||||
|
||||
--- Returns the nearest node with said HintType.
|
||||
--- @param position Vector
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param HintType number
|
||||
--- @param HintGroup? number
|
||||
--- @param Zone? number
|
||||
--- @return ain_node
|
||||
function NikNaks.NodeGraph.FindHintNode( position, NODE_TYPE, HintType, HintGroup, Zone, HULL )
|
||||
return navigation.node and navigation.node:FindHintNode( position, NODE_TYPE, HintType, HintGroup, Zone, HULL )
|
||||
end
|
||||
|
||||
--- A* pathfinding using the NodeGraph.
|
||||
--- @param start_pos Vector|Entity
|
||||
--- @param end_pos Vector|Entity
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param HULL_SIZE? number
|
||||
--- @param options? table
|
||||
--- @param generator? function -- A funtion that allows you to calculate your own cost: func( node, fromNode, CAP_MOVE, elevator, length )
|
||||
--- @return LPathFollower|boolean
|
||||
function NikNaks.NodeGraph.PathFind( start_pos, end_pos, NODE_TYPE, options, HULL_SIZE, generator )
|
||||
return navigation.node and navigation.node:PathFind( start_pos, end_pos, NODE_TYPE, options, HULL_SIZE, generator )
|
||||
end
|
||||
|
||||
--- A cheap lookup function. Checks to see if we can reach the position using nearby nodes.
|
||||
--- Note that this use zones and might have false positives on maps with a broken NodeGraph.
|
||||
--- @param start_pos Vector
|
||||
--- @param end_pos Vector
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param HULL_SIZE? number
|
||||
--- @param max_dis? number -- Distance to nearest node
|
||||
--- @return boolean
|
||||
function NikNaks.NodeGraph.CanMaybeReach( start_pos, end_pos, NODE_TYPE, HULL_SIZE, max_dis )
|
||||
return navigation.node and navigation.node:CanMaybeReach( start_pos, end_pos, NODE_TYPE, HULL_SIZE, max_dis )
|
||||
end
|
||||
|
||||
--- A* pathfinding using the NodeGraph. Returns the result in the callback. Calculates 20 paths pr tick.
|
||||
--- @param start_pos Vector|Entity
|
||||
--- @param end_pos Vector|Entity
|
||||
--- @param callback function -- Returns the result. LPathFollower or false
|
||||
--- @param NODE_TYPE? number
|
||||
--- @param options? table
|
||||
--- @param HULL_SIZE? number
|
||||
--- @param generator? function -- A funtion that allows you to calculate your own cost: func( node, fromNode, CAP_MOVE, elevator, length )
|
||||
function NikNaks.NodeGraph.PathFindASync( start_pos, end_pos, callback, NODE_TYPE, options, HULL_SIZE, generator )
|
||||
return navigation.node and navigation.node:PathFindASync( start_pos, end_pos, callback, NODE_TYPE, options, HULL_SIZE, generator )
|
||||
end
|
||||
|
||||
--- Returns true if the nodegraph for the current map has loaded.
|
||||
--- @return boolean
|
||||
function NikNaks.NodeGraph.HasLoaded()
|
||||
return navigation.node and true or false
|
||||
end
|
||||
|
||||
--- Tries to reload the NodeGraph.
|
||||
function NikNaks.NodeGraph.Reload()
|
||||
navigation.node = nil
|
||||
local _ = navigation.node
|
||||
end
|
||||
|
||||
function NikNaks.NodeGraph.Unload()
|
||||
navigation.node = nil
|
||||
end
|
||||
|
||||
-- Calling self returns the object
|
||||
setmetatable( NikNaks.NodeGraph, NikNaks.NodeGraph )
|
||||
function NikNaks.NodeGraph.__call()
|
||||
return navigation.node
|
||||
end
|
||||
end
|
||||
|
||||
if SERVER then return end
|
||||
|
||||
-- Debug functions
|
||||
local sv_cheats = GetConVar( "sv_cheats" )
|
||||
hook.Add( "PostDrawOpaqueRenderables", "NikNav.Navigation.Debug", function()
|
||||
if not sv_cheats:GetBool() then return end
|
||||
if show_nodegraph:GetBool() and navigation.node then
|
||||
navigation.node:DebugRender()
|
||||
end
|
||||
end )
|
||||
98
addons/niknaks/lua/niknaks/framework/sh_localbsp.lua
Normal file
98
addons/niknaks/lua/niknaks/framework/sh_localbsp.lua
Normal file
@@ -0,0 +1,98 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
|
||||
-- Load the current map
|
||||
--- @type BSPObject
|
||||
local BSP, BSP_ERR = NikNaks.Map()
|
||||
local NikNaks = NikNaks
|
||||
|
||||
--- @type BSPObject
|
||||
NikNaks.CurrentMap = BSP
|
||||
|
||||
if not BSP and BSP_ERR then
|
||||
if BSP_ERR == NikNaks.BSP_ERROR_FILECANTOPEN then
|
||||
ErrorNoHalt("NikNaks are unable to open the mapfile!")
|
||||
elseif BSP_ERR == NikNaks.BSP_ERROR_NOT_BSP then
|
||||
ErrorNoHalt("NikNaks can't read the mapfile (It isn't VBSP)!")
|
||||
elseif BSP_ERR == NikNaks.BSP_ERROR_TOO_NEW then
|
||||
ErrorNoHalt("NikNaks can't read the mapfile (Newer than v20)!")
|
||||
elseif BSP_ERR == NikNaks.BSP_ERROR_FILENOTFOUND then
|
||||
ErrorNoHalt("NikNaks can't read the mapfile (File not found)!")
|
||||
else
|
||||
ErrorNoHalt("NikNaks can't read the mapfile (Unknown)!")
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add( "PreDrawTranslucentRenderables", "NikNaks-FixEyePos", function()
|
||||
EyePos()
|
||||
end )
|
||||
|
||||
-- Local PVS
|
||||
NikNaks.PVS = {}
|
||||
do
|
||||
local last_client, last_pvs
|
||||
local function calcPVS()
|
||||
local leaf, new = BSP:PointInLeafCache( 0, EyePos(), last_client )
|
||||
if not new then return last_pvs end
|
||||
last_client = leaf
|
||||
last_pvs = leaf:CreatePVS()
|
||||
return last_pvs
|
||||
end
|
||||
setmetatable(NikNaks.PVS, {
|
||||
__call = function(_, pos)
|
||||
if pos then
|
||||
return BSP:PVSForOrigin( pos )
|
||||
end
|
||||
if CLIENT then
|
||||
return calcPVS()
|
||||
end
|
||||
end})
|
||||
|
||||
---Will return true if the position is within the current PVS.
|
||||
---@param position Vector
|
||||
---@param position2 Vector If nil, will be the clients EyePosition
|
||||
---@return boolean
|
||||
function NikNaks.PVS.IsPositionVisible( position, position2 )
|
||||
return calcPVS(position2):TestPosition( position )
|
||||
end
|
||||
end
|
||||
|
||||
-- Local PAS
|
||||
NikNaks.PAS = {}
|
||||
do
|
||||
local last_client, last_pas
|
||||
local function calcPAS()
|
||||
local leaf, new = BSP:PointInLeafCache( 0, EyePos(), last_client )
|
||||
if not new then return last_pas end
|
||||
last_client = leaf
|
||||
last_pas = leaf:CreatePVS()
|
||||
return last_pas
|
||||
end
|
||||
setmetatable(NikNaks.PAS, {
|
||||
__call = function(_, pos)
|
||||
if pos then
|
||||
return BSP:PASForOrigin( pos )
|
||||
end
|
||||
if CLIENT then
|
||||
return calcPAS()
|
||||
end
|
||||
end})
|
||||
|
||||
---Will return true if the position is within the current PAS.
|
||||
---@param position Vector
|
||||
---@param position2 Vector If nil, will be the clients EyePosition
|
||||
---@return boolean
|
||||
function NikNaks.PAS.IsPositionVisible( position, position2 )
|
||||
return calcPAS(position2):TestPosition( position )
|
||||
end
|
||||
end
|
||||
1254
addons/niknaks/lua/niknaks/modules/sh_ain_module.lua
Normal file
1254
addons/niknaks/lua/niknaks/modules/sh_ain_module.lua
Normal file
File diff suppressed because it is too large
Load Diff
1264
addons/niknaks/lua/niknaks/modules/sh_bitbuffer.lua
Normal file
1264
addons/niknaks/lua/niknaks/modules/sh_bitbuffer.lua
Normal file
File diff suppressed because it is too large
Load Diff
157
addons/niknaks/lua/niknaks/modules/sh_bsp_brushes.lua
Normal file
157
addons/niknaks/lua/niknaks/modules/sh_bsp_brushes.lua
Normal file
@@ -0,0 +1,157 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
local obj_tostring = "BSP %s [ %s ]"
|
||||
local format = string.format
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
|
||||
--- @class BrushObject
|
||||
local meta_brush = {}
|
||||
meta_brush.__index = meta_brush
|
||||
meta_brush.__tostring = function( self ) return format( obj_tostring, "BSP Brush", self.__id ) end
|
||||
meta_brush.MetaName = "BSP Brush"
|
||||
NikNaks.__metatables["BSP Brush"] = meta_brush
|
||||
|
||||
local DIST_EPSILON = 0.03125
|
||||
local MAX_MAP_BRUSHES = 8192
|
||||
local MAX_MAP_BRUSHSIDES = 65536
|
||||
|
||||
--- Returns an array of the brush-data with brush-sides.
|
||||
--- @return BrushObject[]
|
||||
function meta:GetBrushes()
|
||||
if self._brushes then return self._brushes end
|
||||
|
||||
self._brushes = {}
|
||||
|
||||
local data = self:GetLump( 18 )
|
||||
for id = 1, math.min( data:Size() / 96, MAX_MAP_BRUSHES ) do
|
||||
--- @class BrushObject
|
||||
local t = {}
|
||||
local first = data:ReadLong()
|
||||
local num = data:ReadLong()
|
||||
t.contents = data:ReadLong()
|
||||
t.numsides = num
|
||||
--- @type BrushSideObject[]
|
||||
t.sides = {}
|
||||
t.__id = id
|
||||
t.__map = self
|
||||
|
||||
local n = 1
|
||||
for i = first, first + num - 1 do
|
||||
t.sides[n] = self:GetBrushSides()[i]
|
||||
n = n + 1
|
||||
end
|
||||
|
||||
self._brushes[id] = setmetatable( t, meta_brush )
|
||||
end
|
||||
|
||||
self:ClearLump( 18 )
|
||||
return self._brushes
|
||||
end
|
||||
|
||||
--- Returns an array of brushside-data.
|
||||
--- @return BrushSideObject[]
|
||||
function meta:GetBrushSides()
|
||||
if self._brushside then return self._brushside end
|
||||
|
||||
self._brushside = {}
|
||||
|
||||
local data = self:GetLump( 19 )
|
||||
local planes = self:GetPlanes()
|
||||
for i = 1, math.min( data:Size() / 64, MAX_MAP_BRUSHSIDES ) do
|
||||
--- @class BrushSideObject
|
||||
local t = {}
|
||||
t.plane = planes[ data:ReadUShort() ]
|
||||
t.texinfo = data:ReadShort()
|
||||
t.dispinfo = data:ReadShort()
|
||||
local q = data:ReadShort()
|
||||
t.bevel = bit.band( q, 0x1 ) == 1 -- Seems to be 1 if used for collision detection
|
||||
t.thin = bit.rshift( q, 8 ) == 1 -- For Portal 2 / Alien Swarm
|
||||
self._brushside[i - 1] = t
|
||||
end
|
||||
|
||||
self:ClearLump( 19 )
|
||||
return self._brushside
|
||||
end
|
||||
|
||||
|
||||
function meta_brush:GetIndex()
|
||||
return self.__id or -1
|
||||
end
|
||||
|
||||
--- Returns the content flag the brush has.
|
||||
--- @return number
|
||||
function meta_brush:GetContents()
|
||||
return self.contents
|
||||
end
|
||||
|
||||
--- Returns true if the brush has said content
|
||||
--- @param CONTENTS number
|
||||
--- @return boolean
|
||||
function meta_brush:HasContents( CONTENTS )
|
||||
if CONTENTS == 0 then return self.contents == CONTENTS end
|
||||
return bit.band( self.contents, CONTENTS ) ~= 0
|
||||
end
|
||||
|
||||
-- Texture Stuff
|
||||
|
||||
--- Returns the TexInfo for the brush-side.
|
||||
--- @param side number
|
||||
--- @return table
|
||||
function meta_brush:GetTexInfo( side )
|
||||
return self.__map:GetTexInfo()[self.sides[side].texinfo]
|
||||
end
|
||||
|
||||
--- Returns the TexData for the brush-side.
|
||||
--- @param side number
|
||||
--- @return table
|
||||
function meta_brush:GetTexData( side )
|
||||
return self.__map:GetTexData()[ self:GetTexInfo( side ).texdata]
|
||||
end
|
||||
|
||||
--- Returns the texture for the brush-side.
|
||||
--- @param side number
|
||||
--- @return string
|
||||
function meta_brush:GetTexture( side )
|
||||
local t = self:GetTexData( side ) or {}
|
||||
return t.nameStringTableID
|
||||
end
|
||||
|
||||
--- Returns the Material for the brush-side.
|
||||
--- @param side number
|
||||
--- @return IMaterial
|
||||
function meta_brush:GetMaterial( side )
|
||||
if self._material and self._material[side] then return self._material[side] end
|
||||
if not self._material then self._material = {} end
|
||||
|
||||
self._material[side] = Material( self:GetTexture( side ) or "__error" )
|
||||
return self._material[side]
|
||||
end
|
||||
|
||||
--- Returns true if the point is inside the brush
|
||||
--- @param position Vector
|
||||
--- @return boolean
|
||||
function meta_brush:IsPointInside( position )
|
||||
for i = 1, self.numsides do
|
||||
local side = self.sides[i]
|
||||
local plane = side.plane
|
||||
if plane.normal:Dot( position ) - plane.dist > DIST_EPSILON then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
250
addons/niknaks/lua/niknaks/modules/sh_bsp_entities.lua
Normal file
250
addons/niknaks/lua/niknaks/modules/sh_bsp_entities.lua
Normal file
@@ -0,0 +1,250 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
-- Entities are stored in a KeyValues table.
|
||||
-- However we can't use the KeyValuesToTablePreserveOrder function, since some BSPs have errors within Entity Lump.
|
||||
|
||||
--- @class EntityObject
|
||||
--- @field origin? Vector
|
||||
--- @field angles? Angle
|
||||
--- @field rendercolor? Color
|
||||
--- @field ontrigger? table
|
||||
--- @field classname? string
|
||||
--- @field model? string
|
||||
--- @field targetname? string
|
||||
--- @field world_mins? string
|
||||
--- @field world_maxs? string
|
||||
--- @field scale? number
|
||||
--- @field coldworld? number
|
||||
|
||||
--- Locates the next enter-token
|
||||
--- @param data string
|
||||
--- @param pos number
|
||||
--- @return number
|
||||
local function findNextToken( data, pos )
|
||||
for i = pos, #data do
|
||||
if data[i] == "{" then return i end
|
||||
end
|
||||
|
||||
return -1
|
||||
end
|
||||
|
||||
--- Locates the next exit-token
|
||||
local function findNextExitToken( data, pos )
|
||||
local keypos = 0
|
||||
local ignore = false
|
||||
|
||||
for i = pos, #data do
|
||||
if data[i] == "\"" then ignore = not ignore
|
||||
elseif ignore then continue end
|
||||
|
||||
if data[i] == "{" then keypos = keypos + 1
|
||||
elseif data[i] == "}" then
|
||||
keypos = keypos - 1
|
||||
if keypos == 0 then return i end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert a few things to make it easier to read entities.
|
||||
--- @param t EntityObject
|
||||
local function postEntParse( t )
|
||||
t.origin = util.StringToType( t.origin or "0 0 0", "Vector" )
|
||||
t.angles = util.StringToType( t.angles or "0 0 0", "Angle" )
|
||||
|
||||
if t.rendercolor then
|
||||
local c = util.StringToType( t.rendercolor or "255 255 255", "Vector" )
|
||||
t.rendercolor = Color( c.x, c.y, c.z, 255 )
|
||||
end
|
||||
|
||||
-- Make sure ontrigger is a table.
|
||||
if t.ontrigger and type( t.ontrigger ) ~= "table" then
|
||||
t.ontrigger = { t.ontrigger }
|
||||
end
|
||||
end
|
||||
|
||||
-- A list of data-keys that can have multiple entries.
|
||||
local _tableTypes = {
|
||||
["OnMapSpawn"] = true,
|
||||
["OnTrigger"] = true,
|
||||
["OnStartTouch"] = true,
|
||||
["OnArrivedAtDestinationNode"] = true,
|
||||
["OnPowered"] = true,
|
||||
["OnUnpowered"] = true,
|
||||
["OnExplode"] = true,
|
||||
["OnAllTrue"] = true,
|
||||
}
|
||||
|
||||
--- @return EntityObject
|
||||
local function ParseEntity( str )
|
||||
--- @class EntityObject
|
||||
local t = {}
|
||||
|
||||
for key, value in string.gmatch( str, [["(.-)".-"(.-)"]] ) do
|
||||
value = tonumber( value ) or value
|
||||
if t[key] then
|
||||
if type( t[key] ) ~= "table" then
|
||||
t[key] = { t[key] }
|
||||
else
|
||||
table.insert( t[key], value )
|
||||
end
|
||||
elseif _tableTypes[key] then
|
||||
t[key] = { value }
|
||||
else
|
||||
t[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
postEntParse( t )
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Tries to parse the entity-data.
|
||||
--- @param data string
|
||||
--- @return EntityObject[]
|
||||
local function parseEntityData( data )
|
||||
-- Cut the data into bits
|
||||
local charPos = 1
|
||||
local tabData = {}
|
||||
for _ = 1, #data do -- while true do
|
||||
local nextToken = findNextToken( data, charPos )
|
||||
if nextToken < 0 then
|
||||
break -- No token found. EOF.
|
||||
else
|
||||
local exitToken = findNextExitToken( data, nextToken )
|
||||
if exitToken then
|
||||
tabData[#tabData + 1] = data:sub( nextToken, exitToken )
|
||||
charPos = exitToken
|
||||
else -- ERROR No exit token? Try and parse the rest.
|
||||
tabData[#tabData + 1] = data:sub( nextToken ) .. "}"
|
||||
NikNaks.Msg( [[[BSP] ParseEntity: No closing brace found!]] )
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local tab = {}
|
||||
for id, str in pairs( tabData ) do
|
||||
local t = ParseEntity( str )
|
||||
tab[id - 1] = t
|
||||
end
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
|
||||
--- Returns a list of all raw-entity data within the BSP.
|
||||
--- @return EntityObject[]
|
||||
function meta:GetEntities()
|
||||
if self._entities then return self._entities end
|
||||
|
||||
-- Since it is stringbased, it is best to keep it as a string.
|
||||
local data = self:GetLumpString( 0 )
|
||||
|
||||
-- Parse all entities
|
||||
self._entities = parseEntityData( data )
|
||||
|
||||
return self._entities
|
||||
end
|
||||
|
||||
--- Returns the raw entity data said entity.
|
||||
--- @param index number
|
||||
--- @return EntityObject
|
||||
function meta:GetEntity( index )
|
||||
return self:GetEntities()[index]
|
||||
end
|
||||
|
||||
--- Returns a list of entity data, matching the class.
|
||||
--- @param class string
|
||||
--- @return EntityObject[]
|
||||
function meta:FindByClass( class )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetEntities() ) do
|
||||
local vClass = v.classname
|
||||
if class and string.match( vClass, class ) then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of entity data, matching the model.
|
||||
--- @param model string
|
||||
--- @return EntityObject[]
|
||||
function meta:FindByModel( model )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetEntities() ) do
|
||||
if v.model == model then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of entity data, matching the name ( targetname ).
|
||||
--- @param name string
|
||||
--- @return table
|
||||
function meta:FindByName( name )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetEntities() ) do
|
||||
if v.targetname == name then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of entity data, within the specified box. Note: This (I think) is slower than ents.FindInBox
|
||||
--- @param boxMins Vector
|
||||
--- @param boxMaxs Vector
|
||||
--- @return table
|
||||
function meta:FindInBox( boxMins, boxMaxs )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetEntities() ) do
|
||||
local origin = v.origin
|
||||
if origin and v.origin:WithinAABox( boxMins, boxMaxs ) then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of entity data, within the specified sphere. Note: This (I think) is slower than ents.FindInSphere
|
||||
--- @param origin Vector
|
||||
--- @param radius number
|
||||
function meta:FindInSphere( origin, radius )
|
||||
radius = radius ^ 2
|
||||
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetEntities() ) do
|
||||
local vOrigin = v.origin
|
||||
if vOrigin and vOrigin:DistToSqr( origin ) <= radius then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
444
addons/niknaks/lua/niknaks/modules/sh_bsp_faces.lua
Normal file
444
addons/niknaks/lua/niknaks/modules/sh_bsp_faces.lua
Normal file
@@ -0,0 +1,444 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
local obj_tostring = "BSP %s [ %s ]"
|
||||
local format = string.format
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
|
||||
--- @class FaceObject
|
||||
local meta_face = {}
|
||||
meta_face.__index = meta_face
|
||||
meta_face.__tostring = function( self ) return format( obj_tostring, "Faces", self.__id ) end
|
||||
meta_face.MetaName = "BSP Faces"
|
||||
NikNaks.__metatables["BSP Faces"] = meta_face
|
||||
|
||||
local MAX_MAP_FACES = 65536
|
||||
|
||||
--- Returns all faces. ( Warning, uses a lot of memory )
|
||||
--- @return FaceObject[]
|
||||
function meta:GetFaces()
|
||||
if self._faces then return self._faces end
|
||||
self._faces = {}
|
||||
|
||||
local data = self:GetLump( 7 )
|
||||
for i = 0, math.min( data:Size() / 448, MAX_MAP_FACES ) - 1 do
|
||||
--- @class FaceObject
|
||||
local t = {}
|
||||
t.planenum = data:ReadUShort()
|
||||
t.plane = self:GetPlanes()[ t.planenum ]
|
||||
t.side = data:ReadByte() -- 1 = same direciton as face
|
||||
t.onNode = data:ReadByte() -- 1 if on node, 0 if in leaf
|
||||
t.firstedge = data:ReadLong()
|
||||
t.numedges = data:ReadShort()
|
||||
t.texinfo = data:ReadShort() -- Texture info
|
||||
t.dispinfo = data:ReadShort() -- Displacement info
|
||||
t.surfaceFogVolumeID = data:ReadShort()
|
||||
t.styles = { data:ReadByte(), data:ReadByte(), data:ReadByte(), data:ReadByte() }
|
||||
t.lightofs = data:ReadLong()
|
||||
t.area = data:ReadFloat()
|
||||
t.LightmapTextureMinsInLuxels = { data:ReadLong(), data:ReadLong() }
|
||||
t.LightmapTextureSizeInLuxels = { data:ReadLong(), data:ReadLong() }
|
||||
t.origFace = data:ReadLong()
|
||||
t.numPrims = data:ReadUShort()
|
||||
t.firstPrimID = data:ReadUShort()
|
||||
t.smoothingGroups = data:ReadULong()
|
||||
t.__bmodel = self:FindBModelIDByFaceIndex( i )
|
||||
t.__map = self
|
||||
t.__id = i
|
||||
setmetatable( t, meta_face )
|
||||
self._faces[i] = t
|
||||
end
|
||||
|
||||
self:ClearLump( 7 )
|
||||
return self._faces
|
||||
end
|
||||
|
||||
-- Returns the original face
|
||||
function meta:GetOriginalFace()
|
||||
return self.__map:GetOriginalFaces()[self.origFace]
|
||||
end
|
||||
|
||||
-- We make a small hack to cache and get the entities using brush-models.
|
||||
local function __findEntityUsingBrush( self )
|
||||
if self.__funcBrush then return self.__funcBrush end
|
||||
|
||||
local entities = self:GetEntities()
|
||||
self.__funcBrush = { [0] = entities[0] }
|
||||
|
||||
for _, v in pairs( entities ) do
|
||||
local numMdl = string.match( v.model or "", "*([%d]+)" )
|
||||
|
||||
if numMdl then
|
||||
self.__funcBrush[tonumber( numMdl )] = v
|
||||
end
|
||||
end
|
||||
|
||||
return self.__funcBrush
|
||||
end
|
||||
|
||||
--- Parses a Color object from the given BitBuffer
|
||||
--- @param data BitBuffer
|
||||
--- @return Color
|
||||
local function __readColorRGBExp32 ( data )
|
||||
return NikNaks.ColorRGBExp32ToColor( {
|
||||
r = data:ReadByte(),
|
||||
g = data:ReadByte(),
|
||||
b = data:ReadByte(),
|
||||
exponent = data:ReadSignedByte()
|
||||
} )
|
||||
end
|
||||
|
||||
--- Returns the lightmap samples for the face.
|
||||
--- @return table<string, LightmapSample[]>?
|
||||
function meta_face:GetLightmapSamples()
|
||||
local lightofs = self.lightofs
|
||||
|
||||
if lightofs == -1 then return end
|
||||
if self._lightmap_samples then return self._lightmap_samples end
|
||||
|
||||
--- @class LightmapSample
|
||||
--- @field color Color
|
||||
--- @field exponent number
|
||||
|
||||
--- @type LightmapSample[]
|
||||
local full = {}
|
||||
|
||||
--- @type LightmapSample[]
|
||||
local average = {}
|
||||
|
||||
local samples = { average = average, full = full }
|
||||
self._lightmap_samples = samples
|
||||
|
||||
local has_bumpmap = self:GetMaterial():GetString( "$bumpmap" ) ~= nil
|
||||
local luxel_count = ( self.LightmapTextureSizeInLuxels[1] + 1 ) * ( self.LightmapTextureSizeInLuxels[2] + 1 )
|
||||
|
||||
local lightstyle_count = 0
|
||||
for _, v in ipairs( self.styles ) do
|
||||
if v ~= 255 then lightstyle_count = lightstyle_count + 1 end
|
||||
end
|
||||
|
||||
-- "For faces with bumpmapped textures, there are four times the usual number of lightmap samples"
|
||||
local sample_count = lightstyle_count * luxel_count
|
||||
if has_bumpmap then sample_count = sample_count * 4 end
|
||||
|
||||
local data = self.__map:GetLump( 8 )
|
||||
|
||||
-- Get the average samples
|
||||
-- "Immediately preceeding the lightofs-referenced sample group,
|
||||
-- there are single samples containing the average lighting on the face, one for each lightstyle,
|
||||
-- in reverse order from that given in the styles[] array."
|
||||
local color, exponent
|
||||
data:Seek( ( lightofs * 8 ) - ( 32 * lightstyle_count ) )
|
||||
for _ = 1, lightstyle_count do
|
||||
color, exponent = __readColorRGBExp32( data )
|
||||
table.insert( average, 1, { color = color, exponent = exponent } )
|
||||
end
|
||||
|
||||
-- Get the full samples
|
||||
for _ = 1, sample_count do
|
||||
color, exponent = __readColorRGBExp32( data )
|
||||
table.insert( full, { color = color, exponent = exponent } )
|
||||
end
|
||||
|
||||
return samples
|
||||
end
|
||||
|
||||
|
||||
--- Returns the face-index.
|
||||
--- @return number
|
||||
function meta_face:GetIndex()
|
||||
return self.__id or -1
|
||||
end
|
||||
|
||||
--- Returns the normal for the face
|
||||
--- @return Vector
|
||||
function meta_face:GetNormal()
|
||||
return self.plane.normal
|
||||
end
|
||||
|
||||
--- Returns the texture info for the face.
|
||||
--- @return table
|
||||
function meta_face:GetTexInfo()
|
||||
return self.__map:GetTexInfo()[self.texinfo]
|
||||
end
|
||||
|
||||
--- Returns the texture data for the face.
|
||||
--- @return table
|
||||
function meta_face:GetTexData()
|
||||
return self.__map:GetTexData()[ self:GetTexInfo().texdata ]
|
||||
end
|
||||
|
||||
--- Returns the texture for the face.
|
||||
function meta_face:GetTexture()
|
||||
return self:GetTexData().nameStringTableID
|
||||
end
|
||||
|
||||
--- Returns the material the face use. Note: Materials within the BSP is not loaded.
|
||||
--- @return IMaterial
|
||||
function meta_face:GetMaterial()
|
||||
if self._mat then return self._mat end
|
||||
self._mat = Material( self:GetTexture() or "__error" )
|
||||
return self._mat
|
||||
end
|
||||
|
||||
--- Returns true if the face should render.
|
||||
--- @return boolean
|
||||
function meta_face:ShouldRender()
|
||||
local texinfo = self:GetTexInfo()
|
||||
local flags = texinfo and texinfo.flags or 0
|
||||
return bit.band( flags, 0x80 ) == 0 and bit.band( flags, 0x200 ) == 0
|
||||
end
|
||||
|
||||
--- Returns true if the face-texture is translucent
|
||||
--- @return boolean
|
||||
function meta_face:IsTranslucent()
|
||||
local texinfo = self:GetTexInfo() or 0
|
||||
return bit.band( texinfo.flags, 0x10 ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the face is part of 2D skybox.
|
||||
--- @return boolean
|
||||
function meta_face:IsSkyBox()
|
||||
local texinfo = self:GetTexInfo() or 0
|
||||
return bit.band( texinfo.flags, 0x2 ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the face is part of 3D skybox.
|
||||
--- @return boolean
|
||||
function meta_face:IsSkyBox3D()
|
||||
local texinfo = self:GetTexInfo() or 0
|
||||
return bit.band( texinfo.flags, 0x4 ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the face's texinfo has said flag.
|
||||
--- @return boolean
|
||||
function meta_face:HasTexInfoFlag( flag )
|
||||
local texinfo = self:GetTexInfo() or 0
|
||||
return bit.band( texinfo.flags, flag ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the face is part of the world and not another entity.
|
||||
--- @return boolean
|
||||
function meta_face:IsWorld()
|
||||
return self.__bmodel == 0
|
||||
end
|
||||
|
||||
--- Returns the BModel the face has. 0 if it is part of the world.
|
||||
--- @return number
|
||||
function meta_face:GetBModel()
|
||||
return self.__bmodel
|
||||
end
|
||||
|
||||
--- Returns the entity-object-data that is part of this face.
|
||||
--- @return string EntityData
|
||||
function meta_face:GetEntity()
|
||||
return __findEntityUsingBrush( self.__map )[self.__bmodel]
|
||||
end
|
||||
|
||||
-- Displacments TODO: Fix Displacment Position and Data
|
||||
|
||||
--- Returns true if the face is part of Displacment
|
||||
--- @return boolean
|
||||
function meta_face:IsDisplacment()
|
||||
return self.dispinfo > -1
|
||||
end
|
||||
|
||||
--- Returns the vertex positions for the face. [Not Cached]
|
||||
--- Note this will ignore BModel-positions!
|
||||
--- @return table
|
||||
function meta_face:GetVertexs()
|
||||
if self._vertex then return self._vertex end
|
||||
local t = {}
|
||||
if not self:IsDisplacment() then
|
||||
|
||||
for i = 0, self.numedges - 1 do
|
||||
t[i + 1] = self.__map:GetSurfEdgesIndex( self.firstedge + i )
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
-- This is a displacment
|
||||
-- TODO: Calculate the displacment mesh and return it here
|
||||
--local dispVertStart =
|
||||
end
|
||||
|
||||
--- Returns a table in form of a polygon-mesh. [Not Cached]
|
||||
--- @return PolygonMeshVertex[]
|
||||
function meta_face:GenerateVertexData()
|
||||
--- @type PolygonMeshVertex[]
|
||||
local t = {}
|
||||
local tv = self:GetTexInfo().textureVects
|
||||
local lv = self:GetTexInfo().lightmapVecs
|
||||
local texdata = self:GetTexData()
|
||||
local mat_w, mat_h = texdata.view_width, texdata.view_height
|
||||
local n = self:GetNormal()
|
||||
|
||||
-- Move the faces to match func_brushes (If any)
|
||||
local bNum = self.__bmodel
|
||||
local exPos, exAng
|
||||
if bNum > 0 then
|
||||
-- Get funch_brushes and their location
|
||||
local func_brush = __findEntityUsingBrush( self.__map )[bNum]
|
||||
if func_brush then
|
||||
exPos = func_brush.origin
|
||||
exAng = func_brush.angles
|
||||
end
|
||||
end
|
||||
|
||||
local luxelW = self.LightmapTextureSizeInLuxels[1] + 1
|
||||
local luxelH = self.LightmapTextureSizeInLuxels[2] + 1
|
||||
|
||||
for i = 0, self.numedges - 1 do
|
||||
--- @class PolygonMeshVertex
|
||||
local vert = {}
|
||||
local a = self.__map:GetSurfEdgesIndex( self.firstedge + i )
|
||||
vert.pos = a
|
||||
if bNum > 0 then -- WorldPos -> Entity Brush
|
||||
a = WorldToLocal( a, Angle( 0, 0, 0 ), Vector( 0, 0, 0 ), exAng )
|
||||
vert.pos = a + exPos
|
||||
end
|
||||
|
||||
vert.normal = n
|
||||
-- UV & LV
|
||||
vert.u = ( tv[0][0] * a.x + tv[0][1] * a.y + tv[0][2] * a.z + tv[0][3] ) / mat_w
|
||||
vert.v = ( tv[1][0] * a.x + tv[1][1] * a.y + tv[1][2] * a.z + tv[1][3] ) / mat_h
|
||||
|
||||
vert.lu = ( ( lv[0][0] * a.x + lv[0][1] * a.y + lv[0][2] * a.z + lv[0][3] ) - self.LightmapTextureMinsInLuxels[1] ) / luxelW
|
||||
vert.lv = ( ( lv[1][0] * a.x + lv[1][1] * a.y + lv[1][2] * a.z + lv[1][3] ) - self.LightmapTextureMinsInLuxels[2] ) / luxelH
|
||||
|
||||
vert.userdata = { 0, 0, 0, 0 } -- Todo: Calculate this?
|
||||
|
||||
t[i + 1] = vert
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- @return PolygonMeshVertex[]?
|
||||
local function PolyChop( o_vert )
|
||||
local vert = {}
|
||||
if #o_vert < 3 then return end
|
||||
|
||||
local n = 1
|
||||
local triCount = #o_vert - 2
|
||||
|
||||
for i = 1, triCount do
|
||||
vert[n] = o_vert[1]
|
||||
vert[n + 1] = o_vert[i + 1]
|
||||
vert[n + 2] = o_vert[i + 2]
|
||||
n = n + 3
|
||||
end
|
||||
|
||||
return vert
|
||||
end
|
||||
|
||||
---Returns a table in form of a polygon-mesh for triangles. [Not Cached]
|
||||
---@return PolygonMeshVertex[]?
|
||||
function meta_face:GenerateVertexTriangleData()
|
||||
if self._vertTriangleData then return self._vertTriangleData end
|
||||
self._vertTriangleData = PolyChop( self:GenerateVertexData() )
|
||||
return self._vertTriangleData
|
||||
end
|
||||
|
||||
--- All mesh-data regarding said face. Should use face:GenerateVertexTriangleData intead!
|
||||
--- @return table
|
||||
function meta_face:GenerateMeshData()
|
||||
--- @class PolygonMeshData
|
||||
local t = {}
|
||||
t.verticies = self:GenerateVertexData()
|
||||
t.triangles = PolyChop( t.verticies )
|
||||
t.material = self:GetTexture()
|
||||
return {t}
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
--- @type IMesh[]
|
||||
NIKNAKS_TABOMESH = NIKNAKS_TABOMESH or {}
|
||||
|
||||
--- Builds the mesh if face has none.
|
||||
--- @return IMesh|boolean?
|
||||
function meta_face:BuildMesh()
|
||||
if SERVER then return end
|
||||
if self._mesh then return self._mesh end
|
||||
|
||||
-- Tex
|
||||
local texinfo = self:GetTexInfo()
|
||||
if bit.band( texinfo.flags, 0x80 ) ~= 0 or bit.band( texinfo.flags, 0x200 ) ~= 0 then
|
||||
self._mesh = false
|
||||
return self._mesh
|
||||
end
|
||||
|
||||
local meshData = self:GenerateVertexTriangleData()
|
||||
if not meshData then return self._mesh end
|
||||
|
||||
self._mesh = Mesh( self:GetMaterial() )
|
||||
|
||||
-- Vert
|
||||
mesh.Begin( self._mesh, MATERIAL_TRIANGLES, #meshData )
|
||||
for i = 1, #meshData do
|
||||
local vert = meshData[i]
|
||||
-- > Mesh
|
||||
mesh.Normal( vert.normal )
|
||||
mesh.Position( vert.pos ) -- Set the position
|
||||
mesh.Color(col.r, col.g, col.b, col.a)
|
||||
mesh.TexCoord( 0, vert.u, vert.v ) -- Set the texture UV coordinates
|
||||
mesh.TexCoord( 1, vert.lu, vert.lv ) -- Set the lightmap UV coordinates
|
||||
mesh.TexCoord( 2, vert.lu, vert.lv ) -- Set the lightmap UV coordinates
|
||||
--mesh.TexCoord( 2, self.LightmapTextureSizeInLuxels[1], self.LightmapTextureSizeInLuxels[2] ) -- Set the texture UV coordinates
|
||||
--mesh.TexCoord( 2, self.LightmapTextureMinsInLuxels[1], self.LightmapTextureMinsInLuxels[2] ) -- Set the texture UV coordinates
|
||||
mesh.AdvanceVertex()
|
||||
end
|
||||
|
||||
mesh.End()
|
||||
|
||||
table.insert( NIKNAKS_TABOMESH, self._mesh )
|
||||
return self._mesh
|
||||
end
|
||||
|
||||
--- Returns the mesh generated for the face.
|
||||
--- Note. Need to call face:BuildMesh first.
|
||||
--- @return IMesh|boolean?
|
||||
function meta_face:GetMesh()
|
||||
return self._mesh
|
||||
end
|
||||
|
||||
--- Deletes the mesh generated for the face.
|
||||
--- @return self
|
||||
function meta_face:DeleteMesh()
|
||||
if not self._mesh then return end
|
||||
self._mesh:Destroy()
|
||||
self._mesh = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Generates a mesh for the face and renders it.
|
||||
--- @param dontGenerate boolean
|
||||
--- @return boolean? didGenerateMesh
|
||||
function meta_face:DebugRender( dontGenerate )
|
||||
local _mesh = self:GetMesh()
|
||||
if not _mesh and dontGenerate then return false end
|
||||
_mesh = _mesh or self:BuildMesh()
|
||||
if not IsValid( _mesh ) then return end
|
||||
render.SetMaterial( self:GetMaterial() )
|
||||
_mesh:Draw()
|
||||
return true
|
||||
end
|
||||
|
||||
for _, _mesh in pairs( NIKNAKS_TABOMESH ) do
|
||||
if IsValid( _mesh ) then _mesh:Destroy() end
|
||||
end
|
||||
end
|
||||
487
addons/niknaks/lua/niknaks/modules/sh_bsp_leafs.lua
Normal file
487
addons/niknaks/lua/niknaks/modules/sh_bsp_leafs.lua
Normal file
@@ -0,0 +1,487 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
local obj_tostring = "BSP %s [ %s ]"
|
||||
local format, clamp, min, max = string.format, math.Clamp, math.min, math.max
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
|
||||
--- @class LeafObject
|
||||
local meta_leaf = {}
|
||||
meta_leaf.__index = meta_leaf
|
||||
meta_leaf.__tostring = function( self ) return format( obj_tostring, "Leaf", self.__id ) end
|
||||
meta_leaf.MetaName = "BSP Leaf"
|
||||
NikNaks.__metatables["BSP Leaf"] = meta_leaf
|
||||
|
||||
local MAX_MAP_NODES = 65536
|
||||
local TEST_EPSILON = 0.01
|
||||
local canGenerateParents = false
|
||||
|
||||
--- Generates parentNodes for nodes and leafs.
|
||||
--- @param self BSPObject
|
||||
--- @param nodeNum integer
|
||||
--- @param parent integer
|
||||
--- @param nodes table<Nodes>
|
||||
--- @param leafs table<Leafs>
|
||||
local function makeParents(self, nodeNum, parent, nodes, leafs, firstRun)
|
||||
if firstRun and not canGenerateParents then return end
|
||||
canGenerateParents = false
|
||||
|
||||
nodes[nodeNum].parentNode = parent
|
||||
|
||||
for i = 1, 2 do
|
||||
local j = nodes[nodeNum].children[i]
|
||||
if j < 0 then
|
||||
leafs[-j - 1].parentNode = nodeNum;
|
||||
else
|
||||
makeParents(self, j, nodeNum, nodes, leafs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a table of map nodes
|
||||
--- @return MapNode[]
|
||||
function meta:GetNodes()
|
||||
if self._node then return self._node end
|
||||
self._node = {}
|
||||
local data = self:GetLump( 5 )
|
||||
|
||||
for i = 0, math.min( data:Size() / 256, MAX_MAP_NODES ) - 1 do
|
||||
--- @class MapNode
|
||||
--- @field children number[]
|
||||
local t = {}
|
||||
t.planenum = data:ReadLong()
|
||||
t.plane = self:GetPlanes()[ t.planenum ]
|
||||
t.children = { data:ReadLong(), data:ReadLong() }
|
||||
t.mins = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() )
|
||||
t.maxs = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() )
|
||||
t.firstFace = data:ReadUShort()
|
||||
t.numFaces = data:ReadUShort()
|
||||
t.area = data:ReadShort()
|
||||
t.padding = data:ReadShort()
|
||||
self._node[i] = t
|
||||
end
|
||||
|
||||
self:ClearLump( 5 )
|
||||
|
||||
canGenerateParents = true
|
||||
makeParents(self, 0, -1, self._node, self:GetLeafs(), true)
|
||||
canGenerateParents = false
|
||||
return self._node
|
||||
end
|
||||
|
||||
--- Returns a table of map leafs.
|
||||
--- @return LeafObject[], number num_clusters
|
||||
function meta:GetLeafs()
|
||||
if self._leafs then return self._leafs, self._leafs_num_clusters end
|
||||
|
||||
--- @type LeafObject[]
|
||||
self._leafs = {}
|
||||
|
||||
local lumpversion = self:GetLumpVersion( 10 )
|
||||
local data = self:GetLump( 10 )
|
||||
local size = 240 -- version
|
||||
|
||||
if lumpversion == 0 then
|
||||
size = size + 192 -- byte r, byte g, byte b + char expo
|
||||
end
|
||||
|
||||
if self._version <= 19 or true then
|
||||
size = size + 16
|
||||
end
|
||||
|
||||
local n = 0
|
||||
for i = 0, data:Size() / size - 1 do
|
||||
data:Seek( i * size )
|
||||
|
||||
--- @class LeafObject
|
||||
--- @field mins Vector
|
||||
--- @field maxs Vector
|
||||
local t = {}
|
||||
t.contents = data:ReadLong() -- 32 32 4
|
||||
t.cluster = data:ReadShort() -- 16 48 6
|
||||
|
||||
n = math.max( t.cluster + 1, n )
|
||||
|
||||
local d = data:ReadUShort()
|
||||
t.area = bit.band( d, 0x1FF ) -- 16 64 8
|
||||
t.flags = bit.rshift( d, 9 ) -- 16 80 10
|
||||
|
||||
t.mins = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() ) -- 16 x 3 ( 48 ) 128 16
|
||||
t.maxs = Vector( data:ReadShort(), data:ReadShort(), data:ReadShort() ) -- 16 x 3 ( 48 ) 176 22
|
||||
t.firstleafface = data:ReadUShort() -- 16 192 24
|
||||
t.numleaffaces = data:ReadUShort() -- 16 208 26
|
||||
t.firstleafbrush = data:ReadUShort() -- 16 224 28
|
||||
t.numleafbrushes = data:ReadUShort() -- 16 240 30
|
||||
t.leafWaterDataID = data:ReadShort() -- 16 256 32
|
||||
t.__id = i
|
||||
t.__map = self
|
||||
t.parentNode = -1
|
||||
|
||||
if t.leafWaterDataID > -1 then
|
||||
t.leafWaterData = self:GetLeafWaterData()[t.leafWaterDataID]
|
||||
end
|
||||
|
||||
setmetatable( t, meta_leaf )
|
||||
|
||||
self._leafs[i] = t
|
||||
end
|
||||
|
||||
self._leafs_num_clusters = n
|
||||
self:ClearLump( 10 )
|
||||
|
||||
canGenerateParents = true
|
||||
makeParents(self, 0, -1, self:GetNodes(), self._leafs, true)
|
||||
canGenerateParents = false
|
||||
|
||||
return self._leafs, n
|
||||
end
|
||||
|
||||
--- Returns a list of LeafWaterData. Holds the data of leaf nodes that are inside water.
|
||||
--- @return LeafWaterData[]
|
||||
function meta:GetLeafWaterData()
|
||||
if self._pLeafWaterData then return self._pLeafWaterData end
|
||||
|
||||
local data = self:GetLump( 36 )
|
||||
self._pLeafWaterData = {}
|
||||
|
||||
for i = 0, data:Size() / 80 - 1 do
|
||||
--- @class LeafWaterData
|
||||
local t = {}
|
||||
t.surfaceZ = data:ReadFloat()
|
||||
t.minZ = data:ReadFloat()
|
||||
t.surfaceTexInfoID = data:ReadShort()
|
||||
data:Skip( 2 ) -- A short that is always 0x00
|
||||
self._pLeafWaterData[i] = t
|
||||
end
|
||||
|
||||
self:ClearLump( 36 )
|
||||
return self._pLeafWaterData
|
||||
end
|
||||
|
||||
--- Returns the number of leaf-clusters
|
||||
--- @return number
|
||||
function meta:GetLeafsNumClusters()
|
||||
local _, num_clusters = self:GetLeafs()
|
||||
return num_clusters
|
||||
end
|
||||
|
||||
local mat = Material( "vgui/menu_mode_border" )
|
||||
local defaultColor = Color( 255, 0, 0, 255 )
|
||||
|
||||
--- A simple debug-render function that renders the leaf
|
||||
--- @CLIENT
|
||||
--- @param col Color
|
||||
function meta_leaf:DebugRender( col )
|
||||
render.SetMaterial( mat )
|
||||
render.SetBlend( 0.8 )
|
||||
render.DrawBox( Vector( 0, 0, 0 ), Angle( 0, 0, 0 ), self.maxs, self.mins, col or defaultColor )
|
||||
render.SetBlend( 1 )
|
||||
end
|
||||
|
||||
--- Returns the leaf index.
|
||||
--- @return number
|
||||
function meta_leaf:GetIndex()
|
||||
return self.__id or -1
|
||||
end
|
||||
|
||||
--- Returns the leaf area.
|
||||
--- @return number
|
||||
function meta_leaf:GetArea()
|
||||
return self.area or -1
|
||||
end
|
||||
|
||||
---In most cases, leafs within the skybox share the same value and are have the cluster id of 0.
|
||||
-- However older Source versions doesn't have 3D skyboxes and untested on maps without 3D skybox.
|
||||
--function meta_leaf:In3DSkyBox()
|
||||
-- return self.cluster == 0
|
||||
--end
|
||||
|
||||
--- Returns true if the leaf has the 3D sky within its PVS.
|
||||
--- Note: Seems to be broken from EP2 and up.
|
||||
--- @return boolean
|
||||
function meta_leaf:HasSkyboxInPVS()
|
||||
return bit.band( self.flags, 0x1 ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the leaf has the 3D sky within its PVS.
|
||||
--- Note: Seems to be deprecated. Use Leaf:HasSkyboxInPVS() and BSP:HasSkyBox() instead.
|
||||
--- @return boolean
|
||||
function meta_leaf:Has2DSkyboxInPVS()
|
||||
return bit.band( self.flags, 0x4 ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the leaf has said content
|
||||
--- @return boolean
|
||||
function meta_leaf:HasContents( CONTENTS )
|
||||
if CONTENTS == 0 then return self.contents == CONTENTS end
|
||||
return bit.band( self.contents, CONTENTS ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns the content flag the leaf has.
|
||||
--- @return number
|
||||
function meta_leaf:GetContents()
|
||||
return self.contents
|
||||
end
|
||||
|
||||
--- Returns a list of faces within this leaf. Starting at 1.
|
||||
--- Note: A face can be in multiple leafs.
|
||||
--- @return FaceObject[]
|
||||
function meta_leaf:GetFaces()
|
||||
if self._faces then return self._faces end
|
||||
|
||||
--- @type FaceObject[]
|
||||
self._faces = {}
|
||||
local faces = self.__map:GetFaces()
|
||||
local leafFace = self.__map:GetLeafFaces()
|
||||
local c = self.firstleafface
|
||||
|
||||
for i = 0, self.numleaffaces do
|
||||
local f_id = leafFace[ i + c ]
|
||||
self._faces[i + 1] = faces[f_id]
|
||||
end
|
||||
|
||||
return self._faces
|
||||
end
|
||||
|
||||
--- Returns true if the leaf has water within.
|
||||
--- @return boolean
|
||||
function meta_leaf:HasWater()
|
||||
return self.leafWaterDataID > 0
|
||||
end
|
||||
|
||||
--- Returns the water data, if any.
|
||||
--- @return table|nil
|
||||
function meta_leaf:GetWaterData()
|
||||
return self.leafWaterData
|
||||
end
|
||||
|
||||
--- Returns the water MaxZ within the leaf.
|
||||
--- @return number|nil
|
||||
function meta_leaf:GetWaterMaxZ()
|
||||
return self.leafWaterData and self.leafWaterData.surfaceZ
|
||||
end
|
||||
|
||||
--- Returns the water MinZ within the leaf.
|
||||
--- @return number|nil
|
||||
function meta_leaf:GetWaterMinZ()
|
||||
return self.leafWaterData and self.leafWaterData.minZ
|
||||
end
|
||||
|
||||
--- Returns true if the leaf is outside the map.
|
||||
--- @return boolean
|
||||
function meta_leaf:IsOutsideMap()
|
||||
-- Locations outside the map are always cluster -1. However we check to see if the contnets is solid to be sure.
|
||||
return self.cluster == -1 and self.contents == 1
|
||||
end
|
||||
|
||||
--- Returns the cluster-number for the leaf. Cluster numbers can be shared between multiple leafs.
|
||||
--- @return number
|
||||
function meta_leaf:GetCluster()
|
||||
return self.cluster
|
||||
end
|
||||
|
||||
--- Returns true if the position is within the given leaf.
|
||||
--- @param position Vector
|
||||
--- @return boolean
|
||||
function meta_leaf:IsPositionWithin( position )
|
||||
local l = self.__map:PointInLeaf(0, position)
|
||||
if not l then return false end
|
||||
return l:GetIndex() == self:GetIndex()
|
||||
end
|
||||
|
||||
--- Returns a list of all leafs around the given leaf.
|
||||
--- @param range? Number
|
||||
--- @return table<LeafObject>
|
||||
function meta_leaf:GetAdjacentLeafs()
|
||||
local t, i, s = {}, 1, 2
|
||||
for _, leaf in ipairs( self.__map:AABBInLeafs(0, self.mins, self.maxs, s) ) do
|
||||
if leaf == self then continue end
|
||||
t[i] = leaf
|
||||
i = i + 1
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns true if the leafs are adjacent to each other.
|
||||
--- @return bool
|
||||
function meta_leaf:IsLeafAdjacent( leaf )
|
||||
for _, c_leaf in ipairs( self:GetAdjacentLeafs() ) do
|
||||
if c_leaf == leaf then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Roughly returns the distance from leaf to the given position.
|
||||
--- @param position Vector
|
||||
--- @return number
|
||||
function meta_leaf:Distance( position )
|
||||
local cPos = Vector(clamp(position.x, self.mins.x, self.maxs.x),
|
||||
clamp(position.y, self.mins.y, self.maxs.y),
|
||||
clamp(position.z, self.mins.z, self.maxs.z))
|
||||
return cPos:Distance( position )
|
||||
end
|
||||
|
||||
--- Roughly returns the distance from leaf to the given position.
|
||||
--- @param position Vector
|
||||
--- @return number
|
||||
function meta_leaf:DistToSqr( position )
|
||||
local cPos = Vector(clamp(position.x, self.mins.x, self.maxs.x),
|
||||
clamp(position.y, self.mins.y, self.maxs.y),
|
||||
clamp(position.z, self.mins.z, self.maxs.z))
|
||||
return cPos:DistToSqr( position )
|
||||
end
|
||||
|
||||
--- Returns a list of planes, pointing into the leaf.
|
||||
--- @return Plane[]
|
||||
function meta_leaf:GetBoundaryPlanes()
|
||||
local nodeIndex = self.parentNode
|
||||
local list = {}
|
||||
if not nodeIndex then return t end
|
||||
|
||||
local child = -( self:GetIndex() + 1 )
|
||||
local nodes = self.__map:GetNodes()
|
||||
while ( nodeIndex >= 0 ) do
|
||||
local node = nodes[nodeIndex]
|
||||
local plane = node.plane
|
||||
if( node.children[1] == child ) then
|
||||
table.insert(list, plane)
|
||||
else
|
||||
table.insert(list, {
|
||||
dist = -plane.dist,
|
||||
normal = -plane.normal,
|
||||
type = plane.type
|
||||
})
|
||||
end
|
||||
|
||||
child = nodeIndex
|
||||
nodeIndex = nodes[child].parentNode
|
||||
end
|
||||
return list
|
||||
end
|
||||
|
||||
|
||||
local function locateBoxLeaf( iNode, tab, mins, maxs, nodes, planes, leafs )
|
||||
local cornerMin, cornerMax = Vector(0,0,0), Vector(0,0,0)
|
||||
while iNode >= 0 do
|
||||
local node = nodes[ iNode ]
|
||||
local plane = planes[ node.planenum ]
|
||||
for i = 1, 3 do
|
||||
if( plane.normal[i] >= 0) then
|
||||
cornerMin[i] = mins[i]
|
||||
cornerMax[i] = maxs[i]
|
||||
else
|
||||
cornerMin[i] = maxs[i]
|
||||
cornerMax[i] = mins[i]
|
||||
end
|
||||
end
|
||||
if plane.normal:Dot(cornerMax) - plane.dist <= -TEST_EPSILON then
|
||||
iNode = node.children[2]
|
||||
elseif plane.normal:Dot(cornerMin) - plane.dist >= TEST_EPSILON then
|
||||
iNode = node.children[1]
|
||||
else
|
||||
if not locateBoxLeaf(node.children[1], tab, mins, maxs, nodes, planes, leafs) then
|
||||
return false
|
||||
end
|
||||
return locateBoxLeaf(node.children[2], tab, mins, maxs, nodes, planes, leafs)
|
||||
end
|
||||
end
|
||||
tab[#tab + 1] = leafs[ -1 -iNode ]
|
||||
return true
|
||||
end
|
||||
|
||||
--- Returns a list of leafs within the given two positions.
|
||||
--- @param iNode? number
|
||||
--- @param point Vector
|
||||
--- @param point2 Vector
|
||||
--- @param add? number
|
||||
--- @return table<LeafObject>
|
||||
function meta:AABBInLeafs( iNode, point, point2, add )
|
||||
add = add or 0
|
||||
local mins = Vector(min(point.x, point2.x) - add, min(point.y, point2.y) - add, min(point.z, point2.z) - add)
|
||||
local maxs = Vector(max(point.x, point2.x) + add, max(point.y, point2.y) + add, max(point.z, point2.z) + add)
|
||||
local tab = {}
|
||||
locateBoxLeaf(iNode or 0, tab, mins, maxs, self:GetNodes(), self:GetPlanes(), self:GetLeafs())
|
||||
return tab
|
||||
end
|
||||
|
||||
---Returns true if the AABB is outside the map
|
||||
--- @param position Vector
|
||||
--- @param position2 Vector
|
||||
--- @return boolean
|
||||
function meta:IsAABBOutsideMap( position, position2 )
|
||||
for _, leaf in pairs( self:AABBInLeafs( 0, position, position2 ) ) do
|
||||
if leaf:IsOutsideMap() then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function locateSphereLeaf( iNode, tab, origin, radius, nodes, planes, leafs)
|
||||
while iNode >= 0 do
|
||||
local node = nodes[ iNode ]
|
||||
local plane = planes[ node.planenum ]
|
||||
if plane.normal:Dot(origin) + radius - plane.dist <= -TEST_EPSILON then
|
||||
iNode = node.children[2]
|
||||
elseif plane.normal:Dot(origin) - radius - plane.dist >= TEST_EPSILON then
|
||||
iNode = node.children[1]
|
||||
else
|
||||
if not locateSphereLeaf( node.children[1], tab, origin, radius, nodes, planes, leafs ) then
|
||||
return false
|
||||
end
|
||||
return locateSphereLeaf( node.children[2], tab, origin, radius, nodes, planes, leafs )
|
||||
end
|
||||
end
|
||||
tab[#tab + 1] = leafs[ -1 -iNode ]
|
||||
return true
|
||||
end
|
||||
--- Returns a list of leafs within the given sphere.
|
||||
--- @param iNode number
|
||||
--- @param origin Vector
|
||||
--- @param radius number
|
||||
--- @return table<LeafObject>
|
||||
function meta:SphereInLeafs(iNode, origin, radius)
|
||||
local tab = {}
|
||||
locateSphereLeaf(iNode, tab, origin, radius, self:GetNodes(), self:GetPlanes(), self:GetLeafs())
|
||||
return tab
|
||||
end
|
||||
|
||||
--- Returns true if the sphere is outside the map
|
||||
--- @param position Vector
|
||||
--- @param range number
|
||||
--- @return boolean
|
||||
function meta:IsSphereOutsideMap( position, range )
|
||||
for _, leaf in pairs( self:SphereInLeafs( 0, position, range ) ) do
|
||||
if leaf:IsOutsideMap() then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
--- Returns roughtly the leafs maximum boundary
|
||||
--- @return Vector
|
||||
function meta_leaf:OBBMaxs()
|
||||
return self.maxs
|
||||
end
|
||||
|
||||
--- Returns roughtly the leafs maximum boundary
|
||||
--- @return Vector
|
||||
function meta_leaf:OBBMins()
|
||||
return self.mins
|
||||
end
|
||||
|
||||
--- Returns roughtly the leafs center.
|
||||
--- @return Vector
|
||||
function meta_leaf:GetPos()
|
||||
return (self.mins + self.maxs) / 2
|
||||
end
|
||||
1649
addons/niknaks/lua/niknaks/modules/sh_bsp_module.lua
Normal file
1649
addons/niknaks/lua/niknaks/modules/sh_bsp_module.lua
Normal file
File diff suppressed because it is too large
Load Diff
284
addons/niknaks/lua/niknaks/modules/sh_bsp_pvspas.lua
Normal file
284
addons/niknaks/lua/niknaks/modules/sh_bsp_pvspas.lua
Normal file
@@ -0,0 +1,284 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
local obj_tostring = "BSP %s"
|
||||
local format = string.format
|
||||
|
||||
--- @class BSPObject
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
local meta_leaf = NikNaks.__metatables["BSP Leaf"]
|
||||
|
||||
--[[The data is stored as an array of bit-vectors; for each cluster, a list of which other clusters are visible
|
||||
from it are stored as individual bits (1 if visible, 0 if occluded) in an array, with the nth bit position
|
||||
corresponding to the nth cluster. ]]
|
||||
--- @param vis VisibilityInfo
|
||||
--- @param offset number
|
||||
local function getClusters( vis, offset, PVS )
|
||||
local c = 0
|
||||
local v = offset
|
||||
local pvs_buffer = vis._bytebuff
|
||||
local num_clusters = vis.num_clusters
|
||||
|
||||
while c <= num_clusters do
|
||||
if pvs_buffer[v] == 0 then
|
||||
v = v + 1
|
||||
c = c + 8 * pvs_buffer[v]
|
||||
else
|
||||
local b = 1
|
||||
|
||||
while b ~= 0 do
|
||||
if bit.band( pvs_buffer[v], b ) ~= 0 then
|
||||
PVS[c] = true
|
||||
end
|
||||
|
||||
b = bit.band( b * 2, 0xFF )
|
||||
c = c + 1
|
||||
end
|
||||
end
|
||||
|
||||
v = v + 1
|
||||
end
|
||||
end
|
||||
|
||||
--- PVS ( Potentially Visible Set )
|
||||
do
|
||||
--- @class PVSObject
|
||||
--- @field __map BSPObject
|
||||
local meta_pvs = {}
|
||||
meta_pvs.__index = meta_pvs
|
||||
meta_pvs.__tostring = "BSP PVS"
|
||||
meta_pvs.MetaName = "BSP PVS"
|
||||
NikNaks.__metatables["BSP PVS"] = meta_pvs
|
||||
|
||||
local DVIS_PVS = 1
|
||||
|
||||
--- Creates a new empty PVS-object.
|
||||
--- @return PVSObject
|
||||
function meta:CreatePVS()
|
||||
local t = {}
|
||||
t.__map = self
|
||||
setmetatable( t, meta_pvs )
|
||||
return t
|
||||
end
|
||||
|
||||
--- Uses the given ( or creates a new PVS-object ) and adds the position to it.
|
||||
--- @param position Vector
|
||||
--- @param PVS PVSObject?
|
||||
--- @return PVSObject
|
||||
function meta:PVSForOrigin( position, PVS )
|
||||
PVS = PVS or self:CreatePVS()
|
||||
|
||||
PVS.__map = self
|
||||
local cluster = self:ClusterFromPoint( position )
|
||||
if cluster < 0 then return PVS end -- Empty cluster position.
|
||||
|
||||
local vis = self:GetVisibility()
|
||||
local visofs = vis.VisData[cluster].PVS
|
||||
|
||||
getClusters( vis, visofs, PVS )
|
||||
|
||||
return PVS
|
||||
end
|
||||
|
||||
--- Returns true if the two positions are in same PVS.
|
||||
--- @param position Vector
|
||||
--- @param position2 Vector
|
||||
--- @return boolean
|
||||
function meta:PVSCheck( position, position2 )
|
||||
local PVS = self:PVSForOrigin( position )
|
||||
local cluster = self:ClusterFromPoint( position2 )
|
||||
return PVS[cluster] or false
|
||||
end
|
||||
|
||||
|
||||
--- Adds the position to PVS
|
||||
--- @param position Vector
|
||||
--- @return self
|
||||
function meta_pvs:AddPVS( position )
|
||||
self.__map:PVSForOrigin( position, self )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the position from PVS
|
||||
--- @param position Vector
|
||||
--- @return self
|
||||
function meta_pvs:RemovePVS( position )
|
||||
for id in pairs( self.__map:PVSForOrigin( position ) ) do
|
||||
if id ~= "__map" then self[id] = nil end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the leaf from PVS
|
||||
--- @param leaf LeafObject
|
||||
--- @return self PVSObject
|
||||
function meta_pvs:RemoveLeaf( leaf )
|
||||
self[leaf.cluster] = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns true if the position is visible in the PVS
|
||||
--- @param position Vector
|
||||
--- @return boolean
|
||||
function meta_pvs:TestPosition( position )
|
||||
local cluster = self.__map:ClusterFromPoint( position )
|
||||
return self[cluster] or false
|
||||
end
|
||||
|
||||
--- Create PVS from Leaf
|
||||
--- @return PVSObject
|
||||
function meta_leaf:CreatePVS()
|
||||
local PVS = {}
|
||||
PVS.__map = self.__map
|
||||
setmetatable( PVS, meta_pvs )
|
||||
if self.cluster < 0 then return PVS end -- Leaf invalid. Return empty PVS.
|
||||
|
||||
local vis = self.__map:GetVisibility()
|
||||
local visofs = vis.VisData[self.cluster].PVS
|
||||
|
||||
getClusters( vis, visofs, PVS )
|
||||
|
||||
return PVS
|
||||
end
|
||||
|
||||
--- Returns a list of leafs within this PVS. Note: This is a bit slow.
|
||||
function meta_pvs:GetLeafs()
|
||||
local t = {}
|
||||
local n = 1
|
||||
local leafs = self.__map:GetLeafs()
|
||||
|
||||
for i = 1, #leafs do
|
||||
local leaf = leafs[i]
|
||||
local cluster = leaf.cluster
|
||||
|
||||
if cluster >= 0 and self[cluster] then
|
||||
t[n] = leaf
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns true if the PVS has the given leaf
|
||||
--- @param leaf LeafObject
|
||||
--- @return boolean
|
||||
function meta_pvs:HasLeaf( leaf )
|
||||
if leaf.cluster < 0 then return false end
|
||||
return self[leaf.cluster]
|
||||
end
|
||||
end
|
||||
|
||||
-- PAS
|
||||
do
|
||||
---@class PASObject
|
||||
local meta_pas = {}
|
||||
meta_pas.__index = meta_pas
|
||||
meta_pas.__tostring = "BSP PAS"
|
||||
meta_pas.MetaName = "BSP PAS"
|
||||
NikNaks.__metatables["BSP PAS"] = meta_pas
|
||||
local DVIS_PAS = 2
|
||||
|
||||
--- Creates a new empty PAS-object.
|
||||
--- @return PASObject
|
||||
function meta:CreatePAS()
|
||||
return setmetatable( {}, meta_pas )
|
||||
end
|
||||
|
||||
--- Uses the given ( or creates a new PAS-object ) and adds the position to it.
|
||||
--- @param position Vector
|
||||
--- @param PAS PASObject?
|
||||
--- @return PASObject?
|
||||
function meta:PASForOrigin( position, PAS )
|
||||
PAS = PAS or self:CreatePAS()
|
||||
PAS.__map = self
|
||||
|
||||
local cluster = self:ClusterFromPoint( position )
|
||||
local vis = self:GetVisibility()
|
||||
if cluster < 0 then return end -- err
|
||||
|
||||
local visofs = vis.VisData[cluster].PAS
|
||||
|
||||
getClusters( vis, visofs, PAS )
|
||||
|
||||
return PAS
|
||||
end
|
||||
|
||||
--- Returns true if the two positions are in same PAS
|
||||
--- @param position Vector
|
||||
--- @param position2 Vector
|
||||
--- @return boolean
|
||||
function meta:PASCheck( position, position2 )
|
||||
local PAS = self:PASForOrigin( position )
|
||||
return PAS[self:ClusterFromPoint( position2 )] or false
|
||||
end
|
||||
|
||||
--- Adds the position to PAS
|
||||
--- @param position Vector
|
||||
--- @return PASObject self
|
||||
function meta_pas:AddPAS( position )
|
||||
self.__map:PASForOrigin( position, self )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the position from PAS
|
||||
--- @param position Vector
|
||||
--- @return PASObject self
|
||||
function meta_pas:RemovePAS( position )
|
||||
for id in pairs( self.__map:PASForOrigin( position ) ) do
|
||||
if id ~= "__map" then self[id] = nil end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Removes the leaf from PVS
|
||||
--- @param leaf LeafObject
|
||||
--- @return PASObject self
|
||||
function meta_pas:RemoveLeaf( leaf )
|
||||
self[leaf.cluster] = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- Returns true if the position is visible in the PAS
|
||||
--- @param position Vector
|
||||
--- @return boolean
|
||||
function meta_pas:TestPosition( position )
|
||||
local cluster = self.__map:ClusterFromPoint( position )
|
||||
return self[cluster] or false
|
||||
end
|
||||
|
||||
--- Create PAS from Leaf
|
||||
--- @return PASObject
|
||||
function meta_leaf:CreatePAS()
|
||||
local PAS = setmetatable( {}, meta_pas )
|
||||
if self.cluster < 0 then return PAS end -- Leaf invalid. Return empty PVS.
|
||||
|
||||
local vis = self.__map:GetVisibility()
|
||||
local visofs = vis[ self.cluster ][ DVIS_PAS ]
|
||||
|
||||
getClusters( vis, visofs, PAS )
|
||||
|
||||
return PAS
|
||||
end
|
||||
|
||||
--- Returns true if the PAS has the given leaf
|
||||
--- @param leaf LeafObject
|
||||
--- @return boolean
|
||||
function meta_pas:HasLeaf( leaf )
|
||||
if leaf.cluster < 0 then return false end
|
||||
return self[leaf.cluster]
|
||||
end
|
||||
end
|
||||
463
addons/niknaks/lua/niknaks/modules/sh_bsp_staticprops.lua
Normal file
463
addons/niknaks/lua/niknaks/modules/sh_bsp_staticprops.lua
Normal file
@@ -0,0 +1,463 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
|
||||
local band = bit.band
|
||||
local meta = NikNaks.__metatables["BSP"]
|
||||
|
||||
--- @class StaticProp
|
||||
--- @field Index number
|
||||
--- @field version number
|
||||
--- @field Origin Vector
|
||||
--- @field Angles Angle
|
||||
--- @field PropType string
|
||||
--- @field First_leaf number
|
||||
--- @field LeafCount number
|
||||
--- @field Solid number
|
||||
--- @field Flags? number
|
||||
--- @field Skin number
|
||||
--- @field FadeMinDist number
|
||||
--- @field FadeMaxDist number
|
||||
--- @field LightingOrigin Vector
|
||||
--- @field ForcedFadeScale? number
|
||||
--- @field MinDXLevel? number
|
||||
--- @field MaxDXLevel? number
|
||||
--- @field lightmapResolutionX? number
|
||||
--- @field lightmapResolutionY? number
|
||||
--- @field MinCPULevel? number
|
||||
--- @field MaxCPULevel? number
|
||||
--- @field MinGPULevel? number
|
||||
--- @field MaxGPULevel? number
|
||||
--- @field DiffuseModulation? Color
|
||||
--- @field DisableX360? boolean
|
||||
--- @field FlagsEx? number
|
||||
--- @field UniformScale? number
|
||||
local meta_staticprop = {}
|
||||
meta_staticprop.__index = meta_staticprop
|
||||
meta_staticprop.__tostring = function(self) return "Static Prop" .. (self.PropType and " [" .. self.PropType .. "]" or "") end
|
||||
meta_staticprop.MetaName = "StaticProp"
|
||||
NikNaks.__metatables["StaticProp"] = meta_staticprop
|
||||
|
||||
local version = {}
|
||||
-- Base version from Wiki. Most HL2 maps are version 5.
|
||||
version[4] = function( f, obj, m )
|
||||
obj.Origin = f:ReadVector() -- Vector (3 float) 12 bytes
|
||||
obj.Angles = f:ReadAngle() -- Angle (3 float) 12 bytes
|
||||
obj.PropType = m[f:ReadUShort() + 1] -- unsigned short 2 bytes
|
||||
obj.First_leaf = f:ReadUShort() -- unsigned short 2 bytes
|
||||
obj.LeafCount = f:ReadUShort() -- unsigned short 2 bytes
|
||||
obj.Solid = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.Flags = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.Skin = f:ReadLong() -- int 4 bytes
|
||||
obj.FadeMinDist = f:ReadFloat() -- float 4 bytes
|
||||
obj.FadeMaxDist = f:ReadFloat() -- float 4 bytes
|
||||
obj.LightingOrigin = f:ReadVector() -- Vector (3 float) 12 bytes
|
||||
return 448
|
||||
end
|
||||
|
||||
-- Fade scale added.
|
||||
version[5] = function( f, obj, m)
|
||||
version[4]( f, obj, m )
|
||||
obj.ForcedFadeScale = f:ReadFloat() -- float 4 bytes
|
||||
return 480
|
||||
end
|
||||
|
||||
-- Minimum and maximum DX-level
|
||||
version[6] = function( f, obj, m)
|
||||
version[5]( f, obj, m )
|
||||
obj.MinDXLevel = f:ReadUShort() -- unsigned short 2 bytes
|
||||
obj.MaxDXLevel = f:ReadUShort() -- unsigned short 2 bytes
|
||||
return 512
|
||||
end
|
||||
|
||||
-- Color modulation added
|
||||
version[7] = function( f, obj, m )
|
||||
version[6]( f, obj, m )
|
||||
obj.DiffuseModulation = f:ReadColor()
|
||||
return 544
|
||||
end
|
||||
|
||||
-- Removal of DX-Level. Possible for Linux and console support.
|
||||
version[8] = function( f, obj, m )
|
||||
version[5]( f,obj, m )
|
||||
obj.MinCPULevel = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.MaxCPULevel = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.MinGPULevel = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.MaxGPULevel = f:ReadByte() -- unsigned char 1 byte
|
||||
obj.DiffuseModulation = f:ReadColor()
|
||||
return 544
|
||||
end
|
||||
|
||||
-- Added Dissable-flag for X360
|
||||
version[9] = function( f, obj, m )
|
||||
version[8]( f, obj, m )
|
||||
-- The first byte seems to be the indecator.
|
||||
-- All maps have the first byte as 0x00, where the L4D2 map; 'c2m4_barns.bsp', tells us it is 0x01 is true.
|
||||
obj.DisableX360 = f:ReadByte() == 1 -- The first byte is the indecator
|
||||
-- The last 3 bytes seems to be random data, to fill out the 32bit network-limit
|
||||
f:Skip( 24 )
|
||||
return 576
|
||||
end
|
||||
|
||||
-- This version is for TF2 and some CS:S maps.
|
||||
-- Was build on version 6. Guess they where never meant to be released on consoles and only PC ( Since they use DXLevel )
|
||||
version[10] = function( f, obj, m )
|
||||
version[6]( f, obj, m )
|
||||
obj.lightmapResolutionX = f:ReadLong()
|
||||
obj.lightmapResolutionY = f:ReadLong()
|
||||
return 576
|
||||
end
|
||||
|
||||
-- ( Version 7* ) This version is for some CSGO maps. I guess it was for the console support.
|
||||
version["10A"] = function( f, obj, m )
|
||||
version[9]( f, obj, m )
|
||||
obj.FlagsEx = f:ReadULong()
|
||||
return 608
|
||||
end
|
||||
|
||||
-- The newest CSGO maps. Might have left the console's behind with the newest map versions.
|
||||
version[11] = function( f, obj, m )
|
||||
local q = version[9]( f, obj, m )
|
||||
obj.FlagsEx = f:ReadULong()
|
||||
obj.UniformScale = f:ReadFloat()
|
||||
return q + 64
|
||||
end
|
||||
|
||||
--- @class StaticProp
|
||||
|
||||
--- @param f BitBuffer
|
||||
--- @param ver number
|
||||
--- @return StaticProp, number
|
||||
local function CreateStaticProp( f, ver, m )
|
||||
local obj = {}
|
||||
local startTell = f:Tell()
|
||||
|
||||
version[ver]( f, obj, m )
|
||||
obj.version = ver
|
||||
|
||||
local sizeUsed = f:Tell() - startTell
|
||||
return setmetatable( obj, meta_staticprop ), sizeUsed
|
||||
end
|
||||
|
||||
--- Returns a list of staticprops.
|
||||
--- @return StaticProp[]
|
||||
function meta:GetStaticProps()
|
||||
if self._staticprops then return self._staticprops end
|
||||
|
||||
local gameLump = self:GetGameLump( 1936749168 ) -- 1936749168 == "sprp"
|
||||
local b = gameLump.buffer
|
||||
local propVersion = gameLump.version
|
||||
|
||||
if b:Size() < 1 or not NikNaks._Source:find( "niknak" ) then -- This map doesn't have staticprops, or doesn't support them.
|
||||
self._staticprops = {}
|
||||
self._staticprops_mdl = {}
|
||||
return self._staticprops
|
||||
end
|
||||
|
||||
if propVersion > 11 then
|
||||
ErrorNoHalt( self._mapfile .. " has an unknown static-prop version!" )
|
||||
self._staticprops = {}
|
||||
self._staticprops_mdl = {}
|
||||
return self._staticprops
|
||||
end
|
||||
|
||||
-- Load the model list. This list is used by the static_props.
|
||||
--- @type string[]
|
||||
self._staticprops_mdl = {}
|
||||
|
||||
local n = b:ReadLong()
|
||||
if n > 16384 then -- Check if we overread the max static props.
|
||||
ErrorNoHalt( self._mapfile .. " has more than 16384 models!" )
|
||||
self._staticprops = {}
|
||||
return self._staticprops
|
||||
end
|
||||
|
||||
for i = 1, n do
|
||||
-- All model-paths are saved as char[128]. Any overflow are nullbytes.
|
||||
local model = ""
|
||||
|
||||
for i2 = 1, 128 do
|
||||
local c = string.char( b:ReadByte() )
|
||||
if string.match( c,"[%w_%-%.%/]" ) then -- Just in case, we check for "valid" chars instead.
|
||||
model = model .. c
|
||||
end
|
||||
end
|
||||
|
||||
self._staticprops_mdl[i] = model
|
||||
end
|
||||
|
||||
-- Read the leaf-array. (Unused atm). Prob an index for the static props. However each static-prop already hold said data.
|
||||
b:Skip( 16 * b:ReadLong() )
|
||||
|
||||
-- Read static props
|
||||
local count = b:ReadLong()
|
||||
if count > 16384 then -- Check if we are above the max staticprop.
|
||||
ErrorNoHalt( self._mapfile .. " has more than 16384 staticprops!" )
|
||||
self._staticprops = {}
|
||||
return self._staticprops
|
||||
end
|
||||
|
||||
-- We calculate the amount of static props within this space. It is more stable.
|
||||
local staticStart = b:Tell()
|
||||
local endPos = b:Size()
|
||||
local staticSize = ( endPos - staticStart ) / count
|
||||
local staticUsed
|
||||
|
||||
--- @type StaticProp[]
|
||||
self._staticprops = {}
|
||||
|
||||
-- Check for the 7* version.
|
||||
if staticSize == 608 and propVersion == 10 then
|
||||
propVersion = "10A"
|
||||
end
|
||||
|
||||
for i = 0, count - 1 do
|
||||
-- This is to try and get as much valid data we can.
|
||||
b:Seek( staticStart + staticSize * i )
|
||||
local sObj, sizeused = CreateStaticProp( b, propVersion, self._staticprops_mdl, staticSize )
|
||||
staticUsed = staticUsed or sizeused
|
||||
sObj.Index = i
|
||||
self._staticprops[i] = sObj
|
||||
end
|
||||
|
||||
if staticUsed and staticUsed ~= staticSize then
|
||||
ErrorNoHalt( "Was unable to parse " .. self._mapfile .. "'s StaticProps correctly!" )
|
||||
end
|
||||
|
||||
return self._staticprops
|
||||
end
|
||||
|
||||
--- Returns the static-prop object from said index.
|
||||
--- @param index number
|
||||
--- @return StaticProp
|
||||
function meta:GetStaticProp( index )
|
||||
return self:GetStaticProps()[index]
|
||||
end
|
||||
|
||||
--- Returns a list of all static-prop models used by the map.
|
||||
--- @return string[]
|
||||
function meta:GetStaticPropModels()
|
||||
if self._staticprops_mdl then return self._staticprops_mdl end
|
||||
|
||||
self:GetStaticProps() -- If no model list, then load the gamelump.
|
||||
return self._staticprops_mdl
|
||||
end
|
||||
|
||||
--- Returns a list of all static-props matching the model.
|
||||
--- @param model string
|
||||
--- @return StaticProp[]
|
||||
function meta:FindStaticByModel( model )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetStaticProps() ) do
|
||||
if v.PropType == model then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of all static-props, within the specified box.
|
||||
--- @param boxMins Vector
|
||||
--- @param boxMaxs Vector
|
||||
--- @return StaticProp[]
|
||||
function meta:FindStaticInBox( boxMins, boxMaxs )
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetStaticProps() ) do
|
||||
local origin = v.Origin
|
||||
if origin and v.Origin:WithinAABox( boxMins, boxMaxs ) then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns a list of all static-props, within the specified sphere.
|
||||
--- @param origin Vector
|
||||
--- @param radius number
|
||||
--- @return StaticProp[]
|
||||
function meta:FindStaticInSphere( origin, radius )
|
||||
radius = radius ^ 2
|
||||
local t = {}
|
||||
|
||||
for _, v in pairs( self:GetStaticProps() ) do
|
||||
local spOrigin = v.Origin
|
||||
if spOrigin and spOrigin:DistToSqr( origin ) <= radius then
|
||||
t[#t + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
|
||||
--- Returns the StaticProp index
|
||||
function meta_staticprop:GetIndex()
|
||||
return self.Index
|
||||
end
|
||||
|
||||
--- Returns the origin
|
||||
function meta_staticprop:GetPos()
|
||||
return self.Origin
|
||||
end
|
||||
|
||||
--- Returns the angles
|
||||
function meta_staticprop:GetAngles()
|
||||
return self.Angles
|
||||
end
|
||||
|
||||
--- Returns the model path
|
||||
function meta_staticprop:GetModel()
|
||||
return self.PropType
|
||||
end
|
||||
|
||||
--- Returns the skin index
|
||||
function meta_staticprop:GetSkin()
|
||||
return self.Skin or 0
|
||||
end
|
||||
|
||||
--- @return Color
|
||||
function meta_staticprop:GetColor()
|
||||
return self.DiffuseModulation or color_white
|
||||
end
|
||||
|
||||
function meta_staticprop:GetScale()
|
||||
return self.UniformScale or 1
|
||||
end
|
||||
meta_staticprop.GetModelScale = meta_staticprop.GetScale
|
||||
|
||||
--- Returns the solid enum. See: https://wiki.facepunch.com/gmod/Enums/SOLID
|
||||
function meta_staticprop:GetSolid()
|
||||
return self.Solid
|
||||
end
|
||||
|
||||
--- Returns the lighting origin
|
||||
function meta_staticprop:GetLightingOrigin()
|
||||
return self.LightingOrigin
|
||||
end
|
||||
|
||||
--- Returns the flags
|
||||
function meta_staticprop:GetFlags()
|
||||
return self.Flags
|
||||
end
|
||||
|
||||
--- Returns true if the staticprop has a flag.
|
||||
--- @param flag number
|
||||
--- @return boolean
|
||||
function meta_staticprop:HasFlag( flag )
|
||||
return band( self:GetFlags(), flag ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns true if the static prop is disabled on X360.
|
||||
--- @return boolean
|
||||
function meta_staticprop:GetDisableX360()
|
||||
return self.DisableX360 or false
|
||||
end
|
||||
|
||||
--- Returns the model bounds.
|
||||
--- @return Vector
|
||||
--- @return Vector
|
||||
function meta_staticprop:GetModelBounds()
|
||||
local a, b = NikNaks.ModelSize( self:GetModel() )
|
||||
local s = self:GetScale()
|
||||
return a * s, b * s
|
||||
end
|
||||
meta_staticprop.GetModelRenderBounds = meta_staticprop.GetModelBounds
|
||||
meta_staticprop.GetRenderBounds = meta_staticprop.GetModelBounds
|
||||
|
||||
-- Fade Functions
|
||||
|
||||
function meta_staticprop:GetFadeMinDist()
|
||||
return self.FadeMinDist
|
||||
end
|
||||
|
||||
function meta_staticprop:GetFadeMaxDist()
|
||||
return self.FadeMaxDist
|
||||
end
|
||||
|
||||
function meta_staticprop:GetForceFadeScale()
|
||||
return self.ForcedFadeScale or 1
|
||||
end
|
||||
|
||||
-- "Other"
|
||||
|
||||
--[[ DXLevel
|
||||
0 = Ignore
|
||||
70 = DirectX 7
|
||||
80 = DirectX 8
|
||||
81 = DirectX 8.1
|
||||
90 = DirectX 9
|
||||
95 = DirectX 9+ ( 9.3 )
|
||||
98 = DirectX 9Ex
|
||||
]]
|
||||
function meta_staticprop:GetDXLevel()
|
||||
return self.MinDXLevel or 0, self.MaxDXLevel or 0
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
-- Checks to see if the client has the directX level required to render the static prop.
|
||||
function meta_staticprop:HasDXLevel()
|
||||
local num = render.GetDXLevel()
|
||||
if self.MinDXLevel ~= 0 and num < self.MinDXLevel then return false end
|
||||
if self.MaxDXLevel ~= 0 and num > self.MaxDXLevel then return false end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
--[[ There must be a list of CPU's and what level they are.
|
||||
CPU Level
|
||||
0 = Ignore
|
||||
1 = "Low"
|
||||
2 = "Medium"
|
||||
3 = "High"
|
||||
]]
|
||||
function meta_staticprop:GetCPULevel()
|
||||
return self.MinCPULevel or 0, self.MaxCPULevel or 0
|
||||
end
|
||||
|
||||
--[[ There must be a list of GPU's and what level they are.
|
||||
GPU Level
|
||||
0 = Ignore
|
||||
1 = "Low"
|
||||
2 = "Medium"
|
||||
3 = "High"
|
||||
]]
|
||||
function meta_staticprop:GetGPULevel()
|
||||
return self.MinGPULevel or 0, self.MaxGPULevel or 0
|
||||
end
|
||||
|
||||
-- Allows to set the lightmap resolution for said static-prop.
|
||||
-- Checkout https://tf2maps.net/threads/guide-lightmap-optimization.33113/ for more info
|
||||
function meta_staticprop:GetLightMapResolution()
|
||||
return self.lightmapResolutionX, self.lightmapResolutionY
|
||||
end
|
||||
|
||||
--- Returns the "Further" BitFlags. Seems to be only used for the "STATIC_PROP_FLAGS_EX_DISABLE_CSM" flag.
|
||||
--- @return number
|
||||
function meta_staticprop:GetFlagExs()
|
||||
return self.FlagsEx or 0
|
||||
end
|
||||
|
||||
--- Returns true if the staticprop has an exflag.
|
||||
--- @param flag number
|
||||
--- @return boolean
|
||||
function meta_staticprop:HasFlagEx( flag )
|
||||
return band( self:GetFlagExs(), flag ) ~= 0
|
||||
end
|
||||
|
||||
--- Returns the version of the static props.
|
||||
--- Note: version 7* will be returned as a string: "10A"
|
||||
function meta_staticprop:GetVersion()
|
||||
return self.version
|
||||
end
|
||||
203
addons/niknaks/lua/niknaks/modules/sh_color_extended.lua
Normal file
203
addons/niknaks/lua/niknaks/modules/sh_color_extended.lua
Normal file
@@ -0,0 +1,203 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
local NikNaks = NikNaks
|
||||
local COLOR = FindMetaTable("Color")
|
||||
|
||||
local clamp = math.Clamp
|
||||
local Color, round = Color, math.Round
|
||||
local min, max, abs = math.min, math.max, math.abs
|
||||
local string_format, string_sub = string.format, string.sub
|
||||
|
||||
-- Color enums
|
||||
NikNaks.SERVER_COLOR= Color(156, 241, 255, 200)
|
||||
NikNaks.CLIENT_COLOR= Color(255, 241, 122, 200)
|
||||
NikNaks.MENU_COLOR = Color(100, 220, 100, 200)
|
||||
NikNaks.REALM_COLOR = SERVER and NikNaks.SERVER_COLOR or CLIENT and NikNaks.CLIENT_COLOR or MENU_DLL and NikNaks.MENU_COLOR
|
||||
NikNaks.color_error_server = Color(136, 221, 255)
|
||||
NikNaks.color_error_client = Color(255, 221, 102)
|
||||
NikNaks.color_error_menu = Color(120, 220, 100)
|
||||
|
||||
---Returns the luminance amount. How "bright" a color is between 0 and 255.
|
||||
---@param color Color
|
||||
---@return number
|
||||
function NikNaks.ColorToLuminance(color)
|
||||
return 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b
|
||||
end
|
||||
|
||||
---Returns the luminance amount. How "bright" a color is between 0 and 255.
|
||||
---@return number
|
||||
function COLOR:ToLuminance()
|
||||
return 0.2126 * self.r + 0.7152 * self.g + 0.0722 * self.b
|
||||
end
|
||||
|
||||
-- Hex
|
||||
|
||||
---Converts a color into a hex-string.
|
||||
---@param color Color
|
||||
---@return string
|
||||
function NikNaks.ColorToHex(color)
|
||||
return "#" .. string_format("%X", color.r) .. string_format("%X", color.g) .. string_format("%X", color.b)
|
||||
end
|
||||
|
||||
---Converts a color into a hex-string.
|
||||
---@return string
|
||||
function COLOR:ToHex()
|
||||
return ColorToHex(self)
|
||||
end
|
||||
|
||||
---Converts a hex-stirng into a color.
|
||||
---@param str string
|
||||
---@return Color
|
||||
function NikNaks.HexToColor(str)
|
||||
str = string.gsub(str,"#","")
|
||||
local r = round( tonumber( string_sub(str,1,2), 16) )
|
||||
local g = round( tonumber( string_sub(str,3,4), 16) )
|
||||
local b = round( tonumber( string_sub(str,5,6), 16) )
|
||||
return Color(r, g, b)
|
||||
end
|
||||
|
||||
-- CMYK
|
||||
|
||||
---Converts a color into CMYK variables.
|
||||
---@return number c
|
||||
---@return number m
|
||||
---@return number y
|
||||
---@return number j
|
||||
function COLOR:ToCMYK()
|
||||
local r, g, b = self.r / 255, self.g / 255, self.b / 255
|
||||
local k = 1 - max(r, g, b)
|
||||
local c = (1 - r - k) / ( 1 - k )
|
||||
local m = (1-g-k) / ( 1 - k )
|
||||
local y = (1-b-k) / ( 1 - k )
|
||||
return c, m, y, k
|
||||
end
|
||||
|
||||
---Converts a color into CMYK variables.
|
||||
---@return number c
|
||||
---@return number m
|
||||
---@return number y
|
||||
---@return number j
|
||||
function NikNaks.ColorToCMYK( color )
|
||||
return color:ToCMYK()
|
||||
end
|
||||
|
||||
---Converts CMYK variables into a color.
|
||||
---@param c any
|
||||
---@param m any
|
||||
---@param y any
|
||||
---@param k any
|
||||
---@return Color
|
||||
function NikNaks.CMYKToColor( c, m, y, k )
|
||||
local r = math.Round( 255 * ( 1 - c ) * ( 1 - k ) )
|
||||
local g = math.Round( 255 * ( 1 - m ) * ( 1 - k ) )
|
||||
local b = math.Round( 255 * ( 1 - y ) * ( 1 - k ) )
|
||||
return Color( r, g, b )
|
||||
end
|
||||
|
||||
-- Color manipulation
|
||||
|
||||
---Brightens the color by [0-255]-amount.
|
||||
---@param amount number
|
||||
---@return Color
|
||||
function COLOR:Brighten(amount)
|
||||
local h,s,l = ColorToHSL(self)
|
||||
return HSLToColor(h,s,l + amount)
|
||||
end
|
||||
|
||||
---Darkens the color by [0-255]-amount.
|
||||
---@param amount number
|
||||
---@return Color
|
||||
function COLOR:Darken(amount)
|
||||
return self.lighten(-amount)
|
||||
end
|
||||
|
||||
---Inverts the color.
|
||||
---@return Color
|
||||
function COLOR:Invert()
|
||||
return Color(255 - self.r,255 - self.g,255 - self.b)
|
||||
end
|
||||
|
||||
---Turns the color into a gray-scale.
|
||||
---@return Color
|
||||
function COLOR:ToGrayscale()
|
||||
local H,S,L = self:ToHSL()
|
||||
return HSLToColor(H,0,L)
|
||||
end
|
||||
|
||||
---Cartoonify the color.
|
||||
---@param color Color
|
||||
---@return Color
|
||||
function COLOR:ToCartoon(color)
|
||||
local R,G,B = color.r / 255,color.g / 255,color.b / 255
|
||||
local max_gb = max(G,B)
|
||||
local max_rb = max(R,B)
|
||||
local max_rg = max(R,G)
|
||||
|
||||
local red_matter = 1 - max(R - max_gb,0)
|
||||
local green_matter = 1 - max(G - max_rb,0)
|
||||
local blue_matter = 1 - max(B - max_rg,0)
|
||||
|
||||
return Color(R * green_matter * blue_matter * 255,G * red_matter * blue_matter * 255,B * red_matter * green_matter * 255)
|
||||
end
|
||||
|
||||
-- Color functions
|
||||
|
||||
---Returns true if the color is bright. Useful to check if the text infront should be dark.
|
||||
---@return boolean
|
||||
function COLOR:IsBright()
|
||||
return ColorToLuminance(self) >= 127.5
|
||||
end
|
||||
|
||||
---Returns true if the color is bright. Useful to check if the text infront should be bright.
|
||||
---@return boolean
|
||||
function COLOR:IsDark()
|
||||
return ColorToLuminance(self) < 127.5
|
||||
end
|
||||
|
||||
-- ColorRGBExp32
|
||||
local gamma = 2.2
|
||||
local overbrightFactor = 0.5
|
||||
|
||||
-- convert texture to linear 0..1 value
|
||||
local function TexLightToLinear( col, exponent )
|
||||
return col * ( ( 2 ^ exponent ) / 255 )
|
||||
end
|
||||
|
||||
-- linear (0..4) to screen corrected vertex space (0..1?)
|
||||
local function LinearToVertexLight( col )
|
||||
return overbrightFactor * ( col ^ ( 1 / gamma ) )
|
||||
end
|
||||
|
||||
-- https://github.com/ValveSoftware/source-sdk-2013/blob/master/sp/src/utils/vrad/lightmap.cpp#L3551
|
||||
function NikNaks.ColorRGBExp32ToColor( struct )
|
||||
local exponent = struct.exponent
|
||||
local linearColor = {
|
||||
TexLightToLinear( struct.r, exponent ),
|
||||
TexLightToLinear( struct.g, exponent ),
|
||||
TexLightToLinear( struct.b, exponent )
|
||||
}
|
||||
|
||||
local vertexColor = {
|
||||
math.min( LinearToVertexLight( linearColor[1] ), 1 ),
|
||||
math.min( LinearToVertexLight( linearColor[2] ), 1 ),
|
||||
math.min( LinearToVertexLight( linearColor[3] ), 1 )
|
||||
}
|
||||
|
||||
return Color(
|
||||
math.Round( vertexColor[1] * 255 ),
|
||||
math.Round( vertexColor[2] * 255 ),
|
||||
math.Round( vertexColor[3] * 255 ),
|
||||
255
|
||||
)
|
||||
end
|
||||
|
||||
401
addons/niknaks/lua/niknaks/modules/sh_datetime.lua
Normal file
401
addons/niknaks/lua/niknaks/modules/sh_datetime.lua
Normal file
@@ -0,0 +1,401 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
NikNaks.DateTime = {}
|
||||
local localvars, os_time, os_date, rawget, tonumber, getmetatable = {}, os.time, os.date, rawget, tonumber, getmetatable
|
||||
|
||||
-- TimeZone / Date variables
|
||||
do
|
||||
local UTC_DAY = os_date( "%d", 0 ) - os_date( "!%d", 0 )
|
||||
local UTC_Timezone = tonumber( os_date( "%H", 0 ) ) - tonumber( os_date( "!%H", 0 ) )
|
||||
if UTC_DAY == 30 then
|
||||
UTC_Timezone = UTC_Timezone - 24
|
||||
end
|
||||
|
||||
local UTC_Timezone_dst = tonumber( os_date( "%z" ) ) / 100
|
||||
local DaylightsSaving = UTC_Timezone_dst - UTC_Timezone
|
||||
NikNaks.DateTime.dst = DaylightsSaving
|
||||
NikNaks.DateTime.timezone = UTC_Timezone
|
||||
NikNaks.DateTime.timezone_dst = UTC_Timezone_dst
|
||||
end
|
||||
|
||||
local function is_leap_year( year )
|
||||
return year % 4 == 0 and ( year % 100 ~= 0 or year % 400 == 0 )
|
||||
end
|
||||
|
||||
function NikNaks.DateTime.IsLeapYear( year )
|
||||
return is_leap_year( year or NikNaks.DateTime.year )
|
||||
end
|
||||
|
||||
do
|
||||
local months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
||||
|
||||
function NikNaks.DateTime.DaysInMonth( month, year )
|
||||
if month == 2 and is_leap_year( year or NikNaks.DateTime.year ) then
|
||||
return 29
|
||||
end
|
||||
|
||||
return months[month]
|
||||
end
|
||||
|
||||
function NikNaks.DateTime.Calender( year )
|
||||
year = year or NikNaks.DateTime.year
|
||||
local c = {}
|
||||
c.year = year
|
||||
c.month = {}
|
||||
|
||||
for i = 1, 12 do
|
||||
if i == 2 and is_leap_year( year ) then
|
||||
c.month[i] = 29
|
||||
else
|
||||
c.month[i] = months[i]
|
||||
end
|
||||
end
|
||||
|
||||
return c
|
||||
end
|
||||
end
|
||||
|
||||
-- Date variables
|
||||
local function updatedate()
|
||||
local date = string.Explode( ":", os_date( "%H:%M:%S:%d:%m:%Y" ) )
|
||||
NikNaks.DateTime.day = tonumber( date[4] )
|
||||
NikNaks.DateTime.month = tonumber( date[5] )
|
||||
NikNaks.DateTime.year = tonumber( date[6] )
|
||||
|
||||
-- Calculates next cycle
|
||||
local t_seconds = tonumber( date[1] ) * 3600 + tonumber( date[2] ) * 60 + tonumber( date[3] )
|
||||
local nextUpdate = 86400 - t_seconds
|
||||
timer.Create( "NikNaks_DateUpdate", math.max( nextUpdate, 1 ), 1, updatedate )
|
||||
end
|
||||
updatedate()
|
||||
|
||||
-- Branch metatable
|
||||
setmetatable( NikNaks.DateTime, {
|
||||
__index = function( _, v )
|
||||
local l = rawget( localvars, v )
|
||||
return rawget( NikNaks.DateTime, v ) or l and l()
|
||||
end,
|
||||
__call = function( _, var )
|
||||
return NikNaks.DateTime.Get( var )
|
||||
end
|
||||
} )
|
||||
|
||||
local string_to_var
|
||||
do
|
||||
-- Tries to parse hour, minute and seconds
|
||||
local function findTime( str )
|
||||
local h, m, s, ampm = string.match( str:upper(), "([01]?%d):(%d%d?):?(%d*)%s*([AP][M])" )
|
||||
|
||||
if not h then
|
||||
h, m, s = string.match( str, "(%d%d?):(%d%d?):?(%d*)" )
|
||||
end
|
||||
|
||||
if not h then return nil end
|
||||
h = tonumber( h )
|
||||
m = tonumber( m )
|
||||
s = tonumber( s )
|
||||
|
||||
if ampm then
|
||||
if ampm == "AM" then
|
||||
if h == 12 then h = 0 end
|
||||
else
|
||||
if h ~= 12 then h = h + 12 end
|
||||
end
|
||||
end
|
||||
|
||||
return h, m, s
|
||||
end
|
||||
|
||||
-- Tries to parse year, month, day
|
||||
local findDate
|
||||
do
|
||||
local date_tab = {
|
||||
"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"
|
||||
}
|
||||
|
||||
local date_pattern = "[JFMASOND][AEPUCO][NBRYLGPTVC]"
|
||||
local function findMonthNameAndDate( str )
|
||||
if not string.match( str, date_pattern ) then return nil end
|
||||
|
||||
for m_id, date in ipairs( date_tab ) do
|
||||
if string.match( str, date ) then
|
||||
local d = string.match( str, date .. "%a*%s?(%d%d?)" ) or string.match( str, "(%d%d?)%s?" .. date )
|
||||
return m_id, d and tonumber( d ) or 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function findDate( str )
|
||||
-- The year number tent to mess with the rest, if found replace it if found.
|
||||
local fy = true
|
||||
local y = string.match( str, "(%d%d%d%d)" )
|
||||
local m, d
|
||||
if y then
|
||||
str = string.gsub( str, "%d%d%d%d", "", 1 )
|
||||
y = tonumber( y )
|
||||
else
|
||||
-- Check of YY/MM/DD
|
||||
y, m, d = string.match( str, "(%d+)[/%-](%d%d?)[/%-](%d%d?)" )
|
||||
if not y then -- Year must be today
|
||||
y = NikNaks.DateTime.year
|
||||
fy = false
|
||||
else
|
||||
return tonumber( y ), tonumber( m ), tonumber( d )
|
||||
end
|
||||
end
|
||||
|
||||
-- Find MM/DD
|
||||
m, d = string.match( str, "(%d%d?)[/%-](%d%d?)" )
|
||||
if m and d then
|
||||
return y, tonumber( m ), tonumber( d )
|
||||
end
|
||||
|
||||
-- No date found. Try string-scan for month names
|
||||
m, d = findMonthNameAndDate( str:upper() ) -- Try parse letters
|
||||
if m then
|
||||
return y, m, d
|
||||
end
|
||||
|
||||
-- If only a year is given, then return the first day in that year.
|
||||
if fy then
|
||||
return y, 1, 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Tries to parse timezone. Since os.time use the locate time, this will be negative.
|
||||
local function findOffset( str )
|
||||
if str:sub( -1 ) == "Z" then return -NikNaks.DateTime.timezone_dst end
|
||||
local sign, h, m = str:match( "([%-%+])(%d%d?):?(%d?%d?)$" )
|
||||
if sign then
|
||||
return ( tonumber( sign .. h ) + tonumber( sign .. m ) / 60 ) - NikNaks.DateTime.timezone_dst
|
||||
else
|
||||
-- Use local
|
||||
return 0
|
||||
end
|
||||
end
|
||||
|
||||
function string_to_var( str )
|
||||
str = string.Trim( str )
|
||||
|
||||
if #str ~= 4 then
|
||||
local n = string.match( str, "%d+" )
|
||||
if #n == #str then return tonumber( n ) end
|
||||
end
|
||||
|
||||
--[[
|
||||
Sun, 03 Jan 2010 00:00:00 GMT
|
||||
September 26, 2006 12:12 AM
|
||||
2012-10-06T04:13:00+00:00
|
||||
2012/10/6
|
||||
2008-05-01T07:34:42-5:00
|
||||
2008-05-01 7:34:42Z
|
||||
Thu, 01 May 2008 07:34:42 GMT
|
||||
]]
|
||||
local h, m, s = findTime( str )
|
||||
|
||||
-- Get Time & Date
|
||||
local year, month, day = findDate( str )
|
||||
|
||||
-- Find offset
|
||||
local offsetH = findOffset( str ) or 0
|
||||
|
||||
-- Convert to unix
|
||||
return os_time( {
|
||||
day = day or 1,
|
||||
hour = h or 0,
|
||||
min = m or 0,
|
||||
month = month or 1,
|
||||
sec = tonumber( s ) or 0,
|
||||
year = year
|
||||
} ) + offsetH * 3600
|
||||
end
|
||||
end
|
||||
|
||||
--- @class DateTime
|
||||
local datetime_obj = {}
|
||||
datetime_obj.__index = datetime_obj
|
||||
NikNaks.__metatables["DateTime"] = datetime_obj
|
||||
function NikNaks.DateTime.Get( var, t_zone )
|
||||
if not var then
|
||||
var = os_time()
|
||||
else
|
||||
local _type = type( var )
|
||||
if _type == "string" then
|
||||
var = string_to_var( var )
|
||||
elseif _type == "table" then
|
||||
if var.time then
|
||||
var = var.time + os_time()
|
||||
elseif var.unix then
|
||||
var = var.unix
|
||||
else -- Unknown
|
||||
return nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Unable to create
|
||||
if not var then return nil end
|
||||
|
||||
-- Create object and return
|
||||
--- @class DateTime
|
||||
local t = {}
|
||||
t.unix = var
|
||||
t.timezone = t_zone
|
||||
return setmetatable( t, datetime_obj )
|
||||
end
|
||||
|
||||
function datetime_obj:GetUnix()
|
||||
return self.unix
|
||||
end
|
||||
|
||||
function datetime_obj:TimeUntil( var )
|
||||
local unix
|
||||
local _type = type( var )
|
||||
|
||||
if _type == "string" then
|
||||
unix = string_to_var( var )
|
||||
elseif _type == "table" then
|
||||
if var.time then
|
||||
return var.time -- Will always be relative
|
||||
elseif var.unix then
|
||||
unix = var.unix
|
||||
end
|
||||
end
|
||||
|
||||
return NikNaks.TimeDelta( unix - self.unix, tonumber( os.date( "%Y", self.unix ) ) )
|
||||
end
|
||||
|
||||
-- Local variable functions: DateTime.<X>
|
||||
function localvars.now()
|
||||
return NikNaks.DateTime.Get( os_time() )
|
||||
end
|
||||
localvars.today = localvars.now
|
||||
|
||||
function localvars.yesterday()
|
||||
return NikNaks.DateTime.Get( os_time() - NikNaks.TimeDelta.Day )
|
||||
end
|
||||
|
||||
function localvars.tomorrow()
|
||||
return NikNaks.DateTime.Get( os_time() + NikNaks.TimeDelta.Day )
|
||||
end
|
||||
|
||||
--- Returns the time using os.date
|
||||
--- @param format string
|
||||
--- @return string
|
||||
function datetime_obj:ToDate( format )
|
||||
return os_date( format, self.unix )
|
||||
end
|
||||
datetime_obj.__tostring = function( self )
|
||||
return os_date( nil, self.unix )
|
||||
end
|
||||
|
||||
-- Operations
|
||||
function datetime_obj.__sub( a, b )
|
||||
if not getmetatable( a ) then -- A is most likely a number. Number - Obj = TimeDelta
|
||||
return NikNaks.TimeDelta( a - b.unix )
|
||||
elseif not getmetatable( b ) then -- B is most likely a number. Obj - Number = New Obj
|
||||
return NikNaks.DateTime.Get( a.unix - b )
|
||||
else -- Both are objects
|
||||
if a.unix and b.unix then
|
||||
return NikNaks.TimeDelta( a.unix - b.unix )
|
||||
elseif a.unix then
|
||||
return NikNaks.DateTime.Get( a.unix - ( b.time or b ) )
|
||||
elseif b.unix then
|
||||
return NikNaks.DateTime.Get( b.unix - ( a.time or a ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function datetime_obj.__add( a, b )
|
||||
if not getmetatable( a ) then -- A is most likely a number. Number + Obj = New Obj
|
||||
return NikNaks.DateTime.Get( b.unix + a )
|
||||
elseif not getmetatable( b ) then -- B is most likely a number. Obj - Number = New Obj
|
||||
return NikNaks.DateTime.Get( a.unix + b )
|
||||
else -- Both are objects
|
||||
if a.unix and b.unix then -- Get the higest unix-time and add the delta between the two
|
||||
return NikNaks.DateTime.Get( math.max( a.unix, b.unix ) + abs( a.unix - b.unix ) )
|
||||
elseif a.unix then
|
||||
return NikNaks.DateTime.Get( a.unix + ( b.time or b ) )
|
||||
elseif b.unix then
|
||||
return NikNaks.DateTime.Get( b.unix + ( a.time or a ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function datetime_obj.__concat( a, b )
|
||||
return tostring( a ) .. tostring( b )
|
||||
end
|
||||
|
||||
-- Not supported in Gmod!
|
||||
datetime_obj.__shl = datetime_obj.__sub
|
||||
datetime_obj.__shr = datetime_obj.__add
|
||||
|
||||
-- Sadly Lua doesn't support mixed types for compare-operations
|
||||
function datetime_obj.__eq( a, b )
|
||||
return a.unix == b.unix
|
||||
end
|
||||
|
||||
function datetime_obj.__lt( a, b )
|
||||
return a.unix < b.unix
|
||||
end
|
||||
|
||||
function datetime_obj.__le( a, b )
|
||||
return a.unix <= b.unix
|
||||
end
|
||||
|
||||
-- TimeDelta functions
|
||||
for key, var in pairs( NikNaks.TimeDelta ) do
|
||||
datetime_obj["Add" .. key .. "s"] = function( self, num )
|
||||
self.unix = self.unix + num * var
|
||||
return self
|
||||
end
|
||||
|
||||
datetime_obj["Remove" .. key .. "s"] = function( self, num )
|
||||
self.unix = self.unix - num * var
|
||||
return self
|
||||
end
|
||||
|
||||
datetime_obj["Sub" .. key .. "s"] = datetime_obj["Remove" .. key .. "s"]
|
||||
end
|
||||
|
||||
-- DateTime string debug test
|
||||
if true then return end
|
||||
local t = { "Sun, 01 Sep 2022 00:12:00",
|
||||
"September 01, 2022 12:12 AM",
|
||||
"2022-09-01T00:12:00+02:00",
|
||||
"2022/09/1",
|
||||
"2022-09-01T07:12:00-5:00",
|
||||
"2022-09-01 02:12:00Z",
|
||||
"Thu, 01 Sep 2022 00:12:00" }
|
||||
|
||||
function ParseTest()
|
||||
for _, str in ipairs( t ) do
|
||||
print( str .. string.rep( " ", 30 - #str ), "=>", NikNaks.DateTime.Get( str ) )
|
||||
end
|
||||
end
|
||||
|
||||
function SpeedTest()
|
||||
local n = 20000 * #t
|
||||
local s = SysTime()
|
||||
|
||||
for _, str in ipairs( t ) do
|
||||
for _ = 1, 20000 do
|
||||
NikNaks.DateTime.Get( str )
|
||||
end
|
||||
end
|
||||
|
||||
print( string.format( n .. " took: %fs", SysTime() - s ) )
|
||||
end
|
||||
68
addons/niknaks/lua/niknaks/modules/sh_enums.lua
Normal file
68
addons/niknaks/lua/niknaks/modules/sh_enums.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
|
||||
-- Globals
|
||||
NikNaks.vector_zero = Vector( 0, 0, 0 )
|
||||
NikNaks.vector_down = Vector( 0, 0, -1 )
|
||||
|
||||
NikNaks.angle_up = vector_up:Angle()
|
||||
NikNaks.angle_down = NikNaks.vector_down:Angle()
|
||||
|
||||
-- CAP
|
||||
NikNaks.CAP_MOVE_GROUND = 0x01 -- walk/run
|
||||
NikNaks.CAP_MOVE_JUMP = 0x02 -- jump/leap
|
||||
NikNaks.CAP_MOVE_FLY = 0x04 -- can fly, move all around
|
||||
NikNaks.CAP_MOVE_CLIMB = 0x08 -- climb ladders
|
||||
--CAP_MOVE_SWIM / bits_BUILD_GIVEWAY? = 0x10 -- navigate in water // Removed by Valve: UNDONE - not yet implemented
|
||||
--CAP_MOVE_CRAWL = 0x20 -- crawl // Removed by Valve: UNDONE - not yet implemented
|
||||
|
||||
-- Nodes
|
||||
NikNaks.NODE_TYPE_INVALID =-1 -- Any nodes not matching these
|
||||
NikNaks.NODE_TYPE_ANY = 0
|
||||
NikNaks.NODE_TYPE_DELETED = 1 -- Internal in hammer?
|
||||
NikNaks.NODE_TYPE_GROUND = 2
|
||||
NikNaks.NODE_TYPE_AIR = 3
|
||||
NikNaks.NODE_TYPE_CLIMB = 4
|
||||
--NODE_TYPE_WATER = 5 -- Unused? I have no idea, since CAP_MOVE_SWIM seems unused and the fish use air nodes.
|
||||
|
||||
-- Hulls
|
||||
NikNaks.HULL_HUMAN = 0 -- 30w, 73t // Combine, Stalker, Zombie...
|
||||
NikNaks.HULL_SMALL_CENTERED = 1 -- 40w, 40t // Scanner
|
||||
NikNaks.HULL_WIDE_HUMAN = 2 -- ? // Vortigaunt
|
||||
NikNaks.HULL_TINY = 3 -- 24w, 24t // Headcrab
|
||||
NikNaks.HULL_WIDE_SHORT = 4 -- ? // Bullsquid
|
||||
NikNaks.HULL_MEDIUM = 5 -- 36w, 65t // Cremator
|
||||
NikNaks.HULL_TINY_CENTERED = 6 -- 16w, 8t // Manhack
|
||||
NikNaks.HULL_LARGE = 7 -- 80w, 100t // Antlion Guard
|
||||
NikNaks.HULL_LARGE_CENTERED = 8 -- ? // Mortar Synth / Strider
|
||||
NikNaks.HULL_MEDIUM_TALL = 9 -- 36w, 100t // Hunter
|
||||
NikNaks.NUM_HULLS = 10
|
||||
-- HULL_NONE = 11 Used internal I think.
|
||||
|
||||
-- Errors
|
||||
NikNaks.BSP_ERROR_FILECANTOPEN = 0
|
||||
NikNaks.BSP_ERROR_NOT_BSP = 1
|
||||
NikNaks.BSP_ERROR_TOO_NEW = 2
|
||||
NikNaks.BSP_ERROR_FILENOTFOUND = 3
|
||||
NikNaks.AIN_ERROR_VERSIONNUM = 4
|
||||
NikNaks.AIN_ERROR_ZONEPATCH = 5 -- This error is thrown when the AIN-parser repairs the data. It will still return the data successfully.
|
||||
|
||||
-- naksbot
|
||||
NikNaks.PATHTYPE_NONE=-1 -- In case there are no path-options on the map
|
||||
NikNaks.PATHTYPE_AIN = 0
|
||||
NikNaks.PATHTYPE_NAV = 1
|
||||
NikNaks.PATHTYPE_NIKNAV = 2
|
||||
|
||||
-- How the NPC should move
|
||||
NikNaks.PATHMOVETYPE_GROUND = 0
|
||||
NikNaks.PATHMOVETYPE_FLY = 1
|
||||
58
addons/niknaks/lua/niknaks/modules/sh_file_extended.lua
Normal file
58
addons/niknaks/lua/niknaks/modules/sh_file_extended.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
|
||||
local FILE = FindMetaTable( "File" )
|
||||
|
||||
-- File functions
|
||||
|
||||
--- Returns true if the file is valid and can be written to.
|
||||
--- @return boolean
|
||||
function FILE:IsValid()
|
||||
return tostring( self ) ~= "[NULL File]"
|
||||
end
|
||||
|
||||
--- Writes a vector to the file.
|
||||
--- @param vector Vector
|
||||
function FILE:WriteVector( vector )
|
||||
self:WriteFloat( vector.x )
|
||||
self:WriteFloat( vector.y )
|
||||
self:WriteFloat( vector.z )
|
||||
end
|
||||
|
||||
--- Reads a vector from the file.
|
||||
--- @return Vector
|
||||
function FILE:ReadVector()
|
||||
return Vector( self:ReadFloat(), self:ReadFloat(), self:ReadFloat() )
|
||||
end
|
||||
|
||||
NikNaks.file = {}
|
||||
--- Same as file.Write, but will automatically create folders and return true if successful.
|
||||
--- @param fileName string
|
||||
--- @param contents string
|
||||
--- @return boolean
|
||||
function NikNaks.file.WriteEx( fileName, contents )
|
||||
local a = string.Explode( "/", fileName )
|
||||
assert( #a <= 10, "Unable to create an unreasonable array of folders!" )
|
||||
|
||||
if #a > 1 then
|
||||
file.CreateDir( string.GetPathFromFilename( fileName ) )
|
||||
end
|
||||
|
||||
local f = file.Open( fileName, "wb", "DATA" )
|
||||
if not f then return false end
|
||||
|
||||
f:Write( contents )
|
||||
f:Close()
|
||||
|
||||
return true
|
||||
end
|
||||
69
addons/niknaks/lua/niknaks/modules/sh_model_extended.lua
Normal file
69
addons/niknaks/lua/niknaks/modules/sh_model_extended.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
local NikNaks = NikNaks
|
||||
do
|
||||
local file_Open, file_Exists = file.Open, file.Exists
|
||||
local cache = {}
|
||||
|
||||
--- Returns the model's hull size.
|
||||
--- @param name string
|
||||
--- @return Vector MinVec
|
||||
--- @return Vector MaxVec
|
||||
function NikNaks.ModelSize( name )
|
||||
if cache[name] then
|
||||
return Vector( cache[name][1] ), Vector( cache[name][2] )
|
||||
end
|
||||
|
||||
if not file_Exists( name, "GAME" ) then
|
||||
cache[name] = { NikNaks.vector_zero, NikNaks.vector_zero }
|
||||
return Vector( cache[name][1] ), Vector( cache[name][2] )
|
||||
end
|
||||
|
||||
local f = file_Open( name, "r", "GAME" )
|
||||
|
||||
f:Seek( 104 )
|
||||
|
||||
local hullMin = Vector( f:ReadFloat(), f:ReadFloat(), f:ReadFloat() )
|
||||
local hullMax = Vector( f:ReadFloat(), f:ReadFloat(), f:ReadFloat() )
|
||||
|
||||
f:Close()
|
||||
|
||||
cache[name] = { hullMin, hullMax }
|
||||
return Vector( hullMin ), Vector( hullMax )
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local util_GetModelMeshes, Material = util.GetModelMeshes, Material
|
||||
|
||||
--- Returns the materials used for this model. This can be expensive, so cache the result.
|
||||
--- @param name any
|
||||
--- @param lod? number
|
||||
--- @param bodygroupMask? number
|
||||
--- @return table
|
||||
function NikNaks.ModelMaterials( name, lod, bodygroupMask )
|
||||
local data = util_GetModelMeshes( name, lod or 0, bodygroupMask or 0 )
|
||||
if not data then return {} end
|
||||
|
||||
local t = {}
|
||||
for i = 1, #data do
|
||||
local mat = data[i]["material"]
|
||||
|
||||
if mat then
|
||||
table.insert( t, Material( mat ) )
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
||||
end
|
||||
end
|
||||
438
addons/niknaks/lua/niknaks/modules/sh_pathfind_module.lua
Normal file
438
addons/niknaks/lua/niknaks/modules/sh_pathfind_module.lua
Normal file
@@ -0,0 +1,438 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
local NikNaks = NikNaks
|
||||
local CurTime, setmetatable, IsValid = CurTime, setmetatable, IsValid
|
||||
local min, max, cos = math.min, math.max, math.cos
|
||||
|
||||
--- @class LPathFollower
|
||||
--- @field _segments LPathFollowerSegment[]
|
||||
local meta = {}
|
||||
meta.__index = meta
|
||||
meta.__tostring = function( self )
|
||||
return "LPathFollower Age: " .. self:GetAge()
|
||||
end
|
||||
NikNaks.__metatables["LPathFollower"] = meta
|
||||
|
||||
--[[
|
||||
t._segments = {}
|
||||
t._entity = nil -- Overrides the last position in the segments
|
||||
t._age = CurTime()
|
||||
|
||||
]]
|
||||
--- Creates an empty path-follower
|
||||
--- @param start_pos Vector
|
||||
--- @return LPathFollower
|
||||
function meta.CreatePathFollower( start_pos )
|
||||
--- @class LPathFollower
|
||||
local t = {}
|
||||
t._segments = {}
|
||||
t._start = start_pos
|
||||
t._length = 0
|
||||
t._age = CurTime()
|
||||
t._valid = true
|
||||
t._cursor = 1
|
||||
t._cursor_dis = 0
|
||||
t._cursor_lef = 0
|
||||
|
||||
return setmetatable( t, meta )
|
||||
end
|
||||
|
||||
--- Adds a segment.
|
||||
--- @param from Vector
|
||||
--- @param to Vector
|
||||
--- @param curvature number
|
||||
--- @param move_type number
|
||||
--- @return LPathFollowerSegment
|
||||
function meta:AddSegment( from, to, curvature, move_type )
|
||||
--- @class LPathFollowerSegment
|
||||
local seg = {}
|
||||
seg.curvature = curvature or 0
|
||||
seg.move_type = move_type or 1
|
||||
seg.distanceFromStart = from:Distance( to )
|
||||
|
||||
local n = ( to - from ):GetNormalized()
|
||||
seg.forward = n
|
||||
seg.yaw = ( -n ):Angle().y
|
||||
|
||||
if n.z > 0.7 or n.z < -0.7 then
|
||||
self.how = 9
|
||||
else
|
||||
if n.y < -0.5 then -- North
|
||||
seg.how = 0
|
||||
elseif n.y > 0.5 then -- South
|
||||
seg.how = 2
|
||||
elseif n.x > 0.5 then -- East
|
||||
seg.how = 1
|
||||
else -- West
|
||||
seg.how = 3
|
||||
end
|
||||
end
|
||||
|
||||
seg.length = from:Distance( to )
|
||||
seg.s_lengh = self._length
|
||||
seg.pos = to
|
||||
--seg.ladder
|
||||
--seg.node
|
||||
--seg.area
|
||||
--seg.nna
|
||||
|
||||
self._length = self._length + seg.length
|
||||
self._segments[#self._segments + 1] = seg
|
||||
return seg
|
||||
end
|
||||
|
||||
-- Default easy functions
|
||||
do
|
||||
--- Returns the length of the path.
|
||||
function meta:GetLength()
|
||||
return self._length or 0
|
||||
end
|
||||
|
||||
--- Returns the first segment.
|
||||
function meta:FirstSegment()
|
||||
return self._segments[1]
|
||||
end
|
||||
|
||||
--- Returns the last segment.
|
||||
function meta:LastSegment()
|
||||
return self._segments[#self._segments]
|
||||
end
|
||||
|
||||
--- Returns all segments.
|
||||
function meta:GetAllSegments()
|
||||
return self._segments
|
||||
end
|
||||
|
||||
--- Returns the age of the path.
|
||||
function meta:GetAge()
|
||||
return CurTime() - self._age
|
||||
end
|
||||
|
||||
--- Resets the age of the path.
|
||||
function meta:ResetAge()
|
||||
self._age = CurTime()
|
||||
end
|
||||
|
||||
--- Returns true if the path is valid.
|
||||
function meta:IsValid()
|
||||
return self._valid or false
|
||||
end
|
||||
|
||||
--- Invalidates the path.
|
||||
function meta:Invalidate()
|
||||
self._valid = false
|
||||
end
|
||||
|
||||
--- Returns the starting position of the path.
|
||||
function meta:GetStart()
|
||||
return self._start
|
||||
end
|
||||
|
||||
--- Returns the ending position of the path (Note, will update if the target is an entity).
|
||||
function meta:GetEnd()
|
||||
if IsValid( self.target_ent ) then
|
||||
return self.target_ent:GetPos()
|
||||
end
|
||||
|
||||
return self._segments[#self._segments].pos
|
||||
end
|
||||
end
|
||||
|
||||
-- Cursor
|
||||
local findClosestSeg
|
||||
do
|
||||
--- Returns the cursor position.
|
||||
--- @return number
|
||||
--- @return number
|
||||
local function findCursor( self, distance )
|
||||
local q = self._segments
|
||||
distance = min( self._length, distance )
|
||||
|
||||
for i = 1, #q do
|
||||
if distance <= q[i].s_lengh + q[i].length then
|
||||
return i, distance - ( q[i].s_lengh + q[i].length )
|
||||
end
|
||||
end
|
||||
|
||||
return 1, distance
|
||||
end
|
||||
|
||||
--- Returns the closest segment and its index.
|
||||
--- @param position Vector
|
||||
--- @return LPathFollowerSegment
|
||||
--- @return number
|
||||
findClosestSeg = function( self, position )
|
||||
local c, d, q
|
||||
|
||||
for i, seg in pairs( self._segments ) do
|
||||
local dis = seg.pos:DistToSqr( position )
|
||||
if not c or c > dis then
|
||||
c = dis
|
||||
d = seg
|
||||
q = i
|
||||
end
|
||||
end
|
||||
|
||||
return d, q
|
||||
end
|
||||
|
||||
--- Returns the closest position along the path to said position.
|
||||
--- @param position Vector
|
||||
--- @return Vector
|
||||
local function findClosestBetween( A, dir, position, maxLength )
|
||||
local v = position - A
|
||||
local d = v:Dot( dir )
|
||||
return A + dir * max( 0, min( d, maxLength ) )
|
||||
end
|
||||
|
||||
--- Returns the position on the path by given distance
|
||||
--- @param distance number
|
||||
--- @return Vector
|
||||
function meta:GetPositionOnPath( distance )
|
||||
local seg_id, lef = findCursor( self, distance )
|
||||
local t = self._segments
|
||||
local seg = t[seg_id]
|
||||
return seg.pos + seg.forward * lef
|
||||
end
|
||||
|
||||
--- Returns the closest position along the path to said position.
|
||||
--- @param position Vector
|
||||
--- @return Vector
|
||||
function meta:GetClosestPosition( position )
|
||||
-- Locate the closest points
|
||||
local seg, seg_id = findClosestSeg( self, position )
|
||||
if not seg then return self:GetStart() end -- Fallback to the start pos
|
||||
|
||||
local max_seg = #self._segments
|
||||
|
||||
if seg_id <= 1 then
|
||||
local n_seg = self._segments[seg_id + 1]
|
||||
local v1 = findClosestBetween( self:GetStart(), seg.forward, position, seg.length )
|
||||
local v2 = findClosestBetween( seg.pos, n_seg.forward, position, n_seg.length )
|
||||
if v1:DistToSqr( position ) > v2:DistToSqr( position ) then
|
||||
return v2
|
||||
else
|
||||
return v1
|
||||
end
|
||||
elseif seg_id >= max_seg then
|
||||
local s_seg = self._segments[max_seg - 1]
|
||||
return findClosestBetween( s_seg.pos, seg.forward, position, seg.length )
|
||||
else
|
||||
local p_seg = self._segments[seg_id - 1]
|
||||
local n_seg = self._segments[seg_id + 1]
|
||||
local v1 = findClosestBetween( p_seg.pos, seg.forward, position, seg.length )
|
||||
local v2 = findClosestBetween( seg.pos, n_seg.forward, position, n_seg.length )
|
||||
if v1:DistToSqr( position ) > v2:DistToSqr( position ) then
|
||||
return v2
|
||||
else
|
||||
return v1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Moves the cursor to the start of the path.
|
||||
function meta:MoveCursorToStart()
|
||||
self._cursor_dis = 0
|
||||
self._cursor_lef = 0
|
||||
self._cursor = 1
|
||||
end
|
||||
|
||||
--- Moves the cursor to the end of the path.
|
||||
function meta:MoveCursorToEnd()
|
||||
self._cursor_dis = self:GetLength()
|
||||
self._cursor = #self._segments
|
||||
self._cursor_lef = self._segments[self._cursor].length
|
||||
end
|
||||
|
||||
--- Returns the cursor progress along the path
|
||||
--- @return number
|
||||
function meta:GetCursorPosition()
|
||||
return self._cursor_dis
|
||||
end
|
||||
|
||||
--- Moves the cursor to said distance.
|
||||
--- @param distance number
|
||||
function meta:MoveCursorTo( distance )
|
||||
self._cursor_dis = distance
|
||||
local seg_id, lef = findCursor( self, distance )
|
||||
self._cursor = seg_id
|
||||
self._cursor_lef = lef
|
||||
end
|
||||
|
||||
--- Moves the cursor said distance
|
||||
--- @param distance number
|
||||
function meta:MoveCursor( distance )
|
||||
self._cursor_dis = self._cursor_dis + distance
|
||||
local nd = self._cursor_lef + distance
|
||||
local seg = self._segments[self._cursor]
|
||||
if nd < 0 or nd > seg.length then -- New segment
|
||||
local seg_id, lef = findCursor( self, self._cursor_dis )
|
||||
self._cursor = seg_id
|
||||
self._cursor_lef = lef
|
||||
else
|
||||
self._cursor_lef = nd
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns the closest sequence.
|
||||
--- @param position Vector
|
||||
--- @return LPathFollowerSegment
|
||||
--- @return number
|
||||
function meta:FindClosestSeg( position )
|
||||
return findClosestSeg( self, position )
|
||||
end
|
||||
|
||||
--- Returns the distance from the path.
|
||||
--- @param position Vector
|
||||
--- @return number
|
||||
function meta:FindDistanceFromPath( position )
|
||||
return self:GetClosestPosition( position ):Distance( position )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- NPC stuff
|
||||
do
|
||||
--[[
|
||||
By default in Gmod, you're reuired to create a pathfind object, and then compute it.
|
||||
However I've found many situations where I could reuse a path. I.e a group of NPC's.
|
||||
So to stick to the closest "Gmod way", path:Update can be called on multiple entities and got a goal argument.
|
||||
|
||||
C function here: https://github.com/Joshua-Ashton/Source-PlusPlus/blob/4056819cea889d73626a1cbc09518b2f8ba5dda4/src/game/shared/cstrike/bot/nav_path.cpp#L472
|
||||
]]
|
||||
function meta:Update( ent, toleranceSqrt )
|
||||
-- Make sure it is a valid entity and it has loco
|
||||
if not IsValid( ent ) or not ent.loco then return true end
|
||||
|
||||
local entPos = ent:GetPos()
|
||||
local seq, id = nil, ent._cursor
|
||||
|
||||
-- Located the closest segment, and use that
|
||||
if not id then
|
||||
seq, id = findClosestSeg( self, entPos )
|
||||
ent._cursor = id
|
||||
else
|
||||
seq = self._segments[id]
|
||||
end
|
||||
|
||||
if not seq then return true end -- No segment, we must have reached the end
|
||||
|
||||
local goal = seq.pos
|
||||
if goal:DistToSqr( entPos ) < toleranceSqrt then
|
||||
if id >= #self._segments then return true end -- Reached the end
|
||||
id = id + 1
|
||||
seq = self._segments[id]
|
||||
goal = seq.pos
|
||||
ent._cursor = ent._cursor + 1
|
||||
end
|
||||
|
||||
ent.loco:Approach( goal, 1 )
|
||||
ent.loco:FaceTowards( goal )
|
||||
ent:SetAngles( Angle( 0, ( ent:GetPos() - goal ):Angle().y, 0 ) )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
-- NET
|
||||
do
|
||||
function NikNaks.net.WritePath( path )
|
||||
local n = #path._segments
|
||||
net.WriteUInt( n, 16 )
|
||||
net.WriteFloat( path._age )
|
||||
net.WriteVector( path._start )
|
||||
|
||||
for i = 1, n do
|
||||
local seg = path._segments[i]
|
||||
net.WriteVector( seg.pos - seg.length * seg.forward )
|
||||
net.WriteVector( seg.pos )
|
||||
net.WriteFloat( seg.curvature )
|
||||
net.WriteUInt( seg.move_type, 8 )
|
||||
end
|
||||
end
|
||||
|
||||
function NikNaks.net.ReadPath()
|
||||
local n = net.ReadUInt( 16 )
|
||||
local age = net.ReadFloat()
|
||||
local path = meta.CreatePathFollower( net.ReadVector() )
|
||||
path._age = age
|
||||
|
||||
for _ = 1, n do
|
||||
path:AddSegment( net.ReadVector(), net.ReadVector(), net.ReadFloat(), net.ReadUInt( 8 ) )
|
||||
end
|
||||
|
||||
return path
|
||||
end
|
||||
end
|
||||
|
||||
-- Debug
|
||||
do
|
||||
local mat_goal = Material("editor/assault_rally")
|
||||
local mat_start = Material("effects/powerup_agility_hud")
|
||||
|
||||
local tMat = Material("effects/bluelaser1")
|
||||
local m = Material("hud/arrow_big")
|
||||
|
||||
local point = Material("hud/freezecam_callout_arrow")
|
||||
local cir = Material("hud/cart_point_neutral_opaque")
|
||||
local mat_arrow = Material("vgui/glyph_expand")
|
||||
|
||||
local mat_solider = Material("hud/bomb_carried")
|
||||
local jump_man = Material("hud/death_wheel_1")
|
||||
|
||||
local col_walk = color_white
|
||||
local col_climb = Color(155,55,155)
|
||||
local col_fly = Color(55,55,255)
|
||||
local col_jump = Color(125, 55, 255)
|
||||
|
||||
local mov = bit.bor(NikNaks.CAP_MOVE_GROUND, NikNaks.CAP_MOVE_CLIMB, NikNaks.CAP_MOVE_JUMP)
|
||||
local point_b = Material("hud/cart_point_blue")
|
||||
local point_c = Material("hud/expanding_vert_middle_blue_bg")
|
||||
|
||||
function meta:DebugRender()
|
||||
local l
|
||||
for i, seg in ipairs(self:GetAllSegments()) do
|
||||
render.SetMaterial(cir)
|
||||
render.DrawSprite( seg.pos, 16, 16 )
|
||||
if l then
|
||||
local col = color_white
|
||||
if seg.move_type == NikNaks.CAP_MOVE_CLIMB then
|
||||
col = col_climb
|
||||
elseif seg.move_type == NikNaks.CAP_MOVE_FLY then
|
||||
col = col_fly
|
||||
elseif seg.move_type == NikNaks.CAP_MOVE_JUMP then
|
||||
col = col_jump
|
||||
end
|
||||
local d = seg.pos:Distance(l)
|
||||
local n = (SysTime() * 2) % 1
|
||||
m:SetVector("$color",Vector(col.r,col.g,col.b) / 255)
|
||||
render.SetMaterial(m)
|
||||
render.DrawBeam( seg.pos, l, 30, n, d / 40 + n, col )
|
||||
render.SetMaterial(point)
|
||||
render.DrawBeam( l + seg.forward * 10,l + seg.forward * 30, 20, 0, 1)
|
||||
end
|
||||
l = seg.pos
|
||||
end
|
||||
|
||||
local num = max(1, self:GetLength() / 800)
|
||||
for i = 1, num do
|
||||
local dis = (CurTime() * 100 + i * 800) % self:GetLength()
|
||||
local pos = self:GetPositionOnPath( dis )
|
||||
local q = 1 + cos(SysTime() * 10 + i) * 0.1
|
||||
render.SetMaterial(mat_arrow)
|
||||
render.DrawBeam( pos + Vector(0,0,16), pos, 16, q - 1, q, color_white )
|
||||
render.SetMaterial(mat_solider)
|
||||
render.DrawSprite( pos + Vector(0,0,20), 16, 16 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
290
addons/niknaks/lua/niknaks/modules/sh_timedelta.lua
Normal file
290
addons/niknaks/lua/niknaks/modules/sh_timedelta.lua
Normal file
@@ -0,0 +1,290 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
-- License: https://github.com/Nak2/NikNaks/blob/main/LICENSE
|
||||
|
||||
--- @class TimeDelta
|
||||
--- @operator add:TimeDelta|DateTime
|
||||
--- @operator sub:TimeDelta|DateTime
|
||||
--- @operator mul:TimeDelta
|
||||
--- @operator div:TimeDelta
|
||||
--- @operator pow:TimeDelta
|
||||
--- @operator mod:TimeDelta
|
||||
local meta = {}
|
||||
meta.__index = meta
|
||||
meta.MetaName = "TimeDelta"
|
||||
NikNaks.__metatables["TimeDelta"] = meta
|
||||
|
||||
--- @class TimeDeltaModule
|
||||
--- @operator call:TimeDelta
|
||||
local TimeDelta = {}
|
||||
TimeDelta.Milisecond = 0.001
|
||||
TimeDelta.Second = 1
|
||||
TimeDelta.Minute = 60
|
||||
TimeDelta.Hour = 3600
|
||||
TimeDelta.Day = 86400
|
||||
TimeDelta.Week = 604800
|
||||
TimeDelta.Year = 31536000
|
||||
TimeDelta.Decade = 315359654
|
||||
TimeDelta.Century = 3153596543
|
||||
TimeDelta._steps = { "Year", "Day", "Hour", "Minute", "Second", "Milisecond" }
|
||||
|
||||
setmetatable( TimeDelta, {
|
||||
__index = TimeDelta,
|
||||
__call = function( _, time )
|
||||
return setmetatable( { time = time }, meta )
|
||||
end
|
||||
} )
|
||||
|
||||
NikNaks.TimeDelta = TimeDelta
|
||||
|
||||
|
||||
do
|
||||
local abs = math.abs
|
||||
local floor = math.floor
|
||||
local steps = TimeDelta._steps
|
||||
|
||||
--- Returns the time as a table
|
||||
--- @return table
|
||||
function meta:ToTable()
|
||||
if self._tab then return self._tab end
|
||||
|
||||
local t = {}
|
||||
local num = abs( self.time )
|
||||
local f = self.time < 0 and -1 or 1
|
||||
local isLeapYear = NikNaks.DateTime.IsLeapYear
|
||||
|
||||
local function processStep( i )
|
||||
local step = steps[i]
|
||||
local value = steps[step]
|
||||
|
||||
-- Leap year
|
||||
if num < value then return end
|
||||
|
||||
if i == 1 then -- Since years aren't whole numbers, we need to round the tiniest amount, or floor is going to count down.
|
||||
local n = 0
|
||||
local y = TimeDelta.Year
|
||||
local v = isLeapYear( y ) and 31622400 or 31536000
|
||||
|
||||
while num >= v do
|
||||
local q = 1 * f
|
||||
if num >= v then
|
||||
n = n + 1
|
||||
num = num - v
|
||||
y = y + q
|
||||
v = isLeapYear( y ) and 31622400 or 31536000
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
t[s] = n * f
|
||||
else
|
||||
local n = floor( num / v )
|
||||
t[s] = n * f
|
||||
num = num - v * n
|
||||
end
|
||||
end
|
||||
|
||||
for i = 1, #steps do
|
||||
processStep( i )
|
||||
end
|
||||
|
||||
self._tab = t
|
||||
return t
|
||||
end
|
||||
end
|
||||
|
||||
-- Getters
|
||||
do
|
||||
--- Generic getter function to get the time amount of the given time type
|
||||
--- @param key string The name of the time type to get
|
||||
--- @return number
|
||||
function meta:_getter( key )
|
||||
return self.time / TimeDelta[key]
|
||||
end
|
||||
|
||||
function meta:GetMiliseconds() return self:_getter( "Milisecond" ) end
|
||||
function meta:GetSeconds() return self:_getter( "Second" ) end
|
||||
function meta:GetMinutes() return self:_getter( "Minute" ) end
|
||||
function meta:GetHours() return self:_getter( "Hour" ) end
|
||||
function meta:GetDays() return self:_getter( "Day" ) end
|
||||
function meta:GetWeeks() return self:_getter( "Week" ) end
|
||||
function meta:GetMonths() return self:_getter( "Month" ) end
|
||||
function meta:GetYears() return self:_getter( "Year" ) end
|
||||
function meta:GetDecades() return self:_getter( "Decade" ) end
|
||||
function meta:GetCenturies() return self:_getter( "Century" ) end
|
||||
end
|
||||
|
||||
-- Adders
|
||||
do
|
||||
--- Generic adder function to add given time amount to the TimeDelta
|
||||
--- @param key string The name of the time type to add
|
||||
--- @param num number The amount of time to add
|
||||
--- @return self TimeDelta
|
||||
function meta:_adder( key, num )
|
||||
self.time = self.time + ( num * TimeDelta[key] )
|
||||
self._tab = nil
|
||||
return self
|
||||
end
|
||||
|
||||
--- @param n number
|
||||
function meta:AddMiliseconds( n ) return self:_adder( "Milisecond", n ) end --- @param n number
|
||||
function meta:AddSeconds( n ) return self:_adder( "Second", n ) end --- @param n number
|
||||
function meta:AddMinutes( n ) return self:_adder( "Minute", n ) end --- @param n number
|
||||
function meta:AddHours( n ) return self:_adder( "Hour", n ) end --- @param n number
|
||||
function meta:AddDays( n ) return self:_adder( "Day", n ) end --- @param n number
|
||||
function meta:AddWeeks( n ) return self:_adder( "Week", n ) end --- @param n number
|
||||
function meta:AddMonths( n ) return self:_adder( "Month", n ) end --- @param n number
|
||||
function meta:AddYears( n ) return self:_adder( "Year", n ) end --- @param n number
|
||||
function meta:AddDecades( n ) return self:_adder( "Decade", n ) end --- @param n number
|
||||
function meta:AddCenturies( n ) return self:_adder( "Century", n ) end --- @param n number
|
||||
end
|
||||
|
||||
-- Subtractors
|
||||
do
|
||||
--- Generic subtractor function to subtract given time amount from the TimeDelta
|
||||
--- @param key string The name of the time type to subtract
|
||||
--- @param num number The amount of time to subtract
|
||||
--- @return self TimeDelta
|
||||
function meta:_subtractor( key, num )
|
||||
self.time = self.time - ( num * TimeDelta[key] )
|
||||
self._tab = nil
|
||||
return self
|
||||
end
|
||||
|
||||
function meta:SubMiliseconds( n ) return self:_subtractor( "Milisecond", n ) end --- @param n number
|
||||
function meta:SubSeconds( n ) return self:_subtractor( "Second", n ) end --- @param n number
|
||||
function meta:SubMinutes( n ) return self:_subtractor( "Minute", n ) end --- @param n number
|
||||
function meta:SubHours( n ) return self:_subtractor( "Hour", n ) end --- @param n number
|
||||
function meta:SubDays( n ) return self:_subtractor( "Day", n ) end --- @param n number
|
||||
function meta:SubWeeks( n ) return self:_subtractor( "Week", n ) end --- @param n number
|
||||
function meta:SubMonths( n ) return self:_subtractor( "Month", n ) end --- @param n number
|
||||
function meta:SubYears( n ) return self:_subtractor( "Year", n ) end --- @param n number
|
||||
function meta:SubDecades( n ) return self:_subtractor( "Decade", n ) end --- @param n number
|
||||
function meta:SubCenturies( n ) return self:_subtractor( "Century", n ) end --- @param n number
|
||||
end
|
||||
|
||||
-- ToString
|
||||
do
|
||||
local abs = math.abs
|
||||
local steps = TimeDelta._steps
|
||||
|
||||
function meta:__tostring()
|
||||
local str
|
||||
local si = 0
|
||||
local tab = self:ToTable()
|
||||
local kv = #table.GetKeys( tab )
|
||||
|
||||
for i = 1, #steps do
|
||||
local step = steps[i]
|
||||
|
||||
if tab[step] then
|
||||
si = si + 1
|
||||
|
||||
local number = abs( tab[step] )
|
||||
local middle = ( si == kv and " and " or ", " )
|
||||
step = number == 1 and step or step .. "s"
|
||||
|
||||
if not str then
|
||||
str = number .. " " .. step
|
||||
else
|
||||
str = str .. middle .. number .. " " .. step
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return str or "nil"
|
||||
end
|
||||
end
|
||||
|
||||
function meta:IsNegative()
|
||||
return self.time < 0
|
||||
end
|
||||
|
||||
function meta:IsPositive()
|
||||
return self.time >= 0
|
||||
end
|
||||
|
||||
-- Operations
|
||||
do
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta|DateTime
|
||||
function meta:__add( b )
|
||||
if isnumber( b ) then
|
||||
return TimeDelta( self.time + b )
|
||||
end
|
||||
|
||||
if self.unix and b.time then
|
||||
return NikNaks.DateTime( self.unix + b.time )
|
||||
elseif b.unix and self.time then
|
||||
return NikNaks.DateTime( b.unix + self.time )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta|DateTime
|
||||
function meta:__sub( b )
|
||||
if isnumber( b ) then
|
||||
return TimeDelta( self.time - b )
|
||||
end
|
||||
|
||||
if self.unix and b.time then
|
||||
return NikNaks.DateTime( self.unix - b.time )
|
||||
elseif b.unix and self.time then
|
||||
return NikNaks.DateTime( b.unix - self.time )
|
||||
end
|
||||
end
|
||||
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta
|
||||
function meta:__mul( b )
|
||||
b = isnumber( b ) and b or b.time
|
||||
return TimeDelta( self.time * b )
|
||||
end
|
||||
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta
|
||||
function meta:__div( b )
|
||||
b = isnumber( b ) and b or b.time
|
||||
return TimeDelta( self.time / b )
|
||||
end
|
||||
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta
|
||||
function meta:__pow( b )
|
||||
b = isnumber( b ) and b or b.time
|
||||
return TimeDelta( self.time ^ b )
|
||||
end
|
||||
|
||||
--- @param b TimeDelta|number
|
||||
--- @return TimeDelta
|
||||
function meta:__mod( b )
|
||||
b = isnumber( b ) and b or b.time
|
||||
return TimeDelta( self.time % b )
|
||||
end
|
||||
|
||||
--- @param b TimeDelta
|
||||
function meta:__eq( b )
|
||||
return self.time == b.time
|
||||
end
|
||||
|
||||
--- @param b TimeDelta
|
||||
function meta:__lt( b )
|
||||
return a.time < b.time
|
||||
end
|
||||
|
||||
--- @param b TimeDelta
|
||||
function meta.__le( b )
|
||||
return a.time <= b.time
|
||||
end
|
||||
end
|
||||
158
addons/niknaks/lua/niknaks/modules/sh_util_extended.lua
Normal file
158
addons/niknaks/lua/niknaks/modules/sh_util_extended.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
--[[
|
||||
| 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/
|
||||
--]]
|
||||
|
||||
-- Copyright © 2022-2072, Nak, https://steamcommunity.com/id/Nak2/
|
||||
-- All Rights Reserved. Not allowed to be reuploaded.
|
||||
local NikNaks = NikNaks
|
||||
local tostring, tonumber, tobool, Angle, Vector, string_ToColor = tostring, tonumber, tobool, Angle, Vector, string.ToColor
|
||||
|
||||
|
||||
-- Lua based type fix
|
||||
-- TODO: Note sure if it should be added
|
||||
if false then
|
||||
NikNaks.oldType = type
|
||||
function NikNaks.isnumber( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "number" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.isstring( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "string" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.istable( var )
|
||||
if not getmetatable( var ) then return true end
|
||||
return false
|
||||
end
|
||||
function NikNaks.isfunction( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "function" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.isvector( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "Vector" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.isangle( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "Angle" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.isbool( var )
|
||||
return var == true or var == false or false
|
||||
end
|
||||
function NikNaks.isplayer( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or mt.MetaName ~= "Player" then return false end
|
||||
return true
|
||||
end
|
||||
function NikNaks.isentity( var )
|
||||
local mt = getmetatable( var )
|
||||
if not mt or (mt.MetaName ~= "Player" and mt.MetaName ~= "Entity" ) then return false end
|
||||
return true
|
||||
end
|
||||
local function PatchMetaName( var, str )
|
||||
-- If it has a metatable.
|
||||
local mt = getmetatable(var)
|
||||
if mt then -- Make sure the metatable has the metaname
|
||||
mt.MetaName = str
|
||||
else
|
||||
local tab = {["MetaName"] = str}
|
||||
debug.setmetatable(var, tab)
|
||||
end
|
||||
end
|
||||
PatchMetaName("", "string")
|
||||
PatchMetaName(1, "number")
|
||||
PatchMetaName(function() end, "function")
|
||||
PatchMetaName(coroutine.create(function() end), "thread")
|
||||
|
||||
function NikNaks.type( var )
|
||||
local mt = getmetatable( var )
|
||||
if mt and mt.MetaName then return mt.MetaName end
|
||||
return "table"
|
||||
end
|
||||
end
|
||||
|
||||
--- Same as AccessorFunc, but will make 'Set' functions return self. Allowing you to chain-call.
|
||||
--- @param tab table
|
||||
--- @param varname string
|
||||
--- @param name string
|
||||
--- @param iForce? number
|
||||
function NikNaks.AccessorFuncEx( tab, varname, name, iForce )
|
||||
if not tab then debug.Trace() end
|
||||
tab[ "Get" .. name ] = function( self ) return self[ varname ] end
|
||||
if ( iForce == FORCE_STRING ) then
|
||||
tab[ "Set" .. name ] = function( self, v ) self[ varname ] = tostring( v ) return self end
|
||||
return end
|
||||
if ( iForce == FORCE_NUMBER ) then
|
||||
tab[ "Set" .. name ] = function( self, v ) self[ varname ] = tonumber( v ) return self end
|
||||
return end
|
||||
if ( iForce == FORCE_BOOL ) then
|
||||
tab[ "Set" .. name ] = function( self, v ) self[ varname ] = tobool( v ) return self end
|
||||
return end
|
||||
if ( iForce == FORCE_ANGLE ) then
|
||||
tab[ "Set" .. name ] = function( self, v ) self[ varname ] = Angle( v ) return self end
|
||||
return end
|
||||
if ( iForce == FORCE_COLOR ) then
|
||||
tab[ "Set" .. name ] = function( self, v )
|
||||
if ( NikNaks.type( v ) == "Vector" ) then self[ varname ] = v:ToColor()
|
||||
else self[ varname ] = string_ToColor( tostring( v ) ) end
|
||||
return self
|
||||
end
|
||||
return end
|
||||
if ( iForce == FORCE_VECTOR ) then
|
||||
tab[ "Set" .. name ] = function( self, v )
|
||||
if ( IsColor( v ) ) then self[ varname ] = v:ToVector()
|
||||
else self[ varname ] = Vector( v ) end
|
||||
return self
|
||||
end
|
||||
return end
|
||||
tab[ "Set" .. name ] = function( self, v ) self[ varname ] = v return self end
|
||||
end
|
||||
|
||||
NikNaks.util = {}
|
||||
|
||||
-- Hull
|
||||
do
|
||||
--- Returns a HULL_ENUM fitting the hull given.
|
||||
--- @param vecMin Vector
|
||||
--- @param vecMax Vector
|
||||
--- @return number HULL_ENUM
|
||||
function NikNaks.util.FindHull( vecMin, vecMax )
|
||||
local wide = max(-vecMin.x, -vecMin.y, vecMax.x, vecMax.y)
|
||||
local high = vecMax.z - vecMin.z
|
||||
if wide <= 16 and high <= 8 then
|
||||
return NikNaks.HULL_TINY_CENTERED
|
||||
elseif wide <= 24 and high <= 24 then
|
||||
return NikNaks.HULL_TINY
|
||||
elseif wide <= 40 and high <= 40 then
|
||||
return NikNaks.HULL_SMALL_CENTERED
|
||||
elseif wide <= 36 and high <= 65 then
|
||||
return NikNaks.HULL_MEDIUM
|
||||
elseif wide <= 32 and high <= 73 then
|
||||
return NikNaks.HULL_HUMAN
|
||||
elseif wide <= 36 and high <= 100 then
|
||||
return NikNaks.HULL_MEDIUM_TALL
|
||||
else
|
||||
return NikNaks.HULL_LARGE
|
||||
end
|
||||
end
|
||||
|
||||
--- Returns a HULL_ENUM matching the entitys hull.
|
||||
--- @param entity Entity
|
||||
--- @return number HULL_ENUM
|
||||
function NikNaks.util.FindEntityHull( entity )
|
||||
if entity.GetHull then return entity:GetHull() end
|
||||
local mi, ma = entity:OBBMins(), entity:OBBMaxs()
|
||||
return FindHull( mi, ma )
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user