--[[ | 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