Files
wnsrc/lua/stormfox2/framework/cl_envioment.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

1317 lines
44 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/
--]]
--[[-------------------------------------------------------------------------
Handles enviroments by scanning the mapfile.
---------------------------------------------------------------------------]]
StormFox2.Environment = StormFox2.Environment or {}
local INVALID_VERTS = 0
local WATER_VERTS = 1
local GLASS_VERTS = 2
local GLASS_ROOF_VERTS = 3
local METAL_ROOF_VERTS = 4
StormFox2.Setting.AddCL("window_enable",render.SupportsPixelShaders_2_0())
StormFox2.Setting.AddCL("window_distance",800, nil, nil, 0, 4000)
StormFox2.Setting.AddSV("enable_ice",not game.IsDedicated())
StormFox2.Setting.AddSV("enable_wateroverlay",true, nil, "Effects")
StormFox2.Setting.AddCL("edit_cubemaps",true)
--[[-------------------------------------------------------------------------
Adds a window model for SF to use.
---------------------------------------------------------------------------]]
local mdl = {}
---Adds a window model to the list of valid window-models.
---These windows must have glass in them.
---@param sModel string
---@param vMin? Vector
---@param vMax? Vector
---@client
function StormFox2.Environment.AddWindowModel(sModel,vMin, vMax)
if not vMin or not vMax then
vMin, vMax = StormFox2.util.GetModelSize(sModel)
end
mdl[sModel] = {vMin, vMax}
end
local function GetWindowModel(sModel)
if mdl[sModel] then return mdl[sModel][1],mdl[sModel][2] end
local min,max = StormFox2.util.GetModelSize(sModel)
mdl[sModel] = {min,max}
if math.abs(max.x - min.x) < math.abs(max.y - min.y) then
local x = min.x + (max.x - min.x) / 2
mdl[sModel][1].x = x
mdl[sModel][2].x = x
else
local y = min.y + (max.y - min.y) / 2
mdl[sModel][1].y = y
mdl[sModel][2].y = y
end
return mdl[sModel][1],mdl[sModel][2]
end
local function IsWinModel(sModel)
return mdl[sModel] and true
end
-- CS:GO and l4d2 seems to be the only one with window-props
if IsMounted("csgo") or IsMounted("l4d2") then
StormFox2.Environment.AddWindowModel("models/props/cs_militia/militiawindow02_breakable.mdl")
StormFox2.Environment.AddWindowModel("models/props/cs_militia/wndw01.mdl")
StormFox2.Environment.AddWindowModel("models/props_windows/window_farmhouse_big.mdl")
StormFox2.Environment.AddWindowModel("models/props_windows/window_farmhouse_small.mdl",Vector(-26,-0.8,-31.8),Vector(26,-0.8,34))
StormFox2.Environment.AddWindowModel("models/props_windows/window_industrial.mdl")
StormFox2.Environment.AddWindowModel("models/props_windows/window_urban_sash_48_88_full.mdl",Vector(-21.9,0,2),Vector(21.9,0,85))
StormFox2.Environment.AddWindowModel("models/props_windows/window_urban_sash_48_88_full.mdl",Vector(-21.9,0,2),Vector(21.9,0,85))
StormFox2.Environment.AddWindowModel("models/props/de_house/window_48x36.mdl", Vector(-22.2,4.8,-16),Vector(21.8,4.8,16)) -- ~4 sides
StormFox2.Environment.AddWindowModel("models/props/de_house/window_54x44.mdl", Vector(-25,4.8,7), Vector(25,4.8,47))
StormFox2.Environment.AddWindowModel("models/props/de_house/window_54x76.mdl", Vector(-25,4.8,7.9),Vector(25,4.8,80.1))
StormFox2.Environment.AddWindowModel("models/props/de_inferno/windowbreakable_02.mdl")
StormFox2.Environment.AddWindowModel("models/props/cs_militia/militiawindow02_breakable.mdl", Vector(-55, 0, -35.8), Vector(56, 0, 35.8))
end
--[[-------------------------------------------------------------------------
Localize
---------------------------------------------------------------------------]]
local round = math.Round
local util_TraceLine = util.TraceLine
local table_sort = table.sort
local LocalPlayer = LocalPlayer
--[[-------------------------------------------------------------------------
Make a few SurfaceInfo functions.
---------------------------------------------------------------------------]]
local meta = FindMetaTable("SurfaceInfo")
-- Support caching stuff
local surf_caching = {}
meta.__index = function(a,b)
return meta[b] or surf_caching[a] and surf_caching[a][b]
end
meta.__newindex = function(a,b,c)
if not surf_caching[a] then surf_caching[a] = {} end
surf_caching[a][b] = c
end
hook.Add("EntityRemoved", "ClearSurfaceInfo", function(ent)
for _,surf in ipairs(ent:GetBrushSurfaces() or {}) do
surf_caching[surf] = nil
end
end)
function meta:IsValid( )
if self.b_invalid ~= nil then return self.b_invalid end
local b = #(self:GetVertices()) > 0
self.b_invalid = b
return self.b_invalid
end
function meta:GetVerticesNoParallel( )
if not self:IsValid() then return {} end
if self.v_vertNP then return table.Copy(self.v_vertNP) end
self.v_vertNP = {}
local verts = self:GetVertices()
for i,cv in ipairs(verts) do
local pv,nv = verts[i - 1] or verts[#verts], verts[i + 1] or verts[1]
local cP = ( cv - pv ):Cross( nv - pv )
if cP.x == 0 and cP.y == 0 and cP.z == 0 then continue end -- parallel vector.
table.insert(self.v_vertNP, cv)
end
return table.Copy(self.v_vertNP)
end
function meta:GetCenter( )
if not self:IsValid() then return end
if self.v_cent then return self.v_cent end
local verts = self:GetVertices()
if #verts < 2 then
self.v_cent = verts[1]
return self.v_cent
end
local vmax,vmin = verts[1],verts[1]
for i = 2,#verts do
vmax[1] = math.max(vmax[1],verts[i][1])
vmax[2] = math.max(vmax[2],verts[i][2])
vmax[3] = math.max(vmax[3],verts[i][3])
vmin[1] = math.min(vmin[1],verts[i][1])
vmin[2] = math.min(vmin[2],verts[i][2])
vmin[3] = math.min(vmin[3],verts[i][3])
end
self.v_cent = vmin + (vmax - vmin) / 2
return self.v_cent
end
function meta:GetNormal( )
if not self:IsValid() then return end
if self.v_norm then return self.v_norm end
local p = self:GetVertices()
if #p < 3 then return end -- Invalid brush. (Yes this happens)
local c = p[1]
local s = Vector(0,0,0)
for i = 2,#p do
s = s + ( p[i] - c ):Cross( (p[i + 1] or p[1]) - c )
if s.x ~= 0 and s.y ~= 0 and s.z ~= 0 then -- Check if this isn't a parallel vector.
break -- Got a valid norm
end
end
self.v_norm = s:GetNormalized()
return self.v_norm
end
function meta:GetAngles( )
if not self:IsValid() then return end
if self.a_ang then return self.a_ang end
self.a_ang = self:GetNormal():Angle()
return self.a_ang
end
function meta:GetPerimeter( )
if not self:IsValid() then return end
if self.n_peri then return self.n_peri end
local p = self:GetVertices()
local n = 0
for i = 1,#p do
n = n + p[i]:Distance(p[i + 1] or p[1])
end
self.n_peri = n
return self.n_peri
end
function meta:GetArea( )
if not self:IsValid() then return end
if self.n_area then return self.n_area end
local p = self:GetVertices()
local n = #p
if n < 3 then -- Invalid shape
self.n_area = 0
return 0
--elseif n == 3 then -- Triangle, but cost more?
-- local a,b,c = p[1]:Distance(p[2]),p[2]:Distance(p[3]),p[3]:Distance(p[1])
-- local s = (a + b + c) / 2
-- t_t[self] = sqrt( s * (s - a) * (s - b) * (s - c) )
-- return t_t[self]
else -- Any shape
local a = Vector(0,0,0)
for i,pc in ipairs(p) do
local pn = p[i + 1] or p[1]
a = a + pc:Cross(pn)
end
a = a / 2
self.n_area = a:Distance(Vector(0,0,0))
return self.n_area
end
end
--[[-------------------------------------------------------------------------
Make some adv SurfaceInfo functions.
---------------------------------------------------------------------------]]
function meta:GetUVVerts() -- Creates UV-data out from the shape.
if not self:IsValid() then return end
if self.t_uv then return table.Copy(self.t_uv) end
local t = self:GetVerticesNoParallel()
local a = self:GetNormal():Angle()
local c = self:GetCenter()
local vmin,vmax
for i,v in ipairs(t) do
t[i] = (t[i] - c)
t[i]:Rotate(a)
if not vmin then
vmin = Vector(t[i].x,t[i].y,t[i].z)
vmax = Vector(t[i].x,t[i].y,t[i].z)
else
for ii = 1,3 do
vmin[ii] = math.min(vmin[ii],t[i][ii])
vmax[ii] = math.max(vmax[ii],t[i][ii])
end
end
end
local y_r = vmax.z - vmin.z
local x_r,x_r2 = vmax.x - vmin.x,vmax.y - vmin.y
local min_x = vmin.x
local i2 = 1
if x_r2 > x_r then
x_r = x_r2
i2 = 2
min_x = vmin.y
end
local new_t = {}
for i = 1,#t do
table.insert(new_t, {u = (t[i][i2] - min_x) / x_r,v = (t[i].z - vmin.z) / y_r})
end
self.t_uv = new_t
return table.Copy(self.t_uv)
end
function meta:GetMesh() -- Generates a mesh-table for the surfaceinfo.
if not self:IsValid() then return end
if self.t_mesh then return table.Copy(self.t_mesh) end
local verts = self:GetVerticesNoParallel()
if #verts < 3 then
self.b_invalid = false
return
end
local n = self:GetNormal()
-- Calc the height and width
local h_max,h_min = verts[1].z,verts[1].z
for i = 2,#verts do
local h = verts[i].z
h_max = math.max(h_max,h)
h_min = math.min(h_min,h)
end
local uvt = self:GetUVVerts()
local t = {}
for i = 1,3 do
table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n})
end
for i = 4,#verts do
table.insert(t, {pos = verts[1], u = uvt[1].u,v = uvt[1].v, normal = n})
table.insert(t, {pos = verts[i - 1], u = uvt[i - 1].u,v = uvt[i - 1].v, normal = n})
table.insert(t, {pos = verts[i], u = uvt[i].u,v = uvt[i].v, normal = n})
end
self.t_mesh = t
return table.Copy(self.t_mesh)
end
function meta:GetMinSide()
if not self:IsValid() then return end
if self.n_midi then return self.n_midi end
local mi,ma
local p = self:GetVertices()
for i = 1,#p do
if not mi then
mi = p[i]:Distance(p[i + 1] or p[1])
ma = mi
else
mi = math.min(mi,p[i]:Distance(p[i + 1] or p[1]))
ma = math.max(ma,p[i]:Distance(p[i + 1] or p[1]))
end
end
self.n_midi = mi
self.n_madi = ma
return mi
end
function meta:GetMaxSide()
if not self:IsValid() then return end
if self.n_madi then return self.n_madi end
self:GetMinSide()
return self.n_madi
end
--[[-------------------------------------------------------------------------
Generate meshes and env-points out from the map-data.
---------------------------------------------------------------------------]]
-- Local functions
local round = math.Round
local function IsRoofAngle( Ang )
return Ang:Forward():Dot(Vector(0,0,1)) > 0.3
end
local function DecodeFlag(num)
local nocull = false
local transparent = false
for i = 31,1,-1 do
local n = 2 ^ (i - 1)
if num >= n then
if n == 8192 then -- MATERIAL_VAR_NOCULL
nocull = true
elseif n == 2097152 or n == 4194304 then -- MATERIAL_VAR_TRANSLUCENT
transparent = true
elseif n == 4 then -- MATERIAL_VAR_NO_DRAW
return false
end
num = num - n
end
end
return nocull,transparent
end
local function IsMaterialEmpty( t )
return t.HitTexture == "TOOLS/TOOLSINVISIBLE" or t.HitTexture == "**empty**" or t.HitTexture == "TOOLS/TOOLSNODRAW"
end
local up = Vector( 0, 0, -16000)
local function PlyTrace( ent )
local m,ma = ent:OBBMins(), ent:OBBMaxs()
m.z = 0
ma.z = 5
local from = ent:GetPos()
local lastResult
for i = 1, 3 do
local tr = util.TraceHull({
start = from,
endpos = from + up,
filter = ent,
mins = m,
maxs = ma,
collisiongroup = COLLISION_GROUP_PLAYER
})
if not IsMaterialEmpty(tr) then return tr end
from = tr.HitPos + tr.Normal
lastResult = lastResult or tr
end
return lastResult
end
local nocull = false
local function IsTransparent( iMat )
local k = iMat:GetKeyValues()
local flags = k["$flags"]
-- Decode flags
local b,transparent = DecodeFlag(flags)
nocull = b
return transparent
end
local function fuzzeVectors(t,n)
local t2 = {}
for i,v in ipairs(t) do
t2[i] = v
t2[i].x = math.Round(t2[i].x, n or 1)
t2[i].y = math.Round(t2[i].y, n or 1)
t2[i].z = math.Round(t2[i].z, n or 1)
end
return t2
end
local function FindMatchPointer(data_tab,var)
for var2,t in pairs(data_tab) do
if var.x == var2.x and var.y == var2.y and var.z == var2.z then
return var2
end
end
return
end
local function VertsMatch(t1,t2) -- Returns true if 2 or more vectors match match.
local n = 0
for i = 1,#t1 do
if table.HasValue(t2, t1[i]) then n = n + 1 end
if n >= 2 then return true end
end
return false
end
local t = {["mask"] = MASK_SOLID_BRUSHONLY}
local function MatchTrace(from,to,norm, filter)
if norm and (to - from):Dot(norm) > 0 then return false end -- Check if the window "faces" away from the player
t.start = from
t.endpos = norm and to + norm * 10 or to
t.filter = filter or StormFox2.util.ViewEntity()
local tr = util_TraceLine( t )
return not tr.Hit, tr
end
local function EasyTrace(from,to, mask)
t.start = from
t.endpos = to
t.filter = StormFox2.util.ViewEntity()
t.mask = mask or MASK_SOLID_BRUSHONLY
return not util_TraceLine( t ).Hit
end
local function AdvTrace(from,to, mask)
t.start = from
t.endpos = to
t.filter = StormFox2.util.ViewEntity()
t.mask = mask or MASK_SOLID_BRUSHONLY
return util_TraceLine( t )
end
local function UnderSky(pos, mask) -- Checks if a position is under the sky.
t.start = pos
t.endpos = pos + Vector(0,0,262144)
t.filter = StormFox2.util.ViewEntity()
t.mask = mask
local r = util_TraceLine( t )
return r.HitSky and r.HitPos
end
local t2 = {["mask"] = MASK_SHOT}
local function SkyLine(pos,norm, n, nMaxDistance, filter, sky_mask) -- Shoots tracers out and returns a pos, if the sky is above.
t2.start = pos + norm
t2.endpos = pos + norm * (nMaxDistance or 262144)
t2.filter = filter or StormFox2.util.ViewEntity()
t2.mask = MASK_SHOT
local r = util_TraceLine( t2 )
if r.HitSky then return r.HitPos end
local d = r.HitPos:Distance(pos)
if d < 50 then
return UnderSky(pos, sky_mask or MASK_SOLID_BRUSHONLY) and pos
end
-- Check from start to center
local i = math.min(d / 50, n) - 2
local dis = d / 2 / i
for i2 = 1,i do
local v = pos + norm * (dis * i2)
local p = UnderSky(v, sky_mask or MASK_SOLID_BRUSHONLY)
if p then return v end
end
-- Cehck center and hit
for i = 1,2 do
local v = pos + norm * (d / 2.1 * i)
local p = UnderSky(v, sky_mask or MASK_SOLID_BRUSHONLY)
if p then return v end
end
end
local function UnderSky2(pos) -- Checks if a position is under the sky. But for entities.
t2.start = pos
t2.endpos = pos + Vector(0,0,262144)
t2.filter = LocalPlayer()
local r = util_TraceLine( t2 )
return r.HitSky and r.HitPos
end
local m_C = {} -- Cache the materials basetexture
local function IsWindowMaterial( mat )
if m_C[mat] ~= nil then return m_C[mat] end
local tex = ((mat:GetTexture("$basetexture") or mat):GetName() or "error"):lower()
m_C[mat] = (string.find(tex, "glass", 1, true) or string.find(tex, "window", 1, true)) and IsTransparent(mat)
return m_C[mat]
end
-- Generates a "tree" of matching surfaceinfos
local function FindMatches(tSurfaces, tSurface)
local tGroup = {}
for i,tSurface2 in ipairs(tSurfaces) do
if VertsMatch(tSurface[2],tSurface2[2]) then
local surf = table.remove(tSurfaces,i)
table.insert(tGroup, surf)
for _,v in ipairs(FindMatches(tSurfaces, surf)) do
table.insert(tGroup,v)
end
end
end
table.insert(tGroup, tSurface)
return tGroup
end
local function FindMerges(tSurfaces,tGroups)
for i = 1,#tSurfaces do
if #tSurfaces < 1 then return end
local surf = table.remove(tSurfaces,1)
local group = {}
for _,t in ipairs(FindMatches(tSurfaces, surf)) do
table.insert(group, t[1])
end
table.insert(tGroups,group)
end
end
-- New surface functions
local function SurfaceInfo_FacingOutside( eEnt, SurfaceInfo, NoCull ) -- returns true if the surfaceinfo is facing the outside
if SurfaceInfo.b_OutSide ~= nil then return SurfaceInfo.b_OutSide end
SurfaceInfo.b_OutSide = false
local n = SurfaceInfo:GetNormal()
local p = SurfaceInfo:GetCenter() + eEnt:GetPos()
local hP = SkyLine(p + n * -5,-n, 10, 180, eEnt)
if hP then
SurfaceInfo.b_OutSide = true
SurfaceInfo.v_OutSide = hP
elseif NoCull then
hP = SkyLine(p + n * 5,n, 10, 180, eEnt)
if hP then
SurfaceInfo.b_OutSide = true
SurfaceInfo.v_OutSide = hP
end
end
return SurfaceInfo.b_OutSide
end
local function SurfaceInfo_GetOutSide( SurfaceInfo ) -- returns a position that is under the sky.
if not SurfaceInfo.v_OutSide then return end
return Vector(SurfaceInfo.v_OutSide[1],SurfaceInfo.v_OutSide[2],SurfaceInfo.v_OutSide[3])
end
local function SurfaceInfo_GetType( eEnt, SurfaceInfo )
if #SurfaceInfo:GetVertices() < 3 then return INVALID_VERTS end
if SurfaceInfo:IsWater() then -- Water
return WATER_VERTS
elseif not SurfaceInfo:IsNoDraw() then -- We got a surfacebrush
-- Get texture
local iM = SurfaceInfo:GetMaterial()
local tex = ((iM:GetTexture("$basetexture") or iM):GetName() or "error"):lower()
-- Check
if IsWindowMaterial(iM) and SurfaceInfo_FacingOutside( eEnt, SurfaceInfo ) then -- Glass
if IsRoofAngle( SurfaceInfo:GetAngles() ) then -- Roof
return GLASS_ROOF_VERTS, nocull
else
return GLASS_VERTS, nocull
end
elseif (string.find(tex, "metal", 1, true) or string.find(tex, "sheet", 1, true)) and SurfaceInfo:GetMinSide() > 20 then
if IsRoofAngle( SurfaceInfo:GetAngles() ) then -- Roof
return METAL_ROOF_VERTS
end
end
end
return INVALID_VERTS
end
-- Window "entities"
local window_meta = {}
window_meta.__index = window_meta
window_meta.__tostring = function(self) return "SF_WindowRef[" .. tostring(self[1]) .. "]" end
local function CreateWindowRef(ent,center)
local t = {ent,center}
setmetatable(t, window_meta)
return t
end
function window_meta:IsAlive()
if not IsValid(self[1]) then return false end
return not (self[1]:GetMaxHealth() > 0 and self[1]:Health() <= 0)
end
function window_meta:GetCenter()
if self.mesh then return self[1]:GetPos() + self[1]:OBBCenter() end
local pos = self[2]
local a = self[1]:GetAngles()
return self[1]:GetPos() + pos.y * a:Forward() + a:Up() * pos.z + a:Right() * pos.x
end
function window_meta:Draw()
local ent = self[1]
if not self:IsAlive() then return end
local n = ent:GetAngles():Forward()
if self.mesh then
mesh.Begin( MATERIAL_TRIANGLES, 7 )
for i,vert in ipairs(self.mesh) do
local vP = ent:LocalToWorld(vert.pos)
mesh.Position( vP )
mesh.Normal( n )
mesh.Color(255,255,255,255)
mesh.TexCoord(0, vert.u, -vert.v)
mesh.AdvanceVertex()
end
mesh.End()
elseif self.w and self.h then
local c = self:GetCenter()
render.DrawQuadEasy( c, n, self.w, -self.h, color_white, ent:GetAngles().r )
end
end
STORMFOX_WINDOWMESHES = STORMFOX_WINDOWMESHES or {}
STORMFOX_WATERMESHBUILD = {}
STORMFOX_WATERMESHCOLLISON = {}
STORMFOX_WATERMESHBUILD_SKYBOX = {}
-- In case of reload, we delete the old meshes
for i,v in pairs(STORMFOX_WINDOWMESHES) do
v:Destroy()
STORMFOX_WINDOWMESHES[i] = nil
end
local glass_mapmesh = {} -- Map glass_surfaces = {MeshUV, MeshUV_Scale, RefractMeshUV, RefractMeshUV_Scale}
local glass_dynamic = {} -- {}
local puddle_mapmesh
local surfaceinfos = {}
local norm_mat = Material("stormfox2/effects/window/win")
local refact_mat = Material("stormfox2/effects/window/win_refract")
local puddle_mat = Material("stormfox2/effects/rain_puddle")
local ice = Material("stormfox/effects/ice_water")
local ice_size = 500
local vec_ex = Vector(0,0,1)
-- Scans for nearby window entities.
local function scan_dynamic()
glass_dynamic = {}
if not StormFox2.Setting.GetCache("window_enable", true) then return end
local view = StormFox2.util.GetCalcView()
local tEnts = ents.FindInSphere(view.pos, StormFox2.Setting.GetCache("window_distance",800))
for i,ent in ipairs(tEnts) do
local c = ent:GetClass()
if c == "prop_dynamic" or c == "prop_physics" then -- Models
local c = ent:GetPos() + ent:OBBCenter()
local n = ent:GetAngles():Forward()
local t = (ent:OBBMaxs() - ent:OBBMins()).x + 1
if IsWinModel(ent:GetModel()) and (SkyLine(c + n * t,n, 7, 180, ent) or SkyLine(c + -n * t,-n, 7, 180, ent) ) then
local min,max = GetWindowModel(ent:GetModel())
local c = min + (max - min) / 2
local w,h = max:Distance(Vector(min.x,min.y,max.z)),max.z - min.z
local window = CreateWindowRef(ent,c)
window.w = w
window.h = h
table.insert(glass_dynamic, window)
end
elseif c:sub(0,14) == "func_breakable" or c == "func_brush" then -- Brushes
if ent._sf2_validwin == false then continue end -- Already scanned this
if ent._sf2_validwin == true then
table.insert(glass_dynamic, ent._sf2_mwindow)
else -- Figure out
local surfs = ent:GetBrushSurfaces()
if #surfs < 1 then -- Invalid brush
ent._sf2_validwin = false
continue
elseif #surfs < 2 then -- Properly a nocull
if IsWindowMaterial(surfs[1]:GetMaterial()) and SurfaceInfo_FacingOutside(ent, surfs[1], true) then
local window = CreateWindowRef(ent,surfs[1]:GetCenter())
window.mesh = surfs[1]:GetMesh()
ent._sf2_mwindow = window
ent._sf2_validwin = true
table.insert(glass_dynamic, window)
else
ent._sf2_validwin = false
end
continue
else
local window
local t = {}
for _,surf in ipairs(surfs) do
if surf:GetMinSide() > 10 and IsWindowMaterial(surf:GetMaterial()) and SurfaceInfo_FacingOutside(ent, surf) then
if not window then
window = CreateWindowRef(ent,surf:GetCenter())
end
-- Add mesh
for i,v in ipairs(surf:GetMesh()) do
table.insert(t, v)
end
end
end
if not window then
ent._sf2_validwin = false
continue
end
ent._sf2_validwin = true
window.mesh = t
ent._sf2_mwindow = window
table.insert(glass_dynamic, window)
end
end
end
end
end
hook.Add("PostCleanupMap", "StormFox2.environment.onclean", scan_dynamic)
local scan = function() -- Locates all surfaceinfos we need.
StormFox2.Msg("Scanning surfaces ..")
surfaceinfos = {}
local puddle_surfaceinfos = {}
local temp_glass = {} -- [mat_string][Normal][ID] = surface
-- Scan all brushsurfaces and grab the glass/windows, water and metal. Put them in a table with matching normal.
for i,v in ipairs( game.GetWorld():GetBrushSurfaces() ) do
if not v then continue end
if not v:IsValid() then continue end
if not v:IsWater() and v:GetNormal():Dot(Vector(0,0,1)) < -0.999 and #v:GetVertices() == 4 then -- Can be a puddle
local min,max = v:GetMinSide(),v:GetMaxSide()
if v:GetMinSide() > 64 and math.abs(min / max) > 0.7 and UnderSky(v:GetCenter()) then
table.insert(puddle_surfaceinfos, v)
end
end
local v_type = SurfaceInfo_GetType( game.GetWorld(), v )
if v_type == INVALID_VERTS then continue end -- Invalid or doesn't have a type.
if not surfaceinfos[v_type] then surfaceinfos[v_type] = {} end
table.insert(surfaceinfos[v_type], {v, v:GetCenter()} )
if v_type ~= GLASS_VERTS and v_type ~= GLASS_ROOF_VERTS then continue end -- If it isn't glass. We're done.
-- Make the normal bit fuzzy.
local N = v:GetNormal()
local N_Fuzzy = Vector(N.x,N.y,N.z)
N_Fuzzy[1] = round(N_Fuzzy[1],1)
N_Fuzzy[2] = round(N_Fuzzy[2],1)
N_Fuzzy[3] = round(N_Fuzzy[3],1)
-- We use insertmatch, since vectors are pointers when used as keys.
local Point_N = FindMatchPointer(temp_glass, N_Fuzzy)
if Point_N then
table.insert(temp_glass[Point_N],{v, fuzzeVectors(v:GetVertices())})
else
temp_glass[N_Fuzzy] = {{v, fuzzeVectors(v:GetVertices())}}
end
end
-- We now have temp_glass[v_type][tex_string][Normal][i] = {surface_info, verts}
-- Now we need to group surfaces together.
local temp_group = {}
for N,list in pairs(temp_glass) do
if #list == 1 then -- Only one surface of this type on the map.
table.insert(temp_group, {list[1][1]})
else
FindMerges(list,temp_group)
end
end
temp_glass = nil
coroutine.yield() -- Wait a bit
-- Generate a mesh for each group
StormFox2.Msg("Generating glass-mesh data ..")
local glass_planes = {{},{}}
for group_i,t_group in ipairs(temp_group) do -- For each group.
if #t_group < 1 then continue end
local mesh = {}
-- Add all triangles together
local a = t_group[1]:GetAngles()
local max_height,min_height,max_width,min_width
local max_vec,min_vec
for i,surf in ipairs(t_group) do
if not surf then continue end
for _,t in ipairs(surf:GetMesh() or {}) do
local vec_r = Vector(t.pos[1],t.pos[2],t.pos[3])
vec_r:Rotate(-a) -- x = nil, y = w, z = h
t.vec_r = vec_r
t.pos = t.pos
table.insert(mesh, t)
local h,w = vec_r[3], vec_r[2]
max_height = math.max(max_height or h,h)
min_height = math.min(min_height or h,h)
max_width = math.max(max_width or w,w)
min_width = math.min(min_width or w,w)
if not max_vec then
max_vec = Vector(t.pos[1],t.pos[2],t.pos[3])
min_vec = Vector(t.pos[1],t.pos[2],t.pos[3])
else
max_vec[1] = math.max(max_vec[1],t.pos[1])
max_vec[2] = math.max(max_vec[2],t.pos[2])
max_vec[3] = math.max(max_vec[3],t.pos[3])
min_vec[1] = math.min(min_vec[1],t.pos[1])
min_vec[2] = math.min(min_vec[2],t.pos[2])
min_vec[3] = math.min(min_vec[3],t.pos[3])
end
end
--[[
if GetNocull(surf) and false then
local m = surf:GetMesh()
for i = #m,1,-1 do
local t = table.Copy(m[i])
local vec_r = Vector(t.pos[1],t.pos[2],t.pos[3])
vec_r:Rotate(-n:Angle()) -- x = nil, y = w, z = h
t.vec_r = vec_r
t.normal = -t.normal
table.insert(mesh, t)
end
end]]
end
if not max_width then continue end
local width_range = max_width - min_width
local height_range = max_height - min_height
-- Adjust the UV
for i,v in ipairs(mesh) do
local h,w = v.vec_r[3] - min_height, v.vec_r[2] - min_width
mesh[i].u = w / width_range
mesh[i].v = 1 - (h / height_range)
end
-- Mesh with UV
for _,v in ipairs(mesh) do
table.insert(glass_planes[1] , table.Copy(v))
end
-- Mesh with UV-Scale
for i,v in ipairs(mesh) do
mesh[i].u = mesh[i].u * width_range / 64 + group_i / 7
mesh[i].v = mesh[i].v * height_range / 64 + group_i / 14
end
for _,v in ipairs(mesh) do
table.insert(glass_planes[2] , table.Copy(v))
end
end
temp_group = nil
coroutine.yield() -- Wait a bit
-- Add static models
StormFox2.Msg("Including window models ..")
for _,data in pairs(StormFox2.Map.StaticProps()) do
if not surfaceinfos[GLASS_VERTS] then surfaceinfos[GLASS_VERTS] = {} end
if not data.PropType or not IsWinModel(data.PropType) then continue end
local min,max = GetWindowModel(data.PropType)
local scale = data.UniformScale or data.Scale or 1
local n = data.Angles:Forward()
local t1 = {pos = Vector(min.x,min.y,min.z) * scale, u = 0, v = 1, normal = n}
local t2 = {pos = Vector(min.x,min.y,max.z) * scale, u = 0, v = 0, normal = n}
local t3 = {pos = Vector(max.x,max.y,max.z) * scale, u = 1, v = 0, normal = n}
local t4 = {pos = Vector(max.x,max.y,min.z) * scale, u = 1, v = 1, normal = n}
local w = Vector(max.x,max.y,min.z):Distance(min)
-- Rotate
t1.pos:Rotate(data.Angles)
t2.pos:Rotate(data.Angles)
t3.pos:Rotate(data.Angles)
t4.pos:Rotate(data.Angles)
-- Apply origin
t1.pos = t1.pos + data.Origin
t2.pos = t2.pos + data.Origin
t3.pos = t3.pos + data.Origin
t4.pos = t4.pos + data.Origin
local mesh = {t1,t2,t3,t1,t3,t4}
-- UV Mesh
for _,t in ipairs(mesh) do
table.insert(glass_planes[1] , t)
end
-- 64x64 Mesh
for _,t in ipairs(mesh) do
t.u = t.u * w / 64
t.v = 1 - (t.pos.z / 64)
table.insert(glass_planes[2] , t)
end
table.insert(surfaceinfos[GLASS_VERTS], {nil, data.Origin + t1.pos + (t1.pos - t3.pos) / 2} )
end
coroutine.yield() -- Wait a bit
-- We now got 4 map-wide meshes.
StormFox2.Msg("Generating glass-meshs [" .. #glass_planes[1] .. "] ..")
-- Refract mesh
local obj = Mesh(refact_mat)
obj:BuildFromTriangles(glass_planes[1])
table.insert(STORMFOX_WINDOWMESHES, obj)
local obj2 = Mesh(refact_mat)
obj2:BuildFromTriangles(glass_planes[2])
table.insert(STORMFOX_WINDOWMESHES, obj2)
coroutine.yield() -- Wait a bit
-- Normal Mesh
local obj3 = Mesh(norm_mat)
obj3:BuildFromTriangles(glass_planes[1])
table.insert(STORMFOX_WINDOWMESHES, obj3)
local obj4 = Mesh(norm_mat)
obj4:BuildFromTriangles(glass_planes[2])
table.insert(STORMFOX_WINDOWMESHES, obj4)
glass_planes = nil
glass_mapmesh = {obj,obj2,obj3,obj4}
-- Puddle
obj,obj2,obj3,obj4 = nil,nil,nil,nil
StormFox2.Msg("Generating puddle-meshs [" .. #puddle_surfaceinfos .. "] ..")
local mesh = {}
for i,v in ipairs(puddle_surfaceinfos) do
local r = i % 6
if r > 3 then continue end
for a,t in ipairs(v:GetMesh()) do
if r == 1 then
t.u = -t.u
elseif r == 2 then
t.u = -t.u
t.v = -t.v
elseif r == 3 then
t.v = -t.v
end
table.insert(mesh, t)
end
end
coroutine.yield() -- Wait a bit
puddle_mapmesh = Mesh(puddle_mat)
puddle_mapmesh:BuildFromTriangles(mesh)
table.insert(STORMFOX_WINDOWMESHES, puddle_mapmesh)
-- Generate water mesh
if surfaceinfos[WATER_VERTS] then
StormFox2.Msg("Generating ice-mesh [" .. #surfaceinfos[WATER_VERTS] .. "] ")
local mesh = {}
STORMFOX_WATERMESH = Mesh(ice)
STORMFOX_WATERMESH_SKYBOX = Mesh(ice)
local c = Color(255,255,255)
local t = Vector(0,1,0)
local u = {0,1,0,-1}
for i,v in ipairs(surfaceinfos[WATER_VERTS]) do
local t = v[1]:GetVertices()
if StormFox2.Map.IsInside( v[2] ) then
for i = 1,3 do
local vec = t[i]
table.insert(STORMFOX_WATERMESHBUILD, {
pos = vec + vec_ex,
u = vec.x / ice_size,
v = vec.y / ice_size,
normal = vector_up,
tangent = t,
userdata = u,
color = c})
--table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex})
end
for i = 4,20 do
if #t < i then continue end
local vec = t[1]
table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex,
u = vec.x / ice_size,
v = vec.y / ice_size,
normal = vector_up,
tangent = t,
userdata = u,
color = c})
-- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex})
local vec = t[i - 1]
table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex,
u = vec.x / ice_size,
v = vec.y / ice_size,
normal = vector_up,
tangent = t,
userdata = u,
color = c})
-- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex})
local vec = t[i]
table.insert(STORMFOX_WATERMESHBUILD, {pos = vec + vec_ex,
u = vec.x / ice_size,
v = vec.y / ice_size,
normal = vector_up,
tangent = t,
userdata = u,
color = c})
-- table.insert(STORMFOX_WATERMESHCOLLISON, {pos = vec + vec_ex})
end
if #t >= 3 then
local t2 = {}
if #t == 3 then
for i = 1,3 do
table.insert(t2, t[i] + vec_ex)
table.insert(t2, t[i] - vec_ex)
end
table.insert(t2, t[3] + vec_ex)
table.insert(t2, t[3] - vec_ex)
table.insert(STORMFOX_WATERMESHCOLLISON, t2)
elseif #t == 4 then
for i = 1,4 do
table.insert(t2, t[i] + vec_ex)
table.insert(t2, t[i] - vec_ex)
end
table.insert(STORMFOX_WATERMESHCOLLISON, t2)
else
for i = 1,#t do
table.insert(t2, t[i] + vec_ex)
table.insert(t2, t[i] - vec_ex)
end
table.insert(STORMFOX_WATERMESHCOLLISON, t2)
end
end
elseif CLIENT then
local s = StormFox2.Map.GetSkyboxScale() or 1
for i = 1,3 do
local vec = t[i]
table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)})
end
for i = 4,20 do
if #t < i then continue end
local vec = t[1]
table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)})
local vec = t[i - 1]
table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)})
local vec = t[i]
table.insert(STORMFOX_WATERMESHBUILD_SKYBOX, {pos = vec + (vec_ex / s),u = vec.x / ice_size * s,v = vec.y / ice_size * s, normal = Vector(0,0,1)})
end
end
end
-- Build the water
STORMFOX_WATERMESH:BuildFromTriangles(STORMFOX_WATERMESHBUILD)
STORMFOX_WATERMESH_SKYBOX:BuildFromTriangles(STORMFOX_WATERMESHBUILD_SKYBOX)
end
-- Add window-entities
StormFox2.Msg("Locating window entities ..")
coroutine.yield() -- Wait a bit
scan_dynamic()
coroutine.yield(true)
end
local cor_scan = coroutine.wrap(scan)
local function StartGenerating()
timer.Create("SF_ENV_SCAN", 0.2, 0, function()
if cor_scan() then
cor_scan = nil
timer.Remove("SF_ENV_SCAN")
StormFox2.Msg("Meshes completed.")
end
end)
hook.Remove("StormFox2.InitPostEntity", "StormFox_ENV_SCAN")
end
hook.Add("StormFox2.InitPostEntity", "StormFox_ENV_SCAN", StartGenerating)
--[[-------------------------------------------------------------------------
Renders glass-meshes.
---------------------------------------------------------------------------]]
local TEX_SIZE = 512
local RT_Win = GetRenderTarget( "SF_Win", TEX_SIZE, TEX_SIZE )
local RT_Win64 = GetRenderTarget( "SF_Win_64", TEX_SIZE, TEX_SIZE )
local RT_Win_Ref = GetRenderTarget( "SF_Win_R", TEX_SIZE, TEX_SIZE )
local RT_Win64_Ref = GetRenderTarget( "SF_Win_64_R", TEX_SIZE, TEX_SIZE )
-- Returns the current window-renders
local function GetRenderFunctions()
if not StormFox2.Weather or not StormFox2.Terrain or not StormFox2.Terrain.GetCurrent then return end
local cT = StormFox2.Terrain.GetCurrent() or {}
local cW = StormFox2.Weather.GetCurrent()
return cW._RenderWindow or cT.windRender, cW._RenderWindowRefract or cT.windRenderRef, cW._RenderWindow64x64 or cT.windRender64, cW._RenderWindowRefract64x64 or cT.windRenderRef64
end
local close_window_ents = {} -- glass_dynamic
local Win,Win64,Win_Ref,Win64_Ref = Material("stormfox2/effects/window/win"),Material("stormfox2/effects/window/win_64"),Material("stormfox2/effects/window/win_refract"),Material("stormfox2/effects/window/win_refract_64")
local function Mat_Update(rt_tex,func)
render.PushRenderTarget(rt_tex)
render.Clear( 0, 0, 0, 0 )
render.ClearDepth()
-- render.PushFilterMag( 0 )
-- render.PushFilterMin( 0 )
cam.Start2D()
surface.SetDrawColor(color_white)
func(TEX_SIZE,TEX_SIZE)
cam.End2D()
-- render.PopFilterMag()
-- render.PopFilterMin()
render.PopRenderTarget()
end
-- Update material
hook.Add("Think", "StormFox2.Environment.UpdateRTWIndow", function()
if not StormFox2.Terrain then return end
if not StormFox2.Setting.GetCache("window_enable", true) then return end
local windRender, windRenderRef, windRender64, windRender64Ref = GetRenderFunctions()
-- Refract materials
if windRenderRef then
Win_Ref:SetTexture( "$normalmap", RT_Win_Ref )
Mat_Update(RT_Win_Ref, windRenderRef)
end
if windRender64Ref then
Win64_Ref:SetTexture( "$normalmap", RT_Win64_Ref )
Mat_Update(RT_Win64_Ref, windRender64Ref)
end
-- Regular materials
if windRender then
Win:SetTexture( "$basetexture", RT_Win )
Mat_Update(RT_Win, windRender)
end
if windRender64 then
Win64:SetTexture( "$basetexture", RT_Win64 )
Mat_Update(RT_Win64, windRender64)
end
end)
local function DrawCloseWindows()
for i,v in ipairs(close_window_ents) do
v:Draw()
end
end
hook.Add("PreDrawTranslucentRenderables", "StormFox2.Environment.RenderWindow", function(a,b)
if (b or not StormFox2.Terrain) then return end
if puddle_mapmesh then
--render.SetMaterial(puddle_mat)
--puddle_mapmesh:Draw()
end
if #glass_mapmesh < 4 then return end
if not StormFox2.Setting.GetCache("window_enable", true) then return end
local windRender, windRenderRef, windRender64, windRender64Ref = GetRenderFunctions()
-- Refract materials
if windRenderRef then
render.SetMaterial(Win_Ref)
glass_mapmesh[1]:Draw()
DrawCloseWindows()
end
if windRender64Ref then
render.SetMaterial(Win64_Ref)
glass_mapmesh[2]:Draw()
DrawCloseWindows()
end
-- Regular materials
if windRender then
render.SetMaterial(Win)
glass_mapmesh[3]:Draw()
DrawCloseWindows()
end
if windRender64 then
render.SetMaterial(Win64)
glass_mapmesh[4]:Draw()
DrawCloseWindows()
end
end)
--[[-------------------------------------------------------------------------
Handles the location for the client.
Will check if they're outside, in wind(downfall), near glass, near outside, indoors and roof.
---------------------------------------------------------------------------]]
-- Nearest stuff
local nearest_window, nearest_outside
-- Roof
local roof_type, roof_pos -- Roof_type is hit_type enums from lib/sh_downfall.lua
-- Bools
local is_inside, is_inwind, in_water
local z_distance = 0
local viewPos
local function sort_func(a, b)
return a[2]:DistToSqr(viewPos) < b[2]:DistToSqr(viewPos)
end
local update_windows_tick = 0
local function env_corotinefunction()
local view = StormFox2.util.GetCalcView()
viewPos = view.pos
-- If we're in water, locate the z_position
if bit.band( util.PointContents( viewPos ), CONTENTS_WATER ) == CONTENTS_WATER then
local t = AdvTrace(viewPos,viewPos + Vector(0,0,1000), MASK_WATER)
if t.FractionLeftSolid then
in_water = viewPos.z + 1000 * t.FractionLeftSolid
else
in_water = (viewPos + Vector(0,0,1000)).z
end
else
in_water = nil
end
-- Update close windows
update_windows_tick = update_windows_tick + 1
if update_windows_tick >= 10 then
scan_dynamic()
update_windows_tick = 0
end
-- Update close windows
close_window_ents = {}
for _,v in ipairs(glass_dynamic) do -- ent,c
if not v:IsAlive() then continue end
if viewPos:Distance(v:GetCenter()) > 10000 then continue end
table.insert(close_window_ents, v)
end
-- is inside
is_inwind = StormFox2.Wind.IsEntityInWind(LocalPlayer(),true)
if not is_inwind then
local veh = LocalPlayer():GetVehicle()
if IsValid(veh) then
is_inwind = StormFox2.Wind.IsEntityInWind(veh,true)
end
end
is_inside = not (is_inwind or UnderSky2(viewPos))
-- ZDis
local tr = PlyTrace( StormFox2.util.ViewEntity(), Vector( 0, 0, -16000))
if not tr.Hit then
z_distance = 16000
else
z_distance = tr.Fraction * 16000
end
-- Locate nearest outside / window
if is_inside and not in_water then
-- Nearest window
if surfaceinfos[GLASS_VERTS] then
nearest_window = nil
-- Sort windows
table_sort(surfaceinfos[GLASS_VERTS],sort_func)
-- Check brushes
for i = 1,10 do
if not surfaceinfos[GLASS_VERTS][i] then break end
local surfinfo = surfaceinfos[GLASS_VERTS][i][1]
local winpos = surfaceinfos[GLASS_VERTS][i][2]
local v, tr = MatchTrace(view.pos,winpos,surfinfo and surfinfo:GetNormal())
if v then -- Check window normal and trace
nearest_window = winpos
break
end
end
-- Check ents
local curDist = nearest_window and nearest_window:DistToSqr(view.pos)
for i = 1,#close_window_ents do
if not close_window_ents[i]:IsAlive() then continue end -- Check if it is alive
local dis = close_window_ents[i]:GetCenter():Distance(view.pos)
if curDist and dis > curDist then continue end
local win = close_window_ents[i]
local n = view.ang:Forward()
--debugoverlay.Cross(win:GetCenter()- n * 15, 15, 1, color_white)
local v, tr = MatchTrace(view.pos,win:GetCenter() - n * 15)
if not v then continue end
curDist = dis
nearest_window = win:GetCenter()
end
end
coroutine.yield()
-- Nearest outside
local oldpos = nearest_outside
-- Scan forward the player, then the players view (IF they look down at an angle towards the outside)
-- SkyLine(pos,norm, n, nMaxDistance, filter, sky_mask)
nearest_outside = SkyLine(view.pos,Angle(0,view.ang.y,0):Forward(), 7, 500, nil, MASK_SHOT) or SkyLine(view.pos,view.ang:Forward(), 7, 500, nil, MASK_SHOT)
-- If we don't hit anything, scan around the player
if not nearest_outside then
local lines = math.min(StormFox2.Client.GetQualityNumber() * 2,10)
local r = 360 / lines
for i = 1,lines do
nearest_outside = SkyLine(view.pos,Angle(0,r * i,0):Forward(), 5, 600,nil, MASK_SHOT)
if nearest_outside then break end
end
end
-- If the oldpos is still valid. Check to see if the currentpos is closer.
if oldpos and EasyTrace(view.pos,oldpos) then -- If old pos and we still see old pos
if nearest_outside then
nearest_outside = ( oldpos:DistToSqr(view.pos) < nearest_outside:DistToSqr(view.pos) ) and oldpos or nearest_outside
else
nearest_outside = oldpos
end
end
coroutine.yield()
-- Roof pos
roof_pos, roof_type = StormFox2.DownFall.CheckDrop(viewPos, Vector(0,0,-1), 3, StormFox2.util.ViewEntity())
--debugoverlay.Cross(roof_pos, 15, 1, color_white, true)
end
coroutine.yield()
end
local env_corotine = coroutine.wrap(function()
while true do
env_corotinefunction()
end
end)
local outsideFade = 0
timer.Create("stormfox2.enviroment.think", 0.25, 0, function()
if not StormFox2.Loaded or not _STORMFOX_POSTENTITY then return end
if not StormFox2.Setting.GetCache("window_enable", true) then return end
env_corotine()
if is_inside then
outsideFade = math.Approach(outsideFade, 0, FrameTime() * 20)
else
outsideFade = math.Approach(outsideFade, 1, FrameTime() * 3.2)
end
end)
---Returns a table with the current environment data.
---@return table
---@client
function StormFox2.Environment.Get()
local t = {}
t.outside = not is_inside
t.in_water = in_water
t.z_distance = z_distance
if is_inside and not in_water then
t.nearest_window = is_inside and nearest_window
t.nearest_outside = is_inside and nearest_outside
if roof_pos then
t.roof_z = roof_pos.z
t.roof_type = roof_type
end
end
return t
end
---A float that lerps slowly between 0 and 1 when going inside / outside.
---@return number
---@client
function StormFox2.Environment.GetOutSideFade()
return outsideFade
end
---Returns the clients height over ground.
---@param bForceUpdate boolean?
---@return number height
---@client
function StormFox2.Environment.GetZHeight( bForceUpdate )
local tr = PlyTrace( StormFox2.util.ViewEntity())
if not tr.Hit then
z_distance = 16000
else
z_distance = tr.Fraction * 16000
end
return z_distance
end
--[[NOTES:
- Make breakable windows have this too.
- Make a weather seed generator, and use it for puddles and clouds (better).
]]
--[[
StartGenerating()
timer.Create("StormFox2.enviroment.think", 0.25, 0, function()
env_corotine()
--print(coroutine.resume(env_corotine))
end)]]
--[[-------------------------------------------------------------------------
Ice sheet on the map
---------------------------------------------------------------------------]]
local b = #ents.FindByClass("stormfox_mapice") > 0
---Returns true if the map has ice on it.
---@return boolean hasIce
---@client
function StormFox2.Environment.HasMapIce()
return b
end
---Internal function !
---@param s boolean
---@deprecated
---@client
function StormFox2.Environment._SETMapIce(s)
b = s
end
---Renders the water.
---@param bSkyBox boolean
---@return boolean success
---@client
function StormFox2.Environment.DrawWaterOverlay(bSkyBox)
if not StormFox2.Setting.GetCache("enable_wateroverlay", true) or not StormFox2.Setting.SFEnabled() then return end
if not STORMFOX_WATERMESH_SKYBOX then return false end -- Invalid mesh.
if StormFox2.Environment.HasMapIce() then return false end -- Ice is on the map
if bSkyBox then
STORMFOX_WATERMESH_SKYBOX:Draw()
else
STORMFOX_WATERMESH:Draw()
end
return true
end
--[[Handle cubemaps]]
local lastF = 1
hook.Add("StormFox2.lightsystem.new", "StormFox2.lightsystem.cubemap", function(f)
lastF = f / 100
if not StormFox2.Setting.Get("edit_cubemaps", true) then return end
StormFox2.Map.SetCubeMapDarkness(f / 100)
end)
StormFox2.Setting.Callback("edit_cubemaps",function(switch)
if switch then -- Turn on
StormFox2.Map.SetCubeMapDarkness(lastF)
else -- Turn off
StormFox2.Map.SetCubeMapDarkness(1)
end
end,"sf_cubemap")