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