mirror of
https://github.com/lifestorm/wnsrc.git
synced 2025-12-16 21:33:46 +03:00
1040 lines
30 KiB
Lua
1040 lines
30 KiB
Lua
--[[
|
|
| 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/
|
|
--]]
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
CoffeeLib BSP @ Nak 2021
|
|
|
|
DO NOT USE THIS WITHOUT MY PERMISSON IN YOUR PROJECT!
|
|
If you wish the utilize the same functions, you can download CoffeeLib seperate to your server.
|
|
|
|
CoffeeLib also come with more utility functions.
|
|
|
|
---------------------------------------------------------------------------]]
|
|
-- Check for cache
|
|
--StormFox2.FileWrite("stormfox2/cache/" .. game.GetMap() .. ".dat", DATA)
|
|
StormFox2.Map = {}
|
|
SF_BSPDATA = SF_BSPDATA or {}
|
|
local function ReadVector(f)
|
|
return Vector( f:ReadFloat(), f:ReadFloat(), f:ReadFloat() )
|
|
end
|
|
local CACHE_VERSION = 2
|
|
local CACHE_FIL = "stormfox2/cache/" .. game.GetMap() .. ".dat"
|
|
local CRC = tonumber(file.Size( "maps/" .. game.GetMap() .. ".bsp","GAME" )) -- or tonumber(util.CRC(file.Read("maps/" .. game.GetMap() .. ".bsp","GAME")))
|
|
local file = table.Copy(file)
|
|
local Vector = Vector
|
|
local Color = Color
|
|
local table = table.Copy(table)
|
|
local string = table.Copy(string)
|
|
local util = table.Copy(util)
|
|
|
|
-- Enums
|
|
local NO_TYPE = -1
|
|
local DIRTGRASS_TYPE = 0
|
|
local ROOF_TYPE = 1
|
|
local ROAD_TYPE = 2
|
|
local PAVEMENT_TYPE = 3
|
|
|
|
-- Generator
|
|
local GetBSPData
|
|
local env_stormfox2_settings = {}
|
|
function StormFox2.Map.GetSetting( sName )
|
|
return env_stormfox2_settings[sName]
|
|
end
|
|
local env_stormfox2_materials = {}
|
|
local env_has_mat = false
|
|
do
|
|
local meta = {}
|
|
meta.__index = meta
|
|
|
|
-- Local functions
|
|
local function ReadLumpHeader( BSP, f )
|
|
local t = {}
|
|
if BSP.version ~= 21 or BSP._isL4D2 == false then
|
|
t.fileofs = f:ReadLong()
|
|
t.filelen = f:ReadLong()
|
|
t.version = f:ReadLong()
|
|
t.fourCC = f:ReadLong()
|
|
elseif BSP._isL4D2 == true then
|
|
t.version = f:ReadLong()
|
|
t.fileofs = f:ReadLong()
|
|
t.filelen = f:ReadLong()
|
|
t.fourCC = f:ReadLong()
|
|
else -- Try and figure it out
|
|
local fileofs = f:ReadLong() -- Version
|
|
local filelen = f:ReadLong() -- fileofs
|
|
local version = f:ReadLong() -- filelen
|
|
t.fourCC = f:ReadLong()
|
|
if fileofs <= 8 then
|
|
BSP._isL4D2 = true
|
|
t.version = fileofs
|
|
t.fileofs = filelen
|
|
t.filelen = version
|
|
else
|
|
BSP._isL4D2 = false
|
|
t.fileofs = fileofs
|
|
t.filelen = filelen
|
|
t.version = version
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function CreateStaticProp(f, version, m, staticSize)
|
|
local s = f:Tell()
|
|
local obj = {}
|
|
-- Version 4
|
|
obj.Origin = ReadVector(f) -- Vector (3 float) 12 bytes
|
|
obj.Angles = Angle( f:ReadFloat(),f:ReadFloat(),f:ReadFloat() ) -- Angle (3 float) 12 bytes
|
|
-- Version 4
|
|
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 = ReadVector(f) -- Vector (3 float) 12 bytes
|
|
-- 56 bytes used
|
|
-- Version 5
|
|
if version >= 5 then
|
|
obj.ForcedFadeScale = f:ReadFloat() -- float 4 bytes
|
|
end
|
|
-- 60 bytes used
|
|
-- Version 6 and 7
|
|
if version >= 6 and version <= 7 then
|
|
obj.MinDXLevel = f:ReadUShort() -- unsigned short 2 bytes
|
|
obj.MaxDXLevel = f:ReadUShort() -- unsigned short 2 bytes
|
|
-- Version 8
|
|
elseif version >= 8 then
|
|
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
|
|
end
|
|
-- Version 7
|
|
if version >= 7 then -- color32 ( 32-bit color) 4 bytes
|
|
obj.DiffuseModulation = Color( f:ReadByte(),f:ReadByte(),f:ReadByte(),f:ReadByte() )
|
|
end
|
|
-- Somewhere between here are a lot of troubles. Lets reverse and start from the bottom it to be sure.
|
|
local bSkip = 0
|
|
-- Version 11 UniformScale [4 bytes]
|
|
if version >= 11 then
|
|
f:Seek(s + staticSize - 4)
|
|
obj.UniformScale = f:ReadFloat()
|
|
bSkip = bSkip + 4
|
|
else
|
|
obj.UniformScale = 1 -- Scale is not supported in lower versions
|
|
end
|
|
-- Version 10+ (Bitflags) FlagsEx [4 bytes]
|
|
if version >= 10 then -- unsigned int
|
|
f:Seek(s + staticSize - bSkip - 4)
|
|
obj.flags = f:ReadULong()
|
|
bSkip = bSkip + 4
|
|
end
|
|
-- Version 9 and 10 DisableX360 [4 bytes]
|
|
if version >= 9 and version <= 10 then
|
|
f:Seek(s + staticSize - bSkip - 4)
|
|
obj.DisableX360 = f:ReadLong() -- bool (4 bytes)
|
|
end
|
|
return obj,f:Tell() - s + bSkip
|
|
end
|
|
|
|
function meta:SeekLump(f, num )
|
|
local h = self.lumpheader[ num + 1 ]
|
|
assert(h, "Can't locate lump!")
|
|
assert(h.fileofs > 8 or h.filelen < 1, "Invalid bytes in lumpheader!" .. num)
|
|
f:Seek(h.fileofs)
|
|
return h.filelen
|
|
end
|
|
|
|
function meta:ReadLump(f, num )
|
|
local len = self:SeekLump(f, num )
|
|
local data = f:Read( len )
|
|
return data
|
|
end
|
|
|
|
function meta:LZMAReadLump(f, num )
|
|
self:SeekLump(f, num )
|
|
if f:Read(4):lower() ~= "lzma" then
|
|
return self:ReadLump(f, num )
|
|
end
|
|
local actualSize = f:ReadLong()
|
|
local lzmaSize = f:ReadLong() -- lzmaSize
|
|
local t = {}
|
|
for i = 1,5 do
|
|
table.insert(t, f:ReadByte())
|
|
end
|
|
local str = f:Read( lzmaSize )
|
|
local buf = file.Open("sf_buffer.txt", "wb", "DATA");
|
|
for _, v in ipairs(t) do
|
|
buf:WriteByte(v);
|
|
end
|
|
buf:WriteULong(actualSize); -- this is actually a 64bit int, but i can't write those with gmod functions
|
|
buf:WriteULong(0); -- filling in the unused bytes
|
|
buf:Write(str);
|
|
buf:Close();
|
|
|
|
return util.Decompress(file.Read("sf_buffer.txt"))
|
|
end
|
|
|
|
function meta:ReadGameLumpHeader( f )
|
|
if self._gamelump then return self._gamelump end
|
|
self._gamelump = {}
|
|
local len = self:SeekLump(f, 35 )
|
|
local pos = f:Tell()
|
|
for i = 1, math.min(64, f:ReadLong() ) do
|
|
self._gamelump[i] = {
|
|
id = f:ReadLong(),
|
|
flags = f:ReadUShort(),
|
|
version = f:ReadUShort(),
|
|
fileofs = f:ReadLong(),
|
|
filelen = f:ReadLong()
|
|
}
|
|
end
|
|
return self._gamelump
|
|
end
|
|
|
|
function meta:FindGameLump(f, gLumpID )
|
|
local t = self:ReadGameLumpHeader( f )
|
|
local m
|
|
for k, v in ipairs( t ) do
|
|
if v.id == gLumpID then
|
|
m = k
|
|
break
|
|
end
|
|
end
|
|
return t[m]
|
|
end
|
|
|
|
-- Textures and materials
|
|
do
|
|
local max_data = 256000
|
|
function meta:GetTextures(f)
|
|
local t = {}
|
|
local data = self:LZMAReadLump(f, 43)
|
|
if #data > max_data then
|
|
error("BSP's TexDataStringData is invalid!")
|
|
end
|
|
for s in string.gmatch( data, "[^%z]+" ) do
|
|
table.insert(t, s:lower())
|
|
end
|
|
return t
|
|
end
|
|
end
|
|
|
|
local function LoadMaterialEnt(t)
|
|
local _t = tonumber(t.material_type or "0") or 0
|
|
if _t == 0 then
|
|
_t = DIRTGRASS_TYPE
|
|
elseif _t == 1 then
|
|
_t = ROOF_TYPE
|
|
else
|
|
return -- Invalid
|
|
end
|
|
for k, v in pairs(t) do
|
|
if not string.match(k, "material_%d+") then continue end
|
|
env_stormfox2_materials[v] = _t
|
|
end
|
|
end
|
|
|
|
local _list = {
|
|
["sun_yaw"] = "sunyaw",
|
|
["moon_size"] = "moonsize",
|
|
["min_lightlevel"] = "maplight_min",
|
|
["max_lightlevel"] = "maplight_max",
|
|
["max_detailsprite_darkness"] = "detailsprite_darkness",
|
|
["fog_distance"] = "overwrite_fogdistance",
|
|
["fog_color"] = "fog_color",
|
|
}
|
|
local function LoadSettingsEnt(t)
|
|
for sName, var in pairs(t) do
|
|
if not _list[sName] then continue end
|
|
env_stormfox2_settings[_list[sName]] = var
|
|
end
|
|
end
|
|
|
|
local function LoadENTLump( data, tab )
|
|
env_stormfox2_settings = {}
|
|
env_stormfox2_materials = {}
|
|
env_has_mat = false
|
|
for s in string.gmatch( data, "%{.-%\n}" ) do
|
|
local t = util.KeyValuesToTable("t" .. s)
|
|
-- Convert a few things to make it easier
|
|
t.origin = util.StringToType(t.origin or "0 0 0","Vector")
|
|
t.angles = util.StringToType(t.angles or "0 0 0","Angle")
|
|
local c = util.StringToType(t.rendercolor or "255 255 255","Vector")
|
|
t.rendercolor = Color(c.x,c.y,c.z)
|
|
t.raw = s
|
|
if t.classname == "env_stormfox2_materials" then
|
|
LoadMaterialEnt(t)
|
|
env_has_mat = true
|
|
elseif t.classname == "env_stormfox2_settings" then
|
|
LoadSettingsEnt(t)
|
|
end
|
|
table.insert(tab,t)
|
|
end
|
|
end
|
|
|
|
local function GetModelMaterials(mdl)
|
|
local data = util.GetModelMeshes( mdl, 0, 0 )
|
|
if not data then return {} end
|
|
local t = {}
|
|
for i = 1, #data do
|
|
if not data[i]["material"] then continue end
|
|
table.insert(t, data[i]["material"])
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- Load functions
|
|
function GetBSPData()
|
|
--print("Loading file")
|
|
local mapfile = "maps/"..game.GetMap() .. ".bsp"
|
|
local f = file.Open(mapfile,"rb","GAME")
|
|
if not f then StormFox2.Warning("Unable to load mapfile!") return {} end
|
|
-- Read the header
|
|
if f:Read(4) ~= "VBSP" then
|
|
f:Close()
|
|
error("File not BSP format!")
|
|
end
|
|
local BSP = {}
|
|
setmetatable(BSP, meta)
|
|
BSP.version = f:ReadLong()
|
|
assert( BSP.version <= 21, "BSP is too new" )
|
|
-- Read Lump Header
|
|
BSP.lumpheader = {}
|
|
for i = 1, 64 do
|
|
BSP.lumpheader[i] = ReadLumpHeader( BSP, f )
|
|
end
|
|
-- Ent
|
|
do
|
|
local data
|
|
BSP.Entities = {}
|
|
if file.Exists("maps/" .. game.GetMap() .. "_l_0.lmp", "GAME") then
|
|
StormFox2.Msg("Reading lmp EntityLump file.")
|
|
data = file.Read("maps/" .. game.GetMap() .. "_l_0.lmp", "GAME")
|
|
else
|
|
data = BSP:LZMAReadLump(f, 0 )
|
|
end
|
|
LoadENTLump( data, BSP.Entities )
|
|
end
|
|
-- Static prosp
|
|
BSP.StaticProps = {}
|
|
local m = {}
|
|
do
|
|
local t = BSP:FindGameLump( f, 1936749168 ) -- 1936749168 == "sprp"
|
|
if not t then t = {} end -- No static props? Must be empty or something
|
|
-- Read the models
|
|
f:Seek( t.fileofs )
|
|
local n = f:ReadLong() -- Number of models
|
|
if n > 16384 then
|
|
ErrorNoHalt(game.GetMap() .. ".BSP has more than 16384 models!")
|
|
else
|
|
for i = 1,n do
|
|
local model = ""
|
|
for i2 = 1,128 do
|
|
local c = string.char(f:ReadByte())
|
|
if string.match(c,"[%w_%-%.%/]") then
|
|
model = model .. c
|
|
end
|
|
end
|
|
m[i] = model
|
|
end
|
|
end
|
|
-- Read the leafs ( Unused )
|
|
for i = 1, f:ReadLong() do
|
|
f:ReadShort() -- Unsigned
|
|
end
|
|
-- Read static props
|
|
local count = f:ReadLong()
|
|
if count > 16384 then
|
|
ErrorNoHalt(game.GetMap() .. ".BSP has more than 16384 static props!")
|
|
else
|
|
local endPos = t.filelen + t.fileofs
|
|
local staticSize = (endPos - f:Tell()) / count
|
|
local staticStart = f:Tell()
|
|
local staticUsed = 0
|
|
for i = 0, count - 1 do
|
|
-- This is to try and get as much valid data we can.
|
|
f:Seek(staticStart + staticSize * i)
|
|
local sObj, sizeused = CreateStaticProp(f,t.version, m, staticSize)
|
|
staticUsed = sizeused
|
|
sObj.Index = table.insert(BSP.StaticProps,sObj) - 1
|
|
end
|
|
end
|
|
end
|
|
-- We calculate the amount of static props within this space. It is more stable.
|
|
-- Textures
|
|
BSP.TextureArray = BSP:GetTextures(f)
|
|
-- CubeTextures
|
|
if CLIENT then
|
|
BSP.TextureCube = {}
|
|
-- Scan the brushes on the map
|
|
for _, matp in ipairs(BSP.TextureArray) do
|
|
local m = Material(matp)
|
|
if m:IsError() then continue end
|
|
if not m:GetString("$envmap") then continue end
|
|
BSP.TextureCube[m] = m:GetVector("$envmaptint")
|
|
end
|
|
-- Scan static models
|
|
for _, model in ipairs(m) do
|
|
for _, mat in ipairs(GetModelMaterials(model) or {}) do
|
|
local m = Material(mat)
|
|
if m:IsError() then continue end
|
|
if not m:GetString("$envmap") then continue end
|
|
BSP.TextureCube[m] = m:GetVector("$envmaptint")
|
|
end
|
|
end
|
|
-- Scan props
|
|
if render.GetDXLevel() >= 95 then -- This is a little heavy for some GPUs, make sure it can
|
|
local aS = {}
|
|
for _, tab in ipairs(BSP.Entities or {}) do
|
|
if not tab.classname then continue end
|
|
if tab.classname ~= "prop_dynamic" and tab.classname ~= "prop_dynamic_override" then continue end
|
|
if not tab.model then continue end
|
|
if aS[tab.model] then continue end
|
|
for _, mat in ipairs(GetModelMaterials(tab.model) or {}) do
|
|
local m = Material(mat)
|
|
if m:IsError() then continue end
|
|
if not m:GetString("$envmap") then continue end
|
|
BSP.TextureCube[m] = m:GetVector("$envmaptint")
|
|
end
|
|
aS[tab.model] = true
|
|
end
|
|
end
|
|
end
|
|
f:Close()
|
|
return BSP
|
|
end
|
|
end
|
|
|
|
-- Local functions
|
|
local function ValidateCache( fil )
|
|
if not fil then return false end
|
|
if fil:Read(3) ~= "SF2" then return false end
|
|
if fil:ReadUShort() ~= CACHE_VERSION then return false end
|
|
if fil:ReadULong() ~= CRC then return false end
|
|
return true
|
|
end
|
|
|
|
local function WriteData( f, str )
|
|
f:WriteULong(#str)
|
|
f:Write(str)
|
|
end
|
|
|
|
local function ReadData(f)
|
|
local n = f:ReadULong() or 0
|
|
if n < 1 then return "" end
|
|
return f:Read(n)
|
|
end
|
|
|
|
local function LoadCache()
|
|
if true then return end
|
|
if not file.Exists(CACHE_FIL, "DATA") then
|
|
return
|
|
end
|
|
-- Check if valid
|
|
local f = file.Open(CACHE_FIL, "rb", "DATA")
|
|
if not ValidateCache(f) then
|
|
StormFox2.Warning("Map cache is invalid / outdated! Regenerating ..")
|
|
f:Close()
|
|
return
|
|
end
|
|
StormFox2.Msg("Loading map cache ..")
|
|
local SF_BSPDATA = {}
|
|
SF_BSPDATA.version = f:ReadLong()
|
|
-- Entities
|
|
SF_BSPDATA.Entities = util.JSONToTable(ReadData(f)) or {}
|
|
-- Static props
|
|
SF_BSPDATA.StaticProps = util.JSONToTable(ReadData(f)) or {}
|
|
-- Textures
|
|
SF_BSPDATA.TextureArray = util.JSONToTable(ReadData(f)) or {}
|
|
SF_BSPDATA._hasPak = f:ReadBool()
|
|
|
|
f:Close()
|
|
return SF_BSPDATA
|
|
end
|
|
|
|
local function SaveCache()
|
|
if true then return end
|
|
if not file.Exists("stormfox2/cache", "DATA") then
|
|
file.CreateDir("stormfox2/cache")
|
|
end
|
|
local f = file.Open(CACHE_FIL, "wb", "DATA")
|
|
if not f then Stormfox2.Warning("Unable to save map cache!") end
|
|
f:Write("SF2")
|
|
f:WriteUShort(CACHE_VERSION)
|
|
f:WriteULong(CRC)
|
|
f:ReadLong(SF_BSPDATA.version)
|
|
-- Ent
|
|
WriteData(f, util.TableToJSON(SF_BSPDATA.Entities))
|
|
-- Static
|
|
WriteData(f, util.TableToJSON(SF_BSPDATA.StaticProps))
|
|
-- Textures
|
|
WriteData(f, util.TableToJSON(SF_BSPDATA.TextureArray))
|
|
f:WriteBool(SF_BSPDATA._hasPak)
|
|
StormFox2.Msg("Saved map cache.")
|
|
f:Close()
|
|
end
|
|
|
|
local function LoadMap()
|
|
SF_BSPDATA = LoadCache() -- Try and load cache
|
|
if SF_BSPDATA then -- Managed to load
|
|
SF_BSPDATALOADED = true
|
|
else
|
|
-- Load map ..
|
|
if CoffeeLib and false then
|
|
StormFox2.Msg("Generating map cache using CoffeeLib ..")
|
|
local BSP = Map.ReadBSP()
|
|
SF_BSPDATA = {}
|
|
SF_BSPDATA.version = BSP:GetVersion()
|
|
SF_BSPDATA.Entities = BSP:GetEntities()
|
|
SF_BSPDATA.StaticProps = BSP:GetStaticProps()
|
|
SF_BSPDATA.TextureArray = BSP:GetTextures()
|
|
SF_BSPDATALOADED = true
|
|
SaveCache()
|
|
else
|
|
StormFox2.Msg("Generating new map cache ..")
|
|
SF_BSPDATA = GetBSPData()
|
|
SF_BSPDATALOADED = true
|
|
if SF_BSPDATA then SaveCache() end
|
|
end
|
|
end
|
|
end
|
|
-- Load map
|
|
LoadMap()
|
|
-- Texture Generator
|
|
-- Type Guesser function
|
|
local blacklist = {"gravelfloor002b","swift/","sign","indoor","foliage","model","dirtfloor005c","dirtground010","concretefloor027a","swamp","sand","concret"}
|
|
local function GetTexType(str)
|
|
str = str:lower()
|
|
if str == "error" then return NO_TYPE end
|
|
for _,bl in ipairs(blacklist) do
|
|
if string.find(str,bl,nil,true) then return NO_TYPE end
|
|
end
|
|
-- Dirt grass and gravel
|
|
if string.find(str,"grass") then return DIRTGRASS_TYPE end
|
|
if string.find(str,"dirt") then return DIRTGRASS_TYPE end
|
|
if string.find(str,"gravel") then return DIRTGRASS_TYPE end
|
|
if string.find(str,"ground") then return DIRTGRASS_TYPE end
|
|
-- Roof
|
|
if string.find(str,"roof") then return ROOF_TYPE end
|
|
-- Road
|
|
if string.find(str,"road") then return ROAD_TYPE end
|
|
if string.find(str,"asphalt") then return ROAD_TYPE end
|
|
-- Pavement This is disabled, since it messes most maps up
|
|
--if string.find(str,"pavement") or string.find(str,"cobble") or string.find(str,"concretefloor") then return PAVEMENT_TYPE end
|
|
return NO_TYPE
|
|
end
|
|
--[[-------------------------------------------------------------------------
|
|
Generates the texture-table used by StormFox2.
|
|
---------------------------------------------------------------------------]]
|
|
local function GenerateTextureTree()
|
|
local tree = {}
|
|
if env_has_mat then
|
|
for tex, _t in pairs( env_stormfox2_materials ) do
|
|
tree[tex] = {
|
|
[1] = _t,
|
|
[2] = _t,
|
|
}
|
|
end
|
|
return tree
|
|
end
|
|
-- Load all textures
|
|
for _,tex_string in pairs(StormFox2.Map.AllTextures()) do
|
|
if tree[tex_string] then continue end
|
|
local mat = Material(tex_string)
|
|
if not mat then continue end
|
|
local tex1,tex2 = mat:GetTexture("$basetexture"),mat:GetTexture("$basetexture2")
|
|
if not tex1 and not tex2 then continue end
|
|
-- Guess from the textures
|
|
if tex1 and not tex1:IsError() then
|
|
local t = GetTexType(tex1:GetName())
|
|
if t ~= NO_TYPE then
|
|
tree[tex_string] = {}
|
|
tree[tex_string][1] = t
|
|
end
|
|
end
|
|
if tex2 and not tex2:IsError() then
|
|
local t = GetTexType(tex2:GetName())
|
|
if t ~= NO_TYPE then
|
|
tree[tex_string] = tree[tex_string] or {}
|
|
tree[tex_string][2] = t
|
|
end
|
|
end
|
|
end
|
|
return tree
|
|
end
|
|
--[[-------------------------------------------------------------------------
|
|
Returns a list of map-textures that should be replaced.
|
|
NO_TYPE = -1
|
|
DIRTGRASS_TYPE = 0
|
|
ROOF_TYPE = 1
|
|
ROAD_TYPE = 2
|
|
PAVEMENT_TYPE = 3
|
|
---------------------------------------------------------------------------]]
|
|
|
|
---Returns a list of map-textures that should be replaced.
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.GetTextureTree()
|
|
return SF_TEXTDATA or {}
|
|
end
|
|
|
|
-- Small StormFox Functions
|
|
|
|
---Returns the mapversion.
|
|
---@return number
|
|
---@shared
|
|
function StormFox2.Map.Version()
|
|
return SF_BSPDATA.version or -1
|
|
end
|
|
|
|
---Returns the entities from the mapfile.
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.Entities()
|
|
return SF_BSPDATA.Entities or {}
|
|
end
|
|
|
|
---Returns the staticprops from the mapfile.
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.StaticProps()
|
|
return SF_BSPDATA.StaticProps or {}
|
|
end
|
|
|
|
---Returns all textures from the mapfile.
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.AllTextures()
|
|
return SF_BSPDATA.TextureArray or {}
|
|
end
|
|
|
|
---Returns the filtered textures from the mapfile.
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.Textures()
|
|
return SF_BSPDATA.Textures or {}
|
|
end
|
|
|
|
local last = 1
|
|
|
|
---Sets the brightness of the cubemaps.
|
|
---@param float number
|
|
---@shared
|
|
function StormFox2.Map.SetCubeMapDarkness(float)
|
|
if float == last then return end
|
|
last = float
|
|
for mat, def in pairs(SF_BSPDATA.TextureCube) do
|
|
mat:SetVector("$envmaptint", def * float)
|
|
end
|
|
end
|
|
|
|
---Returns true if the map has PAK files
|
|
---@return boolean
|
|
---@deprecated
|
|
---@shared
|
|
function StormFox2.Map.HasPAK()
|
|
return SF_BSPDATA._hasPak or false
|
|
end
|
|
|
|
---Gets all entities with the given class from the mapfile.
|
|
---@param sClass string
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindClass(sClass)
|
|
local t = {}
|
|
for k,v in pairs(SF_BSPDATA.Entities) do
|
|
if v.classname and string.match(v.classname,sClass) then
|
|
table.insert(t,v)
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
---Gets all entities with the given name from the mapfile.
|
|
---@param sTargetName string
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindTargetName(sTargetName)
|
|
local t = {}
|
|
for k,v in pairs(SF_BSPDATA.Entities) do
|
|
if string.match(v.targetname or "",sTargetName) then
|
|
table.insert(t,v)
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
---Returns the mapdata for the given entity. Will be nil if isn't a map-created entity. Seems to only work server-side.
|
|
---@param eEnt Entity
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindEntity(eEnt)
|
|
local c = eEnt:GetClass()
|
|
local h_id = eEnt:GetKeyValues().hammerid
|
|
if not h_id then return end
|
|
for k,v in pairs(SF_BSPDATA.Entities) do
|
|
if c == v.classname and h_id == v.hammerid then
|
|
return v
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
---Returns the entities inside the map-file, that are within the sphere.
|
|
---@param vPos Vector
|
|
---@param nRadius number
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindEntsInSphere(vPos,nRadius)
|
|
local t = {}
|
|
nRadius = nRadius^2
|
|
for i,v in ipairs(SF_BSPDATA.Entities) do
|
|
if v.origin:DistToSqr(vPos) <= nRadius then
|
|
table.insert(t,v)
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
---Returns the staticprops inside the map-file, that are within the sphere.
|
|
---@param vPos Vector
|
|
---@param nRadius number
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindStaticsInSphere(vPos,nRadius)
|
|
local t = {}
|
|
nRadius = nRadius^2
|
|
for i,v in ipairs(SF_BSPDATA.StaticProps) do
|
|
if v.Origin:DistToSqr(vPos) <= nRadius then
|
|
table.insert(t,v)
|
|
end
|
|
end
|
|
return t
|
|
end
|
|
|
|
---Tries to locates the entity data from the mapfile, using the hammer_id.
|
|
---@param nHammerID number
|
|
---@return table
|
|
---@shared
|
|
function StormFox2.Map.FindHammerid(nHammerID)
|
|
for k,v in pairs(ents.GetAll()) do
|
|
local h_id = v:GetKeyValues().hammerid
|
|
if not h_id then return end
|
|
if h_id == nHammerID then
|
|
return v
|
|
end
|
|
end
|
|
return
|
|
end
|
|
-- Map functions
|
|
local min,max,sky,sky_scale,has_Sky,map_radius = Vector(0,0,0),Vector(0,0,0),Vector(0,0,0),1,false
|
|
|
|
---Returns the maxsize of the map.
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.MaxSize()
|
|
return max
|
|
end
|
|
|
|
---Returns the minsize of the map.
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.MinSize()
|
|
return min
|
|
end
|
|
|
|
---Returns the radius of the map.
|
|
---@return number
|
|
---@shared
|
|
function StormFox2.Map.RadiusSize()
|
|
return map_radius or 0
|
|
end
|
|
|
|
local clamp = math.Clamp
|
|
---Clamps the vector to the size of the map.
|
|
---@param vec Vector
|
|
---@deprecated
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.ClampPos(vec)
|
|
vec.x = clamp(vec.x, min.x + 1, max.x - 1)
|
|
vec.y = clamp(vec.y, min.y + 1, max.y - 1)
|
|
vec.z = clamp(vec.z, min.z + 1, max.z - 1)
|
|
return vec
|
|
end
|
|
|
|
---Returns the true center of the map. Often Vector( 0, 0, 0 )
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.GetCenter()
|
|
return (StormFox2.Map.MaxSize() + StormFox2.Map.MinSize()) / 2
|
|
end
|
|
|
|
---Returns true if the position is within the map
|
|
---@param vec Vector
|
|
---@return boolean
|
|
---@shared
|
|
function StormFox2.Map.IsInside(vec)
|
|
if vec.x > max.x then return false end
|
|
if vec.y > max.y then return false end
|
|
if vec.z > max.z then return false end
|
|
if vec.x < min.x then return false end
|
|
if vec.y < min.y then return false end
|
|
if vec.z < min.z then return false end
|
|
return true
|
|
end
|
|
|
|
---Returns the skybox-position.
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.GetSkyboxPos()
|
|
return sky
|
|
end
|
|
|
|
---Returns the skybox-scale.
|
|
---@return number
|
|
---@shared
|
|
function StormFox2.Map.GetSkyboxScale()
|
|
return sky_scale
|
|
end
|
|
|
|
---Returns true if the map has a 3D skybox.
|
|
---@return boolean
|
|
---@shared
|
|
function StormFox2.Map.Has3DSkybox()
|
|
return has_Sky
|
|
end
|
|
|
|
---Converts the given position to skybox.
|
|
---@param vPosition Vector
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.SkyboxToWorld(vPosition)
|
|
return (vPosition - sky) * sky_scale
|
|
end
|
|
|
|
---Converts the given skybox position to world.
|
|
---@param vPosition Vector
|
|
---@return Vector
|
|
---@shared
|
|
function StormFox2.Map.WorldtoSkybox(vPosition)
|
|
return (vPosition / sky_scale) + sky
|
|
end
|
|
|
|
local list = {}
|
|
---Checks if the mapfile has/had said entity-class.
|
|
---@param sClass string
|
|
---@return boolean
|
|
---@shared
|
|
function StormFox2.Map.HadClass(sClass)
|
|
if list[sClass] ~= nil then return list[sClass] end
|
|
list[sClass] = #StormFox2.Map.FindClass(sClass) > 0
|
|
return list[sClass]
|
|
end
|
|
|
|
local bCold = false
|
|
---Returns true if it is a cold map
|
|
---@return boolean
|
|
---@shared
|
|
function StormFox2.Map.IsCold()
|
|
return bCold
|
|
end
|
|
|
|
local bSnow = false
|
|
---Returns true if the map has a snow-texture
|
|
---@return boolean
|
|
---@shared
|
|
function StormFox2.Map.HasSnow()
|
|
return bSnow
|
|
end
|
|
|
|
--[[-------------------------------------------------------------------------
|
|
Controls map relays easier
|
|
dusk = night_events
|
|
dawn = day_events
|
|
---------------------------------------------------------------------------]]
|
|
local relay = {}
|
|
hook.Add("StormFox2.InitPostEntity", "StormFox2.MapInteractions.Init", function()
|
|
-- Locate all logic_relays on the map
|
|
for _,ent in ipairs( ents.FindByClass("logic_relay") ) do
|
|
local name = ent:GetName()
|
|
name = string.match(name, "-(.+)$") or name
|
|
if name == "dusk" then name = "night_events" end
|
|
if name == "dawn" then name = "day_events" end
|
|
if not relay[name] then relay[name] = {} end
|
|
table.insert(relay[name], ent)
|
|
end
|
|
end)
|
|
if SERVER then
|
|
function StormFox2.Map.CallLogicRelay(sName,b)
|
|
if sName == "dusk" then sName = "night_events" end
|
|
if sName == "dawn" then sName = "day_events" end
|
|
if b ~= nil and b == false then
|
|
sName = sName .. "_off"
|
|
end
|
|
if not relay[sName] then return end
|
|
for _, ent in ipairs(relay[sName]) do
|
|
if not IsValid(ent) then continue end
|
|
ent:Fire( "Trigger", "" );
|
|
end
|
|
end
|
|
local l_w
|
|
---Internally used to call weather logic_relays.
|
|
---@param name string
|
|
---@server
|
|
function StormFox2.Map.w_CallLogicRelay( name )
|
|
name = string.lower( name )
|
|
if l_w then
|
|
if l_w == name then
|
|
return
|
|
else -- Turn "off" the last logic relay
|
|
StormFox2.Map.CallLogicRelay("weather_" .. l_w, false)
|
|
end
|
|
end
|
|
StormFox2.Map.CallLogicRelay("weather_onchange")
|
|
l_w = name
|
|
StormFox2.Map.CallLogicRelay("weather_" .. name, true)
|
|
end
|
|
|
|
---Returns true if the map has said logic_relay.
|
|
---@param sName string
|
|
---@param isToggle boolean
|
|
---@return boolean
|
|
---@server
|
|
function StormFox2.Map.HasLogicRelay(sName,isToggle)
|
|
if sName == "dusk" then sName = "night_events" end
|
|
if sName == "dawn" then sName = "day_events" end
|
|
if isToggle ~= nil and isToggle == false then
|
|
sName = sName .. "_off"
|
|
end
|
|
return relay[sName] and true or false
|
|
end
|
|
else -- Clients don't know the relays
|
|
local t = {}
|
|
for k,v in ipairs(StormFox2.Map.FindClass("logic_relay")) do
|
|
local sName = v.targetname
|
|
if not sName then break end
|
|
if sName == "dusk" then sName = "night_events" end
|
|
if sName == "dawn" then sName = "day_events" end
|
|
t[sName] = true
|
|
end
|
|
---Returns true if the map has said logic_relay.
|
|
---@param sName string
|
|
---@return boolean
|
|
---@client
|
|
function StormFox2.Map.HasLogicRelay(sName)
|
|
if sName == "dusk" then sName = "night_events" end
|
|
if sName == "dawn" then sName = "day_events" end
|
|
return t[sName] and true or false
|
|
end
|
|
end
|
|
-- Generates the texture-tree
|
|
--if not SF_TEXTDATAMAP or table.Count(SF_TEXTDATAMAP) < 1 then
|
|
SF_TEXTDATAMAP = GenerateTextureTree()
|
|
--end
|
|
-- Find some useful variables we can use
|
|
if StormFox2.Map.Entities()[1] then
|
|
max = util.StringToType( StormFox2.Map.Entities()[1]["world_maxs"], "Vector" )
|
|
min = util.StringToType( StormFox2.Map.Entities()[1]["world_mins"], "Vector" )
|
|
map_radius = math.max(max.x, max.y, max.z, -min.x, -min.y, -min.z) * 1.41
|
|
bCold = StormFox2.Map.Entities()[1]["coldworld"] and true or false
|
|
else
|
|
StormFox2.Warning("This map doesn't have an entity lump! Might cause some undocumented behaviors.")
|
|
-- gm_flatgrass
|
|
max = Vector(15360, 15360, -12288)
|
|
min = Vector(15360, 15360, -12800)
|
|
map_radius = 15360 * 1.41
|
|
bCold = false
|
|
end
|
|
local sky_cam = StormFox2.Map.FindClass("sky_camera")[1]
|
|
if sky_cam then
|
|
has_Sky = true
|
|
sky = util.StringToType( sky_cam.origin, "Vector" )
|
|
sky_scale = tonumber(sky_cam.scale) or 1
|
|
end
|
|
for _,tab in pairs(StormFox2.Map.Textures()) do
|
|
if string.find(tab.nameStringTableID:lower(), "snow") then
|
|
bSnow = true
|
|
break
|
|
end
|
|
end
|
|
|
|
-- Modify the texture tree, if there are changes
|
|
--[[
|
|
local INVALID = -2
|
|
local NO_TYPE = -1
|
|
local DIRTGRASS_TYPE = 0
|
|
local ROOF_TYPE = 1
|
|
local ROAD_TYPE = 2
|
|
local PAVEMENT_TYPE = 3
|
|
]]
|
|
|
|
local modifyData = {} -- Holds the list of modified materials
|
|
-- Gnerates SF_TEXTDATA from SF_TEXTDATAMAP and modifyData
|
|
local function GenerateTEXTDATA()
|
|
-- Create a copy from the map-data
|
|
SF_TEXTDATA = table.Copy( SF_TEXTDATAMAP )
|
|
-- Modify the data
|
|
for sMat,v in pairs( modifyData ) do
|
|
if v == -1 then
|
|
SF_TEXTDATA[sMat] = {-1, -1}
|
|
else
|
|
if not SF_TEXTDATA[sMat] then
|
|
SF_TEXTDATA[sMat] = {}
|
|
end
|
|
local mat = Material(sMat)
|
|
if mat:GetTexture("$basetexture") then
|
|
SF_TEXTDATA[sMat][1] = v
|
|
end
|
|
--if mat:GetTexture("$basetexture2") then Broken
|
|
-- SF_TEXTDATA[sMat][2] = v
|
|
--end
|
|
end
|
|
end
|
|
end
|
|
|
|
if SERVER then
|
|
local map_tex_file = "stormfox2/tex_setting/" .. game.GetMap() .. ".txt"
|
|
local function SaveMData()
|
|
StormFox2.FileWrite(map_tex_file, util.TableToJSON(modifyData))
|
|
end
|
|
local function LoadMData()
|
|
if file.Exists(map_tex_file, "DATA") then
|
|
modifyData = util.JSONToTable( file.Read(map_tex_file, "DATA") ) or {}
|
|
end
|
|
GenerateTEXTDATA()
|
|
end
|
|
LoadMData()
|
|
hook.Add("stormfox2.postlib", "stormfox2.lib.texturesetting", function()
|
|
StormFox2.Network.ForceSet("texture_modification", modifyData)
|
|
function StormFox2.Map.ModifyMaterialType( sMat, v )
|
|
if v < -1 then
|
|
modifyData[sMat] = nil
|
|
else
|
|
modifyData[sMat] = v
|
|
end
|
|
SaveMData()
|
|
StormFox2.Network.ForceSet("texture_modification", modifyData)
|
|
GenerateTEXTDATA()
|
|
end
|
|
end)
|
|
else
|
|
GenerateTEXTDATA() -- We don't know if we'll ever get a list of modified materials.
|
|
hook.Add("StormFox2.data.change", "stormfox2.lib.texturesetting", function(key, _)
|
|
if key ~= "texture_modification" then return end
|
|
modifyData = StormFox2.Data.Get("texture_modification", {})
|
|
GenerateTEXTDATA()
|
|
StormFox2.Terrain.Update()
|
|
end)
|
|
end |