This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 8064ba84d8
commit 73479cff9e
7338 changed files with 1718883 additions and 14 deletions

View 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

View 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

View 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 )

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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