This commit is contained in:
lifestorm
2024-08-04 22:55:00 +03:00
parent 0e770b2b49
commit 94063e4369
7342 changed files with 1718932 additions and 14 deletions

View File

@@ -0,0 +1,69 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
--[[-------------------------------------------------------------------------
This scripts job is to sort out the computers and potatoes.
---------------------------------------------------------------------------]]
StormFox2.Client = StormFox2.Client or {}
StormFox2.Setting.AddCL("quality_ultra",false)
StormFox2.Setting.AddCL("quality_target",60,nil,nil, 0, 300)
local conDetect = 1
local t_num = {1, 1, 1, 1, 1, 1}
local i = 1
local q_num = 1
-- Calculate the avageFPS for the client and make a value we can use.
local bi,buffer = 0,0
local avagefps = 1 / RealFrameTime()
timer.Create("StormFox2.Client.PotatoSupport",0.25,0,function()
if not system.HasFocus() then -- The player tabbed out.
bi,buffer = 0,0
return
end
if bi < 10 then
buffer = buffer + 1 / RealFrameTime()
bi = bi + 1
else
avagefps = buffer / bi
bi,buffer = 0,0
local q = StormFox2.Setting.GetCache("quality_ultra",false)
local delta_fps = avagefps - StormFox2.Setting.GetCache("quality_target",80)
local delta = math.Clamp(delta_fps / 8,-3,3)
conDetect = math.Clamp(math.Round(conDetect + delta, 1),0,q and 20 or 7)
table.insert(t_num, conDetect)
table.remove(t_num, 1)
local a = 0
for _,v in ipairs( t_num ) do
a = a + v
end
q_num = (q_num + (a / #t_num)) / 2
end
end)
--[[<Client>-----------------------------------------------------------------
Returns a number based on the clients FPS.
7 is the max without the user enabling 'sf_quality_ultra', where it then goes up to 20.
---------------------------------------------------------------------------]]
---Returns a number based on the clients FPS. Where 7 is max (or 20 if sf_quality_ultra is enabled)
---@return number qualityNumber
---@return number avagefps
---@client
function StormFox2.Client.GetQualityNumber()
if not system.HasFocus() then
return 1, 1 / RealFrameTime()
end
-- Players have complained not seeing the particles when their FPS is low
--if game.SinglePlayer() then I've now had multiplayer complaints.
q_num = math.max(0.5, q_num)
--end
return q_num, avagefps
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,374 @@
--[[
| 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/
--]]
-- Fix overlapping tables
StormFox2.Time = StormFox2.Time or {}
local clamp,max,min = math.Clamp,math.max,math.min
StormFox2.Sun = StormFox2.Sun or {}
StormFox2.Moon = StormFox2.Moon or {}
StormFox2.Sky = StormFox2.Sky or {}
local sunVisible
-- Pipe Dawg
---Returns an obstruction-number between 0 - 1 fot the sun.
---@return number sunVisible
---@client
function StormFox2.Sun.GetVisibility()
return sunVisible or 1
end
-- Pixel are a bit crazy if called twice
hook.Add("Think","StormFox2.Sun.PixUpdater",function()
if not StormFox2.Loaded then return end
if not _STORMFOX_SUNPIX then -- Create pixel
_STORMFOX_SUNPIX = util.GetPixelVisibleHandle()
else
sunVisible = util.PixelVisible( LocalPlayer():GetPos() + StormFox2.Sun.GetAngle():Forward() * 4096, StormFox2.Mixer.Get("sun_size",30), _STORMFOX_SUNPIX )
end
end)
-- Sun overwrite
SF_OLD_SUNINFO = SF_OLD_SUNINFO or util.GetSunInfo() -- Just in case
---Overrides util.GetSunInfo to use SF2 variables.
---@ignore
---@client
---@return table GetSunInfo
function util.GetSunInfo()
if not StormFox2.Loaded or not sunVisible then -- In case we mess up
if SF_OLD_SUNINFO then
return SF_OLD_SUNINFO
else
return {}
end
end
local tab = {
["direction"] = StormFox2.Sun.GetAngle():Forward(),
["obstruction"] = sunVisible * (StormFox2.Mixer.Get("skyVisibility") / 100)}
return tab
end
-- SkyRender
--[[-------------------------------------------------------------------------
Render layers
StarRender = Stars
SunRender
BlockStarRender (Will allow you to block out stars/sun)
Moon
CloudBox (Like a skybox, just with transparency. Will fade between states)
CloudLayer (Moving clouds)
---------------------------------------------------------------------------]]
hook.Add("PostDraw2DSkyBox", "StormFox2.SkyBoxRender", function()
if not StormFox2 then return end
if not StormFox2.Loaded then return end
-- Just to be sure. I hate errors in render hooks.
if not StormFox2.util then return end
if not StormFox2.Sun then return end
if not StormFox2.Moon then return end
if not StormFox2.Moon.GetAngle then return end
if not StormFox2.Setting.SFEnabled() then return end
local c_pos = StormFox2.util.RenderPos()
local sky = StormFox2.Setting.GetCache("enable_skybox", true)
local use_2d = StormFox2.Setting.GetCache("use_2dskybox",false)
cam.Start3D( Vector( 0, 0, 0 ), EyeAngles() ,nil,nil,nil,nil,nil,1,32000) -- 2d maps fix
render.OverrideDepthEnable( false,false )
render.SuppressEngineLighting(true)
render.SetLightingMode( 0 )
if not use_2d or not sky then
hook.Run("StormFox2.2DSkybox.StarRender", c_pos)
-- hook.Run("StormFox2.2DSkybox.BlockStarRender",c_pos)
hook.Run("StormFox2.2DSkybox.SunRender", c_pos) -- No need to block, shrink the sun.
hook.Run("StormFox2.2DSkybox.Moon", c_pos)
end
hook.Run("StormFox2.2DSkybox.CloudBox", c_pos)
hook.Run("StormFox2.2DSkybox.CloudLayer", c_pos)
hook.Run("StormFox2.2DSkybox.PostCloudLayer",c_pos)
hook.Run("StormFox2.2DSkybox.FogLayer", c_pos)
render.SuppressEngineLighting(false)
render.SetLightingMode( 0 )
render.OverrideDepthEnable( false, false )
cam.End3D()
render.SetColorMaterial()
end)
-- Render Sun
local sunMat = Material("stormfox2/effects/sun/sun_mat")
local sunMat2 = Material("stormfox2/effects/sun/sun_mat2")
local sunMat3 = Material("stormfox2/effects/sun/sunflare")
local sunDot = 1
hook.Add("StormFox2.2DSkybox.SunRender","StormFox2.RenderSun",function(c_pos)
local SunA = StormFox2.Sun.GetAngle()
local SunN = -SunA:Forward()
local sun = util.GetSunInfo()
local viewAng = StormFox2.util.RenderAngles()
-- Calculate dot
local rawDot = ( SunA:Forward():Dot( viewAng:Forward() ) - 0.8 ) * 5
if sun and sun.obstruction and sun.obstruction > 0 then
sunDot = rawDot
else
sunDot = 0
end
-- Calculate close to edge
local z = 1
local p = math.abs(math.sin(math.rad(SunA.p))) -- How far we are away from sunset
if p < 0.1 then
z = 0.8 + p * 0.2
end
local s_size = StormFox2.Sun.GetSize() / 2
local s_size2 = s_size * 1.2
local s_size3 = s_size * 3 -- * math.max(0, rawDot)
local c_c = StormFox2.Sun.GetColor() or color_white
local c = Color(c_c.r,c_c.g,c_c.b,c_c.a)
render.SetMaterial(sunMat)
-- render.DrawQuadEasy( SunN * -200, SunN, s_size, s_size, c, 0 )
render.SuppressEngineLighting(true)
render.SetMaterial(sunMat2)
render.DrawQuadEasy( SunN * -200, SunN, s_size2, s_size2, c, 0 )
if sunDot > 0 then
local a = (StormFox2.Mixer.Get("skyVisibility") / 100 - 0.5) * 2
if a > 0 then
c.a = a * 255
render.SetMaterial(sunMat3)
render.DrawQuadEasy( SunN * -200, SunN, s_size3 * sunDot , s_size3 * sunDot, c, 0 )
end
end
render.SuppressEngineLighting(false)
end)
-- Sun and moon beams
local beams = StormFox2.Setting.AddCL("enable_sunbeams", true)
local matSunbeams = Material( "pp/sunbeams" )
matSunbeams:SetTexture( "$fbtexture", render.GetScreenEffectTexture() )
local function SunRender( sunAltitude )
if sunDot <= 0 then return false end
-- Check if we see the sun at all
local vis = StormFox2.Sun.GetVisibility()
if ( vis == 0 ) then return false end
-- Brightness multiplier
local bright
if sunAltitude > 0 and sunAltitude < 30 then
bright = 1
elseif sunAltitude >= 30 then
bright = 1.3 - 0.02 * sunAltitude
else -- Under 0
bright = 0.06 * sunAltitude + 1
end
if bright < 0 then return end -- Too far up in the sky
local direciton = StormFox2.Sun.GetAngle():Forward()
local beampos = EyePos() + direciton * 4096
-- And screenpos
local scrpos = beampos:ToScreen()
local mul = vis * sunDot * bright
if mul >= 0 then
local s_size = StormFox2.Sun.GetSize()
render.UpdateScreenEffectTexture()
matSunbeams:SetFloat( "$darken", .96 )
matSunbeams:SetFloat( "$multiply",0.7 * mul)
matSunbeams:SetFloat( "$sunx", scrpos.x / ScrW() )
matSunbeams:SetFloat( "$suny", scrpos.y / ScrH() )
matSunbeams:SetFloat( "$sunsize", s_size / 850 )
render.SetMaterial( matSunbeams )
render.DrawScreenQuad()
end
return true
end
local function MoonRender( sunAltitude )
-- Calculate brightness
local skyVis = StormFox2.Mixer.Get("skyVisibility") / 100
if skyVis <= 0 then return end
-- Brightness of the moon
local mP = StormFox2.Moon.GetPhase()
if mP == SF_MOON_NEW then return end -- No moon
-- Sun checkk
local mul = 1
if sunAltitude > -20 then
mul = -0.2 * sunAltitude - 3
end
-- Phase multiplier (Full moon is 1, goes down to 0.25)
local pmul = math.min(1, (-0.0714286 * mP^2 + 0.571429 * mP - 0.285714) * 1.17)
local brightness = skyVis * mul * pmul
if brightness <= 0 then return end
local viewAng = StormFox2.util.RenderAngles()
-- Calculate dot
local moonAng = StormFox2.Moon.GetAngle()
local rawDot = ( moonAng:Forward():Dot( viewAng:Forward() ) - 0.8 ) * 5
brightness = brightness * rawDot
if brightness <= 0 then return end
local direciton = StormFox2.Moon.GetAngle():Forward()
local beampos = EyePos() + direciton * 4096
-- And screenpos
local scrpos = beampos:ToScreen()
local s_size = StormFox2.Moon.GetSize()
render.UpdateScreenEffectTexture()
matSunbeams:SetFloat( "$darken", .5 )
matSunbeams:SetFloat( "$multiply",0.15 * brightness)
matSunbeams:SetFloat( "$sunx", scrpos.x / ScrW() )
matSunbeams:SetFloat( "$suny", scrpos.y / ScrH() )
matSunbeams:SetFloat( "$sunsize", s_size / 950 )
render.SetMaterial( matSunbeams )
render.DrawScreenQuad()
end
hook.Add( "RenderScreenspaceEffects", "StormFox2.Sun.beams", function()
if ( not render.SupportsPixelShaders_2_0() ) then return end
if not StormFox2.Setting.SFEnabled() or not beams:GetValue() then return end
local sunAltitude = StormFox2.Sun.GetAltitude()
if sunAltitude > -15 then
SunRender(sunAltitude)
--else TODO: Looks kinda aweful sadly.
--MoonRender(sunAltitude)
end
end )
-- Render moon
-- Setup params and vars
local CurrentMoonTexture = Material("stormfox2/effects/moon/rt_moon")
local Mask_25 = Material("stormfox2/effects/moon/25.png")
local Mask_0 = Material("stormfox2/effects/moon/0.png")
local Mask_50 = Material("stormfox2/effects/moon/50.png")
local Mask_75 = Material("stormfox2/effects/moon/75.png")
local texscale = 512
local RTMoonTexture = GetRenderTargetEx( "StormFox_RTMoon", texscale, texscale, 1, MATERIAL_RT_DEPTH_NONE, 2, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888)
-- Functions to update the moon phase
local lastRotation = -1
local lastCurrentPhase = -1
local lastMoonMat
local function RenderMoonPhase(rotation,currentPhase)
--currentPhase = SF_MOON_FIRST_QUARTER - 0.01
if currentPhase == SF_MOON_NEW then return end -- New moon. No need to render.
-- Check if there is a need to re-render
local moonMat = StormFox2.Mixer.Get("moonTexture",lastMoonMat)
if type(moonMat) ~= "string" then return end -- Something went wrong. Lets wait.
if lastCurrentPhase == currentPhase and lastMoonMat and lastMoonMat == moonMat then
-- Already rendered
return true
end
lastCurrentPhase = currentPhase
lastMoonMat = moonMat
moonMat = Material(moonMat)
render.PushRenderTarget( RTMoonTexture )
render.OverrideAlphaWriteEnable( true, true )
render.ClearDepth()
render.Clear( 0, 0, 0, 0 )
cam.Start2D()
-- Render moon
surface.SetDrawColor(color_white)
surface.SetMaterial(moonMat)
surface.DrawTexturedRectUV(0,0,texscale,texscale,-0.01,-0.01,1.01,1.01)
-- Mask Start
-- render.OverrideBlendFunc( true, BLEND_ZERO, BLEND_SRC_ALPHA, BLEND_DST_ALPHA, BLEND_ZERO )
render.OverrideBlend(true, BLEND_ZERO, BLEND_SRC_ALPHA,0,BLEND_DST_ALPHA, BLEND_ZERO,0)
-- Render mask
surface.SetDrawColor(color_white)
-- New to first q; 0 to 50%
if currentPhase < SF_MOON_FIRST_QUARTER then
local s = 7 - 3.5 * currentPhase
surface.SetMaterial(Mask_25)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation)
if currentPhase >= SF_MOON_WAXIN_CRESCENT then
-- Ex step
local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation))
surface.SetMaterial(Mask_0)
surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.51),texscale / 2 + y * (-texscale * 0.51),texscale * 1,texscale,rotation)
end
elseif currentPhase == SF_MOON_FIRST_QUARTER then -- 50%
surface.SetMaterial(Mask_50)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale,texscale,rotation)
elseif currentPhase < SF_MOON_FULL then -- 50% to 100%
local s = (currentPhase - SF_MOON_FIRST_QUARTER) * 3
surface.SetMaterial(Mask_75)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation + 180)
local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation))
surface.SetMaterial(Mask_0)
if s < 0.2 then
surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.5),texscale / 2 + y * (-texscale * 0.51),texscale * 1,texscale,rotation + 180)
elseif s < 1 then
surface.DrawTexturedRectRotated(texscale / 2 + x * (-texscale * 0.5),texscale / 2 + y * (-texscale * 0.51),texscale * 0.9,texscale,rotation + 180)
end
elseif currentPhase == SF_MOON_FULL then
-- FULL MOON
elseif currentPhase < SF_MOON_LAST_QUARTER then
local s = 12 - (currentPhase - SF_MOON_FIRST_QUARTER) * 3
surface.SetMaterial(Mask_75)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation)
local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation))
surface.SetMaterial(Mask_0)
if s < 0.05 then
surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.5),texscale / 2 + y * (texscale * 0.51),texscale * 1,texscale,rotation)
elseif s < 1 then
surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.5),texscale / 2 + y * (texscale * 0.51),texscale * 0.9,texscale,rotation)
end
elseif currentPhase == SF_MOON_LAST_QUARTER then
surface.SetMaterial(Mask_50)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale,texscale,rotation + 180)
elseif currentPhase < SF_MOON_WANING_CRESCENT + 1 then
local s = (currentPhase - (SF_MOON_WANING_CRESCENT - 1)) * 3.5
surface.SetMaterial(Mask_25)
surface.DrawTexturedRectRotated(texscale / 2,texscale / 2,texscale * s,texscale,rotation + 180)
if currentPhase >= SF_MOON_WAXIN_CRESCENT then
-- Ex step
local x,y = math.cos(math.rad(-rotation)),math.sin(math.rad(-rotation))
surface.SetMaterial(Mask_0)
surface.DrawTexturedRectRotated(texscale / 2 + x * (texscale * 0.51),texscale / 2 + y * (texscale * 0.51),texscale,texscale,rotation)
end
end
-- Mask End
render.OverrideBlend(false)
render.OverrideAlphaWriteEnable( false )
cam.End2D()
render.OverrideAlphaWriteEnable( false )
render.PopRenderTarget()
CurrentMoonTexture:SetTexture("$basetexture",RTMoonTexture)
end
hook.Add("StormFox2.2DSkybox.Moon","StormFox2.RenderMoon",function(c_pos)
local phase = StormFox2.Moon.GetPhase()
if phase <= 0 then return end
local moonScale = StormFox2.Mixer.Get("moonSize",20)
local moonAng = StormFox2.Moon.GetAngle()
local N = moonAng:Forward()
local NN = -N
local sa = moonAng.y
-- Render texture
-- currentYaw
RenderMoonPhase( ((moonAng.p < 270 and moonAng.p > 90) and 180 or 0),phase)
local c = StormFox2.Mixer.Get("moonColor",Color(170,170,170))
local a = StormFox2.Mixer.Get("skyVisibility",100) * 2
-- Dark moonarea
-- PrintTable(CurrentMoonTexture:GetKeyValues())
render.SetMaterial( CurrentMoonTexture )
local aa = max(0,(3.125 * a) - 57.5)
render.DrawQuadEasy( N * 200, NN, moonScale , moonScale, Color(c.r,c.g,c.b, aa ), sa )
end)
if true then return end
-- Render Sky
local scale = 256 * 1.5
local galixmat = Material("stormfox2/effects/nightsky3")
local c = Color(255,255,255)
hook.Add("StormFox2.2DSkybox.StarRender", "StormFox2.2DSkyBox.NS", function(c_pos)
render.SetMaterial( galixmat )
c.a = StormFox2.Mixer.Get("starFade",100) * 2.55
c.a = 255
local p = (0.001) * StormFox2.Time.GetSpeed_RAW()
local ang = Angle((RealTime() * p) % 360,0,0)
local n = ang:Forward() * 256
-- render.DrawQuadEasy(n, -n, scale * 4, scale, c, (ang.p < 270 and ang.p > 90) and 30 or 30 + 180)
-- render.DrawSphere(Vector(0,0,0), -10, 30, 30, c)
end)

View File

@@ -0,0 +1,175 @@
--[[
| 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/
--]]
--[[
Date
SV StormFox2.Date.SetYearDay( nDay ) Sets the yearday.
SH StormFox2.Date.GetYearDay() Gets the yearday.
SH StormFox2.Date.GetWeekDay( bNumbers ) Returns the weekday. Returns a number if bNumbers is true.
SH StormFox2.Date.GetMonth( bNumbers ) Returns the month. Returns a number if bNumbers is true.
SH StormFox2.Date.GetShortMonth() Returns the month in a 3-letter string.
Sh StormFox2.Date.GetDay() Returns the day within the month.
Sh StormFox2.Date.Get( bNumbers ) Returns the date in string format. MM/DD or DD/MM depending on location and settings. Returns in numbers if bNumbers is true.
]]
StormFox2.Setting.AddSV("real_time",false)
StormFox2.Date = {}
if SERVER then
---Sets the yearday. [0-365]
---@param nDay number
---@server
function StormFox2.Date.SetYearDay( nDay )
StormFox2.Network.Set("day", nDay % 365)
end
end
---Returns the day within the year. [0 - 364]
---@return number
---@shared
function StormFox2.Date.GetYearDay()
return StormFox2.Data.Get("day",0)
end
local day, month, weekday = -1,-1,-1
local function calcDate( nDay )
local t = string.Explode("-", os.date( "%d-%m-%w", nDay * 86400 ), false)
return tonumber(t[1]),tonumber(t[2]),tonumber(t[3])
end
hook.Add("StormFox2.data.change", "StormFox2.date.update", function(sKey, nDay)
if sKey ~= "day" then return end
day,month,weekday = calcDate( nDay )
end)
do
local t = {
[0] = "Sunday",
[1] = "Monday",
[2] = "Tuesday",
[3] = "Wednesday",
[4] = "Thursday",
[5] = "Friday",
[6] = "Saturday"
}
---Returns the current weekday ["Monday" - "Sunday"]. Does also accept a number between 0 - 6.
---@param number nil|number
---@return string
---@shared
function StormFox2.Date.GetWeekDay( number )
if type(number) == "number" then -- FFS people
return t[ number % 7 ] or "Unknown"
end
return t[ weekday ] or "Unknown"
end
end
do
local t = {
[1] = "January",
[2] = "February",
[3] = "March",
[4] = "April",
[5] = "May",
[6] = "June",
[7] = "July",
[8] = "August",
[9] = "September",
[10] = "October",
[11] = "November",
[12] = "December"
}
---Returns the current month ["January" - "December"]. Also accepts a number between 1 - 12.
---@param number nil|number
---@return string
---@shared
function StormFox2.Date.GetMonth( number )
if type(number) == "number" then -- FFS people
return t[ number % 13 ] or "Unknown"
end
return t[ month ] or "Unknown"
end
end
---Returns the current month in short ["Jan" - "Dec"]. Also accepts a number between 1 - 12.
---@param number nil|number
---@return string
---@shared
function StormFox2.Date.GetShortMonth( number )
return string.sub(StormFox2.Date.GetMonth( number ),0,3)
end
--- Returns the day of the month: 1 - 31.
---@return number
---@shared
function StormFox2.Date.GetDay()
return day
end
local country = system.GetCountry() or "UK"
local crazy_countries = {"AS", "BT", "CN", "FM", "GU", "HU", "JP", "KP", "KR", "LT", "MH", "MN", "MP", "TW", "UM", "US", "VI"}
local default = table.HasValue(crazy_countries, country)
if CLIENT then
StormFox2.Setting.AddCL("use_monthday",default,"Display MM/DD instead of DD/MM.")
end
local tOrdinal = {"st", "nd", "rd"}
local function ordinal(n)
local digit = tonumber(string.sub(n, -1))
local two_dig = tonumber(string.sub(n,-2))
if digit > 0 and digit <= 3 and two_dig ~= 11 and two_dig ~= 12 and two_dig ~= 13 then
return n .. tOrdinal[digit]
else
return n .. "th"
end
end
---Returns the current date-format: "6/11/22". Based on systems country location or clients setting.
---@return string
---@shared
function StormFox2.Date.Get( )
local m = StormFox2.Date.GetMonth( )
local d = StormFox2.Date.GetDay()
if bNumbers and m < 10 then
m = "0" .. m
elseif not bNumbers then
d = ordinal(d)
end
local rev
if CLIENT then
rev = StormFox2.Setting.GetCache("use_monthday",default)
else
rev = default
end
local e = bNumbers and " / " or " "
if not rev then
return d .. e .. m
else
return m .. e .. d
end
end
if SERVER then
-- Sets the starting day.
if StormFox2.Setting.Get("real_time", false) then
StormFox2.Network.Set("day", tonumber(os.date("%j")))
else
StormFox2.Network.Set("day", cookie.GetNumber("sf_date", math.random(0,364)))
end
-- Saves the day for next start.
hook.Add("ShutDown","StormFox2.Day.Save",function()
cookie.Set("sf_date",StormFox2.Date.GetYearDay())
end)
-- Sets the day to the current day, if real_time gets switched on.
StormFox2.Setting.Callback("real_time",function(switch)
if not switch then return end
StormFox2.Network.Set("day", os.date("%j"))
end,"sf_convar_data")
end

View File

@@ -0,0 +1,89 @@
--[[
| 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/
--]]
StormFox2.Setting.AddSV("random_round_weather",true,nil,"Weather")
local gamemodes = {"terrortown"}
local isRGame = table.HasValue(gamemodes, engine.ActiveGamemode())
local nightBlock = false
local function SelectRandom()
-- Temp
local tmin,tmax = StormFox2.Setting.Get("min_temp",-10), StormFox2.Setting.Get("max_temp",20)
StormFox2.Temperature.Set( math.random(tmin, tmax) )
-- Wind
StormFox2.Wind.SetForce( math.random(1, 20))
StormFox2.Wind.SetYaw( math.random(360))
-- Select random weather
local w_name
local w_p = math.Rand(0.4, 0.9)
if math.random(0,10) > 5 then
w_name = table.Random(StormFox2.Weather.GetAllSpawnable())
elseif math.random(1, 2) > 1 then
w_name = "Cloud"
else
w_name = "Clear"
end
local w_t = StormFox2.Weather.Get(w_name)
if w_t.thunder and w_t.thunder(w_p) then
StormFox2.Thunder.SetEnabled( true, w_t.thunder(w_p), math.random(1,3) * 60 )
else
StormFox2.Thunder.SetEnabled( false )
end
-- Set random time
local start = StormFox2.Setting.Get("start_time",-1) or -1
if start < 0 then
if nightBlock then
StormFox2.Time.Set( math.random(500, 900 ) )
w_p = math.Rand(0.4, 0.75) -- Reroll
else
StormFox2.Time.Set( math.random(60, 1080) )
end
end
StormFox2.Weather.Set( w_name, w_p )
end
hook.Add("StormFox2.Settings.PGL", "StormFox2.DefaultGamemodeSettings", function()
local GM = gmod.GetGamemode()
if not StormFox2.Setting.Get("random_round_weather", true) then return end
if not isRGame and not GM.OnPreRoundStart then return end
if not GM.SF2_Settings then
GM.SF2_Settings = {
["auto_weather"] = 0,
["hide_forecast"] = 1,
["openweathermap_enabled"] = 0,
["time_speed"] = 1,
["maplight_auto"] = 1
}
-- These gamemodes are quick-roundbased. 2~6 mins or so. Block the exspensive light-changes.
if not StormFox2.Ent.light_environments then
GM.SF2_Settings["allow_weather_lightchange"] = 0
nightBlock = true
end
end
if GM.PreRoundStart then
_SFGMPRERS = _SFGMPRERS or GM.PreRoundStart
function GM.PreRoundStart( ... )
_SFGMPRERS( ... )
if not StormFox2.Setting.Get("random_round_weather") then return end
SelectRandom()
end
end
end)
-- Random TTT round
if SERVER then
hook.Add("TTTPrepareRound", "StormFox2.TTT", function()
if not StormFox2.Setting.Get("random_round_weather") then return end
SelectRandom()
end)
end

View File

@@ -0,0 +1,333 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
StormFox2.Sun.SetTimeUp(nTime) Sets how long the sun is on the sky.
StormFox2.Sun.IsUp() Returns true if the sun is on the sky.
StormFox2.Moon.SetTimeUp(nTime) Sets how long the moon is on the sky.
---------------------------------------------------------------------------]]
local clamp = math.Clamp
StormFox2.Sun = StormFox2.Sun or {}
StormFox2.Moon = StormFox2.Moon or {}
StormFox2.Sky = StormFox2.Sky or {}
-- SF_SKY_DAY = 0
-- SF_SKY_SUNRISE = 1
-- SF_SKY_SUNSET = 2
-- SF_SKY_CEVIL = 3
-- SF_SKY_BLUE_HOUR = 4
-- SF_SKY_NAUTICAL = 5
-- SF_SKY_ASTRONOMICAL = 6
-- SF_SKY_NIGHT = 7
StormFox2.Setting.AddSV("sunyaw",88,nil, "Effects", 0, 360)
StormFox2.Setting.AddSV("moonlock",true,nil,"Effects")
local phase = StormFox2.Setting.AddSV("moonphase",true,nil,"Effects")
StormFox2.Setting.AddSV("enable_skybox",true,nil, "Effect")
StormFox2.Setting.AddSV("use_2dskybox",false,nil, "Effects")
StormFox2.Setting.AddSV("overwrite_2dskybox","",nil, "Effects")
if CLIENT then -- From another file
StormFox2.Setting.AddSV("darken_2dskybox", false, nil, "Effect")
end
-- Sun and Sun functions
---Returns the time when the sun rises.
---@return TimeNumber
---@shared
function StormFox2.Sun.GetSunRise()
return StormFox2.Setting.Get("sunrise")
end
---Returns the time when the sun sets.
---@return TimeNumber
---@shared
function StormFox2.Sun.GetSunSet()
return StormFox2.Setting.Get("sunset")
end
---Returns the time when sun is at its higest.
---@return TimeNumber
---@shared
function StormFox2.Sun.GetSunAtHigest()
return (StormFox2.Sun.GetSunRise() + StormFox2.Sun.GetSunSet()) / 2
end
---Returns the sun and moon-yaw. (Normal 90)
---@return number yaw
function StormFox2.Sun.GetYaw()
return StormFox2.Setting.Get("sunyaw")
end
---Returns true if the sun is up
---@param nTime? TimeNumber
---@return boolean
function StormFox2.Sun.IsUp(nTime)
return StormFox2.Time.IsBetween(StormFox2.Sun.GetSunRise(), StormFox2.Sun.GetSunSet(),nTime)
end
--[[-------------------------------------------------------------------------
Returns the sun-size. (Normal 30)
---------------------------------------------------------------------------]]
---Returns the sunsize.
---@return number
---@shared
function StormFox2.Sun.GetSize()
return StormFox2.Mixer.Get("sun_size",30) or 30
end
--[[-------------------------------------------------------------------------
Returns the sun-color.
---------------------------------------------------------------------------]]
function StormFox2.Sun.GetColor()
return StormFox2.Mixer.Get("sunColor",Color(255,255,255))
end
local sunVisible = 0
--[[-------------------------------------------------------------------------
Returns the sunangle for the current or given time.
---------------------------------------------------------------------------]]
local function GetSunPitch()
local p = StormFox2.Time.GetCycleTime() * 360
return p
end
function StormFox2.Sun.GetAngle()
local a = Angle(-GetSunPitch(),StormFox2.Sun.GetYaw(),0)
return a
end
-- Returns the sun altitude. 0 degree at sunrise/set and 90 degrees at noon.
function StormFox2.Sun.GetAltitude()
local a = GetSunPitch(nTime)
if a > 90 and a < 270 then
return 180 - a
elseif a > 270 then
return -(360 - a)
end
return a
end
-- We need a sun-stamp. We can't go by time.
local sunOffset = 5 -- Sunset needs to be pushed
local stamp = {
[0] = {SF_SKY_SUNRISE,6,"SunRise"}, -- 6
[6] = {SF_SKY_DAY, 168,"Day"}, -- 180 - 6
[174 + sunOffset] = {SF_SKY_SUNSET,6,"SunSet"}, -- 174 + 6
[180 + sunOffset] = {SF_SKY_CEVIL,4,"Cevil"}, -- 4
[184 + sunOffset] = {SF_SKY_BLUE_HOUR,2,"Blue Hour"}, -- 6
[186 + sunOffset] = {SF_SKY_NAUTICAL,6,"Nautical"}, -- 12
[192 + sunOffset] = {SF_SKY_ASTRONOMICAL,6,"Astronomical"}, -- 18
[198 + sunOffset] = {SF_SKY_NIGHT,168,"Night"}, -- 144
[342] = {SF_SKY_ASTRONOMICAL,6,"Astronomical"}, -- 18
[348] = {SF_SKY_NAUTICAL,6,"Nautical"}, -- 12
[354] = {SF_SKY_BLUE_HOUR,2,"Blue Hour"}, -- 6
[356] = {SF_SKY_CEVIL,4,"Cevil"}, -- 4
[360] = {SF_SKY_SUNRISE,6,"SunRise"},
[370] = {SF_SKY_SUNRISE,6,"SunRise"}, -- 6
}
-- Make an array of keys
local stamp_arr = table.GetKeys(stamp)
table.sort(stamp_arr, function(a,b) return a < b end)
-- Fix calculating second argument
for id, pitch in pairs(stamp_arr) do
local n_pitch = stamp_arr[id + 1] or stamp_arr[1]
local ad = math.AngleDifference(n_pitch, pitch)
if ad == 0 then
ad = stamp[n_pitch][2]
end
stamp[pitch][2] = ad
end
-- Calculate the sunsize
local lC,lCV = -1,-1
local function GetsunSize()
if lC > CurTime() then return lCV end
lC = CurTime() + 2
local x = StormFox2.Sun.GetSize() or 20
lCV = (-0.00019702 * x^2 + 0.149631 * x - 0.0429803) / 2
return lCV
end
-- Returns: Stamp-ptch, Sun-pitch, Stamp-pitch
local function GetStamp(nTime,nOffsetDegree)
local sunSize = GetsunSize()
local p = ( GetSunPitch(nTime) + (nOffsetDegree or 0) ) % 360
-- Offset the sunsize
if p > 90 and p < 270 then -- Sunrise
p = (p - sunSize) % 360
else -- Sunset
p = (p + sunSize) % 360
end
-- Locate the sunstamp by angle
local c_pitch, id = -1
for n, pitch in pairs(stamp_arr) do
if p >= pitch and c_pitch < pitch then
id = n
c_pitch = pitch
end
end
return stamp_arr[id], p, stamp_arr[id + 1] or stamp_arr[1]
end
--[[-------------------------------------------------------------------------
Returns the sun-stamp.
First argument:
0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour
4 = nautical, 5 = astronomical, 6 = night
Second argument:
Pitch
Second argument
Next stamp
0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour
4 = nautical, 5 = astronomical, 6 = night
---------------------------------------------------------------------------]]
local function GetStamp(nTime,nOffsetDegree)
local sunSize = GetsunSize()
local p = ( GetSunPitch(nTime) + (nOffsetDegree or 0) ) % 360
-- Offset the sunsize
if p > 90 and p < 270 then -- Sunrise
p = (p - sunSize) % 360
else -- Sunset
p = (p + sunSize) % 360
end
-- Locate the sunstamp by angle
local c_pitch, id = -1
for n, pitch in pairs(stamp_arr) do
if p >= pitch and c_pitch < pitch then
id = n
c_pitch = pitch
end
end
if not id then
return SF_SKY_DAY, p, SF_SKY_CEVIL
end
return stamp_arr[id],p,stamp_arr[id + 1] or stamp_arr[1]
end
--[[-------------------------------------------------------------------------
Returns the sun-stamp.
First argument:
0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour
4 = nautical, 5 = astronomical, 6 = night
Second argument:
Percent used of the current stamp
Third argument
Next stamp
0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour
4 = nautical, 5 = astronomical, 6 = night
Forth argument
Stamps pitch length
---------------------------------------------------------------------------]]
local nP = 0
function StormFox2.Sky.GetStamp(nTime,nOffsetDegree)
local c_stamp,p,n_stamp = GetStamp(nTime,nOffsetDegree) -- p is current angle
local per = (p - c_stamp) / (n_stamp - c_stamp)
return stamp[c_stamp][1], per, stamp[n_stamp][1],stamp[c_stamp][2] -- 1 = Stamp, 2 = Type of stamp
end
-- Returns the last stamp
local lastStamp = 0
function StormFox2.Sky.GetLastStamp()
return lastStamp
end
-- Sky hook. Used to update the sky colors and other things.
local nextStamp = -1
hook.Add("StormFox2.Time.Changed","StormFox2.Sky.UpdateStamp",function()
nextStamp = -1
end)
timer.Create("StormFox2.Sky.Stamp", 1, 0, function()
--local c_t = CurTime()
--if c_t < nextStamp then return end
local stamp,n_t = StormFox2.Sky.GetStamp(nil,6) -- Look 6 degrees into the furture so we can lerp the colors.
--nextStamp = c_t + (n_t * SunDelta) / StormFox2.Time.GetSpeed()
--[[-------------------------------------------------------------------------
This hook gets called when the sky-stamp changes. This is used to change the sky-colors and other things.
First argument:
0 = day, 1 = golden hour, 2 = cevil, 3 = blue hour
4 = nautical, 5 = astronomical, 6 = night
Second argument:
The lerp-time to change the variable.
---------------------------------------------------------------------------]]
if lastStamp == stamp then return end -- Don't call it twice.
lastStamp = stamp
local delta = 180 / (StormFox2.Setting.Get("sunset") - StormFox2.Setting.Get("sunrise"))
hook.Run("StormFox2.Sky.StampChange", stamp, 6 / math.max(1, delta) )
end)
-- Moon and its functions
--[[-------------------------------------------------------------------------
Moon phases
---------------------------------------------------------------------------]]
SF_MOON_NEW = 0
SF_MOON_WAXIN_CRESCENT = 1
SF_MOON_FIRST_QUARTER = 2
SF_MOON_WAXING_GIBBOUS = 3
SF_MOON_FULL = 4
SF_MOON_WANING_GIBBOUS = 5
SF_MOON_LAST_QUARTER = 6
SF_MOON_WANING_CRESCENT = 7
--[[-------------------------------------------------------------------------
Returns the moon phase for the current day
---------------------------------------------------------------------------]]
function StormFox2.Moon.GetPhase()
if not phase:GetValue() then return SF_MOON_FULL end
return StormFox2.Data.Get("moon_phase",SF_MOON_FULL)
end
--[[-------------------------------------------------------------------------
Returns the moon phase name
---------------------------------------------------------------------------]]
function StormFox2.Moon.GetPhaseName(nTime)
local n = StormFox2.Moon.GetPhase(nTime)
if n == SF_MOON_NEW then return "New Moon" end
if n == SF_MOON_WAXIN_CRESCENT then return "Waxin Crescent" end
if n == SF_MOON_FIRST_QUARTER then return "First Quarter" end
if n == SF_MOON_WAXING_GIBBOUS then return "Waxing Gibbous" end
if n == SF_MOON_FULL then return "Full Moon" end
if n == SF_MOON_WANING_GIBBOUS then return "Waning Gibbous" end
if n == SF_MOON_LAST_QUARTER then return "Last Quarter" end
if n == SF_MOON_WANING_CRESCENT then return "Waning Crescent" end
end
--[[-------------------------------------------------------------------------
Returns the angle for the moon. First argument can also be a certain time.
---------------------------------------------------------------------------]]
local tf = 0
local a = 7 / 7.4
function StormFox2.Moon.GetAngle(nTime)
local p = 180 + StormFox2.Time.GetCycleTime() * 360
if StormFox2.Setting.Get("moonlock",false) then
return Angle(-p % 360, StormFox2.Sun.GetYaw(),0)
end
--if true then return Angle(200,StormFox2.Data.Get("sun_yaw",90),0) end
local rDay = StormFox2.Date.GetYearDay()
p = p + ( StormFox2.Moon.GetPhase() - 4 ) * 45
return Angle(-p % 360,StormFox2.Sun.GetYaw(),0)
end
-- It might take a bit for the server to tell us the day changed.
hook.Add("StormFox2.data.change", "StormFox2.moon.datefix", function(sKey, nDay)
if sKey ~= "day" then return end
tf = 0
end)
--[[-------------------------------------------------------------------------
Returns true if the moon is up.
---------------------------------------------------------------------------]]
function StormFox2.Moon.IsUp()
local t = StormFox2.Moon.GetAngle().p
local s = StormFox2.Mixer.Get("moonSize",20) / 6.9
return t > 180 - s or t < s
end
--[[-------------------------------------------------------------------------
Returns the moon size
---------------------------------------------------------------------------]]
function StormFox2.Moon.GetSize()
return StormFox2.Mixer.Get("moonSize",20)
end

View File

@@ -0,0 +1,788 @@
--[[
| 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/
--]]
StormFox2.Setting.AddSV("maplight_min",0,mil, "Effects", 0, 100)
StormFox2.Setting.AddSV("maplight_max",80,nil, "Effects", 0, 100)
StormFox2.Setting.AddSV("maplight_smooth",true,nil, "Effects",0,1)
StormFox2.Setting.AddSV("maplight_auto", true, nil, "Effects")
StormFox2.Setting.AddSV("maplight_lightenv", false,nil, "Effects")
StormFox2.Setting.AddSV("maplight_colormod", false,nil, "Effects")
StormFox2.Setting.AddSV("maplight_dynamic", false,nil, "Effects")
StormFox2.Setting.AddSV("maplight_lightstyle", false,nil, "Effects")
--[[----------------------------------------------------------------]]--
StormFox2.Setting.AddSV("maplight_updaterate",game.SinglePlayer() and 6 or 3,nil, "Effects")
StormFox2.Setting.AddSV("overwrite_extra_darkness",-1,nil, "Effects", -1, 1)
StormFox2.Setting.SetType( "overwrite_extra_darkness", "special_float")
StormFox2.Setting.AddSV("allow_weather_lightchange",true,nil, "Weather")
if CLIENT then
StormFox2.Setting.AddCL("extra_darkness",render.SupportsPixelShaders_2_0(),nil,"Effects",0,1)
StormFox2.Setting.AddCL("extra_darkness_amount",0.75,nil, "Effects",0,1)
StormFox2.Setting.SetType( "extra_darkness_amount", "float" )
end
-- Converts a lightlvl to char
local function convertToBZ( nNum ) -- From b to z
local byte = math.Round(6 * nNum / 25 + 98)
return string.char(byte)
end
local function convertToAZ( nNum )
return string.char(97 + nNum / 4)
end
local SetLightStyle, SetLightEnv
if SERVER then
-- Sets the lightstyle
local function _SetLightStyle( char )
engine.LightStyle(0,char)
net.Start(StormFox2.Net.LightStyle)
net.WriteUInt(string.byte(char), 7)
net.Broadcast()
end
local var = 'm'
-- Making it a timer, gives other scripts time to overwrite it.
SetLightStyle = function( char )
if char == 'a' then char = 'b' end -- 'a' will break all light on the map
if char == var then return end
var = char
if timer.Exists("sf_lightstyleset") then return end
timer.Create( "sf_lightstyleset", 1, 1, function()
_SetLightStyle( var )
end)
end
local oldLight = 'm'
local has_faded = false
SetLightEnv = function( char)
if not StormFox2.Ent.light_environments then return end
if char == oldLight then return end
oldLight = char
for _,light in ipairs(StormFox2.Ent.light_environments) do -- Doesn't lag
if not IsValid(light) then continue end
if has_faded then
light:Fire("FadeToPattern", char ,0)
else
light:Fire("SetPattern", char ,0)
end
if char == "a" then
light:Fire("TurnOff","",0)
else
light:Fire("TurnOn","",0)
end
light:Activate()
end
has_faded = true
end
else
local last_sv
net.Receive(StormFox2.Net.LightStyle, function(len)
local c_var = net.ReadUInt(7)
if last_sv and last_sv == c_var then return end -- No need
last_sv = c_var
timer.Simple(1, function()
render.RedownloadAllLightmaps( true, true )
--MsgC(color_white,"Redownload ligthmap [" .. last_sv .. "]\n")
end)
end)
end
--[[ Diffrent types of maplight options.
available = Return true to indicate this option is available. No function = always.
on = When you switch it on
off = When you switch it off
change = When the lightvl changes. Secondary is when the smoothness ends.
]]
local mapLights = {}
local e_light_env = 0
local e_lightstyle = 1
local e_colormod = 2
local e_lightdynamic = 3
local lastSetting = {}
-- light_environment (SV) Fast, but not all maps have it
mapLights[e_light_env] = {}
mapLights[e_light_env]["available"] = function()
return StormFox2.Ent.light_environments and true or false
end
if SERVER then
mapLights[e_light_env]["on"] = function(lightLvl)
SetLightEnv( convertToAZ(lightLvl) )
end
mapLights[e_light_env]["off"] = function(lightLvl)
if lastSetting[e_lightdynamic] then
SetLightEnv('b')
else
SetLightEnv('m')
end
end
mapLights[e_light_env]["change"] = mapLights[e_light_env]["on"]
end
-- light_style (SV) Laggy on large maps
mapLights[e_lightstyle] = {}
if SERVER then
mapLights[e_lightstyle]["on"] = function(lightLvl)
SetLightStyle( convertToBZ(lightLvl) )
end
mapLights[e_lightstyle]["off"] = function(lightLvl)
SetLightStyle('m')
timer.Remove( "sf_lightstyle" )
end
local nextSet
mapLights[e_lightstyle]["change"] = function(lightLvl, full) -- We make this a 30sec timer, since lightstyle is so slow and laggy.
if not full then return end -- Ignore 'smoothness' light
if timer.Exists("sf_lightstyle") then
nextSet = convertToBZ(lightLvl)
else
SetLightStyle(convertToBZ(lightLvl))
timer.Create("sf_lightstyle", 5, 1, function()
if not nextSet then return end
SetLightStyle(nextSet)
nextSet = nil
end)
end
end
end
-- ColorMod (CL) A fast alternative
mapLights[e_colormod] = {}
local cmod_on
if CLIENT then
local fardetarget = 0
mapLights[e_colormod]["on"] = function(lightLvl)
cmod_on = (1 - (lightLvl / 80)) * 0.7
fardetarget = 0
end
mapLights[e_colormod]["off"] = function(lightLvl)
cmod_on = nil
end
mapLights[e_colormod]["change"] = function(lightLvl)
cmod_on = (1 - (lightLvl / 80)) * 0.7
end
local tab = {
[ "$pp_colour_addr" ] = -0.09,
[ "$pp_colour_addg" ] = -0.1,
[ "$pp_colour_addb" ] = -0.05,
[ "$pp_colour_brightness" ] = 0,
[ "$pp_colour_contrast" ] = 1,
[ "$pp_colour_colour" ] = 1,
[ "$pp_colour_mulr" ] = 0,
[ "$pp_colour_mulg" ] = 0,
[ "$pp_colour_mulb" ] = 0
}
hook.Add( "RenderScreenspaceEffects", "StormFox2.MapLightCMod", function()
if not cmod_on then return end
local darkness = cmod_on
local env = StormFox2.Environment.Get()
if not env.outside then
if not env.nearest_outside then
darkness = 0
else
local dis = 1 - ( env.nearest_outside:DistToSqr(StormFox2.util.RenderPos() or EyePos()) / 90000 )
dis = math.Clamp(dis, 0, 1)
darkness = darkness * 0.2 + darkness * 0.8 * dis
end
end
fardetarget = math.Approach(fardetarget, darkness, FrameTime() * 0.5)
local r_var = fardetarget
local tL = math.min(255,StormFox2.Thunder.GetLight() or 0) / 255
if tL > 0 then
r_var = r_var * (1 - tL)
end
tab[ "$pp_colour_addr" ] = -0.09 * r_var
tab[ "$pp_colour_addg" ] = -0.1 * r_var
tab[ "$pp_colour_addb" ] = -0.05 * r_var
tab[ "$pp_colour_brightness" ] = 0 -r_var * 0.15
tab[ "$pp_colour_colour" ] = 1 - r_var * 0.5 -- We're not good at seeing colors in the dark.
tab[ "$pp_colour_contrast" ] = 1 + r_var * 0.08 -- Lower the contrast, however; Bright things are still bright
DrawColorModify( tab )
end)
end
-- Dynamic Light
local dsize = 3000
local dlengh = 9000 - 30
mapLights[e_lightdynamic] = {}
local dLight = -1
local function tick()
if not SF_SUN_PROTEX then return end
local vis = dLight
-- While the best option is to delete the project-texture.
-- Sadly, doing so would allow another mod to override it, as they're limited.
if vis < 0 then return end
local sunang = StormFox2.Sun.GetAngle() -- Angle towards sun
local p = sunang.p % 360
local tL = math.min(255,StormFox2.Thunder.GetLight() or 0)
if tL > 0 then
p = 270
sunang.p = p
vis = tL / 2
else
if p > 350 then -- 15 before
SF_SUN_PROTEX:SetBrightness(0)
SF_SUN_PROTEX:Update()
return
elseif p > 345 then
vis = vis * math.Clamp(-p / 5 + 70, 0, 1)
elseif p <= 190 then
SF_SUN_PROTEX:SetBrightness(0)
SF_SUN_PROTEX:Update()
return
elseif p <= 195 then
vis = vis * math.Clamp(p / 5 - 38, 0, 1)
end
end
local sunnorm = sunang:Forward() -- Norm of sun
local viewpos = StormFox2.util.RenderPos() -- Point of render
local pos = viewpos + sunnorm * dlengh
pos.x = math.Round(pos.x / 50) * 50
pos.y = math.Round(pos.y / 50) * 50
SF_SUN_PROTEX:SetPos(pos)
if math.Round(sunang.p) == 0 then -- All source light gets a bit, glitchy at 0 or 180 pitch
SF_SUN_PROTEX:SetAngles(Angle(179,sunang.y,0))
else
SF_SUN_PROTEX:SetAngles(Angle(sunang.p + 180,sunang.y,0))
end
SF_SUN_PROTEX:SetBrightness(vis * 2)
SF_SUN_PROTEX:Update()
end
mapLights[e_lightdynamic]["on"] = function(lightLvl)
if SERVER then
SetLightStyle( 'b' )
SetLightEnv('b')
StormFox2.Shadows.SetDisabled( true )
else
RunConsoleCommand("r_flashlightdepthres", 8192)
dLight = lightLvl
if IsValid(SF_SUN_PROTEX) then
SF_SUN_PROTEX:Remove()
end
SF_SUN_PROTEX = ProjectedTexture()
SF_SUN_PROTEX:SetTexture("stormfox2/effects/dynamic_light")
SF_SUN_PROTEX:SetOrthographic( true , dsize, dsize, dsize, dsize)
SF_SUN_PROTEX:SetNearZ(0)
SF_SUN_PROTEX:SetFarZ( 12000 )
SF_SUN_PROTEX:SetQuadraticAttenuation( 0 )
SF_SUN_PROTEX:SetShadowDepthBias(0.000005)
SF_SUN_PROTEX:SetShadowFilter(0.05) -- Meed tp blur the shadows a bit.
SF_SUN_PROTEX:SetShadowSlopeScaleDepthBias(2)
SF_SUN_PROTEX:SetEnableShadows(true)
hook.Add("Think", "StormFox2.MapLightDynamic", tick)
end
end
mapLights[e_lightdynamic]["off"] = function(lightLvl)
if SERVER then
SetLightStyle( 'm' )
SetLightEnv('m')
StormFox2.Shadows.SetDisabled( false )
else
dLight = 0
if SF_SUN_PROTEX then
SF_SUN_PROTEX:Remove()
SF_SUN_PROTEX = nil
end
hook.Remove("Think", "StormFox2.MapLightDynamic")
end
end
if CLIENT then
mapLights[e_lightdynamic]["change"] = function(lightlvl)
dLight = lightlvl
end
end
-- MapLight functions
local function EnableMapLight(str, lightlvl)
if not mapLights[str] then
error("Unknown light")
end
if not mapLights[str]["on"] then return end
mapLights[str]["on"](lightlvl)
end
local function DisableMapLight(str, lightlvl)
if not mapLights[str] then
error("Unknown light")
end
if not mapLights[str]["off"] then return end
mapLights[str]["off"](lightlvl)
end
local function ChangeMapLight(str, lightlvl, full)
if not mapLights[str] then
error("Unknown light")
end
if not mapLights[str]["change"] then return end
mapLights[str]["change"](lightlvl, full)
end
-- Function that will remember and enable / disable a setting.
local function checkSetting(e_type, bool, lightlvl)
if bool and lastSetting[e_type] then return end
if not bool and not lastSetting[e_type] then return end
if bool then
EnableMapLight(e_type, lightlvl)
else
DisableMapLight(e_type, lightlvl)
end
lastSetting[e_type] = bool
end
-- Called when one of the settings change
local function SettingMapLight( lightlvl )
-- Stop all light-settings when SF gets turned off.
if not StormFox2.Setting.GetCache("enable", true) then
checkSetting(e_lightstyle, false, lightlvl)
checkSetting(e_colormod, false, lightlvl)
checkSetting(e_lightdynamic,false, lightlvl)
checkSetting(e_light_env, false, lightlvl)
return
end
-- Choose e_light_env or e_colormod
if StormFox2.Setting.Get("maplight_auto") then
checkSetting(e_lightdynamic,false, lightlvl)
checkSetting(e_lightstyle, false, lightlvl)
if StormFox2.Ent.light_environments then
checkSetting(e_colormod, false, lightlvl)
checkSetting(e_light_env, true, lightlvl)
else
checkSetting(e_light_env, false, lightlvl)
checkSetting(e_colormod, true, lightlvl)
end
else
-- Can be enabled for all
checkSetting(e_colormod, StormFox2.Setting.Get("maplight_colormod", false), lightlvl)
-- Choose dynamic or lightstyle
if StormFox2.Setting.Get("maplight_dynamic", false) then
checkSetting(e_lightstyle, false, lightlvl)
checkSetting(e_light_env, false, lightlvl)
checkSetting(e_lightdynamic,true, lightlvl)
else
checkSetting(e_lightdynamic,false, lightlvl)
checkSetting(e_lightstyle, StormFox2.Setting.Get("maplight_lightstyle", false),lightlvl)
checkSetting(e_light_env, StormFox2.Setting.Get("maplight_lightenv", false), lightlvl)
end
end
end
-- Called when lightlvl has changed
local function ChangedMapLight( lightlvl, isSmoothLight)
local LastUpdate = not isSmoothLight
if StormFox2.Setting.GetCache("maplight_auto", true) then
if StormFox2.Ent.light_environments then
ChangeMapLight(e_light_env, lightlvl, LastUpdate)
return true
else
ChangeMapLight(e_colormod, lightlvl, LastUpdate)
end
else
local a = false
if StormFox2.Setting.GetCache("maplight_dynamic", false) then
ChangeMapLight(e_lightdynamic, lightlvl, LastUpdate)
a = true
end
if StormFox2.Setting.GetCache("maplight_lightstyle", false) then
ChangeMapLight(e_lightstyle, lightlvl, LastUpdate)
a = true
end
if StormFox2.Setting.GetCache("maplight_lightenv", false) then
ChangeMapLight(e_light_env, lightlvl, LastUpdate)
a = true
end
if StormFox2.Setting.GetCache("maplight_colormod", false) then
ChangeMapLight(e_colormod, lightlvl, LastUpdate)
end
return a
end
end
-- Sets the detail-light
local SetDetailLight
if CLIENT then
-- Detail MapLight
-- Use default detail material (Just in case)
local detailstr = {["detail/detailsprites"] = true}
-- Find map detail from BSP
local mE = StormFox2.Map.Entities()[1]
if mE and mE["detailmaterial"] then
detailstr[mE["detailmaterial"]] = true
end
-- Add EP2 by default
local ep2m = Material("detail/detailsprites_ep2")
if ep2m and not ep2m:IsError() then
detailstr["detail/detailsprites_ep2"] = true
end
local detail = {}
for k,v in pairs(detailstr) do
table.insert(detail, (Material(k)))
end
SetDetailLight = function(lightAmount)
lightAmount = math.Clamp(lightAmount / 100, 0, 1)
local v = Vector(lightAmount,lightAmount,lightAmount)
for i, m in ipairs(detail) do
m:SetVector("$color",v)
end
end
end
-- Returns light-variables
local f_mapLight = StormFox2.Setting.GetCache("maplight_max",80)
local f_mapLightRaw = 100
local c_last_char = 'm'
---Returns the current light-amount.
---@return number
---@shared
function StormFox2.Map.GetLight()
return f_mapLight
end
---Returns the current raw light-amount. Ignores settings.
---@return number
---@shared
function StormFox2.Map.GetLightRaw()
return f_mapLightRaw
end
---Returns the current light-char. Source use letters to indecate light.
---@return string
---@shared
function StormFox2.Map.GetLightChar()
return c_last_char
end
local function getMaxLight(curLight)
if curLight <= 0 then return 0 end
local n = StormFox2.Setting.GetCache("maplight_max",80)
if n <= 0 then return 0 end
return math.Clamp(curLight / n, 0, 1) * 100
end
-- On launch. Setup light
local init = false
do
local chicken, egg = false, false
local function tryInit()
if not chicken or not egg then return end
SettingMapLight(f_mapLight)
hook.Run("StormFox2.lightsystem.new", f_mapLight, getMaxLight(f_mapLight))
if CLIENT then SetDetailLight(f_mapLight) end
init = true
end
hook.Add("StormFox2.PostEntityScan", "stormfox2.lightsystem.init", function()
chicken = true
tryInit()
end)
hook.Add("stormfox2.postinit", "stormfox2.lightsystem.init2", function()
egg = true
tryInit()
end)
end
-- On settings change
for _, conv in ipairs({"enable","maplight_auto", "maplight_lightenv", "maplight_colormod", "maplight_dynamic", "maplight_lightstyle"}) do
StormFox2.Setting.Callback(conv,function(var)
SettingMapLight(f_mapLight)
end, conv .. "MLCheck")
end
-- Allows us to use SetLight, without removing the lerp.
local lil = true
local function SetLightInternal(f, isSmoothLight)
if f < 0 then f = 0 elseif
f > 100 then f = 100 end
if f_mapLight == f and lil == isSmoothLight then return end -- Ignore
f_mapLight = f
lil = isSmoothLight
c_last_char = convertToAZ(f)
if not init then return end
-- 2D Skybox
if SERVER then
local str = StormFox2.Setting.GetCache("overwrite_2dskybox","")
local use_2d = StormFox2.Setting.GetCache("use_2dskybox",false)
if use_2d and str ~= "painted" then
StormFox2.Map.Set2DSkyBoxDarkness( f * 0.009 + 0.1, true )
end
end
-- SetMapLight
ChangedMapLight(f, isSmoothLight)
if CLIENT then SetDetailLight(f) end
-- Tell scripts to update
hook.Run("StormFox2.lightsystem.new", f, getMaxLight(f))
end
local t = {}
---Sets the maplight using a number between 0 - 100. last_update should be true, if we aren't lerping.
---Clients need to run this too for internal stuff, but won't change the maplight.
---@param int number
---@param last_update boolean
---@shared
function StormFox2.Map.SetLight( int, last_update )
t = {} -- Remove light lerping
SetLightInternal(int, last_update)
end
--[[ Lerp light
People complain if we use lightStyle too much (Even with settings), so I've removed lerp from maps without light_environment.
]]
---Lerps the light towards the goal. Make "isSmooth" false if you're calling it rapidly.
---@param int number
---@param nLerpTime number
---@param isSmooth boolean
---@shared
function StormFox2.Map.SetLightLerp(int, nLerpTime, isSmooth )
local smooth = StormFox2.Setting.GetCache("maplight_smooth",true)
local num = StormFox2.Setting.GetCache("maplight_updaterate", 3)
-- No lights to smooth and/or setting is off
local _5sec = 0.08 * StormFox2.Time.GetSpeed_RAW()
t = {}
if not smooth or nLerpTime <= _5sec or not f_mapLight or num <= 1 then
SetLightInternal( int )
return
end
-- Are we trying to lerp towards current value?
if f_mapLight and f_mapLight == int then
return
end
-- Start lerping ..
-- We make a time-list of said values.
local st = StormFox2.Time.Get() -- Start Time
local st_lerpt = nLerpTime / num -- Each "step"'s time
-- Too fast of a light change. Can bug out.
if st_lerpt < 5 then -- Set the each step to min 5 seconds.
st_lerpt = 5
num = math.floor(nLerpTime / 5)
if num <= 1 then -- Only change once.
SetLightInternal( int )
return
end
end
local st_lerp = math.abs(f_mapLight - int) / num -- Each "step"'s value
-- from: f_mapLight
-- to: f
for i = 0, num - 1 do
table.insert(t, {
(st + (i * st_lerpt)) % 1440, -- Time when applied
math.floor(math.Approach(f_mapLight, int, st_lerp * (i + 1))),-- The light value
i ~= num - 1 or isSmooth -- Isn't last
})
end
--print("From:",f_mapLight, "TO:", f, "step:",st_lerp, "nums:",num)
--StormFox2.Map.SetLight( math.Approach(f_mapLight, f, n), true )
end
timer.Create("StormFox2.lightupdate", 2, 0, function()
if #t <= 0 then return end
local n = t[1]
local time = StormFox2.Time.Get()
if n[1] > time or math.abs(time - n[1]) > 720 then return end -- Wait.
-- Trigger
local v = table.remove(t, 1)
SetLightInternal( v[2], v[3] ) -- Set the light, and lightsystel if last.
--print("SetLight", v[2], v[3])
end)
if SERVER then
-- Control light
hook.Add("StormFox2.weather.postchange", "StormFox2.weather.setlight", function( sName ,nPercentage, nDelta )
local night, day
if StormFox2.Setting.GetCache("allow_weather_lightchange") then
night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight
else
local c = StormFox2.Weather.Get("Clear")
night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight
end
local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings
local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer())
-- Calc maplight
local isSmooth = false
local stamp, mapLight = StormFox2.Sky.GetLastStamp()
if stamp >= SF_SKY_CEVIL then
mapLight = night
elseif stamp <= SF_SKY_DAY then
mapLight = day
else
local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL )
local f = StormFox2.Sky.GetLastStamp() / delta
if smooth then
mapLight = Lerp((f + 0.5) / 2, day, night)
isSmooth = true
elseif f <= 0.5 then
mapLight = day
else
mapLight = night
end
end
f_mapLightRaw = mapLight
-- Apply settings
local newLight = minlight + mapLight * (maxlight - minlight) / 100
local sec = 15 * StormFox2.Time.GetSpeed_RAW()
StormFox2.Map.SetLightLerp(newLight, math.min(sec, nDelta or sec), isSmooth )
end)
-- Min and maxlight hotupdate
local function hotUpdate()
local night, day
if StormFox2.Setting.GetCache("allow_weather_lightchange") then
night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight
else
local c = StormFox2.Weather.Get("Clear")
night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight
end
local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings
local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer())
-- Calc maplight
local isSmooth = false
local stamp, mapLight = StormFox2.Sky.GetLastStamp()
if stamp >= SF_SKY_CEVIL then
mapLight = night
elseif stamp <= SF_SKY_DAY then
mapLight = day
else
local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL )
local f = StormFox2.Sky.GetLastStamp() / delta
if smooth then
mapLight = Lerp((f + 0.5) / 2, day, night)
isSmooth = true
elseif f <= 0.5 then
mapLight = day
else
mapLight = night
end
end
f_mapLightRaw = mapLight
-- Apply settings
local newLight = minlight + mapLight * (maxlight - minlight) / 100
StormFox2.Map.SetLight( newLight )
end
StormFox2.Setting.Callback("maplight_min", hotUpdate)
StormFox2.Setting.Callback("maplight_max", hotUpdate)
else -- Fake darkness. Since some maps are bright
hook.Add("StormFox2.weather.postchange", "StormFox2.weather.setlight", function( sName ,nPercentage, nDelta )
if not StormFox2.Map or not StormFox2.Map.SetLightLerp then return end
local minlight,maxlight = StormFox2.Setting.GetCache("maplight_min",0),StormFox2.Setting.GetCache("maplight_max",80) -- Settings
local smooth = StormFox2.Setting.GetCache("maplight_smooth",game.SinglePlayer())
local night, day
if StormFox2.Setting.GetCache("allow_weather_lightchange") then
night,day = StormFox2.Data.GetFinal("mapNightLight", 0), StormFox2.Data.GetFinal("mapDayLight",100) -- Maplight
else
local c = StormFox2.Weather.Get("Clear")
night,day = c:Get("mapNightLight",0), c:Get("mapDayLight",80) -- Maplight
end
-- Calc maplight
local isSmooth = false
local stamp, mapLight = StormFox2.Sky.GetLastStamp()
if stamp >= SF_SKY_CEVIL then
mapLight = night
elseif stamp <= SF_SKY_DAY then
mapLight = day
else
local delta = math.abs( SF_SKY_DAY - SF_SKY_CEVIL )
local f = StormFox2.Sky.GetLastStamp() / delta
if smooth then
mapLight = Lerp((f + 0.5) / 2, day, night)
isSmooth = true
elseif f <= 0.5 then
mapLight = day
else
mapLight = night
end
end
f_mapLightRaw = mapLight
-- Apply settings
local newLight = minlight + mapLight * (maxlight - minlight) / 100
local sec = 15 * StormFox2.Time.GetSpeed_RAW()
StormFox2.Map.SetLightLerp(newLight, math.min(sec, nDelta or sec), isSmooth )
end)
local function exp(n)
return n * n
end
local mat_screen = Material( "stormfox2/shader/pp_dark" )
local mat_ColorMod = Material( "stormfox2/shader/color" )
mat_ColorMod:SetTexture( "$fbtexture", render.GetScreenEffectTexture() )
local texMM = GetRenderTargetEx( "_SF_DARK", -1, -1, RT_SIZE_FULL_FRAME_BUFFER, MATERIAL_RT_DEPTH_NONE, 0, 0, IMAGE_FORMAT_RGB888 )
-- Renders pp_dark
local function UpdateStencil( darkness )
if not render.SupportsPixelShaders_2_0() then return end -- How old is the GPU!?
render.UpdateScreenEffectTexture()
render.PushRenderTarget(texMM)
render.Clear( 255 * darkness, 255 * darkness, 255 * darkness, 255 * darkness )
render.ClearDepth()
render.OverrideBlend( true, BLEND_ONE_MINUS_SRC_COLOR, BLEND_ONE_MINUS_SRC_COLOR, 0, BLEND_ONE, BLEND_ZERO, BLENDFUNC_ADD )
render.SetMaterial(mat_ColorMod)
render.DrawScreenQuad()
render.OverrideBlend( false )
render.PopRenderTarget()
mat_screen:SetTexture( "$basetexture", texMM )
end
local fade = 0
hook.Add("RenderScreenspaceEffects","StormFox2.Light.MapMat",function()
if not StormFox2.Map.GetLightRaw then return end
if not StormFox2.Setting.SFEnabled() then return end
-- How old is the GPU!?
if not render.SupportsPixelShaders_2_0() then return end
local a = 1 - StormFox2.Map.GetLightRaw() / 100
if a <= 0 then -- Too bright
fade = 0
return
end
-- Check settings
local scale = StormFox2.Setting.GetCache("overwrite_extra_darkness",-1)
if scale == 0 then return end -- Force off.
if scale < 0 then
if not StormFox2.Setting.GetCache("extra_darkness",true) then return end
scale = StormFox2.Setting.GetCache("extra_darkness_amount",1)
end
if scale <= 0 then return end
-- Calc the "fade" between outside and inside
local t = StormFox2.Environment.Get()
if t.outside then
fade = math.min(2, fade + FrameTime())
elseif t.nearest_outside then
-- Calc dot
local view = StormFox2.util.GetCalcView()
if not view then return end
local d = view.pos:DistToSqr(t.nearest_outside)
if d < 15000 then
fade = math.min(2, fade + FrameTime())
elseif d > 40000 then -- Too far away
fade = math.max(0, fade - FrameTime())
else
local v1 = view.ang:Forward()
local v2 = (t.nearest_outside - view.pos):GetNormalized()
if v1:Dot(v2) < 0.6 then -- You're looking away
fade = math.max(0, fade - FrameTime())
else -- You're looking at it
fade = math.min(2, fade + FrameTime())
end
end
else
fade = math.max(0, fade - FrameTime())
end
if fade <= 0 then return end
-- Render
UpdateStencil(exp(a * scale * math.min(1, fade)))
render.SetMaterial(mat_screen)
local w,h = ScrW(),ScrH()
render.OverrideBlend( true, 0, BLEND_ONE_MINUS_SRC_COLOR, 2, BLEND_ONE, BLEND_ZERO, BLENDFUNC_ADD )
render.DrawScreenQuadEx(0,0,w,h)
render.OverrideBlend( false )
end)
end
--[[
TODO:
1) Add option to limit the angle of the shadows.
2) Shadow color to match skies?
]]

View File

@@ -0,0 +1,155 @@
--[[
| 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/
--]]
-- Weather mixed updates the variables live, unlike the Data.
-- This can't be set tho.
StormFox2.Mixer = {}
-- Local function
local function isColor(t)
return t.r and t.g and t.b and true or false
end
---Tries to blend two variables together. Will return first variable if unable to.
---@param nFraction number
---@param vFrom any
---@param vTo any
---@return any
---@shared
local function Blender(nFraction, vFrom, vTo)
-- Will it blend?
-- Nils should be false, if one of them is a boolean
if type(vFrom) == "nil" and type(vTo) == "boolean" then
vFrom = false
end
if type(vTo) == "nil" and type(vFrom) == "boolean" then
vTo = false
end
-- If the same value, then return it
if vTo == vFrom then return vTo end
-- In case of two diffrent variables.
if type(vFrom) ~= type(vTo) then
StormFox2.Warning("Mixer called with values of two different types[" .. type(vFrom) .. "," .. type(vTo) .. "]")
debug.Trace()
return vFrom
elseif type(vTo) == "string" or type(vTo) == "IMaterial" or type(vTo) == "boolean" then -- String, material or bool. Return vTo.
return vTo
elseif type(vTo) == "number" then -- Number
return Lerp(nFraction, vFrom, vTo)
elseif type(vTo) == "table" then -- Objects
local t = vTo.__MetaName and vTo.__MetaName == "CCT_Color" or false
local f = vFrom.__MetaName and vFrom.__MetaName == "CCT_Color" or false
if t and f then
local v = StormFox2.util.CCTColor( Lerp( nFraction, vFrom:GetKelvin(), vTo:GetKelvin() ) )
return v:ToRGB()
else
local s = f and vFrom:ToRGB() or vFrom
local e = t and vTo:ToRGB() or vTo
local r = Lerp( nFraction, s.r or 255, e.r )
local g = Lerp( nFraction, s.g or 255, e.g )
local b = Lerp( nFraction, s.b or 255, e.b )
local a = Lerp( nFraction, s.a or 255, e.a )
return Color( r, g, b, a )
end
end
--StormFox2.Warning("ERROR: Unsupported mix value type[" .. type(vTo) .. "]. Returning original value")
--debug.Trace()
return vFrom
end
local cache = {}
local cStamp,nStamp,nStampFraction = 0,0,0
local function GetVar( wWeather, sKey )
local v1 = wWeather:Get(sKey, cStamp)
if cStamp == nStamp or nStampFraction <= 0 then
return v1
end
local v2 = wWeather:Get(sKey, nStamp)
local v = Blender(nStampFraction, v1, v2)
return v
end
StormFox2.Mixer.Blender = Blender
local function vOd(a, b)
if a == nil then return b end
return a
end
---Blends the current weather key-variables together. Will return zDefault if fail. The result will be cached, unless you set the currentP variable.
---Mixer allows for live-precise variables.
---@param sKey string
---@param zDefault any
---@param currentP? number
---@return any
---@shared
function StormFox2.Mixer.Get( sKey, zDefault, currentP )
if not currentP and cache[sKey] ~= nil then return cache[sKey] end
if not StormFox2.Weather then return zDefault end -- Not loaded yet
-- Get the current weather
local cW = StormFox2.Weather.GetCurrent()
-- In case thw weather-type is clear, no need to calculate.
if not cW or cW.Name == "Clear" then return vOd( GetVar(cW, sKey), zDefault) end
-- Get the percent, and check if we should cache.
local shouldCache = not currentP
currentP = currentP or StormFox2.Weather.GetPercent()
if currentP >= 1 then -- No need to mix things, weather is at max.
if shouldCache then
cache[sKey] = GetVar(cW, sKey)
return vOd(cache[sKey], zDefault)
else
return vOd(GetVar(cW, sKey), zDefault)
end
end
-- Get the default weather to mix with.
local clearW = StormFox2.Weather.Get( "Clear" )
local var1 = GetVar(clearW, sKey)
local var2 = GetVar(cW, sKey)
if shouldCache then
cache[sKey] = Blender(currentP, var1, var2)
return vOd(cache[sKey], zDefault)
else
return vOd(Blender(currentP, var1, var2), zDefault)
end
end
StormFox2.Mixer.Blender = Blender
--[[t.Function = {}
t.Static = {}
t.Dynamic = {}
t.SunStamp = {}
]]
-- Resets the values after a few frames. This is calculated live and should be cached.
local max_frames = 4
local i = 0
local percent = 0
hook.Add("Think", "StormFox2.mixerreset", function()
i = i + 1
if i < max_frames then return end
i = 0
cache = {}
-- Current Stamp
local nTime = StormFox2.Time.Get()
local stamp, percent, next_stamp, pitch_length = StormFox2.Sky.GetStamp(nTime, nil, true) -- cpercent goes from 0 - 1
if not pitch_length then return end
local pitch_left = pitch_length * (1 - percent)
local forward = 6
if pitch_left >= 6 then -- Only look 6 degrees in the furture.
cStamp = stamp
nStamp = stamp
nStampFraction = 0
else
cStamp = stamp
nStamp = next_stamp
nStampFraction = 1 - ( pitch_left / (math.min(pitch_length, 6)) )
end
end)

View File

@@ -0,0 +1,146 @@
--[[
| 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/
--]]
StormFox2.Setting.AddSV("modifyshadows",true,nil, "Effects")
StormFox2.Setting.AddSV("modifyshadows_rate",game.IsDedicated() and 2 or 0.25,nil, "Effects", 0, 10)
StormFox2.Setting.SetType("modifyshadows_rate", "float")
if CLIENT then
net.Receive(StormFox2.Net.Shadows, function()
timer.Simple(0.2, function()
for k, ent in ipairs(ents.GetAll()) do
ent:MarkShadowAsDirty()
ent:PhysWake()
end
end)
end)
end
if CLIENT then return end
StormFox2.Shadows = {}
--[[-------------------------------------------------------------------------
Shadow controls
---------------------------------------------------------------------------]]
local lastP = -1
---Sets the shadow angles on the map using a given pitch number.
---@param nPitch number
---@server
function StormFox2.Shadows.SetAngle( nPitch )
if not StormFox2.Ent.shadow_controls then return end
local nPitch = (nPitch + 180) % 360
if nPitch == lastP then return end
lastP = nPitch
local str = nPitch .. " " .. StormFox2.Sun.GetYaw() .. " " .. 0 .. " "
for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do
ent:Fire( "SetAngles" , str , 0 )
end
net.Start(StormFox2.Net.Shadows)
net.Broadcast()
end
---Sets the shadow color
---@param sColor table
---@server
function StormFox2.Shadows.SetColor( sColor )
if not StormFox2.Ent.shadow_controls then return end
local s = sColor.r .. " " .. sColor.g .. " " .. sColor.b
for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do
ent:SetKeyValue( "color", s )
end
end
---Sets the shadow distance
---@param dis number
---@server
function StormFox2.Shadows.SetDistance( dis )
if not StormFox2.Ent.shadow_controls then return end
for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do
ent:SetKeyValue( "SetDistance", dis )
end
end
---Disable / Enables shadows
---@param bool boolean
---@server
function StormFox2.Shadows.SetDisabled( bool )
if not StormFox2.Ent.shadow_controls then return end
for _,ent in ipairs( StormFox2.Ent.shadow_controls ) do
ent:SetKeyValue( "SetShadowsDisabled", bool and 1 or 0 )
end
end
-- Simple function to set the light
local n
local function SetDarkness(l)
if n and n == l then return end
n = l
local c = 255 - 68 * n
StormFox2.Shadows.SetColor( Color(c,c,c) )
end
local l = 0
local function shadowTick()
-- Limit update rate
if l >= CurTime() then return end
local rate = StormFox2.Setting.GetCache("modifyshadows_rate", 2)
l = CurTime() + rate
local sP = StormFox2.Sun.GetAngle().p % 360
--360 -> 180 Day
local c = math.abs(math.AngleDifference(sP, 270)) -- 0 - 180. Above 90 is night.
if c > 90 then -- Night
StormFox2.Shadows.SetAngle( 270 )
else
StormFox2.Shadows.SetAngle( sP )
end
if c < 80 then
SetDarkness(1)
elseif c < 85 then
SetDarkness(17 - 0.2*c)
elseif c < 95 then
SetDarkness(0)
elseif c < 100 then
SetDarkness(0.1*c-9.5)
else
SetDarkness(0)
end
end
local function enable()
if not StormFox2.Ent.shadow_controls then return end
hook.Add("Think", "StormFox2.shadow.rate", shadowTick)
end
local function disable()
hook.Remove("Think", "StormFox2.shadow.rate")
if not StormFox2.Ent.shadow_controls then return end
local a = StormFox2.Map.FindClass('shadow_control')
if not a or #a < 1 then
StormFox2.Shadows.SetAngle( 270 )
StormFox2.Shadows.SetColor( Color(187, 187, 187) )
else
local p = (a[1]["angles"] or Angle(90,0,0)).p + 180
StormFox2.Shadows.SetAngle( p )
local c = string.Explode(" ", a[1]["color"] or "187 187 187")
StormFox2.Shadows.SetColor( Color(c[1],c[2],c[3]) )
end
end
if StormFox2.Setting.Get("modifyshadows", true) then
enable()
end
StormFox2.Setting.Callback("modifyshadows",function(b)
if b then
enable()
else
disable()
end
end)

View File

@@ -0,0 +1,266 @@
--[[
| 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/
--]]
-- Holds temperature and wind
--[[-------------------------------------------------------------------------
Temperature is not universal. A few contries cling on to fahrenheit,
so we need another function to display the temperature correctly.
StormFox runs on celsius, but can convert the temperature to whatever you wish.
Clients have to use these functions:
StormFox2.Temperature.GetDisplay() -- Returns the temperature in what their setting is set to.
StormFox2.Temperature.GetDisplaySymbol() -- Returns the temperature symbol for their setting.
StormFox2.Temperature.GetDisplayDefault() -- Returns the default temperature setting for their country.
StormFox2.Temperature.GetDisplayType() -- Returns the temperature setting clients have set to.
Fun facts:
At -90C, we need specialized air or our brain "forgets" to breathe.
You'll be unconscious in an hour in 10C water. Dead in 3.
At -180C oxygen will liquidfy.
Coldest recorded temp on Earth is -90C
---------------------------------------------------------------------------]]
StormFox2.Temperature = {}
local convert_from,convert_to = {},{}
local p1,p2,p3,p4,p5 = 3 / 2, 33 / 100, 4 / 5, 21 / 40,240 / 54
convert_to["fahrenheit"] = function(nC) return nC * 1.8 + 32 end
convert_to["kelvin"] = function(nC) return nC + 273.15 end
convert_to["rankine"] = function(nC) return (nC + 273.15) * 1.8 end
convert_to["delisle"] = function(nC) return (100 - nC) * p1 end
convert_to["newton"] = function(nC) return nC * p2 end
convert_to["réaumur"] = function(nC) return nC * p3 end
convert_to["rømer"] = function(nC) return nC * p4 + 7.5 end
convert_to["wedgwood"] = function(nC) return (nC - 580.8) * p5 end
convert_to["gas_mark"] = function(nC)
if nC >= 135 then
return 1 + (nC - 135) / 13.9
else
return 1 / ((135 - nC) / 13.9 * 2)
end
end
convert_to["banana"] = function(nC) -- 380 kJ of energy in an average size banana. Takes about 344,49kJ to heat up an avage room by 10c. 1 banana = 1.1c
return 10 * (nC - 10) / 11
end
local p1,p2,p3,p4,p5,p6 = 5 / 9, 2 / 3, 100 / 33, 5 / 4, 40 / 21,54 / 240
convert_from["fahrenheit"] = function(nF) return (nF - 32) / 1.8 end
convert_from["kelvin"] = function(nK) return nK - 273.15 end
convert_from["rankine"] = function(nR) return (nR - 491.67) * p1 end
convert_from["delisle"] = function(nD) return 100 - nD * p2 end
convert_from["newton"] = function(nN) return nN * p3 end
convert_from["réaumur"] = function(nR) return nR * p4 end
convert_from["rømer"] = function(nR) return (nR - 7.5) * p5 end
convert_from["wedgwood"] = function(nW) return (nW * p6) + 580.8 end
convert_from["gas_mark"] = function(nG)
if nG >= 1 then
return 0.1 * (139 * nG + 1211)
else
return 135 - 6.95 / nG
end
end
convert_from["banana"] = function(nB)
return 1.1 * nB + 10
end
local symbol = {
["celsius"] = "°C",
["fahrenheit"] = "°F",
["rankine"] = "°R",
["delisle"] = "°D",
["newton"] = "°N",
["réaumur"] = "°Ré",
["rømer"] = "°Rø",
["wedgwood"] = "°W",
["gas_mark"] = "°G",
["banana"] = "°B",
["kelvin"] = "K"
}
--[[<Shared>------------------------------------------------------------------
Returns the current temperature. Valid temperatures:
- celsius : default
- fahrenheit
- kelvin
- rankine
- delisle
- newton
- réaumur
- rømer
---------------------------------------------------------------------------]]
local tempOverwrite
---Returns the current temperature. sType can be "celsius" (default), "fahrenheit", "kelvin" .. ect
---@param sType? string
---@return Color
---@shared
function StormFox2.Temperature.Get(sType)
local n = tempOverwrite or StormFox2.Data.Get( "Temp", 20 )
if not sType or sType == "celsius" then return n end
if not convert_to[sType] then
StormFox2.Warning("Invalid temperature type [" .. tostring(sType) .. "].", true)
end
return convert_to[sType](n)
end
--[[<Shared>-----------------------------------------------------------------
Returns the list of valid temperatures.
- celsius : default
- fahrenheit
- kelvin
- rankine
- delisle
- newton
- réaumur
- rømer
---------------------------------------------------------------------------]]
---Returns all temperature units supported.
---@return table
---@shared
function StormFox2.Temperature.GetTypes()
local t = table.GetKeys(convert_to)
table.insert(t,"celsius")
return t
end
--[[<Shared>-----------------------------------------------------------------
Converts temperature between two types
Valid temperatures:
- celsius : default
- fahrenheit
- kelvin
- rankine
- delisle
- newton
- réaumur
- rømer
- wedgwood
---------------------------------------------------------------------------]]
---Converts temperature from one unit to another. E.g StormFox2.Temperature.Convert("celsius","fahrenheit",0) -> 32.
---@param sTypeFrom string
---@param sTypeTo string
---@param nNumber number
---@return number
---@shared
function StormFox2.Temperature.Convert(sTypeFrom,sTypeTo,nNumber)
if sTypeFrom and sTypeFrom ~= "celsius" then
if not convert_from[sTypeFrom] then
error("Invalid temperature type [" .. sTypeFrom .. "].")
end
nNumber = convert_from[sTypeFrom](nNumber)
end
if sTypeTo and sTypeTo ~= "celsius" then
if not convert_to[sTypeTo] then
error("Invalid temperature type [" .. sTypeTo .. "].")
end
nNumber = convert_to[sTypeTo](nNumber)
end
return nNumber
end
if SERVER then
---Sets the temperature in ceilsius. Second argument is the lerp time in seconds (default: 2).
---@param nCelsius number
---@param nLerpTime? number
---@server
function StormFox2.Temperature.Set(nCelsius,nLerpTime)
if nCelsius < -273.15 then -- ( In space, there are 270.45 C )
nCelsius = -273.15
end
StormFox2.Network.Set("Temp",nCelsius,nLerpTime or 2 * StormFox2.Time.GetSpeed_RAW())
end
else
local country = system.GetCountry() or "UK"
local fahrenheit_countries = {"BS","PW","BZ","KY","FM","MH","US","PR","VI","GU"}
--[[Bahamas, Palau, Belize, the Cayman Islands, the Federated States of Micronesia, the Marshall Islands,
and the United States and its territories such as Puerto Rico, the U.S. Virgin Islands, and Guam.
]]
local default_temp = table.HasValue(fahrenheit_countries, country) and "fahrenheit" or "celsius"
local temp_type = default_temp
--[[<Client>------------------------------------------------------------------
Sets the display temperature. Returns true if given a valid temperature-type.
Valid temperatures:
- celsius : default
- fahrenheit
- kelvin
- rankine
- delisle
- newton
- réaumur
- rømer
---------------------------------------------------------------------------]]
---Sets the display temperature.
---@param sType string
---@return boolean success
---@client
function StormFox2.Temperature.SetDisplayType(sType)
StormFox2.Setting.Set("display_temperature",convert_to[sType] and sType or "celsius")
if convert_to[sType] then
return true
end
return sType == "celsius"
end
---Returns the current display temperature type.
---@return string
---@client
function StormFox2.Temperature.GetDisplayType()
return temp_type
end
---Converts the current (or given) temperature to the clients temperature-unit. Ideal for displays.
---@param nCelcius number
---@return number
---@client
function StormFox2.Temperature.GetDisplay(nCelcius)
if nCelcius then
return StormFox2.Temperature.Convert(nil,temp_type,nCelcius)
end
return StormFox2.Temperature.Get(temp_type)
end
---Returns the clients temperature-unit symbol. ("°C", "°F" ..)
---@return string
---@client
function StormFox2.Temperature.GetDisplaySymbol()
return symbol[temp_type] or "°C"
end
---Returns the default temperature, based on client-country.
---@return string
---@client
function StormFox2.Temperature.GetDisplayDefault()
return default_temp
end
-- Load the temperature settings.
-- Setup setting
StormFox2.Setting.AddCL("display_temperature",default_temp)
local t = {}
for k, v in pairs(symbol) do
if t[k] then continue end
t[k] = string.upper(k[1]) .. string.sub(k, 2) .. " " .. v
end
StormFox2.Setting.SetType( "display_temperature", t, {"celsius", "fahrenheit", "kelvin"} )
StormFox2.Setting.Callback("display_temperature",function(sType)
temp_type = convert_to[sType] and sType or "celsius"
end,"StormFox2.temp.type")
-- Load setting
local sType = StormFox2.Setting.Get("display_temperature",default_temp)
temp_type = convert_to[sType] and sType or "celsius"
hook.Remove("stormfox2.postlib", "StormFox2.TemperatureSettings")
---Sets the "local" temperature. This will override the server variable until called again with 'nil'.
---@param nCelcius? number
---@client
function StormFox2.Temperature.SetLocal( nCelcius )
tempOverwrite = nCelcius
end
end

View File

@@ -0,0 +1,535 @@
--[[
| 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/
--]]
StormFox2.Thunder = {}
---Returns true if it is thundering.
---@return boolean
---@shared
function StormFox2.Thunder.IsThundering()
return StormFox2.Data.Get("nThunder", 0) > 0
end
---Returns the amount of posible strikes pr minute.
---@return number
---@shared
function StormFox2.Thunder.GetActivity()
return StormFox2.Data.Get("nThunder", 0)
end
if SERVER then
local THUNDER_MAKE_SKYBOX = 0
local THUNDER_TRACE_ERROR = 1
local THUNDER_SUCCESS = 2
local function ETHull(pos,pos2,size,mask)
local t = util.TraceHull( {
start = pos,
endpos = pos2,
maxs = Vector(size,size,4),
mins = Vector(-size,-size,0),
mask = mask
} )
t.HitPos = t.HitPos or pos + pos2
return t
end
-- Damages an entity
local function StrikeDamageEnt( ent )
if not ent or not IsValid(ent) then return end
local effectdata = EffectData()
effectdata:SetOrigin( ent:GetPos() )
effectdata:SetEntity(ent)
effectdata:SetMagnitude(2)
effectdata:SetScale(3)
for i = 1,100 do
util.Effect( "TeslaHitboxes", effectdata, true, true )
end
local ctd = DamageInfo()
ctd:IsDamageType(DMG_SHOCK)
ctd:SetDamage(math.Rand(90,200))
local vr = VectorRand()
vr.z = math.abs(vr.z)
ctd:SetDamageForce(vr * 1000)
ctd:SetInflictor(game.GetWorld())
ctd:SetAttacker(game.GetWorld())
ent:TakeDamageInfo(ctd)
end
-- Damanges an area
local function StrikeDamagePos( vPos )
local b_InWater = bit.band( util.PointContents( vPos ), CONTENTS_WATER ) == CONTENTS_WATER
local t = {}
for _,ent in ipairs(ents.FindInSphere(vPos,750)) do
-- It hit in water, and you're nearby
if b_InWater and ent:WaterLevel() > 0 then
StrikeDamageEnt(ent)
table.insert(t, ent)
elseif ent:GetPos():Distance( vPos ) < 150 then
StrikeDamageEnt(ent)
table.insert(t, ent)
end
end
hook.Run("StormFox2.Thunder.OnStrike", vPos, t)
end
-- Make STrike
local Sway = 100
-- Traces a lightningstrike from sky to ground.
local BrushZ = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local function MakeStrikeDown( vPos, nTraceSize )
if not nTraceSize then nTraceSize = 512 end
-- Find the sky position at the area
local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 )
if not SkyPos then -- Unable to find sky above. Get the higest point
SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z)
SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos
end
--debugoverlay.Box(SkyPos, Vector(1,1,1) * -20, Vector(1,1,1) * 20, 15, Color(255,0,0))
-- Find the strike distance (Some maps are suuuper tall)
local tr = ETHull(SkyPos - vector_up * 10, Vector( vPos.x, vPos.y, StormFox2.Map.MinSize().z), 256, MASK_SOLID_BRUSHONLY )
if tr.AllSolid or tr.Fraction == 0 then -- This is inside solid and should instead be done in the skybox.
return THUNDER_MAKE_SKYBOX, SkyPos
end
SkyPos.z = math.min( SkyPos.z, tr.HitPos.z + 8000 )
local line = {}
table.insert(line,pos)
-- Create a line down
local olddir = Vector(0,0,-1)
local m_dis = math.max(1, ( SkyPos.z - StormFox2.Map.MinSize().z ) / 20000)
local pos = SkyPos - vector_up * 5
local _n = nTraceSize / 40
for i = 20, 1, -1 do
-- Random sway
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir
randir:Normalize()
randir.z = -math.abs(randir.z)
olddir = randir
local pos2 = pos + randir * math.Rand(800, 1000) * m_dis
local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local tr = ETHull(pos, pos2, nTraceSize - i * _n )
--debugoverlay.Line(pos, pos2, 10)
if not tr.Hit then
table.insert(line, pos2)
pos = pos2
else
if tr.HitSky then -- We hit the side of the skybox. Go to other way
olddir = -olddir
else
table.insert(line, tr.HitPos)
return THUNDER_SUCCESS, line, tr
end
end
end
return line, tr
end
-- Creates a lightniingstrike up ( Useful when forcing a lightning strike to hit something )
local function MakeStrikeUp( vPos )
local SkyPos = StormFox2.DownFall.FindSky( vPos, vector_up, 8 )
if not SkyPos then -- Unable to find sky above. Get the higest point
SkyPos = Vector(vPos.x, vPos.y, StormFox2.Map.MaxSize().z)
SkyPos = StormFox2.DownFall.FindSky( SkyPos, vector_up, 1 ) or SkyPos
end
local olddir = Vector(0,0,-1)
local pos = vPos
local m_dis = math.max(1, ( StormFox2.Map.MaxSize().z - StormFox2.Map.MinSize().z ) / 20000)
local list = {pos}
for i = 1, 20 do
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway)):Forward() + olddir
randir:Normalize()
randir.z = -math.abs(randir.z)
olddir = randir
local pos2 = pos + -randir * math.Rand(800, 1000) * m_dis
if pos2.z >= SkyPos.z then
table.insert(list, Vector(pos2.x, pos2.y, SkyPos.z))
break
else
pos = pos2
table.insert(list, pos)
end
end
return table.Reverse(list)
end
local function LightFluff(tList, b_InSkybox )
local n_Length = math.Rand(.4,0.7)
local n_Light = math.random(200, 250)
net.Start(StormFox2.Net.Thunder)
net.WriteBool( true )
net.WriteUInt(#tList, 5)
for i = 1, #tList do
net.WriteVector(tList[i])
end
net.WriteBool( b_InSkybox )
net.WriteFloat(n_Length)
net.WriteUInt(n_Light,8)
net.Broadcast()
end
---Creates a lightningstrike at a given position. Will return a hit entity as a second argument.
---@param pos Vector
---@return boolean success
---@return Entity
---@server
function StormFox2.Thunder.CreateAt( pos )
local t_Var, tList, tr
local b_InSkybox = false
local vMapMin = StormFox2.Map.MinSize()
local vMapMax = StormFox2.Map.MaxSize()
if not pos then
pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) )
end
local bInside = pos.x >= vMapMin.x and pos.x <= vMapMax.x and vMapMin.y and pos.y <= vMapMax.y
if bInside then
t_Var, tList, tr = MakeStrikeDown( pos )
if t_Var == THUNDER_MAKE_SKYBOX then
bInside = false
end
end
if not bInside then -- Outside the map
tList = MakeStrikeUp( Vector(pos.x, pos.y, StormFox2.Map.MinSize().z) )
b_InSkybox = true
end
if not tList then return false end -- Unable to create lightning strike here.
local hPos = tr and tr.HitPos or tList[#tList]
if not hPos then return end
if tr and IsValid( tr.Entity ) then
table.insert(tList, tr.Entity:GetPos() + tr.Entity:OBBCenter())
hPos = tr.Entity:GetPos() + tr.Entity:OBBCenter()
end
StrikeDamagePos( hPos )
LightFluff(tList, b_InSkybox )
return true, tr and IsValid( tr.Entity ) and tr.Entity
end
---Creates a lightning strike to hit the given position / entity.
---@param zPosOrEnt Vector|Entity
---@param bRangeDamage number
---@return boolean success
---@server
function StormFox2.Thunder.Strike( zPosOrEnt, bRangeDamage )
-- Strike the entity
if not bRangeDamage and zPosOrEnt.Health then
local ent = zPosOrEnt
timer.Simple(0.4, function()
StrikeDamageEnt( ent )
end)
end
if zPosOrEnt.GetPos then
if zPosOrEnt.OBBCenter then
zPosOrEnt = zPosOrEnt:GetPos() + zPosOrEnt:OBBCenter()
else
zPosOrEnt = zPosOrEnt:GetPos()
end
end
if bRangeDamage then
timer.Simple(0.4, function() StrikeDamagePos( zPosOrEnt ) end)
end
local b_InSkybox = not StormFox2.Map.IsInside( zPosOrEnt )
--if b_InSkybox then
-- zPosOrEnt = StormFox2.Map.WorldtoSkybox( zPosOrEnt )
--end
local tList = MakeStrikeUp( zPosOrEnt )
LightFluff(tList, false )
return true
end
---Creates a rumble.
---@param pos Vector
---@param bLight boolean
---@server
function StormFox2.Thunder.Rumble( pos, bLight )
if not pos then
local vMapMin = StormFox2.Map.MinSize()
local vMapMax = StormFox2.Map.MaxSize()
pos = Vector( math.Rand(vMapMin.x, vMapMax.x), math.Rand(vMapMin.y, vMapMax.y), math.Rand(vMapMax.z, vMapMin.z / 2) )
end
local n_Length = bLight and math.Rand(.4,0.7) or 0
local n_Light = bLight and math.random(150, 250) or 0
net.Start( StormFox2.Net.Thunder )
net.WriteBool( false )
net.WriteVector( pos )
net.WriteFloat(n_Length)
net.WriteUInt(n_Light,8)
net.Broadcast()
end
-- Enables thunder and makes them spawn at random, until set off or another weather gets selected
local b = false
local n = math.max(StormFox2.Map.MaxSize().x, StormFox2.Map.MaxSize().y, -StormFox2.Map.MinSize().x,-StormFox2.Map.MinSize().y)
if StormFox2.Map.Has3DSkybox() then
n = n * 1.5
end
do
local n = 0
---Enables / Disables thunder.
---@param bEnable boolean
---@param nActivityPrMinute? number
---@param nTimeAmount? number
---@server
function StormFox2.Thunder.SetEnabled( bEnable, nActivityPrMinute, nTimeAmount )
n = 0
if bEnable then
StormFox2.Network.Set("nThunder", nActivityPrMinute)
if nTimeAmount then
StormFox2.Network.Set("nThunder", 0, nTimeAmount)
end
else
StormFox2.Network.Set("nThunder", 0)
end
end
hook.Add("Think", "StormFox2.thunder.activity", function()
if not StormFox2.Thunder.IsThundering() then return end
if n >= CurTime() then return end
local a = StormFox2.Thunder.GetActivity()
n = CurTime() + math.random(50, 60) / a
-- Strike or rumble
if math.random(1,3) < 3 then
StormFox2.Thunder.CreateAt()
else
StormFox2.Thunder.Rumble( nil, math.random(10) > 1 )
end
end)
end
else
lightningStrikes = lightningStrikes or {}
local _Light, _Stop, _Length = 0,0,0
---Returns light created by thunder.
---@return number
---@client
function StormFox2.Thunder.GetLight()
if _Light <= 0 then return 0 end
if _Stop < CurTime() then
_Light = 0
return 0
end
local t = (_Stop - CurTime()) / _Length
local c = math.abs(math.sin( t * math.pi ))
local l = _Light * c
return math.random(l, l * 0.5) -- Flicker a bit
end
-- 0 - 2000
local CloseStrikes = {"sound/stormfox2/amb/thunder_strike.ogg"}
-- 2000 - 20000
local MediumStrikes = {"sound/stormfox2/amb/thunder_strike.ogg", "sound/stormfox2/amb/thunder_strike2.ogg"}
-- 20000 +
local FarStrikes = {}
if IsMounted("csgo") or IsMounted("left4dead2") then
table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_1.wav")
table.insert(FarStrikes, "ambient/weather/thunderstorm/lightning_strike_4.wav")
end
if #FarStrikes < 1 then
table.insert(FarStrikes, "sound/stormfox2/amb/thunder_strike2.ogg")
end
local snd_buffer = {}
local function StrikeEffect( pos, n_Length )
local dlight = DynamicLight( 1 )
if ( dlight ) then
dlight.pos = pos
dlight.r = 255
dlight.g = 255
dlight.b = 255
dlight.brightness = 6
dlight.Decay = 3000 / n_Length
dlight.Size = 256 * 8
dlight.DieTime = CurTime() + n_Length
end
local effectdata = EffectData()
local s = math.random(5, 8)
effectdata:SetOrigin( pos + vector_up * 4 )
effectdata:SetMagnitude( s / 2 )
effectdata:SetNormal(vector_up)
effectdata:SetRadius( 8 )
util.Effect( "Sparks", effectdata, true, true )
end
local function PlayStrike( vPos, nViewDis, viewPos )
local snd = ""
if nViewDis <= 2000 then
snd = table.Random(CloseStrikes)
elseif nViewDis <= 15000 then
snd = table.Random(MediumStrikes)
else
snd = table.Random(FarStrikes)
end
if string.sub(snd, 0, 6 ) == "sound/" or string.sub(snd,-4) == ".ogg" then
sound.PlayFile( snd, "3dnoplay", function( station, errCode, errStr )
if ( IsValid( station ) ) then
station:Set3DFadeDistance( 0, 10 )
station:SetVolume( 1)
station:SetPos(vPos)
station:Play()
end
end)
else
surface.PlaySound( snd )
end
end
--[[
Sound moves about 343 meters pr second
52.49 hU = 1 meter ~ 18.004 hU pr second
]]
local b = true
local function SndThink()
if #snd_buffer < 1 then
hook.Remove("Think","StormFox2.Thunder.SndDis")
b = false
return
end
local r = {}
local view = StormFox2.util.ViewEntity():GetPos()
local c = CurTime() - 0.2
for k,v in ipairs( snd_buffer ) do
local travel = (c - v[2]) * 18004
local vDis = view:Distance( v[1] )
if vDis - travel < 0 then
table.insert(r, k)
PlayStrike( v[1], vDis, view )
end
end
for i = #r, 1, -1 do
table.remove(snd_buffer, r[i])
end
end
hook.Add("Think","StormFox2.Thunder.SndDis", SndThink)
local function Strike( tList, b_InSkybox, n_Length, n_Light )
table.insert(lightningStrikes, {CurTime() + n_Length, n_Length, b_InSkybox, tList, true})
local pos = tList[#tList][1]
sound.Play("ambient/energy/weld" .. math.random(1,2) .. ".wav", pos)
if not b then
hook.Add("Think","StormFox2.Thunder.SndDis", SndThink)
end
table.insert(snd_buffer, {pos, CurTime()})
local c = CurTime()
_Light = 255
_Length = .7
_Stop = math.max(c + _Length, _Stop)
end
local function Rumble( vPos, n_Length, n_Light )
-- Thunder is at 120dB
local c = CurTime()
_Light = n_Light
_Length = n_Length
_Stop = math.max(c + n_Length, _Stop)
sound.Play("ambient/atmosphere/thunder" .. math.random(3,4) .. ".wav", StormFox2.util.ViewEntity():GetPos(), 150)
end
local Sway = 120
net.Receive( StormFox2.Net.Thunder, function(len)
local b_Strike = net.ReadBool()
if b_Strike then
local tList = {}
local old
local n = net.ReadUInt(5)
for i = 1, n do
local randir = Angle(math.Rand(-Sway,Sway) + 90,math.random(360),math.Rand(-Sway,Sway))
local new = net.ReadVector()
if old then
randir = randir:Forward() + (new - old):Angle():Forward() * 2
else
randir = randir:Forward()
end
old = new
tList[i] = {new,math.Rand(1.2,1.5),randir,math.random(0,1)}
--debugoverlay.Sphere(new, 15, 15, Color(255,255,255), true)
end
local b_InSkybox = net.ReadBool()
Strike(tList, b_InSkybox, net.ReadFloat(), net.ReadUInt(8))
else
local vPos = net.ReadVector()
Rumble(vPos, net.ReadFloat(), net.ReadUInt(8) )
end
end)
-- Render Strikes
local tex = {(Material("stormfox2/effects/lightning"))}
local texend = {(Material("stormfox2/effects/lightning_end")),(Material("stormfox2/effects/lightning_end2"))}
for k, v in ipairs( texend ) do
v:SetFloat("$nofog",1)
end
local t = 0
hook.Add("PostDrawOpaqueRenderables","StormFox2.Render.Lightning",function(a,sky)
if a or #lightningStrikes < 1 then return end
if sky then return end -- Doesn't work yet
local r = {}
local c = CurTime()
local col = Color( 255, 255, 255, 255)
for k, v in ipairs( lightningStrikes ) do
-- Render world or skybox
--if v[3] ~= sky then continue end
-- Remove if dead
if v[1] < c then
table.insert(r, k)
continue
end
local life = 1 - ( v[1] - c ) / v[2] -- 0 - 1
if life < 0.6 then
col.a = 425 * life
else
col.a = 637.5 * (1 - life)
end
local fuzzy = life < 0.6
local i = 0.6 / #v[4]
if v[5] and not fuzzy then
StrikeEffect( v[4][#v[4]][1] , life )
lightningStrikes[k][5] = false
end
-- Render beams
render.SetMaterial(tex[1])
render.StartBeam(#v[4])
local l = math.Rand(0.4, 0.8)
for k2, v2 in ipairs( v[4] ) do
if life < k2 * i then break end
local tp = 0.1
render.AddBeam( v2[1], 400, (l * (k2 - 1)) % 1, col )
end
render.EndBeam()
-- Render strikes
if fuzzy then
for k2, v2 in ipairs( v[4] ) do
if life < k2 * i or k2 + 3 >= #v[4] then break end
local n2 = life * 2- k2 * 0.04
local vec = v2[1]
local tp = 1 / #v[4] * k2
local n = k2 % #texend + 1
render.SetMaterial(texend[n])
local w,h = texend[n]:Width() * n2,texend[n]:Height() * n2
render.DrawBeam( vec, vec + v2[3] * h * v2[2], w * v2[2], 1 - n2, 1, col )
end
else
local v1 = v[4][1][1]
local v2 = v[4][#v[4]][1]
local vc = (v1 + v2) / 2
vc.z = v2.z
local vc2 = Vector(vc.x,vc.y,v1.z)
local a = math.max(0, 1 - life - 0.2)
col.a = a * 1275
render.SetMaterial(Material("stormfox2/effects/lightning_light"))
render.DrawBeam(vc, vc2, 24400, 0.3 , 0.7, col)
end
end
for i = #r, 1, -1 do
table.remove(lightningStrikes, r[i])
end
end)
end

View File

@@ -0,0 +1,779 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
My god, look at the time.
StormFox2.Time.GetTime(bNearestSecond) [nTime] -- Returns the time number. Between 0 and 1440
StormFox2.Time.TimeToString(nTime = current time,bUse12Hour) [sTime] -- Returns the time as a string.
StormFox2.Time.IsDay() [bIsDay] -- Returns true if its day
StormFox2.Time.IsNight() [bIsNight] -- Returns true if its night
StormFox2.Time.GetStamp(nTime = current time) [nTimeStamp] -- Returns a timestamp.
SERVER
StormFox2.Time.Set(nTime or string) -- Sets the time. Also supports a string "12:00" or "5:00 AM".
StormFox2.Time.SetSpeed(nSpeed) -- Sets the timespeed.
Hooks:
StormFox2.Time.Set -- Called when the time gets set.
StormFox2.Time.NewStamp NewStamp OldStamp -- Callend when the time matches a new stamp
BASE_TIME is CurTime + StartTime
---------------------------------------------------------------------------]]
---A number between 0 and 1440. Where 720 is midday and 0 / 1440 is midnight.
---@class TimeNumber : number
local floor,ceil,random = math.floor, math.ceil, math.random
StormFox2.Time = StormFox2.Time or {}
-- Settings
local s_start = StormFox2.Setting.AddSV("start_time",-1,nil, "Time", -1, 1440) -- Sets the starttime
local s_real = StormFox2.Setting.AddSV("real_time",false,nil, "Time") -- Sets the startime to match OS
local s_random = StormFox2.Setting.AddSV("random_time",false,nil,"Time") -- Makes the time random
local s_continue = StormFox2.Setting.AddSV("continue_time",true,nil,"Time"):SetRadioDefault() -- Make the time continue from last
s_start:SetRadioAll( s_real, s_random, s_continue )
StormFox2.Setting.SetType("start_time","Time_toggle")
local day_length = StormFox2.Setting.AddSV("day_length", 12,nil,"Time",-1, 24 * 60 * 7 )
:SetMenuType("special_float")
local night_length = StormFox2.Setting.AddSV("night_length", 12,nil,"Time",-1, 24 * 60 * 7 )
:SetMenuType("special_float")
local sun_rise = StormFox2.Setting.AddSV("sunrise",360,nil, "Time", 0, 1440)
StormFox2.Setting.SetType("sunrise", "Time")
local sun_set = StormFox2.Setting.AddSV("sunset",1080,nil, "Time", 0, 1440)
StormFox2.Setting.SetType("sunset", "Time")
--[[
Pause
day_length = <= 0
night_length = <= 0
Only day
day_length = > 0
night_length = < 0
Only night
day_length = < 0
night_length = >= 0
]]
--[[ ---- EDIT NOTE ----
x Instead of using Settings directly in UpdateMath. MAke UpdateMath use arguments instead.
These settings are send from the server to client on SetTime or join-data.
When changing settings on the server, wait a few ticks to set them. Sometimes there are multiple settings being changed at the same time.
Best to wait a bit.
StartTime also got removed .. need to fix that.
]]
-- Returns the total time in minutes for a day
local BASE_TIME = 0
--[[
Calculates the regular time
cycleTime - The total time it takes for a day to pass
Enums to keep me sane
- finishTime = The finished number between 0 and 1440. This is the ingame time
- cycleTime = The total seconds it takes for a day to pass
- dayTime = The total seconds it takes for "day-light" to pass
- nightTime = The total seconds it takes for a night to pass
- sunTime = The total of ingame the sun is up
- nightTime = The total of ingame the sun is down
- cyclePercentDay= The percent of the day, being day-light (Only with day and night on)
]]
-- Math Box to set and get time
local Get, Set, UpdateMath, isInDay, isDay, dayLength, nightLength, netWriteData
local GetCache, IsDayCache, CycleCache, FinsihToCycle, GetCycleRaw
local CR
do
local SF_PAUSE = 0
local SF_NORMAL = 1
local SF_DAYONLY = 2
local SF_NIGHTONLY = 3
local SF_REAL = 4
local cycleLength
local curType -- The time-type
-- Returns the percent from the given time ( 0 - 1440) between starttime and endtime
-- Also loops around if from is higer than to
local function lerp1440( time, from, to )
if from < to then
return ( time - from ) / ( to - from )
elseif time >= from then
return ( time - from ) / ( 1440 - from + to )
else
local ex = 1440 - from
return ( time + ex ) / ( to + ex )
end
end
local sunTimeUp, nightTimeUp, sunSet, sunRise
function isInDay( finishTime )
if not sunRise then return true end -- Not loaded yet
if sunRise < sunSet then
return finishTime >= sunRise and finishTime <= sunSet
end
return (finishTime >= sunRise and finishTime <= 1440 ) or finishTime <= sunSet
end
-- Splits cycletime into dayPercent and nightPercent
local function CycleToPercent( cycleTime )
if cycleTime <= dayLength then -- It is day
return cycleTime / dayLength, nil
else -- It is night
return nil, (cycleTime - dayLength) / nightLength
end
end
-- Takes dayPercent or nightPercent and convert it to cycletime
local function PercentToCycle( dayPercent, nightPercent )
if dayPercent then
return dayPercent * dayLength
else
return dayLength + nightLength * nightPercent
end
end
-- returns percent of the day that has passed at the given time
local function finishDayToPercent( finishTime )
return lerp1440( finishTime, sunRise, sunSet )
end
-- returns percent of the night that has passed at the given time
local function finishNightToPercent( finishTime )
return lerp1440( finishTime, sunSet, sunRise )
end
-- Takes the ingame 0-1440 and converts it to the cycle-area
function FinsihToCycle( finishTime )
if isInDay( finishTime ) then -- If day
return finishDayToPercent( finishTime ) * dayLength
else
return dayLength + finishNightToPercent( finishTime ) * nightLength
end
end
local function CycleToFinish( cycle )
if cycle <= dayLength then -- Day time
local percent = cycle / dayLength
return ( sunRise + sunTimeUp * percent ) % 1440
else -- NightTime
local percent = ( cycle - dayLength ) / nightLength
return ( sunSet + nightTimeUp * percent ) % 1440
end
end
-- Get
local function TimeFromSettings( )
-- The seconds passed in the day
local chunk = ((CurTime() - BASE_TIME) % cycleLength)
return CycleToFinish( chunk )
end
local function TimeFromSettings_DAY( )
local p_chunk = ((CurTime() - BASE_TIME) % cycleLength) / cycleLength
return (sunRise + p_chunk * sunTimeUp) % 1440
end
local function TimeFromSettings_NIGHT( )
local p_chunk = ((CurTime() - BASE_TIME) % cycleLength) / cycleLength
return (sunSet + p_chunk * nightTimeUp) % 1440
end
local function TimeFromPause()
return BASE_TIME
end
-- Is cheaper than converting things around
function isDay()
if not cycleLength then return true end -- Not loaded yet
if curType == SF_NIGHTONLY then
return false
elseif curType == SF_DAYONLY then
return true
else
local l = (CurTime() - BASE_TIME) % cycleLength
return l <= dayLength
end
end
function Get()
if not cycleLength then return 720 end -- Not loaded yet
local num
if not curType or curType == SF_NORMAL then
num = TimeFromSettings( )
GetCache = num
IsDayCache = isDay()
elseif curType == SF_REAL then
num = ( CurTime() / 60 - BASE_TIME ) % 1440
GetCache = num
IsDayCache = isInDay( num )
elseif curType == SF_PAUSE then
num = TimeFromPause( )
GetCache = num
IsDayCache = isInDay( num )
elseif curType == SF_DAYONLY then
num = TimeFromSettings_DAY( )
GetCache = num
IsDayCache = true
else
num = TimeFromSettings_NIGHT( )
GetCache = num
IsDayCache = false
end
return num
end
function Set( snTime )
if not curType or curType == SF_NORMAL then
BASE_TIME = CurTime() - FinsihToCycle( snTime )
elseif curType == SF_REAL then
BASE_TIME = CurTime() / 60 - snTime
elseif curType == SF_PAUSE then
BASE_TIME = snTime
-- If you pause the time, we should save it if we got s_continue on.
if SERVER and StormFox2.Loaded and s_continue:GetValue() then
cookie.Set("sf2_lasttime", tostring(snTime))
end
elseif curType == SF_DAYONLY then
local p = math.Clamp(lerp1440( snTime, sunRise, sunSet ), 0, 1)
BASE_TIME = CurTime() - p * dayLength
elseif curType == SF_NIGHTONLY then
local p = math.Clamp(lerp1440( snTime, sunSet, sunRise ), 0, 1)
BASE_TIME = CurTime() - p * nightLength
end
GetCache = nil -- Delete time cache
-- Gets called when the user changes the time, or time variables. Tells scripts to recalculate things.
if not StormFox2.Loaded then return end
hook.Run("StormFox2.Time.Changed")
end
function UpdateMath(nsTime, blockSetTime)
local nsTime = nsTime or ( cycleLength and Get() )
sunSet = sun_set:GetValue()
sunRise = sun_rise:GetValue()
dayLength = day_length:GetValue() * 60
nightLength = night_length:GetValue() * 60
--print(sunSet)
--print(sunRise)
--print(dayLength)
--print(nightLength)
if s_real:GetValue() then -- Real time
cycleLength = 60 * 60 * 24
curType = SF_REAL
elseif dayLength <= 0 and nightLength <= 0 or sunSet == sunRise then -- Pause type
curType = SF_PAUSE
cycleLength = 0
elseif nightLength <= 0 then -- Day only
cycleLength = dayLength
curType = SF_DAYONLY
elseif dayLength <= 0 then -- Night only
cycleLength = nightLength
curType = SF_NIGHTONLY
else
cycleLength = dayLength + nightLength
curType = SF_NORMAL
end
if sunRise < sunSet then
sunTimeUp = sunSet - sunRise
else
sunTimeUp = (1440 - sunRise) + sunSet
end
nightTimeUp = 1440 - sunTimeUp
if not nsTime or blockSetTime then return end -- No valid time currently
Set( nsTime )
if SERVER then
net.Start( StormFox2.Net.Time )
net.WriteString( tostring( BASE_TIME ) )
net.Broadcast()
end
end
local function GetDayPercent()
if not IsDayCache then return -1 end
local chunk = ((CurTime() - BASE_TIME) % cycleLength)
return chunk / dayLength
end
local function GetNightPercent()
if IsDayCache then return -1 end
local chunk = ((CurTime() - BASE_TIME) % cycleLength)
return (chunk - dayLength) / nightLength
end
function GetCycleRaw()
if not cycleLength then return 0 end -- Not loaded, or pause on launch
if CR then return CR end
CR = ((CurTime() - BASE_TIME) % cycleLength)
return CR
end
-- Returns how far the day has progressed 0 = sunRise, 0.5 = sunSet, 1 = sunRise
function StormFox2.Time.GetCycleTime()
if CycleCache then return CycleCache end
if curType == SF_REAL then
local t = Get()
if isInDay( t ) then
CycleCache = lerp1440( t, sunRise, sunSet ) / 2
else
CycleCache = 0.5 + lerp1440( t, sunSet, sunRise ) / 2
end
return CycleCache
end
if curType == SF_PAUSE then -- When paused, use the time to calculate
if isInDay( BASE_TIME ) then
CycleCache = lerp1440( BASE_TIME, sunRise, sunSet ) / 2
else
CycleCache = 0.5 + lerp1440( BASE_TIME, sunSet, sunRise ) / 2
end
return math.Clamp(CycleCache, 0, 1)
end
if IsDayCache then
CycleCache = GetDayPercent() / 2
return math.Clamp(CycleCache, 0, 1)
elseif cycleLength then
CycleCache = GetNightPercent() / 2 + 0.5
return math.Clamp(CycleCache, 0, 1)
else -- Idk
return 0.5
end
end
end
-- Cache clear. Wait 4 frames to update the time-cache, calculating it for every function is too costly.
do
local i = 0
hook.Add("Think", "StormFox2.Time.ClearCache", function()
CR = nil
i = i + 1
if i >= 2 then
i = 0
GetCache = nil
CycleCache = nil
end
end)
end
-- In most cases, multiple settings will update at the same time. Wait a second.
local function updateTimeSettings( )
if timer.Exists("SF_SETTIME") then return end
timer.Create("SF_SETTIME", 0.2, 1, function()
UpdateMath( nil, CLIENT ) -- If we're the client, then don't update the BASE_TIME
end)
end
-- If any of the settings change, update the math behind it. This will also fix time and update clients if done on server.
day_length:AddCallback( updateTimeSettings,"SF_TIMEUPDATE")
night_length:AddCallback( updateTimeSettings,"SF_TIMEUPDATE")
sun_rise:AddCallback( updateTimeSettings,"SF_TIMEUPDATE")
sun_set:AddCallback( updateTimeSettings,"SF_TIMEUPDATE")
s_real:AddCallback( updateTimeSettings,"SF_TIMEUPDATE")
-- Make real-time change day and night length
if SERVER then
s_real:AddCallback( function( b )
if not b then return end
local dt = string.Explode(":",os.date("%H:%M:%S"))
nsTime = tonumber(dt[1]) * 60 + tonumber(dt[2]) + tonumber(dt[3]) / 60
StormFox2.Time.Set(nsTime)
end,"SF_REALTIME_S")
end
-- Update the math within Get and Set. Will also try and adjust the time
if SERVER then -- Server controls the time
local start_time = math.Clamp(cookie.GetNumber("sf2_lasttime",-1) or -1, -1, 1439)
if s_continue:GetValue() and start_time >= 0 then
-- Continue time from last
else
if s_start:GetValue() >= 0 then -- Start time is on
start_time = s_start:GetValue()
elseif s_real:GetValue() then -- Real time
local dt = string.Explode(":",os.date("%H:%M:%S"))
start_time = tonumber(dt[1]) * 60 + tonumber(dt[2]) + tonumber(dt[3]) / 60
else -- if s_random:GetValue() or start_time < 0 then Make it random if all options are invalid
start_time = math.Rand(0, 1400)
end
end
UpdateMath( start_time, false )
---Sets the time. TimeNumber is a number between 0 and 1440.
---@param nsTime TimeNumber
---@return boolean success
---@see TimeNumber
---@server
function StormFox2.Time.Set( nsTime )
if nsTime and type( nsTime ) == "string" then
nsTime = StormFox2.Time.StringToTime(nsTime)
end
if not nsTime then return false end
Set( nsTime )
net.Start( StormFox2.Net.Time )
net.WriteString( tostring( BASE_TIME ) ) -- Sending the current time might add a delay to clients. Better to send the new base.
net.Broadcast()
return true
end
-- Tell new clients the settings
hook.Add("StormFox2.data.initspawn", "StormFox2.Time.SendOnJoin", function( ply )
net.Start( StormFox2.Net.Time )
net.WriteString( tostring( BASE_TIME ) )
net.Send( ply )
end)
else
UpdateMath( 720, true ) -- Set the starting time to 720. We don't know any settings yet.
net.Receive( StormFox2.Net.Time, function(len)
BASE_TIME = tonumber( net.ReadString() ) or 0
end)
end
---Returns the current time. TimeNumber is a number between 0 and 1440.
---@param bNearestSecond boolean
---@return TimeNumber
---@shared
function StormFox2.Time.Get( bNearestSecond )
if bNearestSecond then
return math.floor(GetCache and GetCache or Get())
end
return GetCache and GetCache or Get()
end
---Returns the current timespeed / 60. Used for internal calculations.
---@deprecated
---@return number
---@shared
function StormFox2.Time.GetSpeed_RAW()
if not nightLength or StormFox2.Time.IsPaused() then return 0 end
if IsDayCache then
return 1 / dayLength
end
return 1 / nightLength
end
---Returns the current timespeed. "How many seconds pr real second".
---@return number
---@shared
function StormFox2.Time.GetSpeed()
return StormFox2.Time.GetSpeed_RAW() * 60
end
-- Be able to load time
local function thinkingBox(sVar) -- Converts string to something useful
local h,m = string.match(sVar,"(%d?%d):?(%d?%d)")
local ampm = string.match(sVar,"[ampAMP]+") or ""
if not h or not m then return end
if #ampm > 0 then
if tonumber(h) > 12 then ampm = "" end
end
if #ampm < 1 then ampm = "" end
return h .. ":" .. m .. " " .. ampm
end
---Returns the given time as a number. Supports both "13:00" and "1:00 PM"
---@param sTime string
---@return TimeNumber|string
---@shared
function StormFox2.Time.StringToTime(sTime)
sTime = sTime or StormFox2.Time.Get()
str = thinkingBox(sTime)
if not str then return end
local a = string.Explode( ":", str )
if #a < 2 then return end
local h,m = string.match(a[1],"%d+"),string.match(a[2],"%d+")
local ex = string.match(a[2]:lower(),"[amp]+")
if not h or not m then return end
h,m = tonumber(h),tonumber(m)
if ex then
-- 12clock to 24clock
if ex == "am" and h == 12 then
h = h - 12
end
if h < 12 and ex == "pm" then
h = h + 12
end
end
return ( h * 60 + m ) % 1440
end
---A syncronised number used by the client to calculate the time. Use instead StormFox2.Time.Get to get the current time.
---@return number
---@shared
function StormFox2.Time.GetBASE_TIME()
return BASE_TIME
end
---Returns the given or current time in a string format. Will use client setting if bUse12Hour is nil.
---@param nTime? TimeNumber
---@param bUse12Hour? boolean
---@return string
---@shared
function StormFox2.Time.TimeToString(nTime,bUse12Hour)
if CLIENT and bUse12Hour == nil then
bUse12Hour = StormFox2.Setting.GetCache("12h_display")
end
if not nTime then nTime = StormFox2.Time.Get(true) end
local h = floor(nTime / 60)
local m = floor(nTime % 60 )
if not bUse12Hour then return h .. ":" .. (m < 10 and "0" or "") .. m end
local e = "PM"
if h < 12 or h == 0 then
e = "AM"
end
if h == 0 then
h = 12
elseif h > 12 then
h = h - 12
end
return h .. ":" .. (m < 10 and "0" or "") .. m .. " " .. e
end
-- Easy functions
---Returns true if the current or given time is doing the day.
---@param nsTime? TimeNumber
---@return boolean
---@shared
function StormFox2.Time.IsDay( nsTime )
if not nsTime then -- Cheaper and faster than to convert things around.
return IsDayCache
end
return isInDay( nsTime )
end
---Returns true if the current or given time is doing the night.
---@param nTime? TimeNumber
---@return boolean
---@shared
function StormFox2.Time.IsNight(nTime)
return not StormFox2.Time.IsDay(nTime)
end
---Returns true if the current or given time is between FromTime to ToTime.
-- E.g Dinner = StormFox2.Time.IsBetween(700,740)
---@param nFromTime TimeNumber
---@param nToTime TimeNumber
---@param nCurrentTime? TimeNumber
---@return boolean
---@shared
function StormFox2.Time.IsBetween(nFromTime,nToTime,nCurrentTime)
if not nCurrentTime then nCurrentTime = StormFox2.Time.Get() end
if nFromTime > nToTime then
return nCurrentTime >= nFromTime or nCurrentTime <= nToTime
end
return nFromTime <= nCurrentTime and nToTime >= nCurrentTime
end
---Returns the time between Time and Time2 in minutes.
---@param nTime TimeNumber
---@param nTime2 TimeNumber
---@return number
---@shared
function StormFox2.Time.DeltaTime(nTime,nTime2)
if nTime2 >= nTime then return nTime2 - nTime end
return (1440 - nTime) + nTime2
end
-- Time stamp
---Returns the current (or given time) hour-number. E.g at 11:43 will return 11.
---@param nTime? TimeNumber
---@param b12Hour? boolean
---@return number
---@shared
function StormFox2.Time.GetHours( nTime, b12Hour )
if not nTime then nTime = StormFox2.Time.Get() end
if not b12Hour then return floor( nTime / 60 ) end
local h = floor( nTime / 60 )
if h == 0 then
h = 12
elseif h > 12 then
h = h - 12
end
return h
end
---Returns the current (or given time) minute-number. E.g at 11:43 will return 43.
---@param nTime? TimeNumber
---@return number
---@shared
function StormFox2.Time.GetMinutes( nTime )
if not nTime then nTime = StormFox2.Time.Get() end
return floor( nTime % 60 )
end
---Returns the current (or given time) seconds-number. E.g at 11:43:22 will return 22.
---@param nTime? TimeNumber
---@return number
---@shared
function StormFox2.Time.GetSeconds( nTime )
if not nTime then nTime = StormFox2.Time.Get() end
return floor( nTime % 1 ) * 60
end
---Returns the current (or given time) "AM" or "PM" string. E.g 20:00 / 8:00 PM will return "PM".
---@param nTime? TimeNumber
---@return string
---@shared
function StormFox2.Time.GetAMPM( nTime )
if not nTime then nTime = StormFox2.Time.Get() end
local h = floor( nTime / 60 )
if h < 12 or h == 0 then
return "AM"
end
return "PM"
end
--[[
Allows to pause and resume time
]]
local lastT
-- (Internal) Second argument is nil or a table of the old settings from StormFox2.Time.Pause()
---Returns true if the time is paused.
---@return boolean
---@shared
function StormFox2.Time.IsPaused()
local dl = day_length:GetValue()
local nl = night_length:GetValue()
return dl <= 0 and nl <= 0, lastT
end
if SERVER then
---Pauses the time.
---@server
function StormFox2.Time.Pause()
local dl = day_length:GetValue()
local nl = night_length:GetValue()
if dl <= 0 and nl <= 0 then return end -- Already paused time
lastT = { dl, nl }
day_length:SetValue( 0 )
night_length:SetValue( 0 )
end
---Resumes the time.
---@server
function StormFox2.Time.Resume()
if not StormFox2.Time.IsPaused() then return end
if lastT then
day_length:SetValue( lastT[1] )
night_length:SetValue( lastT[2] )
lastT = nil
else
day_length:SetValue( 12 )
night_length:SetValue( 12 )
end
end
end
---Returns the seconds until we reached the given time.
---Remember to lisen for the hook: "StormFox2.Time.Changed". In case an admin changes the time / time-settings.
---@param nTime TimeNumber
---@return number
---@shared
function StormFox2.Time.SecondsUntil( nTime )
if StormFox2.Time.IsPaused() then return -1 end
local c_cycleTime = GetCycleRaw() -- Seconds past sunrise
local t_cycleTime = FinsihToCycle( nTime ) -- Seconds past sunrise to said time
return ( t_cycleTime - c_cycleTime ) % ( dayLength + nightLength )
end
-- Default Time Display
if CLIENT then
-- 12h countries
local country = system.GetCountry() or "GB"
local h12_countries = {"GB","IE","US","CA","AU","NZ","IN","PK","BD","MY","MT","EG","MX","PH"}
--[[United Kingdom, Republic of Ireland, the United States, Canada (sorry Quebec),
Australia, New Zealand, India, Pakistan, Bangladesh, Malaysia, Malta, Egypt, Mexico and the former American colony of the Philippines
]]
local default_12 = table.HasValue(h12_countries, country)
StormFox2.Setting.AddCL("12h_display",default_12,"Changes how time is displayed.","Time")
StormFox2.Setting.SetType( "12h_display", {
[false] = "24h clock",
[true] = "12h clock"
} )
---Returns the current time as a string. Useful for displays.
---@param nTime? TimeNumber
---@return string
---@client
function StormFox2.Time.GetDisplay(nTime)
local use_12 = StormFox2.Setting.GetCache("12h_display",default_12)
return StormFox2.Time.TimeToString(nTime,use_12)
end
-- In case the date changes, call the next-day hook
hook.Add("StormFox2.data.change","StormFox2.Date.NextDay", function(sKey, zVar, nDelta)
if sKey == "day" then
hook.Run("StormFox2.Time.NextDay")
end
end)
else
local nextDay = -1
local _b = false
hook.Add("Think", "StormFox2.Time.NextDayCheck", function()
if nextDay <= CurTime() then -- Calculate next day
local sec = StormFox2.Time.SecondsUntil( 1440 )
if sec == -1 then -- Time is paused, will never be next day
nextDay = CurTime() + 500
else
nextDay = CurTime() + sec
if _b then
hook.Run("StormFox2.Time.NextDay")
end
_b = true
end
end
end)
-- The time and or timespeed changed. Recalculate when the day changes
hook.Add("StormFox2.Time.Changed", "StormFox2.Time.NextDayCalc", function()
nextDay = -1
_b = false
end)
-- We use the date-functions to increase the day
hook.Add("StormFox2.Time.NextDay", "StormFox2.Data.NextDay", function()
local nDay = StormFox2.Date.GetYearDay() + 1
StormFox2.Date.SetYearDay( nDay )
end)
end
-- A few hooks
do
local last = -1
local loaded = false
local function checkDNTrigger()
if not loaded then return end
local stamp, mapLight = StormFox2.Sky.GetLastStamp()
local dN
if stamp >= SF_SKY_CEVIL then
dN = 1
else
dN = 0
end
if last == dN then return end
last = dN
if dN == 0 then -- Day
hook.Run("StormFox2.Time.OnDay")
else -- Night
hook.Run("StormFox2.Time.OnNight")
end
end
hook.Add("StormFox2.InitPostEntity", "StormFox2.time.strigger",function()
timer.Simple(5, function()
loaded = true
checkDNTrigger()
end)
end)
-- StormFox2.weather.postchange will be called after something changed. We check the stamp in there.
hook.Add("StormFox2.weather.postchange", "StormFox2.time.trigger2", checkDNTrigger)
end
--[[
Make sure to save the time on shutdown, or when we switch to s_continue while pause is on.
]]
if SERVER then
local function onSave()
cookie.Set("sf2_lasttime", tostring( StormFox2.Time.Get( true ) ) )
StormFox2.Msg("Saved time.")
end
if s_continue:GetValue() then
hook.Add("ShutDown", "StormFox2.TimeSave",onSave)
end
s_continue:AddCallback(function(var)
if var then
hook.Add("ShutDown", "StormFox2.TimeSave",onSave)
if curType == SF_PAUSE then
cookie.Set("sf2_lasttime", tostring( StormFox2.Time.Get( true ) ) )
end
else
hook.Remove("ShutDown", "StormFox2.TimeSave",onSave)
end
end, "sf2_savetime")
end

View File

@@ -0,0 +1,447 @@
--[[
| 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/
--]]
-- Updates the weather for given players
local lastSet = 0
local CurrentWeather
local CurrentPercent = 1
local function isColor(t)
return t.r and t.g and t.b and true or false
end
local function Blender(nFraction, vFrom, vTo) -- Will it blend?
-- Nils should be false, if one of them is a boolean
if type(vFrom) == "nil" and type(vTo) == "boolean" then
vFrom = false
end
if type(vTo) == "nil" and type(vFrom) == "boolean" then
vTo = false
end
-- If the same value, then return it
if vTo == vFrom then return vTo end
-- In case of two diffrent variables.
if type(vFrom) ~= type(vTo) then
StormFox2.Warning("Mixer called with values of two different types[" .. type(vFrom) .. "," .. type(vTo) .. "]")
debug.Trace()
return vFrom
elseif type(vTo) == "string" or type(vTo) == "IMaterial" or type(vTo) == "boolean" then -- String, material or bool. Return vTo.
return vTo
elseif type(vTo) == "number" then -- Number
return Lerp(nFraction, vFrom, vTo)
elseif type(vTo) == "table" and isColor(vTo) then -- Color
local r = Lerp( nFraction, vFrom.r or 255, vTo.r )
local g = Lerp( nFraction, vFrom.g or 255, vTo.g )
local b = Lerp( nFraction, vFrom.b or 255, vTo.b )
local a = Lerp( nFraction, vFrom.a or 255, vTo.a )
return Color( r, g, b, a )
end
--StormFox2.Warning("ERROR: Unsupported mix value type[" .. type(vTo) .. "]. Returning original value")
--debug.Trace()
return vFrom
end
local function IsSame(sName, nPercentage, nDelta)
if CurrentPercent ~= nPercentage then return false end
if not CurrentWeather then return false end
return CurrentWeather.Name == sName
end
local function ApplyWeather(sName, nPercentage, nDelta)
hook.Run("StormFox2.weather.prechange", sName ,nPercentage )
if nDelta and nDelta <= 0 then
nDelta = nil
elseif nDelta then
local sp = StormFox2.Time.GetSpeed_RAW()
if sp > 0 then
nDelta = nDelta / sp
end
end
local bSameWeather = sName == (CurrentWeather and CurrentWeather.Name or "Clear")
if CurrentWeather and CurrentWeather.OnChange then
CurrentWeather:OnChange( sName, nPercentage, nDelta )
end
if CurrentWeather and CurrentWeather.OnRemove and not bSameWeather then
CurrentWeather:OnRemove( sName, nPercentage, nDelta )
end
local clear = StormFox2.Weather.Get( "Clear" )
CurrentWeather = StormFox2.Weather.Get( sName )
CurrentPercent = nPercentage
local stamp = StormFox2.Sky.GetLastStamp()
if sName == "Clear" then
nPercentage = 1
end
if nPercentage >= 1 then
for _,key in ipairs( StormFox2.Weather.GetKeys() ) do
local v = CurrentWeather:Get( key, stamp )
if type(v) == "table" and not (v.r and v.g and v.b) then
StormFox2.Data.Set(key, v)
else
StormFox2.Data.Set(key, v, nDelta)
end
end
elseif nPercentage <= 0 then
for _,key in ipairs( StormFox2.Weather.GetKeys() ) do
StormFox2.Data.Set(key, clear:Get( key, stamp ), nDelta)
end
else -- Mixing bin
for _,key in ipairs( StormFox2.Weather.GetKeys() ) do
local var2,b_nomix = CurrentWeather:Get( key, stamp )
local d = nDelta
if type(var2) == "table" and not (var2.r and var2.g and var2.b) then
d = nil
end
if b_nomix then
StormFox2.Data.Set(key, var2, d)
else
local var1 = clear:Get( key, stamp )
if var2 and not var1 then -- This is not a default variable
if type(var2) == "number" then
var1 = 0
end
end
if not var1 and var2 then -- THis is not a default varable
StormFox2.Data.Set(key, var2, d)
elseif var1 and var2 then
StormFox2.Data.Set(key, Blender(nPercentage, var1, var2), d)
end
end
end
end
if CurrentWeather.Init and not bSameWeather then
CurrentWeather.Init()
end
if CurrentWeather.Tick10 then
CurrentWeather.Tick10()
end
hook.Run("StormFox2.weather.postchange", sName ,nPercentage, nDelta )
end
hook.Add("StormFox2.Sky.StampChange","StormFox2.Weather.Stamp",function(_,nLerpTime)
ApplyWeather(CurrentWeather and CurrentWeather.Name or "Clear", CurrentPercent, nLerpTime)
end)
---Returns the current weather-type.
---@return Weather
function StormFox2.Weather.GetCurrent()
return CurrentWeather or StormFox2.Weather.Get( "Clear" )
end
---Returns the current weather percent.
---@return number Percent
function StormFox2.Weather.GetPercent()
return StormFox2.Data.Get("w_Percentage",CurrentPercent)
end
---Returns the weather percent we're lerping to.
---@return number
function StormFox2.Weather.GetFinishPercent()
return CurrentPercent
end
---Returns the current weather description. Like 'Snow', 'Storm' .. ect.
---Second argument isn't translated.
---@return string Description
---@return string Description_Untranslated
function StormFox2.Weather.GetDescription()
local c = StormFox2.Weather.GetCurrent()
if not c.GetName then
return c.Name
end
local a,b = c:GetName(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), StormFox2.Weather.GetPercent())
return a,b or a
end
local errM = Material("error")
---Returns the current weather-icon.
---@return userdata Material
function StormFox2.Weather.GetIcon()
local c = StormFox2.Weather.GetCurrent()
if not c.GetIcon then
return errM
end
return c.GetIcon(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), StormFox2.Weather.GetPercent())
end
local SF_UPDATE_WEATHER = 0
local SF_INIT_WEATHER = 1
if SERVER then
local l_data
---Sets the weather.
---@server
---@param sName string
---@param nPercentage number
---@param nDelta? number
---@return boolean success
function StormFox2.Weather.Set( sName, nPercentage, nDelta )
if not StormFox2.Setting.GetCache("enable", true) then return end -- Just in case
if nDelta and l_data and nDelta == l_data then
if IsSame(sName, nPercentage) then return false end
end
l_data = nDelta
-- Default vals
if not nDelta then
nDelta = 4
end
if not nPercentage then
nPercentage = 1
end
-- Unknown weathers gets replaced with 'Clear'
if not StormFox2.Weather.Get( sName ) then
StormFox2.Warning("Unknown weather: " .. tostring(sName))
sName = "Clear"
end
-- In case we set the weather to clear, change it so it is the current weather at 0 instead
if sName == "Clear" and CurrentWeather and nDelta > 0 then
nPercentage = 0
sName = CurrentWeather.Name
elseif sName == "Clear" then
nPercentage = 1
end
lastSet = CurTime()
net.Start( StormFox2.Net.Weather )
net.WriteBit(SF_UPDATE_WEATHER)
net.WriteUInt( math.max(0, StormFox2.Data.GetLerpEnd( "w_Percentage" )), 32)
net.WriteFloat(nPercentage)
net.WriteString(sName)
net.WriteFloat(CurTime() + nDelta)
net.Broadcast()
ApplyWeather(sName, nPercentage, nDelta)
if sName == "Clear" then
nPercentage = 0
end
StormFox2.Data.Set("w_Percentage",nPercentage,nDelta)
return true
end
net.Receive( StormFox2.Net.Weather, function(len, ply) -- OI, what weather?
local lerpEnd = StormFox2.Data.GetLerpEnd( "w_Percentage" )
net.Start( StormFox2.Net.Weather )
net.WriteBit(SF_INIT_WEATHER)
net.WriteUInt( math.max(0, StormFox2.Data.GetLerpEnd( "w_Percentage" )), 32)
net.WriteFloat( CurrentPercent )
net.WriteString( StormFox2.Weather.GetCurrent().Name )
net.WriteFloat( StormFox2.Data.Get("w_Percentage",CurrentPercent) )
net.Send(ply)
end)
-- Handles the terrain logic
timer.Create("StormFox2.terrain.updater", 4, 0, function()
local cW = StormFox2.Weather.GetCurrent()
local cT = StormFox2.Terrain.GetCurrent()
if not cW then return end -- No weather!?
local terrain = cW:Get("Terrain")
if not cT and not terrain then return end -- No terrain detected
if cT and terrain and cT == terrain then return end -- Same terrain detected
if terrain then -- Switch terraintype out. This can't be the same as the other
StormFox2.Terrain.Set(terrain.Name)
elseif not terrain and not cT.lock then -- This terrain doesn't have a lock. Reset terrain
StormFox2.Terrain.Reset()
elseif not terrain and cT.lock then -- Check the lock of cT and see if we can reset
if cT:lock() then -- Lock tells us we can reset the terrain
StormFox2.Terrain.Reset()
end
end
end)
local tS = CurTime()
-- In case no weather was set
timer.Simple(8, function()
-- Clear up weather when it reaches 0
timer.Create("StormFox2.weather.clear",1,0,function()
if not CurrentWeather then return end
if CurrentWeather.Name == "Clear" then return end
local p = StormFox2.Weather.GetPercent()
if p <= 0 then
StormFox2.Weather.Set("Clear", 1, 0)
end
end)
if CurrentWeather then return end
StormFox2.Weather.Set("Clear", 1, 0)
end)
else
local hasLocalWeather = false
local svWeather
local function SetW( sName, nPercentage, nDelta )
-- Block same weather
if IsSame(sName, nPercentage) then return false end
ApplyWeather(sName, nPercentage, nDelta)
if sName == "Clear" then
nPercentage = 0
end
StormFox2.Data.Set("w_Percentage",nPercentage,nDelta)
end
---Sets the weather on the client. Server-side stuff won't be set.
---@client
---@param sName? string
---@param nPercentage? number
---@param nDelta? number
---@param nTemperature? number
function StormFox2.Weather.SetLocal( sName, nPercentage, nDelta, nTemperature)
-- If nil then remove the local weather
if not sName then
return StormFox2.Weather.RemoveLocal()
end
-- Unknown weathers gets replaced with 'Clear'
if not StormFox2.Weather.Get( sName ) then
StormFox2.Warning("Unknown weather: " .. tostring(sName))
sName = "Clear"
end
if not hasLocalWeather then
svWeather = {StormFox2.Weather.GetCurrent().Name, StormFox2.Weather.GetFinishPercent(), StormFox2.Temperature.Get()}
end
StormFox2.Temperature.SetLocal(nTemperature)
-- Block same weather
SetW(sName, nPercentage or 1, nDelta)
hasLocalWeather = true
end
---Removes the local weather.
---@client
function StormFox2.Weather.RemoveLocal()
if not hasLocalWeather then return end
SetW(svWeather[1], svWeather[2], 4)
StormFox2.Temperature.SetLocal(nil)
svWeather = nil
hasLocalWeather = false
end
net.Receive( StormFox2.Net.Weather, function(len)
local flag = net.ReadBit() == SF_UPDATE_WEATHER
local wTarget = net.ReadUInt(32)
local nPercentage = net.ReadFloat()
local sName = net.ReadString()
if flag then
local nDelta = net.ReadFloat() - CurTime()
-- Calculate the time since server set this
if not hasLocalWeather then
SetW(sName, nPercentage, nDelta)
else
svWeather[1] = sName
svWeather[2] = nPercentage
end
else
local current = net.ReadFloat()
if not hasLocalWeather then
local secondsLeft = wTarget - CurTime()
if secondsLeft <= 0 then
SetW(sName, nPercentage, 0)
else
SetW(sName, current, 0)
SetW(sName, nPercentage, secondsLeft)
end
else
svWeather[1] = sName
svWeather[2] = nPercentage
end
end
end)
-- Ask the server what weather we have
hook.Add("StormFox2.InitPostEntity", "StormFox2.terrain", function()
net.Start( StormFox2.Net.Weather )
net.SendToServer()
end)
end
hook.Add("Think", "StormFox2.Weather.Think", function()
if not CurrentWeather then return end
if not CurrentWeather.Think then return end
if not StormFox2.Setting.SFEnabled() then return end
CurrentWeather:Think()
end)
timer.Create("StormFox2.Weather.tickslow", 1, 0, function()
if not CurrentWeather then return end
if not CurrentWeather.TickSlow then return end
CurrentWeather.TickSlow()
end)
hook.Add("StormFox2.weather.postchange", "StormFox2.weather.slowtickinit", function()
if not CurrentWeather then return end
if not CurrentWeather.TickSlow then return end
CurrentWeather.TickSlow()
end)
if CLIENT then
local c_tab = {"PostDrawTranslucentRenderables", "PreDrawTranslucentRenderables", "HUDPaint"}
for i,v in ipairs(c_tab) do
hook.Add(v, "StormFox2.Weather." .. v, function(...)
if not CurrentWeather then return end
if not CurrentWeather[v] then return end
CurrentWeather[v](...)
end)
end
end
-- Some functions to make it easier.
---Returns true if it is raining, or if current weather is child of rain.
---@return boolean
---@shared
function StormFox2.Weather.IsRaining()
local wT = StormFox2.Weather.GetCurrent()
if wT.Inherit == "Rain" then return true end
if wT.Name ~= "Rain" then return false end
return StormFox2.Temperature.Get() > -2 or false
end
---Returns true if it is snowing.
---@return boolean
---@shared
function StormFox2.Weather.IsSnowing()
local wT = StormFox2.Weather.GetCurrent()
if wT.Name ~= "Rain" then return false end
return StormFox2.Temperature.Get() <= -2 or false
end
---Returns the rain / snow amount. Between 0 - 1.
---@return number
---@shared
function StormFox2.Weather.GetRainAmount()
if not StormFox2.Weather.IsRaining() then return 0 end
return StormFox2.Weather.GetPercent()
end
---Returns true if the current weather is raining, snowing or inherit from rain.
---@return boolean
---@shared
function StormFox2.Weather.HasDownfall()
local wT = StormFox2.Weather.GetCurrent()
if wT.Inherit == "Rain" then return true end
return wT.Name == "Rain"
end
-- Downfall
---Returns true if the entity is hit by rain or any downfall.
---@param eEnt Entity
---@param bDont_cache boolean
---@return boolean
---@shared
function StormFox2.DownFall.IsEntityHit(eEnt, bDont_cache)
if not StormFox2.Weather.HasDownfall() then return false end
return (StormFox2.Wind.IsEntityInWind(eEnt,bDont_cache))
end
---Checks to see if the given point is hit by rain.
---@param vPos Vector
---@return boolean
---@shared
function StormFox2.DownFall.IsPointHit(vPos)
if not StormFox2.Weather.HasDownfall() then return false end
local t = util.TraceLine( {
start = vPos,
endpos = vPos + -StormFox2.Wind.GetNorm() * 262144,
mask = StormFox2.DownFall.Mask
} )
return t.HitSky
end

View File

@@ -0,0 +1,87 @@
--[[
| 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/
--]]
StormFox2.Setting.AddSV("darken_2dskybox", false, nil, "Effect")
local convar = GetConVar("sv_skyname")
local mat_2dBox = "skybox/" .. convar:GetString()
local last_f = 1
local function OnChange( str )
StormFox2.Map.Set2DSkyBoxDarkness( last_f )
end
cvars.RemoveChangeCallback("sv_skyname", "sf_skynamehook")
cvars.AddChangeCallback( "sv_skyname", OnChange, "sf_skynamehook" )
local t = {"bk", "dn", "ft", "lf", "rt", "up"}
---Sets the 2D skybox darkness. Mostly used for internal stuff.
---@param f number
---@param bRemember boolean
---@param bDark boolean
function StormFox2.Map.Set2DSkyBoxDarkness( f, bRemember, bDark )
if bRemember then
last_f = f
end
local sky = convar:GetString()
if sky == "painted" then return end
if bDark == nil then
bDark = StormFox2.Setting.GetCache("darken_2dskybox", false)
end
if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() or not bDark then
f = 1
end
mat_2dBox = "skybox/" .. sky
local vec = Vector( f, f, f)
for k,v in ipairs( t ) do
local m = Material(mat_2dBox .. v)
if m:IsError() then continue end
m:SetVector("$color", vec)
m:SetInt("$nofog", 1)
m:SetInt("$ignorez", 1)
end
end
StormFox2.Setting.Callback("darken_2dskybox", function(vVar)
StormFox2.Map.Set2DSkyBoxDarkness( last_f, false, vVar )
end, "darken_2dskybox")
local function SkyThink(b, str)
if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() then return end
if b == nil then
b = StormFox2.Setting.GetCache("use_2dskybox", false)
end
if not b then
return RunConsoleCommand("sv_skyname", "painted")
end
local s = str or StormFox2.Setting.GetCache("overwrite_2dskybox", "")
if s == "" then
local lS = 0
if StormFox2.Sky and StormFox2.Sky.GetLastStamp then -- Something happen
lS = StormFox2.Sky.GetLastStamp()
end
local sky_options = StormFox2.Weather.GetCurrent():Get("skyBox", lS)
s = (table.Random(sky_options))
else
StormFox2.Map.Set2DSkyBoxDarkness( last_f )
end
RunConsoleCommand("sv_skyname", s)
end
StormFox2.Setting.Callback("use_2dskybox",SkyThink,"2dskybox_enable")
StormFox2.Setting.Callback("overwrite_2dskybox",function(str) SkyThink(nil, str) end,"2dskybox_enable2")
hook.Add("StormFox2.weather.postchange", "StormFox2.weather.set2dsky", function( _ )
if not StormFox2.Setting.GetCache("use_2dskybox", false) then return end
SkyThink()
end)

View File

@@ -0,0 +1,339 @@
--[[
| 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/
--]]
local INVALID_VERTS = 0
local WATER_VERTS = 1
StormFox2.Setting.AddSV("enable_ice",not game.IsDedicated())
StormFox2.Setting.AddSV("enable_wateroverlay",true, nil, "Effects")
--[[-------------------------------------------------------------------------
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()
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.
---------------------------------------------------------------------------]]
-- New surface functions
local function SurfaceInfo_GetType( eEnt, SurfaceInfo )
if #SurfaceInfo:GetVertices() < 3 then return INVALID_VERTS end
if SurfaceInfo:IsWater() then -- Water
return WATER_VERTS
end
return INVALID_VERTS
end
local ice = Material("stormfox2/effects/ice_water")
local ice_size = 500
local vec_ex = Vector(0,0,1)
STORMFOX_WATERMESHCOLLISON = {}
local scan = function() -- Locates all surfaceinfos we need.
StormFox2.Msg("Scanning surfaces ..")
surfaceinfos = {}
-- 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() then continue 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()} )
end
coroutine.yield()
-- Generate water mesh
if surfaceinfos[WATER_VERTS] then
StormFox2.Msg("Generating ice-mesh [" .. #surfaceinfos[WATER_VERTS] .. "] ")
local mesh = {}
for i,v in ipairs(surfaceinfos[WATER_VERTS]) do
if StormFox2.Map.IsInside( v[2] ) then
local t = v[1]:GetVertices()
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
end
end
end
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)
local bIce = false
local function SpawnIce()
for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do
v:Remove()
end
local e = ents.Create("stormfox_mapice")
e:SetPos(Vector(0,0,0))
e:Spawn()
bIce = true
end
local function RemoveIce()
bIce = false
for k,v in ipairs(ents.FindByClass("stormfox_mapice")) do
v:Remove()
end
end
timer.Create("stormfox2.spawnice", 8, 0, function()
if not StormFox2.Setting.GetCache("enable_ice") then
if bIce then
RemoveIce()
end
return
end
if bIce and StormFox2.Temperature.Get() > -1 then
RemoveIce()
elseif not bIce and StormFox2.Temperature.Get() <= -8 then
SpawnIce()
end
end)

View File

@@ -0,0 +1,114 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
StormFox2.Sun.SetTimeUp(nTime) Sets how long the sun is on the sky.
StormFox2.Sun.IsUp() Returns true if the sun is on the sky.
StormFox2.Moon.SetTimeUp(nTime) Sets how long the moon is on the sky.
---------------------------------------------------------------------------]]
local clamp = math.Clamp
StormFox2.Sun = StormFox2.Sun or {}
StormFox2.Moon = StormFox2.Moon or {}
StormFox2.Sky = StormFox2.Sky or {}
-- SunRise and SunSet
---Sets the time for sunrise.
---@param nTime TimeNumber
---@server
function StormFox2.Sun.SetSunRise(nTime)
StormFox2.Setting.Set("sunrise", nTime)
end
---Sets the tiem for sunsets.
---@param nTime TimeNumber
---@server
function StormFox2.Sun.SetSunSet(nTime)
StormFox2.Setting.Set("sunset", nTime)
end
---Sets the sunyaw. This will also affect the moon.
---@param nYaw number
---@server
function StormFox2.Sun.SetYaw(nYaw)
StormFox2.Setting.Set("sunyaw",nYaw)
end
---Sets the sunsize. (Default: 30)
---@param n number
---@server
function StormFox2.Sun.SetSize(n)
StormFox2.Network.Set("sun_size",n)
end
---Sets the suncolor.
---@param cColor table
---@deprecated
---@server
function StormFox2.Sun.SetColor(cColor)
StormFox2.Network.Set("sunColor",cColor)
end
-- Moon
--[[-------------------------------------------------------------------------
Sets the moon phase, and increases it once pr day
---------------------------------------------------------------------------]]
hook.Add("StormFox2.Time.NextDay","StormFox2.MoonPhase",function()
StormFox2.Moon.SetPhase( StormFox2.Moon.GetPhase() + 1 )
end)
---Sets the moon phase. A number between 0 and 7.
---@param moon_phase number
---@server
function StormFox2.Moon.SetPhase( moon_phase )
StormFox2.Network.Set("moon_phase",moon_phase % 8)
end
StormFox2.Moon.SetPhase( math.random(0, 7) )
-- Skybox
local function SkyTick(b)
b = b and StormFox2.Setting.SFEnabled()
if b then -- Reenable skybox
local _2d = StormFox2.Setting.GetCache("use_2dskybox", false)
if not _2d then
RunConsoleCommand("sv_skyname", "painted")
else
local sky_over = StormFox2.Setting.GetCache("overwrite_2dskybox", "")
if sky_over == "" then
sky_over = StormFox2.Weather.GetCurrent():Get("skyBox",StormFox2.Sky.GetLastStamp()) or "skybox/sky_day02_06_hdrbk"
if type(sky_over) == "table" then
sky_over = table.Random(sky_over)
end
end
RunConsoleCommand("sv_skyname", sky_over)
end
else -- Disable skybox
local map_ent = StormFox2.Map.Entities()[1]
if not map_ent then
StormFox2.Warning("No map-entity?")
RunConsoleCommand("sv_skyname", "skybox/sky_day02_06_hdrbk")
return
end
local sky_name = map_ent["skyname"] or "skybox/sky_day02_06_hdrbk"
StormFox2.Map.Set2DSkyBoxDarkness( 1 )
RunConsoleCommand("sv_skyname", sky_name)
end
end
local func = function(b)
SkyTick(StormFox2.Setting.GetCache("enable_skybox", true))
end
StormFox2.Setting.Callback("enable", func, "disable_heavens")
StormFox2.Setting.Callback("clenable", func, "disable_heavenscl")
StormFox2.Setting.Callback("enable_skybox",SkyTick,"enable_skybox_call")

View File

@@ -0,0 +1,276 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Render clouds
---------------------------------------------------------------------------]]
local cos,sin,rad = math.cos,math.sin,math.rad
local max,min,clamp,ceil,abs = math.max,math.min,math.Clamp,math.ceil,math.abs
local z_level = -.8
local eye_mult = -.0001
-- Generate dome mesh
local Render_Dome = Mesh()
local top_height = 20
local sc = 20
local stage = 0
local e_r = rad(45)
local t_s = 1
local function UVMulti(uv,mul)
return (uv - 0.5) * mul + 0.5
end
mesh.Begin( Render_Dome, MATERIAL_TRIANGLES, 24 )
for i = 1,8 do
local yaw = rad(45 * i)
-- Generate the top
-- L
local c,s = cos(yaw),sin(yaw)
local L = {Vector(c * sc,s * sc,0.1 * -sc),(1 + c) / 2 * t_s,(1 + s) / 2 * t_s}
mesh.Position(L[1])
mesh.TexCoord( stage, L[2], L[3])
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
-- R
local c,s = cos(yaw + e_r),sin(yaw + e_r)
local R = {Vector(c * sc,s * sc,0.1 * -sc),(1 + c) / 2 * t_s, (1 + s) / 2 * t_s}
mesh.Position(R[1])
mesh.TexCoord( stage, R[2],R[3] )
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
-- T
mesh.Position(Vector(0,0,0.1 * top_height))
mesh.TexCoord( stage, 0.5 * t_s,0.5 * t_s )
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
-- Generate side1
mesh.Position(L[1])
mesh.TexCoord( stage, L[2], L[3])
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
local R2 = {R[1] * 1.4 - Vector(0,0,4),UVMulti(R[2],1.4),UVMulti(R[3],1.4)}
mesh.Position(R2[1])
mesh.TexCoord( stage, R2[2],R2[3] )
mesh.Color(255,255,255,0)
mesh.AdvanceVertex()
mesh.Position(R[1])
mesh.TexCoord( stage, R[2],R[3] )
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
-- Generate side 2
mesh.Position(L[1])
mesh.TexCoord( stage, L[2], L[3])
mesh.Color(255,255,255,255)
mesh.AdvanceVertex()
mesh.Position(L[1] * 1.4 - Vector(0,0,4))
mesh.TexCoord( stage, UVMulti(L[2], 1.4), UVMulti(L[3],1.4))
mesh.Color(255,255,255,0)
mesh.AdvanceVertex()
mesh.Position(R2[1])
mesh.TexCoord( stage, R2[2],R2[3] )
mesh.Color(255,255,255,0)
mesh.AdvanceVertex()
end
mesh.End()
-- Local functions
local matrix = Matrix()
local function RenderDome(pos,mat,alpha)
matrix:Identity()
matrix:Translate( vector_origin + pos )
--mat:SetAlpha(alpha)
cam.PushModelMatrix(matrix)
render.SetBlend(alpha / 255)
render.SetMaterial(mat)
Render_Dome:Draw()
render.SetBlend(1)
cam.PopModelMatrix()
end
local lastRT
local function RTRender(RT,blend)
lastRT = RT
render.PushRenderTarget( RT )
render.ClearDepth()
render.Clear( 0, 0, 0, 0 )
cam.Start2D()
if not blend then return end
render.OverrideAlphaWriteEnable( true, true )
end
local function RTMask(srcBlend,destBlend,srcBlendAlpha,destBlendAlpha)
local srcBlend = srcBlend or BLEND_ZERO
local destBlend = destBlend or BLEND_SRC_ALPHA --
local blendFunc = 0 -- The blend mode used for drawing the color layer
local srcBlendAlpha = srcBlendAlpha or BLEND_DST_ALPHA -- Determines how a rendered texture's final alpha should be calculated.
local destBlendAlpha = destBlendAlpha or BLEND_ZERO --
local blendFuncAlpha = 0 --
render.OverrideBlend( true, srcBlend, destBlend, blendFunc, srcBlendAlpha, destBlendAlpha, blendFuncAlpha)
end
local function RTEnd(Mat_Output)
render.OverrideBlend( false )
render.OverrideAlphaWriteEnable( false )
cam.End2D()
render.PopRenderTarget()
-- Apply changes
Mat_Output:SetTexture("$basetexture",lastRT)
end
local function DrawTextureRectWindow(w,h,o_x,o_y) -- Render function that supports fractions (surface libary is whole numbers only)
if o_x < 0 then o_x = o_x + w end
if o_y < 0 then o_y = o_y + h end
o_x = o_x % w
o_y = o_y % h
local m = Matrix()
m:Identity()
m:Translate(Vector(o_x % w,o_y % h))
cam.PushModelMatrix(m)
surface.DrawTexturedRect(0,0,w,h)
surface.DrawTexturedRect(-w,0,w,h)
surface.DrawTexturedRect(0,-h,w,h)
surface.DrawTexturedRect(-w,-h,w,h)
cam.PopModelMatrix()
end
-- Load materials
-- Side clouds
local side_clouds = {}
for _,fil in ipairs(file.Find("materials/stormfox2/effects/clouds/side_cloud*.png","GAME")) do
local png = Material("stormfox2/effects/clouds/" .. fil,"nocull noclamp alphatest")
png:SetInt("$flags",2099250)
table.insert(side_clouds,{png,png:GetInt("$realwidth") / png:GetInt("$realheight")})
end
-- Top clouds
local layers = 4
local sky_mats = {}
local offset = {}
local params = {}
params[ "$basetexture" ] = ""
params[ "$translucent" ] = 0
params[ "$vertexalpha" ] = 1
params[ "$vertexcolor" ] = 1
params[ "$nofog" ] = 1
params[ "$nolod" ] = 1
params[ "$nomip" ] = 1
params["$additive"] = 0
for i = 1,layers do
sky_mats[i] = CreateMaterial("StormFox_RTSKY" .. i,"UnlitGeneric",params)
end
local cloudbig = Material("stormfox2/effects/clouds/clouds_big.png","nocull noclamp smooth")
-- 8240
cloudbig:SetInt("$flags",2099250)
cloudbig:SetFloat("$nocull",1)
cloudbig:SetFloat("$nocull",1)
cloudbig:SetFloat("$additive",0)
local sky_rts = {}
local texscale = 512
for i = 1,layers do
sky_rts[i] = GetRenderTargetEx( "StormFox_Sky" .. i, texscale, texscale, 1, MATERIAL_RT_DEPTH_NONE, 2, CREATERENDERTARGETFLAGS_UNFILTERABLE_OK, IMAGE_FORMAT_RGBA8888)
offset[i] = {i * 99,i * 33}
end
local function safeCall(...)
hook.Run("StormFox2.2DSkybox.CloudLayerRender", ...)
end
local function UpdateCloudMaterial(layer,cloud_alpha)
local blend = true
local d_seed = layer * 33
render.PushFilterMag( TEXFILTER.ANISOTROPIC )
render.PushFilterMin( TEXFILTER.ANISOTROPIC )
-- Start RT render
RTRender(sky_rts[layer],blend)
-- Render RT texture
surface.SetMaterial(cloudbig)
surface.SetDrawColor(Color(255,255,255,cloud_alpha))
--surface.DrawTexturedRect(0,0,texscale,texscale)
DrawTextureRectWindow(texscale,texscale,offset[layer][1] + d_seed,offset[layer][2] + d_seed)
-- If we error in here, gmod will crash.
local b, reason = pcall(safeCall, texscale, texscale, layer)
if not b then ErrorNoHalt(reason) end
-- Mask RT tex
-- RTMask()
-- surface.SetDrawColor(Color(255,255,255,255 - cloud_alpha))
-- surface.SetMaterial(cloudbig)
-- DrawTextureRectWindow(texscale,texscale,offset[layer][1] + d_seed,offset[layer][2] + d_seed)
-- End RT tex
RTEnd(sky_mats[layer])
render.PopFilterMag()
render.PopFilterMin()
end
local col = Color(255,255,255,175)
local v = Vector(0,0,-20)
local function RenderCloud(mat_id,yaw,s_size,alpha, pos)
local mat = side_clouds[mat_id]
if not mat then return end
render.SetMaterial(mat[1])
local pitch = 0.11 * s_size
local n = Angle(pitch,yaw,0):Forward()
col.a = math.max(175 * alpha, 255)
render.DrawQuadEasy( n * -200 + pos + v, n, s_size * mat[2] , s_size, col, 180 )
end
local function LerpColor(f, col1, col2)
return Color( Lerp(f, col1.r, col2.r), Lerp(f, col1.g, col2.g), Lerp(f, col1.b, col2.b) )
end
-- Cloud movement
hook.Add("PreRender","StormFox2.Client.CloudMove",function()
local w_ang = rad(StormFox2.Wind.GetYaw())
local w_force = max(StormFox2.Wind.GetForce(),0.1) * 0.08 * RealFrameTime()
local x_w,y_w = cos(w_ang) * w_force,sin(w_ang) * w_force
for i = 1,layers do
local ri = (layers - i + 1)
local x,y = offset[i][1],offset[i][2]
offset[i] = {x + x_w * ri ,y + y_w * ri}
end
end)
hook.Add("StormFox2.2DSkybox.CloudLayer","StormFox2.Client.Clouds",function(eye)
if not StormFox2 then return end
if not StormFox2.Mixer then return end
local cl_amd = StormFox2.Mixer.Get("clouds",0)
--if cl_amd <= 0 then return end
-- Update material-color
local c = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255)
-- Render sideclouds
local vec = Vector(c.r,c.g,c.b) / 255
for k,v in ipairs(side_clouds) do
v[1]:SetVector("$color",vec)
end
local cloud_speed = StormFox2.Time.GetSpeed_RAW() * 0.1
local sideclouds = 10 * cl_amd
for i = 1,sideclouds do
local a = 1
if i < sideclouds and i == math.floor(sideclouds) then
a = sideclouds - math.floor(sideclouds)
end
local row = math.floor(i / 3)
local m_id = i % #side_clouds + 1
local y_start = (i % 3) * 120 + row * 33
local size = (3 + i % 5) * 24
RenderCloud(m_id,y_start + i + SysTime() * cloud_speed, size, a, eye * eye_mult * 10 * i / 10 )
end
-- Render top clouds
local up = Vector(0,0,1)
local n = max(0,min(math.ceil(layers * cl_amd),layers))
local thunder = 0
if StormFox2.Thunder then
thunder = min(255,StormFox2.Thunder.GetLight() or 0) / 25
end
for i = 1,n do
local ri = n - i + layers
local cloud_amplifier = 1 + .4 * (1 - (i / n))
if i == 1 then
cloud_amplifier = cloud_amplifier + thunder
end
UpdateCloudMaterial(i,255)
sky_mats[i]:SetVector("$color",Vector(min(c.r * cloud_amplifier,255),min(c.g * cloud_amplifier,255),min(c.b * cloud_amplifier,255)) / 255 )
RenderDome(up * (z_level + 0.4 * ri) + eye * eye_mult,sky_mats[i],255)
end
end)

View File

@@ -0,0 +1,129 @@
--[[
| 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/
--]]
local function niceName(sName)
if sName[1] == "#" then
sName = sName:sub(2)
end
sName = string.Replace(sName, "_", " ")
local str = ""
for s in string.gmatch(sName, "[^%s]+") do
str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " "
end
return string.TrimRight(str, " ")
end
-- SF Settings
local SWin
hook.Add("ContextMenuClosed", "Stormfox2.ContextMC", function()
if not SWin or not IsValid(SWin) then return end
SWin:Remove()
SWin = nil
end)
local setc = Color(55,55,65,255)
local setc2 = Color(255,255,255,55)
local matc = Material("gui/workshop_rocket.png")
local function CreateWindow( icon, window, bAccess )
window:SetTitle("")
window:DockPadding(0, 0, 8, 0)
window:ShowCloseButton(false)
window.c_Time = CurTime() + 0.5
window.c = 0.5
function window:Paint(w,h)
if window.c < 0.99 then
window.c = Lerp( FrameTime() * 10, window.c, 1 )
elseif window.c < 1 then
window.c = 1
end
local f = window.c
surface.SetDrawColor(setc)
surface.SetMaterial(matc)
DisableClipping(true)
surface.DrawTexturedRectUV(-16, 0, 16, h + 2, 0, 0.23, 0.3,0.77)
surface.DrawTexturedRectUV(0, 0,w * f,h + 2, 0.3,0.23, 0.7,0.77)
surface.DrawTexturedRectUV(w * f, 0, 16, h + 2, 0.7,0.23, 1, 0.77)
DisableClipping(false)
end
local cl = vgui.Create("DButton", window)
cl:SetText("")
cl:SetSize(80,82)
cl.Paint = function() end
local cli = vgui.Create("DImage", cl)
cli:SetPos(8,0)
cli:SetSize(64,64)
cli:SetImage("stormfox2/hud/settings.png")
local label = vgui.Create("DLabel", cl )
label:Dock( BOTTOM )
label:SetText( niceName(language.GetPhrase("#client") .. " " .. language.GetPhrase("#superdof_pp.settings")))
label:SetContentAlignment( 5 )
label:SetTextColor( color_white )
label:SetExpensiveShadow( 1, Color( 0, 0, 0, 200 ) )
label:SizeToContentsX()
local sv = vgui.Create("DButton", window)
sv:SetText("")
sv:SetPos(80,0)
sv:SetSize(80,82)
sv.Paint = function() end
local svi = vgui.Create("DImage", sv)
svi:SetPos(8,0)
svi:SetSize(64,64)
svi:SetImage("stormfox2/hud/controller.png")
local label = vgui.Create("DLabel", sv )
label:Dock( BOTTOM )
label:SetText( niceName(language.GetPhrase("#spawnmenu.utilities.server_settings")))
label:SetContentAlignment( 5 )
label:SetTextColor( color_white )
label:SetExpensiveShadow( 1, Color( 0, 0, 0, 200 ) )
label:SizeToContentsX()
sv.DoClick = function()
surface.PlaySound("buttons/button14.wav")
window:Remove()
StormFox2.Menu.OpenSV()
end
cl.DoClick = function()
surface.PlaySound("buttons/button14.wav")
window:Remove()
StormFox2.Menu.Open()
end
local w,h = icon:LocalToScreen(0,0)
window:SetPos(w,h)
SWin = window
function window:Think()
if self.c_Time > CurTime() then return end
local x,y = self:CursorPos()
if x > 0 and x < self:GetWide() and y > 0 and y < self:GetTall() then return end
self:Remove()
end
if not bAccess then
sv:SetCursor( "no" )
sv:SetDisabled( true )
svi:SetDisabled( true )
label:SetTextColor( Color( 255,255,255, 105) )
sv:SetToolTip(niceName(language.GetPhrase("#administrator_applications")))
end
surface.PlaySound("garrysmod/ui_click.wav")
end
local function OpenWindow(icon, window)
-- We can't check for IsListenServerHost, so lets hope the addminmod does that.
CAMI.PlayerHasAccess(LocalPlayer(), "StormFox Settings",function(bAccess)
CreateWindow( icon, window, bAccess )
end)
end
list.Set( "DesktopWindows", "StormFoxSetting", {
title = "SF " .. niceName(language.GetPhrase("#spawnmenu.utilities.settings")),
icon = "stormfox2/hud/settings.png",
width = 80 * 2,
height = 84,
onewindow = true,
init = OpenWindow
} )

View File

@@ -0,0 +1,204 @@
--[[
| 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/
--]]
-- Breath Efect
do
local threshold = 2 -- IRL it is 7.2C, but i think the community would like to tie it closer to snow.
StormFox2.Setting.AddCL("enable_breath", true)
local m_mats = {(Material("particle/smokesprites_0001")),(Material("particle/smokesprites_0002")),(Material("particle/smokesprites_0003"))}
local function GetMouth( ply )
local att = ply:LookupAttachment("mouth")
if att <= 0 then return end -- No mouth?
return ply:GetAttachment(att)
end
local function DoEffect(ply, size)
if not _STORMFOX_PEM then return end -- Just in case
local pos, ang
local e = 1
if ply ~= StormFox2.util.ViewEntity() then -- "Someone else"
local att = GetMouth( ply )
if not att then return end
pos = att.Pos
ang = att.Ang
else -- Our viewpoint
-- Check the view
local view = StormFox2.util.GetCalcView()
if view.drawviewer then -- Third person
local att = GetMouth( ply )
if not att then return end
pos = att.Pos
ang = att.Ang
else
e = 2
ang = Angle(-view.ang.p,view.ang.y,0)
pos = view.pos + ang:Forward() * 3 + ang:Up() * -2
end
end
local l = StormFox2.Map.GetLight() / 100
local p = _STORMFOX_PEM["2D"]:Add(table.Random(m_mats),pos)
p:SetStartSize(1)
p:SetEndSize(size)
p:SetStartAlpha(math.min(255, 15 + math.random(55,135) * l * e))
p:SetEndAlpha(0)
p:SetLifeTime(0)
p:SetGravity(Vector(0,0,4))
p:SetDieTime(1)
p:SetLighting(false)
p:SetRoll(math.random(360))
p:SetRollDelta(math.Rand(-0.5,0.5))
p:SetVelocity(ang:Forward() * 2 + ply:GetVelocity() / 5)
end
-- Runs the effect on the player and returns next time it should be called.
local function CheckEffect(ply)
if not IsValid( ply) then return end
if not ply:Alive() then return end
if ply:WaterLevel() >= 3 then return end
if ply:InVehicle() then
local e = ply:GetVehicle()
if not IsValid( e ) then return end
if e:GetClass() ~= "prop_vehicle_jeep" then return end -- An open vehicle
end
if not StormFox2.Wind.IsEntityInWind(ply) then return end
local len = ply:GetVelocity():Length()
local t = math.Clamp(1.5 - (len / 100),0.2,1.5)
DoEffect(ply,5 + (len / 100))
return math.Rand(t,t * 2)
end
-- The most optiomal way is to check within the renderhook.
local function RunEffect(ply)
if not StormFox2.Setting.GetCache("enable_breath") then return end
if not StormFox2.Setting.SFEnabled() then return end
if StormFox2.Temperature.Get() > threshold then return end -- Breaht is visible at 7.2C or below
if (ply._sfbreath or 0) >= CurTime() then return end
local cE = CheckEffect( ply )
if not cE then
ply._sfbreath = CurTime() + 1
return
end
ply._sfbreath = CurTime() + cE
end
hook.Add("PostPlayerDraw", "StormFox2.Effect.Breath", RunEffect)
-- We also need to check when the player is in first person.
timer.Create("StormFox2.Effect.BreathT", 1, 0, function()
local LP = LocalPlayer( )
if not IsValid( LP ) then return end
RunEffect( LP )
end)
end
-- Depth Filter
local W,H = ScrW(), ScrH()
local depth_r = GetRenderTarget("SF_DepthFilter", W,H, true)
local depthLayer = Material( "stormfox2/shader/depth_layer" )
local a = 0
local l
-- Patch: Some people out there don't update the resources when updating the mod.
if depthLayer:GetKeyValues()["$detail"] then
StormFox2.Warning("stormfox2/shader/depth_layer.vmt is outdated! Hotpatching, but be sure to update the resources!")
depthLayer:SetUndefined("$detail")
depthLayer:SetUndefined("$detailtexturetransform")
depthLayer:SetUndefined("$detailblendmode")
depthLayer:SetUndefined("$detailscale")
depthLayer:SetUndefined("$additive")
end
hook.Add("StormFox2.weather.postchange", "StormFox2.DepthFilter.Reset", function(b)
if l and l == b then return end
l = b
a = 0
end)
local depthLayer = Material( "stormfox2/shader/depth_layer" )
local function updateDepthTexture(fFunc, a)
render.PushRenderTarget(depth_r)
cam.Start2D()
render.Clear(0,0,0,0,true, false)
fFunc(W,H, a)
cam.End2D()
render.PopRenderTarget()
depthLayer:SetTexture("$basetexture", depth_r)
return depthLayer
end
local invis_col = Color(255,0,0,0)
local function RenderDepthFilter()
-- Reset everything to known good fpr stencils
render.SetStencilWriteMask( 0xFF )
render.SetStencilTestMask( 0xFF )
render.SetStencilReferenceValue( 0 )
render.SetStencilCompareFunction( STENCIL_ALWAYS )
render.SetStencilPassOperation( STENCIL_REPLACE )
render.SetStencilFailOperation( STENCIL_ZERO )
render.SetStencilZFailOperation( STENCIL_ZERO )
render.ClearStencil()
-- Enable stencil
render.SetStencilEnable( true )
render.SetStencilReferenceValue( 1 )
render.SetStencilCompareFunction( STENCIL_ALWAYS )
-- Render Mask
local eA = EyeAngles()
cam.Start3D( EyePos(), eA )
render.SetColorMaterial()
local f_D = StormFox2.Fog.GetEnd()
if f_D > 2000 then
render.DrawSphere(EyePos(), -2000, 30, 30, invis_col)
else
--[[
Stencils look bad for heavy effects, since there is a clear pixel-line.
I've tried smoothing it, but it would require rendering the world twice within an RT.
So instead we make it a plain with the fog-distance.
Its bad in some cases, yes, but only solution I know for now.
]]
render.DrawQuadEasy(EyePos() + eA:Forward() * f_D, -eA:Forward(), ScrW() * 5, ScrH() * 5, invis_col, 0)
end
cam.End3D()
-- Now, only draw things that have their pixels set to 1. This is the hidden parts of the stencil tests.
render.SetStencilCompareFunction( STENCIL_EQUAL )
-- Render Depth-filter
cam.Start2D()
render.SetMaterial(depthLayer)
render.DrawScreenQuad()
cam.End2D()
-- Let everything render normally again
render.SetStencilEnable( false )
--render.PopRenderTarget()
end
hook.Add("RenderScreenspaceEffects", "StormFox2.Downfall.DepthRender", function()
if render.GetDXLevel() < 95 then return end
if not render.SupportsPixelShaders_2_0() then return end
if LocalPlayer():WaterLevel() >= 3 then return end -- Don't render SF effects under wanter.
local obj = StormFox2.Setting.GetObject("depthfilter")
if not obj then return end
if not obj:GetValue() then return end
-- Check if weather has depth. If not reset alpha
local dFr = StormFox2.Weather.GetCurrent().DepthFilter
if not dFr then a = 0 return end
-- Calc alpha
if StormFox2.Environment.Get().outside then
a = math.Approach(a, 1, FrameTime() * .8)
else
a = math.Approach(a, 0, FrameTime() * 5) -- Quick fadeaway
end
if a <= 0 then return end -- If alpha is 0 or below, don't render.
-- Update RT
updateDepthTexture(dFr, a)
-- Update screenspace effect
render.UpdateScreenEffectTexture()
render.UpdateFullScreenDepthTexture()
-- Render the depthfilter
RenderDepthFilter()
end)

View File

@@ -0,0 +1,269 @@
--[[
| 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/
--]]
-- This is a localization backup, in case the file didn't transferre.
if not file.Exists("resource/localization/en/stormfox.properties", "GAME") then return end
local str = [[
#StormFox2.weather = weather
# https://github.com/Facepunch/garrysmod/blob/master/garrysmod/resource/localization/en/community.properties Check this first
# weather, day, night, sun, lightning, snow, cloud
#Misc
sf_auto=Auto
sf_customization=Customization
sf_window_effects=Window Effects
footprints=footprints
temperature=temperature
fog=Fog
#Weather
sf_weather.clear=Clear
sf_weather.clear.windy=Windy
sf_weather.cloud=Cloudy
sf_weather.cloud.thunder=Thunder
sf_weather.cloud.storm=Storm
sf_weather.rain=Raining
sf_weather.rain.sleet=Sleet
sf_weather.rain.snow=Snowing
sf_weather.rain.thunder=Thunder
sf_weather.rain.storm=Storm
sf_weather.fog.low=Haze
sf_weather.fog.medium=Fog
sf_weather.fog.high=Thick Fog
sf_weather.lava=Lava
sf_weather.fallout=Nuclear Fallout
#Tool
sf_tool.name=StormFox2 Tool
sf_tool.desc=Allows you to edit StormFox2 map settings.
sf_tool.surface_editor=Surface Editor
sf_tool.surface_editor.desc=Allows you to edit surface-types.
sf_tool.surface_editor.entertext=Write the texture path and click add.
sf_tool.light_editor=Light Editor
sf_tool.light_editor.desc=Allows you to add/remove sf-lights.
sf_enable_breath=Enables breath
sf_enable_breath.desc=Makes players breath visible in cold.
#Settings
sf_enable=Enable StormFox
sf_enable.desc=Enable / Disable StormFox 2
sf_clenable=Enable StormFox
sf_clenable.desc=Enable / Disable StormFox 2. Requires sf_allow_csenable.
sf_allow_csenable=Allow Clients to disable StormFox 2
sf_allow_csenable.desc=Enabling this will allow clients to disable StormFox 2.
sf_mthreadwarning=These settings can boost your FPS:\n%s\nWarning\: This Might crash on some older AMD CPUs!
sf_holdc=Hold C
sf_weatherpercent=Weather Amount
sf_setang=Set Angle
sf_setang.desc=Sets the wind-angle to your view.
sf_setwind=Sets the windspeed in m/s
sf_wcontoller=SF Controller
sf_map.light_environment.check=This map support fast lightchanges.
sf_map.light_environment.problem=This map will cause lagspikes for clients, when the light changes.
sf_map.env_wind.none=This map doesn't support windgusts.
sf_map.logic_relay.check=This map has custom day/night relays.
sf_map.logic_relay.none=This map doens't have custom day/night relays.
sf_windmove_players=Affect players
sf_windmove_players.desc=Affect player movment in strong wind.
sf_windmove_foliate=Affect Foliate
sf_windmove_foliate.desc=Foliate moves with the wind.
sf_windmove_props=Affect Props
sf_windmove_props.desc=Props will move with the wind. This can cause lag!
sf_windmove_props_break=Damage Props
sf_windmove_props_break.desc=Props will take damage in the wind.
sf_windmove_props_makedebris=Change CollisionGroup
sf_windmove_props_makedebris.desc=Will make props change collisiongroup, reducing lag.
sf_windmove_props_unfreeze=Unfreeze props.
sf_windmove_props_unfreeze.desc=Unfreeze props being moved by the wind.
sf_windmove_props_unweld=Unweld props.
sf_windmove_props_unweld.desc=Unweld props being moved by the wind.
sf_windmove_props_max=Max props being moved.
sf_windmove_props_max.desc=Max amount of props moving. This can cause lag!
sf_enable_fogz=Overwrite farZ fog
sf_enable_fogz.desc=Overwrites the maps farZ fog. This might look bad on some maps.
sf_enable_ice=Enable ice
sf_enable_ice.desc=Creates ice over water.
sf_overwrite_fogdistance=Default fog-distance.
sf_overwrite_fogdistance.desc=Overwrites the default fog-distance.
sf_hide_forecast=Hide Forecast
sf_hide_forecast.desc=Stops clients from updating the forecast.
sf_allow_weather_lightchange=Allow weather maplight
sf_allow_weather_lightchange.desc=Allows the weather to modify the maplight.
sf_addnight_temp=Add Night Temperature
sf_addnight_temp.desc=The amount the temperature drops doing night.
sf_apply_settings=Apply settings.
sf_reset_settings=Reset settings.
sf_enable_skybox=Enable Skybox
sf_enable_skybox.desc=Allows StormFox to use the skybox.
sf_use_2dskybox=Use 2D Skybox
sf_use_2dskybox.desc=Makes StormFox use 2D skyboxes instead.
sf_overwrite_2dskybox=Overwrite 2D skybox
sf_overwrite_2dskybox.desc=Overwrites the 2D skybox with another.
sf_darken_2dskybox=Darken 2D Skybox
sf_darken_2dskybox.desc=Match the skybox brightness with the map.
sf_random_round_weather=Random weather each round.
sf_random_round_weather.desc=Gamemodes like TTT will have random weathers between each round.
sf_quickselect=Quick Select.
sf_quickselect.desc=Quick select time settings.
sf_depthfilter=Depth Filter
sf_depthfilter.desc=Render 2D weather-effects on clients screen.
sf_enable_sunbeams=Enable Sunbeams
sf_enable_sunbeams.desc=Enable sunbeams when the sun is low.
sf_edit_cubemaps=Edit Cubemaps
sf_edit_cubemaps.desc=Change cubemap tint of map-materials.
#Details
sf_quality_target=FPS Target
sf_quality_target.desc=Adjusts the quality to reach targeted FPS.
sf_quality_ultra=Ultra Quality
sf_quality_ultra.desc=Allows for more effects.
sf_12h_display=Time Display
sf_12h_display.disc=Choose 12-hour or 24-hour clock.
sf_display_temperature=Temperature Units
sf_display_temperature.desc=Choose a temperature unit.
sf_use_monthday=Date Display
sf_use_monthday.disc=Choose MM/DD or DD/MM.
#Wind
sf_wind=Wind
sf_winddescription.calm=Calm
sf_winddescription.light_air=Light Air
sf_winddescription.light_breeze=Light Breeze
sf_winddescription.gentle_breeze=Gentle Breeze
sf_winddescription.moderate_breeze=Moderate Breeze
sf_winddescription.fresh_breeze=Fresh Breeze
sf_winddescription.strong_breeze=Strong Breeze
sf_winddescription.near_gale=Near Gale
sf_winddescription.gale=Gale
sf_winddescription.strong_gale=Strong Gale
sf_winddescription.storm=Storm
sf_winddescription.violent_storm=Violent Storm
#Hurricane is also known as Category 1
sf_winddescription.hurricane=Hurricane
sf_winddescription.cat2=Category 2
sf_winddescription.cat3=Category 3
sf_winddescription.cat4=Category 4
sf_winddescription.cat5=Category 5
#Time
sf_continue_time=Continuous Time
sf_continue_time.desc=Continue time from last time.
sf_real_time=Real time
sf_real_time.desc=Use the servers OS Time.
sf_start_time=Start time
sf_start_time.desc=Sets the start time.
sf_random_time=Random time
sf_random_time.desc=Sets the time randomly on server-launch.
sf_day_length=Day Length
sf_day_length.desc=How long the day is in minutes.
sf_night_length=Night Length
sf_night_length.desc=How long the night is in minutes.
#Sun
sf_sunrise=SunRise
sf_sunrise.desc=Sets the time the sun rises.
sf_sunset=SunSet
sf_sunset.desc=Sets the time the sun sets.
sf_sunyaw=SunYaw
sf_sunyaw.desc=Sets the yaw for the sun.
#Moon
sf_moonsize=Moon Size
sf_moonsize.desc=The default moon size.
sf_moonphase=Moon Phases
sf_moonphase.desc=Enable Moon Phases.
sf_moonlock=Moon Lock
sf_moonlock.desc=Locks the moon to the sun's rotation.
#'Maplight
sf_maplight_max=Max Maplight
sf_maplight_max.desc=The max lightlevel. You can adjust this if the map is too bright/dark.
sf_maplight_min=Min Maplight
sf_maplight_min.desc=The min lightlevel. You can adjust this if the map is too bright/dark.
sf_maplight_smooth=Maplight Lerp.
sf_maplight_smooth.desc=Enables smooth light transitions.
sf_maplight_updaterate=Maplight UpdateRate
sf_maplight_updaterate.desc=The max amount of times StormFox will update the maplight doing transitions. Will cause lag on large maps!
sf_maplight_auto.desc=Select the best/fastes option for the map.
sf_maplight_lightenv.desc=Enable light_environment.
sf_maplight_colormod.desc=Enable colormod.
sf_maplight_dynamic.desc=Enable dynamic light/shadows.
sf_maplight_lightstyle.desc=Enable lightstyle.
sf_modifyshadows=Modify shadows
sf_modifyshadows.desc=Modify default shadows to follow the sun.
sf_modifyshadows_rate=Modify shadow rate
sf_modifyshadows_rate.desc=The seconds between each shadow-update.
sf_extra_lightsupport=Extra Lightsupport
sf_extra_lightsupport.desc=Utilize engine.LightStyle to change the map-light. This can cause lag-spikes, but required on certain maps.
#Effects
sf_enable_fog=Enable Fog
sf_enable_fog.desc=Allow StormFox to edit the fog.
sf_allow_fog_change=Allow clients to toggle fog.
sf_allow_fog_change.desc=Enabling this will allow clients to toggle fog.
sf_footprint_enabled=Enable Footprints
sf_footprint_enabled.desc=Enable footprint effects.
sf_footprint_playeronly=Player Footprints Only.
sf_footprint_playeronly.desc=Only players make footprints.
sf_footprint_distance=Footprint Render Distance
sf_footprint_distance.desc=Max render distance for footprints.
sf_footprint_max=Max Footprints
sf_footprint_max.desc=Max amount of footprints
sf_edit_tonemap=Enable tonemap
sf_edit_tonemap.desc=Allow StormFox to edit the tonemap.
sf_enable_wateroverlay=Render water overlay
sf_enable_wateroverlay.desc=Enables water-overlay for weather-types.
sf_extra_darkness=Extra Darkness
sf_extra_darkness.desc=Adds a darkness-filter to make bright maps darker.
sf_extra_darkness_amount=Extra Darkness Amount
sf_extra_darkness_amount.desc=Scales the darkness-filter.
sf_overwrite_extra_darkness=Overwrite Extra Darkness
sf_overwrite_extra_darkness.desc=Overwrites the players sf_extra_darkness.
sf_footprint_enablelogic=Enables Serverside Footprints
sf_footprint_enablelogic.desc=Enables server-side footprints.
sf_window_enable=Enable window effects
sf_window_enable.desc=Enables window weather effects.
sf_window_distance=Window Render Distance
sf_window_distance.desc=The render distance for breakable windows.
sf_override_foliagesway=Override Foliagesway
sf_override_foliagesway.desc=Overrides and applies foliagesway to most foliage on launch.
#Weather
sf_auto_weather=Auto weather
sf_auto_weather.desc=Automatically change weather over time.
sf_max_weathers_prweek=Max Weathers Pr Week
sf_max_weathers_prweek.desc=Max amount of weathers pr week.
sf_temp_range=Temperature range
sf_temp_range.desc=The min and max temperature.
sf_temp_acc=Temperature change.
sf_temp_acc.desc=The max temperature changes pr day.
sf_weather_damage=Weather Damage
sf_weather_damage.desc=Allow weather to cause damage
sf_max_wind=Maximum wind
sf_max_wind.desc=The maximum generated wind in m/s.
]]
for k, v in ipairs( string.Explode("\n", str)) do
if string.match(v, "%s-#") then continue end
local a,b = string.match(v, "%s*(.+)=(.+)")
if not a or not b then continue end
language.Add(a, b)
end

View File

@@ -0,0 +1,334 @@
--[[
| 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/
--]]
StormFox2.Menu = StormFox2.Menu or {}
local function niceName(sName)
if sName[1] == "#" then
sName = sName:sub(2)
end
sName = string.Replace(sName, "_", " ")
local str = ""
for s in string.gmatch(sName, "[^%s]+") do
str = str .. string.upper(s[1]) .. string.sub(s, 2) .. " "
end
return string.TrimRight(str, " ")
end
local function wrapText(sText, wide)
wide = wide - 10
local tw,th = surface.GetTextSize(language.GetPhrase(sText))
local lines,b = 1, false
local s = ""
for w in string.gmatch(sText, "[^%s,]+") do
local tt = s .. (b and " " or "") .. w
if surface.GetTextSize(tt) >= wide then
s = s .. "\n" .. w
lines = lines + 1
else
s = tt
end
b = true
end
return s, lines
end
local m = Material("gui/gradient")
local function paintKnob(self,x, y) -- Skin doesn't have x or y pos
local skin = self:GetSkin()
if ( self:GetDisabled() ) then return skin.tex.Input.Slider.H.Disabled( x, y, 15, 15 ) end
if ( self.Depressed ) then
return skin.tex.Input.Slider.H.Down( x, y, 15, 15 )
end
if ( self.Hovered ) then
return skin.tex.Input.Slider.H.Hover( x, y, 15, 15 )
end
skin.tex.Input.Slider.H.Normal( x, y, 15, 15 )
end
local notchesColor = Color(0,0,0,100)
-- Tips
local function AddTip( self, text )
if IsValid( self.tTip ) then return end
self.tTip = vgui.Create("DTooltip")
self.tTip:SetText( text )
self.tTip.TargetPanel = self
self.tTip:PositionTooltip()
end
local function RemoveTip( self )
if IsValid( self.tTip ) then
self.tTip:Remove()
end
self.tTip = nil
end
local tabs = {
[1] = {"Start","#start",(Material("stormfox2/hud/menu/dashboard.png")),function(board)
board:AddTitle("#information")
local dash = vgui.Create("DPanel", board)
dash.Paint = empty
dash:Dock(TOP)
dash:SetTall(80)
local fps, qu, sup, mth
-- FPS
local p = vgui.Create("SF_Setting_Ring", dash)
p:SetText(string.upper(language.GetPhrase("#fps")) .. ": ")
p:SetSize(74, 74)
p:SetPos(24,10)
function p:Think()
if (self.u_t or 0) > SysTime() then return end
if not system.HasFocus() then return end
self.u_t = SysTime() + 1
local t = StormFox2.Setting.GetCache("quality_target",144)
local _, avgFPS = StormFox2.Client.GetQualityNumber()
self:SetValue( avgFPS / t)
p:SetText(string.upper(language.GetPhrase("#fps")) .. ": " .. math.floor(avgFPS))
end
fps = p
-- Quality
local p = vgui.Create("SF_Setting_Ring", dash)
p:SetText(language.GetPhrase("#effects"))
p:SetSize(74, 74)
p:SetPos(106,10)
function p:Think()
if (self.u_t or 0) > SysTime() then return end
if not system.HasFocus() then return end
self.u_t = SysTime() + 1
local max_q = StormFox2.Setting.GetCache("quality_ultra",false) and 20 or 7
local q, _ = StormFox2.Client.GetQualityNumber()
local f = q / max_q
self:SetValue( f )
p:SetText(language.GetPhrase("#effects") .. "\n" .. math.floor(f * 100) .. "%")
end
qu = p
-- Support GPU
local p = vgui.Create("SF_Setting_Ring", dash)
p:SetText(niceName(language.GetPhrase("#support")))
p:SetSize(74, 74)
p:SetPos(188,10)
--p:SetColor(255,0,0)
local t = {render.SupportsPixelShaders_1_4(),render.SupportsVertexShaders_2_0(), render.SupportsPixelShaders_2_0(), render.SupportsHDR()}
local v = 0
local s ="#problems.no_problems"
for k,v2 in ipairs(t) do
if not v2 then
if k == 1 then
s = "#problem.no_ps14"
elseif k == 2 then
s = "#problem.no_vs20"
elseif k == 3 then
s = "#problem.no_ps20"
else
s = "#problem.no_hdr"
end
break
end
v = v + 1
end
p:SetTooltip(s)
local f = v / #t
p:SetValue(f)
local c = HSLToColor(120 * f, 1, 0.5 * f)
--p:SetColor(c.r,c.g,c.b)
p:SetText(niceName(language.GetPhrase("#support")) .. "\n" .. v .. "/" .. #t)
sup = p
-- MThread
local p = vgui.Create("SF_Setting_Ring", dash)
p:SetText(niceName(language.GetPhrase("#MThread")))
p:SetSize(74, 74)
p:SetPos(188,10)
--p:SetColor(255,0,0)
local t = {["cl_threaded_bone_setup"] = 1,
["cl_threaded_client_leaf_system"] = 1,
["r_threaded_client_shadow_manager"] = 1,
["r_threaded_particles"] = 1,
["r_threaded_renderables"] = 1,
["r_queued_ropes"] = 1,
["studio_queue_mode"] = 1,
["gmod_mcore_test"] = 1,
["mat_queue_mode"] = 2}
local v = 0
local s = "\n"
for k,v2 in pairs(t) do
if GetConVar(k):GetInt() ~= v2 then
s = s .. k .. " " .. v2 .. "\n"
continue
end
v = v + 1
end
local f = v / table.Count(t)
p:SetValue(f)
local c = HSLToColor(120 * f, 1, 0.5 * f)
--p:SetColor(c.r,c.g,c.b)
p:SetText(niceName(language.GetPhrase("#MThread")) .. "\n" .. v .. "/" .. table.Count(t))
if f < 1 then
p:SetTooltip(string.format(language.GetPhrase("#sf_mthreadwarning"), s))
else
p:SetTooltip(language.GetPhrase("#problems.no_problems"))
end
mth = p
function dash:PerformLayout(w, h)
local a = w / 5
local x = -fps:GetTall() / 2
fps:SetPos(x + a, h - fps:GetTall())
qu:SetPos(x + a*2, h - qu:GetTall())
sup:SetPos(x + a*3, h - sup:GetTall())
mth:SetPos(x + a*4, h - sup:GetTall())
end
-- Fps Slider
local FPSTarget = vgui.Create("SF_Setting", board)
FPSTarget:SetSetting("quality_target")
board:MarkUsed("quality_target")
do
local obj = StormFox2.Setting.GetObject("quality_target")
local slider = vgui.Create("DButton", FPSTarget)
local text = vgui.Create("DTextEntry", FPSTarget)
FPSTarget:MoveDescription(340)
slider:SetPos(5,15)
slider:SetSize( 300, 25 )
slider:SetText("")
text:SetPos( 304, 19)
text:SetSize( 40,20 )
local hot = Color(255,0,0,205)
-- Text set
function text:OnEnter( str )
str = str:lower()
if str == language.GetPhrase("#all"):lower() or str == "all" then
str = 0
else
str = tonumber( str ) or 0
end
obj:SetValue(str)
end
-- Slider skin functions
function slider:GetNotchColor()
return notchesColor
end
function slider:GetNotches()
return math.floor(self:GetWide() / 21)
end
-- Slider paint
function slider:Paint( w, h )
local var = self._OvR or StormFox2.Setting.GetCache("quality_target", 144)
local cV = 300 - var
local skin = self:GetSkin()
skin:PaintNumSlider(self,w,h)
-- "warm"
surface.SetMaterial(m)
surface.SetDrawColor(hot)
local wi = w / 300 * 260
surface.DrawTexturedRectUV(wi - 7, 4, w - wi, h - 6, 1,0,0,1)
paintKnob(self, 1 + (w - 16) / 300 * cV,-0.5)
end
function slider:UpdateText( var )
if var > 0 then
text:SetText(var)
else
text:SetText(niceName(language.GetPhrase("#all")))
end
end
-- Slider think
function slider:Think()
if self:IsDown() then
self._down = true
self._OvR = math.Clamp(1 - (self:LocalCursorPos() - 6) / (self:GetWide() - 12), 0, 1) * 300
if self._OvR < 40 then
AddTip(self, "#frame_blend_pp.desc2")
else
RemoveTip( self )
end
self:UpdateText( math.Round(self._OvR, 0) )
else
if not text:IsEditing() then
self:UpdateText( math.Round(obj:GetValue(), 0) )
end
self._OvR = nil
RemoveTip( self )
if self._down then
self._down = nil
local var = math.Clamp(1 - (self:LocalCursorPos() - 6) / (self:GetWide() - 12), 0, 1) * 300
obj:SetValue( math.Round(var, 0) )
end
end
end
slider:UpdateText(math.Round(obj:GetValue(), 0))
end
FPSTarget:Dock(TOP)
-- EnableDisable
local p = board:AddSetting("clenable")
--local qs = board:AddSetting("quality_target")
board:AddSetting("quality_ultra")
board:AddTitle("#sf_customization")
local l = vgui.Create("DPanel", board)
l:DockMargin(10,0,0,0)
l:SetTall(24)
l:Dock(TOP)
function l:Paint(w,h)
local md = StormFox2.Setting.GetCache("use_monthday",false) and os.date( "%m/%d/%Y" ) or os.date( "%d/%m/%Y" )
local dt = StormFox2.Setting.GetCache("display_temperature")
local hs = string.Explode(":", os.date( "%H:%M") or "17:23")
local n = hs[1] * 60 + hs[2]
local str = niceName(language.GetPhrase("#time")) .. ": " .. StormFox2.Time.GetDisplay(n) .. " " .. md
str = str .. " " .. niceName(language.GetPhrase("#temperature")) .. ": " .. math.Round(StormFox2.Temperature.Convert(nil,dt,22), 1) .. StormFox2.Temperature.GetDisplaySymbol()
draw.DrawText(str, "DermaDefaultBold", 5, 0, color_black, TEXT_ALIGN_LEFT)
end
board:AddSetting("12h_display")
board:AddSetting("use_monthday")
board:AddSetting("display_temperature")
end},
[2] = {"Effects","#effects",(Material("stormfox2/hud/menu/settings.png")),function(board)
board:AddTitle(language.GetPhrase("#effects"))
local fog = board:AddSetting("enable_fog")
board:AddSetting("extra_darkness")
board:AddSetting("extra_darkness_amount")
board:AddSetting("enable_breath")
board:AddSetting("enable_sunbeams")
board:AddSetting("edit_cubemaps")
board:AddTitle(language.GetPhrase("#footprints"))
board:AddSetting("footprint_enabled")
board:AddSetting("footprint_playeronly")
board:AddSetting("footprint_distance")
board:AddSetting("footprint_max")
board:AddTitle(language.GetPhrase("#sf_window_effects"))
board:AddSetting("window_enable")
board:AddSetting("window_distance")
fog:SetDisabled(not StormFox2.Setting.GetCache("allow_fog_change"))
StormFox2.Setting.Callback("allow_fog_change",function(vVar,_,_, self)
fog:SetDisabled(not vVar)
end,fog)
end},
[3] = {"Misc","#misc",(Material("stormfox2/hud/menu/other.png")),function(board)
board:AddTitle("SF2 " .. language.GetPhrase("spawnmenu.utilities.settings"))
local panel = board:AddSetting("mapfile_cl")
panel:SetTitle("#makepersistent")
panel:SetDescription(language.GetPhrase("#persistent_mode") .. " data\\stormfox2\\cl_settings\\" .. game.GetMap() .. ".json")
end},
[4] = {"DLC","DLC",(Material("stormfox2/hud/menu/dlc.png")), function(board)
hook.Run("stormfox2.menu.dlc", board)
end}
}
---Opens the client-settings.
---@client
function StormFox2.Menu.Open()
if _SFMENU and IsValid(_SFMENU) then
_SFMENU:Remove()
_SFMENU = nil
end
local p = vgui.Create("SF_Menu")
_SFMENU = p
p:SetTitle("StormFox " .. niceName(language.GetPhrase("#client")) .. " ".. language.GetPhrase("#spawnmenu.utilities.settings"))
p:CreateLayout(tabs, StormFox2.Setting.GetAllClient())
p:SetCookie("sf2_lastmenucl")
_SFMENU:MakePopup()
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
--[[
| 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/
--]]
--[[<Ignore All>-------------------------------------------------------------------------
We overwrite the sky variables. Its much better to handle it clientside.
---------------------------------------------------------------------------]]
-- Override skypaint- Since its set by each tick.
local g_SkyPaint_tab = {}
function g_SkyPaint_tab.IsValid() return true end
local g_datacache = {}
function g_SkyPaint_tab:GetNetworkVars()
return g_datacache
end
-- Setup data
local function AddDataCache(name,defaultdata)
g_datacache[name] = defaultdata
g_SkyPaint_tab["Get" .. name] = function()
return g_datacache[name]
end
g_SkyPaint_tab["Set" .. name] = function(self,var)
g_datacache[name] = var or defaultdata
end
return g_SkyPaint_tab["Set" .. name]
end
_STORMFOX_TOPCOLOROR = AddDataCache("TopColor", Vector( 0.2, 0.5, 1.0 ) )
AddDataCache("BottomColor", Vector( 0.8, 1.0, 1.0 ) )
AddDataCache("FadeBias", 1 )
AddDataCache("SunNormal", Vector( 0.4, 0.0, 0.01 ) )
AddDataCache("SunColor", Vector( 0.2, 0.1, 0.0 ) )
AddDataCache("SunSize", 2.0 )
AddDataCache("DuskColor", Vector( 1.0, 0.2, 0.0 ) )
AddDataCache("DuskScale", 1 )
AddDataCache("DuskIntensity", 1 )
AddDataCache("DrawStars", true )
AddDataCache("StarLayers", 1 )
AddDataCache("StarSpeed", 0.01 )
AddDataCache("StarScale", 0.5 )
AddDataCache("StarFade", 1.5 )
AddDataCache("StarTexture", "skybox/starfield" )
AddDataCache("HDRScale", 0.66 )
-- Override the skypaint directly
local SkyPaintEnt
local c = false
if #ents.FindByClass("env_skypaint") > 0 then
SkyPaintEnt = ents.FindByClass("env_skypaint")[1]
end
hook.Add("Think","StormFox2.sky.paintFix",function()
if not IsValid(g_SkyPaint) then return end
-- Disable skybox and reset entity
if not StormFox2.Setting.GetCache("enable_skybox", true) or not StormFox2.Setting.SFEnabled() then
if SkyPaintEnt and type(g_SkyPaint) ~= "Entity" then
g_SkyPaint = SkyPaintEnt
c = false
end
return
end
if type(g_SkyPaint) ~= "Entity" then
return
end
if g_SkyPaint:GetClass() == "env_skypaint" then
-- We'll hande it from here
SkyPaintEnt = g_SkyPaint
g_SkyPaint = g_SkyPaint_tab
c = true
end
end)
-- Local functions
local min,max,abs,app = math.min,math.max,math.abs,math.Approach
local function ColVec(col,div)
if not div then
return Vector(col.r,col.g,col.b)
end
return Vector(col.r / div,col.g / div,col.b / div)
end
-- Read and set the skydata
hook.Add("Think","StormFox2.sky.think",function()
if not IsValid(g_SkyPaint) then return end
if not StormFox2.Time then return end
if not StormFox2.Mixer then return end
if not StormFox2.Setting.SFEnabled() then return end
if not StormFox2.Setting.GetCache("enable_skybox", true) then return end
if StormFox2.Setting.GetCache("use_2dskybox",false,nil, "Effects") then return end
if not c then return end -- Make sure we use the table, and not the entity.
-- Top color + Thunder
local fogAm
if StormFox2.Fog then
fogAm = StormFox2.Fog.GetAmount()
end
local thunder = 0
if StormFox2.Thunder then
local cl_amd = StormFox2.Mixer.Get("clouds",0) or 0
thunder = min(255,StormFox2.Thunder.GetLight() or 0) * 0.1 + (cl_amd * .9)
end
local t_data = StormFox2.Mixer.Get("topColor") or Color( 51, 127.5, 255 )
local t_color = Color(max(thunder,t_data.r),max(thunder,t_data.g),max(thunder,t_data.b))
local b_color = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255)
if fogAm and fogAm > 0.75 then
t_color = StormFox2.Mixer.Blender((fogAm - .75) * 3, t_color, StormFox2.Fog.GetColor())
end
g_SkyPaint:SetTopColor(ColVec(t_color,255))
g_SkyPaint:SetBottomColor(ColVec(b_color,255))
g_SkyPaint:SetFadeBias(StormFox2.Mixer.Get("fadeBias",0.2))
g_SkyPaint:SetDuskColor(ColVec(StormFox2.Mixer.Get("duskColor",color_white) or color_white,255))
g_SkyPaint:SetDuskIntensity(StormFox2.Mixer.Get("duskIntensity",1.94))
g_SkyPaint:SetDuskScale(StormFox2.Mixer.Get("duskScale",0.29))
-- Stars
local n = StormFox2.Mixer.Get("starFade",100) * 0.015
if n <= 0 then
g_SkyPaint:SetDrawStars(false)
g_SkyPaint:SetStarFade(0)
else
g_SkyPaint:SetDrawStars(true)
g_SkyPaint:SetStarSpeed((StormFox2.Mixer.Get("starSpeed") or 0.001) * StormFox2.Time.GetSpeed_RAW())
g_SkyPaint:SetStarFade(n)
g_SkyPaint:SetStarScale(StormFox2.Mixer.Get("starScale") or 0.5)
g_SkyPaint:SetStarTexture(StormFox2.Mixer.Get("starTexture","skybox/starfield"))
end
-- SunSize
local s_size = StormFox2.Mixer.Get("sunSize",2) * (StormFox2.Mixer.Get("skyVisibility",100) / 100)
g_SkyPaint:SetSunSize(s_size / 10)
if StormFox2.Sun and StormFox2.Sun.GetAngle then
g_SkyPaint:SetSunNormal(StormFox2.Sun.GetAngle():Forward())
local sF = StormFox2.Mixer.Get("sunFade", 1)
g_SkyPaint:SetSunColor(ColVec(StormFox2.Mixer.Get("sunColor"), 1550 / sF))
end
g_SkyPaint:SetHDRScale(StormFox2.Mixer.Get("HDRScale",0.7))
end)
-- Debug
if true then return end
local x = 0
local x2 = 0
local function drawVal(text, val)
if type(val) == "table" then
val = val.r .. " " .. val.g .. " " .. val.b
end
draw.DrawText(text .. ": " .. tostring(val), "DermaDefault", x2, x * 20, color_white, TEXT_ALIGN_LEFT)
x = x + 1
end
hook.Add("HUDPaint", "SF_DEBUG.Sky", function()
local t_color = StormFox2.Mixer.Get("topColor") or Color( 51, 127.5, 255 )
local b_color = StormFox2.Mixer.Get("bottomColor") or Color(204, 255, 255)
x = 1
x2 = 10
drawVal("StormFox2 Debug","")
x2 = 20
drawVal("TopColor",t_color)
drawVal("BottomColor",t_color)
drawVal("fadeBias",StormFox2.Mixer.Get("fadeBias",0.2))
drawVal("duskIntensity",StormFox2.Mixer.Get("duskIntensity",1.94))
drawVal("duskScale",StormFox2.Mixer.Get("duskScale",0.29))
drawVal("starFade",StormFox2.Mixer.Get("starFade",100))
drawVal("starScale",StormFox2.Mixer.Get("starScale",0.5))
drawVal("starSpeed",StormFox2.Mixer.Get("starSpeed",0.001))
drawVal("starTexture",StormFox2.Mixer.Get("starTexture","skybox/starfield"))
end)

View File

@@ -0,0 +1,33 @@
--[[
| 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/
--]]
local s = string.Explode("-", os.date("%m-%d"))
if s[1] ~= "10" then return end
if tonumber(s[2]) < 20 then return end
-- Layers are large by design. (Kinda like a fish lense). Center is "large".
local mat = Material("hud/killicons/default")
local c = Color(255,255,255,0)
local dist = 80 -- 80 to 60
local size = 32 -- 64 to 32
hook.Add("StormFox2.2DSkybox.CloudLayerRender", "StormFox2.IAmNotHere", function(w, h, layer)
local d = StormFox2.Date.GetYearDay()
if d % 2 == 1 then return end
if layer ~= 1 then return end
local rotate = d * 33 % 360
local p = StormFox2.Weather.GetPercent()
c.a = math.min(105, (p - 0.1) * 1000)
if c.a <= 0 then return end
local x, y, ang = math.cos(math.rad(rotate)) * dist, math.sin(math.rad(rotate)) * dist, t
surface.SetDrawColor(c)
surface.SetMaterial(mat)
surface.DrawTexturedRectRotated(w / 2 + x,h / 2 + y, size,size, 90 - rotate)
end)

View File

@@ -0,0 +1,499 @@
--[[
| 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/
--]]
StormFox2.WeatherGen = StormFox2.WeatherGen or {}
-- Settings
local autoWeather = StormFox2.Setting.AddSV("auto_weather",true,nil, "Weather")
local hideForecast = StormFox2.Setting.AddSV("hide_forecast",false,nil, "Weather")
-- OpenWeatherMap
--[[
Some of these settings are fake, allow the client to set the settings, but never retrive the secured ones.
So you can't see the correct location or api_key and variables won't be networked to the clients.
]]
StormFox2.Setting.AddSV("openweathermap_key", "", nil,"Weather") -- Fake'ish setting
StormFox2.Setting.AddSV("openweathermap_location", "", nil,"Weather") -- Fake'ish setting
StormFox2.Setting.AddSV("openweathermap_city","",nil,"Weather") -- Fake'ish setting
local OWEnabled = StormFox2.Setting.AddSV("openweathermap_enabled",false,nil,"Weather")
StormFox2.Setting.AddSV("openweathermap_lat",52,nil,"Weather",-180,180) -- Unpercise setting
StormFox2.Setting.AddSV("openweathermap_lon",-2,nil,"Weather",-180,180) -- Unpercise setting
-- Generator
StormFox2.Setting.AddSV("min_temp",-10,nil,"Weather")
StormFox2.Setting.AddSV("max_temp",20,nil, "Weather")
StormFox2.Setting.AddSV("max_wind",50,nil, "Weather")
StormFox2.Setting.AddSV("addnight_temp",-7,nil, "Weather")
local function toStr( num )
local c = tostring( num )
return string.rep("0", 4 - #c) .. c
end
local default
local function SplitSetting( str )
if #str< 20 then return default end -- Invalid, use default
local tab = {}
local min = math.min(100, string.byte(str, 1,1) - 33 ) / 100
local max = math.min(100, string.byte(str, 2,2) - 33 ) / 100
tab.amount_min = math.min(min, max)
tab.amount_max = math.max(min, max)
local min = math.min(1440,tonumber( string.sub(str, 3, 6) ) or 0)
local max = math.min(1440,tonumber( string.sub(str, 7, 10) ) or 0)
tab.start_min = math.min(min, max)
tab.start_max = math.max(min, max)
local min = tonumber( string.sub(str, 11, 14) ) or 0
local max = tonumber( string.sub(str, 15, 18) ) or 0
tab.length_min = math.min(min, max)
tab.length_max = math.max(min, max)
tab.thunder = string.sub(str, 19, 19) == "1"
tab.pr_week = tonumber( string.sub(str, 20) ) or 0
return tab
end
local function CombineSetting( tab )
local c =string.char( 33 + (tab.amount_min or 0) * 100 )
c = c .. string.char( 33 + (tab.amount_max or 0) * 100 )
c = c .. toStr(math.Clamp( math.Round( tab.start_min or 0), 0, 1440 ) )
c = c .. toStr(math.Clamp( math.Round( tab.start_max or 0), 0, 1440 ) )
c = c .. toStr(math.Clamp( math.Round( tab.length_min or 360 ), 180, 9999) )
c = c .. toStr(math.Clamp( math.Round( tab.length_max or 360 ), 180, 9999) )
c = c .. (tab.thunder and "1" or "0")
c = c .. tostring( tab.pr_week or 2 )
return c
end
local default_setting = {}
default_setting["Rain"] = CombineSetting({
["amount_min"] = 0.4,
["amount_max"] = 0.9,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 360,
["length_max"] = 1200,
["pr_week"] = 3
})
default_setting["Cloud"] = CombineSetting({
["amount_min"] = 0.2,
["amount_max"] = 0.7,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 360,
["length_max"] = 1200,
["pr_week"] = 3
})
default_setting["Clear"] = CombineSetting({
["amount_min"] = 1,
["amount_max"] = 1,
["start_min"] = 0,
["start_max"] = 1440,
["length_min"] = 360,
["length_max"] = 1440,
["pr_week"] = 7
})
-- Morning fog
default_setting["Fog"] = CombineSetting({
["amount_min"] = 0.15,
["amount_max"] = 0.30,
["start_min"] = 360,
["start_max"] = 560,
["length_min"] = 160,
["length_max"] = 360,
["pr_week"] = 1
})
default = CombineSetting({
["amount_min"] = 0.4,
["amount_max"] = 0.9,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 300,
["length_max"] = 1200,
["pr_week"] = 0
})
StormFox2.WeatherGen.ConvertSettingToTab = SplitSetting
StormFox2.WeatherGen.ConvertTabToSetting = CombineSetting
if StormFox2.Weather and StormFox2.Weather.Loaded then
for _, sName in ipairs( StormFox2.Weather.GetAll() ) do
local str = default_setting[sName] or default
StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather")
end
else
hook.Add("stormfox2.postloadweather", "StormFox2.WeatherGen.Load", function()
for _, sName in ipairs( StormFox2.Weather.GetAll() ) do
local str = default_setting[sName] or default
StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather")
end
end)
end
-- API
local days = 2
local days_length = days * 1440
local hours_8 = 60 * 8
forecast = forecast or {}
local nul_icon = Material("gui/noicon.png")
-- Is it rain or inherits from rain?
local function isWTRain(wT)
if wT.Name == "Rain" then return true end
if wT.Inherit == "Rain" then return true end
return false
end
local function fkey( x, a, b )
return (x - a) / (b - a)
end
-- The tables are already in order.
local function findNext( tab, time ) -- First one is time
for i, tab in ipairs( tab ) do
if time > tab[1] then continue end
return i
end
return 0
end
local function calcPoint( tab, time )
local i = findNext( tab, time )
local _next = tab[i]
local _first = tab[i - 1]
local _procent = 1
if not _first then
_first = _next
else
_procent = fkey(time, _first[1], _next[1])
end
return _procent, _first, _next
end
net.Receive("StormFox2.weekweather", function(len)
forecast = {}
forecast.unix_time = net.ReadBool()
forecast.temperature = net.ReadTable()
forecast.weather = net.ReadTable()
forecast.wind = net.ReadTable()
forecast.windyaw = net.ReadTable()
for _, v in pairs( forecast.temperature ) do
if not forecast._minTemp then
forecast._minTemp = v[2]
else
forecast._minTemp = math.min(forecast._minTemp, v[2])
end
if not forecast._maxTemp then
forecast._maxTemp = v[2]
else
forecast._maxTemp = math.max(forecast._maxTemp, v[2])
end
end
if not forecast._minTemp then return end -- Invalid forecast
-- Make sure there is at least 10C between
local f = 10 - math.abs(forecast._minTemp - forecast._maxTemp)
if f > 0 then
forecast._minTemp = forecast._minTemp - f / 2
forecast._maxTemp = forecast._maxTemp + f / 2
end
-- Calculate / make a table for each 4 hours
forecast._ticks = {}
local lastW
for i = 0, days_length + 1440, hours_8 do
local _first = findNext( forecast.weather, i - hours_8 / 2 )
local _last = findNext( forecast.weather, i + hours_8 / 2 ) or _first
if not _first then continue end --????
local m = 0
local w_type = {
["fAmount"] = 0,
["sName"] = "Clear"
}
for i = _first, _last do
local w_data = forecast.weather[i]
if not w_data then continue end
if w_data[2].fAmount == 0 or w_data[2].fAmount < m then continue end
m = w_data[2].fAmount
w_type = w_data[2]
end
local _tempP, _tempFirst, _tempNext = calcPoint( forecast.temperature, i )
if _tempNext then
--local _tempP = fkey( i, )
forecast._ticks[i] = {
["fAmount"] = w_type.fAmount,
["sName"] = w_type.sName,
["nTemp"] = Lerp(_tempP, _tempFirst[2], _tempNext[2]),
["bThunder"] = w_type.bThunder or nil
}
end
end
--PrintTable(forecast)
hook.Run("StormFox2.WeatherGen.ForcastUpdate")
end)
---Returns the forecast data.
---@return table
---@client
function StormFox2.WeatherGen.GetForecast()
return forecast
end
---Returns true if we're using unix time for the forecast.
---@return boolean
---@client
function StormFox2.WeatherGen.IsUnixTime()
return forecast.unix_time or false
end
-- Render forcast
local bg = Color(26,41,72, 255)
local rc = Color(155,155,155,4)
local ca = Color(255,255,255,12)
local tempBG = Color(255,255,255,15)
local sorter = function(a,b) return a[1] < b[1] end
local m_box = Material("vgui/arrow")
local m_c = Material("gui/gradient_up")
local function DrawTemperature( x, y, w, h, t_list, min_temp, max_temp, bExpensive, offX, offY )
surface.SetDrawColor(rc)
surface.DrawRect(x, y, w, h)
surface.SetDrawColor(ca)
surface.DrawLine(x, y + h, x + w, y + h)
render.SetScissorRect( x + offX , y - 25 + offY, x + w + offX, y + h + offY, true )
local unix = StormFox2.WeatherGen.IsUnixTime()
local temp_p = fkey(0, max_temp, min_temp)
local tempdiff = max_temp - min_temp
local yT = h / tempdiff
local div = 10
if tempdiff < 25 then
div = 10
elseif tempdiff < 75 then
div = 20
elseif tempdiff < 150 then
div = 100
elseif tempdiff < 300 then
div = 200
elseif tempdiff < 500 then
div = 300
else
div = 1000
end
local s = math.ceil(min_temp / div) * div
local counts = (max_temp - s) / div
for temp = s, max_temp, div do
local tOff = temp - min_temp
local ly = y + h - (tOff * yT)
if temp == 0 then
surface.SetDrawColor(color_white)
surface.SetMaterial(m_box)
surface.DrawTexturedRectUV(x, ly, w, 1, 0, 0.5, w / 1 * 0.3, 0.6)
else
surface.SetDrawColor(ca)
surface.DrawLine(x, ly, x + w, ly)
end
end
local curTim = unix and os.time() or StormFox2.Time.Get()
local oldX, oldY, oldP
surface.SetTextColor(color_white)
surface.SetFont("SF_Display_H3")
for i, data in ipairs( t_list ) do
if not data then break end
local time_p
if unix then
time_p = (data[1] - curTim) / (1440 * 60 * 1.5)
else
time_p = (data[1] - curTim) / days_length
end
local temp_p = data[2]
local pointx = time_p * w + x
local pointy = y + h - (temp_p * h)
if oldX then
if oldX > x + w then break end
surface.SetDrawColor(color_white)
surface.DrawLine(pointx, pointy, oldX, oldY)
if bExpensive then
local triangle = {
{ x = oldX , y = oldY , u = 0,v = oldP},
{ x = pointx, y = pointy, u = 1,v = temp_p},
{ x = pointx, y = y + h , u = 1,v = 0},
{ x = oldX , y = y + h , u = 0,v = 0},
}
surface.SetMaterial(m_c)
surface.SetDrawColor(tempBG)
surface.DrawPoly( triangle )
end
surface.SetTextPos(pointx - 5, pointy - 14)
local temp = min_temp + temp_p * tempdiff
temp = math.Round(StormFox2.Temperature.GetDisplay(temp), 1) .. StormFox2.Temperature.GetDisplaySymbol()
surface.DrawText(temp)
end
oldX = pointx
oldY = pointy
oldP = temp_p
end
render.SetScissorRect(0,0,0,0,false)
--PrintTable(t_list)
end
local lM = Material("vgui/loading-rotate")
local lL = Material("stormfox2/logo.png")
local function DrawDisabled( str, w, h )
draw.DrawText(str, "SF_Display_H", w / 2, h / 4, color_white, TEXT_ALIGN_CENTER)
surface.SetDrawColor(color_white)
surface.SetMaterial(lL)
surface.DrawTexturedRectRotated(w / 2, h / 3 * 2, 64, 64, 0)
surface.SetMaterial(lM)
surface.DrawTexturedRectRotated(w / 2, h / 3 * 2, 128, 128, (CurTime() * 100)% 360)
end
---Renders the forecast.
---@param w number
---@param h number
---@param bExpensive boolean
---@param offX? number
---@param offY? number
function StormFox2.WeatherGen.DrawForecast(w,h,bExpensive, offX, offY)
offX = offX or 0
offY = offY or 0
local y = 0
surface.SetDrawColor(bg)
surface.DrawRect(0,0,w,h)
-- Check if enabled, else render disable message
if not autoWeather:GetValue() then
local s = language.GetPhrase("sf_auto_weather") or "sf_auto_weather"
local d = language.GetPhrase("#addons.preset_disabled") or "Disabled"
s = s.. ": " .. string.match(d, "%w+")
DrawDisabled( s, w, h )
return
end
if hideForecast:GetValue() then
local s = language.GetPhrase("sf_hide_forecast") or "sf_hide_forecast"
local d = language.GetPhrase("#addons.preset_enabled") or "Enabled"
s = s.. ": " .. string.match(d, "%w+")
DrawDisabled( s, w, h )
return
end
if not forecast or not forecast._minTemp then
local c = string.rep(".", CurTime() % 3 + 1)
DrawDisabled( "No data yet" .. c, w, h )
return
end
local unix = StormFox2.WeatherGen.IsUnixTime()
local curTim = unix and os.time() or StormFox2.Time.Get()
-- Draw Temperature
-- Convert it into a list of temperature w procent
local c_temp = StormFox2.Temperature.Get()
local min_temp = math.min(c_temp, forecast._minTemp)
local max_temp = math.max(c_temp, forecast._maxTemp)
local abs = math.abs(max_temp - min_temp) * 0.1
min_temp = min_temp - abs
max_temp = max_temp + abs
local t = {}
t[1] = { curTim, fkey( c_temp, min_temp, max_temp ) }
for i, data in ipairs( forecast.temperature ) do
local time = data[1]
if time <= curTim then continue end -- Ignore anything before
table.insert(t, {time, fkey( data[2], min_temp, max_temp ) } )
end
DrawTemperature( w * 0.05, h * 0.5 ,w * 0.9, h * 0.4,t, min_temp, max_temp, bExpensive, offX, offY)
-- Draw current weahter
surface.SetDrawColor(color_white)
surface.SetMaterial(StormFox2.Weather.GetIcon())
surface.SetFont("SF_Display_H")
local tex = StormFox2.Weather.GetDescription()
local tw, th = surface.GetTextSize(tex)
local wide = tw + 48
surface.DrawTexturedRect(w / 2 - 48,h * 0.05, 40,40)
draw.DrawText(tex, "SF_Display_H", w / 2 , h * 0.07, color_white, TEXT_ALIGN_LEFT)
-- Draw DayIcons
surface.SetDrawColor(color_white)
local s = w / 12
if not unix then
local c = math.ceil(curTim / hours_8) * hours_8
for i = c, days_length + c - 420, hours_8 do
-- Render Time
local t_stamp = StormFox2.Time.GetDisplay( i % 1440 )
local delt = i - curTim
local x = math.ceil(w * 0.9 / days_length * delt)
draw.DrawText(t_stamp, "SF_Display_H3", x , h * 0.9, color_white)
-- Render icon
local day = forecast._ticks[i]
if day then
local w_type = StormFox2.Weather.Get(day.sName)
if not w_type then
surface.SetMaterial(nul_icon)
else
surface.SetMaterial(w_type.GetIcon( i % 1440, day.nTemp, day.nWind or 0, day.bThunder or false, day.fAmount or 0) )
surface.DrawTexturedRect(x, h * 0.25, s, s)
local name = w_type:GetName(i % 1440, day.nTemp, day.nWind or 0, day.bThunder or false, day.fAmount or 0)
draw.DrawText(name, "SF_Display_H2", x + s / 2, h * 0.25 + s, color_white, TEXT_ALIGN_CENTER)
end
end
end
else
--(1440 * 60 * 1.5)
local wP = ( w * 0.9 ) / (1440 * 60 * 1.5)
local _12 = StormFox2.Setting.Get("12h_display", false)
local z = 0
for i, data in pairs( forecast.weather ) do
local unixT = data[1] or 0
if unixT < curTim then continue end
z = z + 1
if z > 6 then continue end
local delta = unixT - curTim
local t_stamp
local fakeTime = os.date( "%H:%M", unixT )
if _12 then
t_stamp = os.date( "%I:%M %p", unixT )
else
t_stamp = fakeTime
end
local x = math.ceil(wP * delta)
draw.DrawText("[" .. t_stamp .. "]", "SF_Display_H3", x , h * 0.9, color_white)
local day = data[2]
if day then
local n = string.Explode(":", fakeTime)
local f = n[1] * 60 + n[2]
local w_type = StormFox2.Weather.Get(day.sName)
local l_temp = 0
for id, tD in ipairs( forecast.temperature ) do
if tD[1] > unixT then break end
l_temp = tD[2]
end
local l_wind = 0
for id, tD in ipairs( forecast.wind ) do
if tD[1] > unixT then break end
l_wind = tD[2]
end
if not w_type then
surface.SetMaterial(nul_icon)
surface.DrawTexturedRect(x, h * 0.25, s, s)
else
surface.SetMaterial(w_type.GetIcon( f, l_temp, l_wind, day.bThunder or false, day.fAmount or 0) )
surface.DrawTexturedRect(x, h * 0.25, s, s)
local name = w_type:GetName(i % 1440, l_temp, l_wind, day.bThunder or false, day.fAmount or 0)
draw.DrawText(name, "SF_Display_H2", x + s / 2, h * 0.25 + s, color_white, TEXT_ALIGN_CENTER)
end
end
end
end
end

View File

@@ -0,0 +1,189 @@
--[[
| 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/
--]]
-- All concommands for stormfox 2
local function SendMsg( ply, message )
message = "[SF2]: " .. message
if not ply then print( message ) return end
ply:PrintMessage(HUD_PRINTTALK, message)
end
-- Menu commands
if CLIENT then
-- Server menu
concommand.Add('stormfox2_svmenu', StormFox2.Menu.OpenSV, nil, "Opens SF serverside menu")
-- Client menu
concommand.Add('stormfox2_menu', StormFox2.Menu.Open, nil, "Opens SF clientside menu")
-- Controller
concommand.Add('stormfox2_controller', StormFox2.Menu.OpenController, nil, "Opens SF controller menu")
else -- Console only
concommand.Add("stormfox2_settings_reset", function( ply, cmd, args, argStr )
if ply and IsValid(ply) and not ply:IsListenServerHost() then return end -- Nope, console only
StormFox2.Setting.Reset()
end)
end
-- Weather
concommand.Add("stormfox2_setweather", function(ply, _, arg, _)
if CLIENT then return end
-- Check if valid weather
if #arg < 1 then
SendMsg(ply, "Weather can't be nil")
return
end
local s = string.upper(string.sub(arg[1],0,1)) .. string.lower(string.sub(arg[1], 2))
if not StormFox2.Weather.Get(s) then
SendMsg(ply, "Invalid weather [" .. s .. "]")
return
end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Weather.Set( s, tonumber( arg[2] or "1" ) or 1)
end)
end)
concommand.Add("stormfox2_setthunder", function(ply, _, _, argS)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
local n = tonumber(argS) or (StormFox2.Thunder.IsThundering() and 6 or 0)
StormFox2.Thunder.SetEnabled( n > 0, n )
end)
end)
-- Time and Date
concommand.Add("stormfox2_settime", function(ply, _, _, argS)
if CLIENT then return end
-- Check if valid
if not argS or string.len(argS) < 1 then
SendMsg(ply, "You need to type an input! Use formats like 'stormfox2_settime 19:00' or 'stormfox2_settime 7:00 PM'")
return
end
local tN = StormFox2.Time.StringToTime(argS)
if not tN then
SendMsg(ply, "Invalid input! Use formats like '19:00' or '7:00 PM'")
return
end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Time.Set( argS )
end)
end)
concommand.Add("stormfox2_setyearday", function(ply, _, _, argStr)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Date.SetYearDay( tonumber(argStr) or 0 )
end)
end)
concommand.Add("stormfox2_setwind", function(ply, _, _, argStr)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Wind.SetForce( tonumber(argStr) or 0 )
end)
end)
concommand.Add("stormfox2_setwindangle", function(ply, _, _, argStr)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Wind.SetYaw( tonumber(argStr) or 0 )
end)
end)
concommand.Add("stormfox2_settemperature", function(ply, _, _, argStr)
if CLIENT then return end
local temp = tonumber( string.match(argStr, "-?[%d]+") or "0" ) or 0
if string.match(argStr, "[fF]") then
temp = StormFox2.Temperature.Convert("fahrenheit","celsius",temp) or temp
end
StormFox2.Permission.EditAccess(ply,"StormFox WeatherEdit", function()
StormFox2.Temperature.Set( temp )
end)
end)
local function SetSetting( arg, arg2, ply )
if not arg or arg == "" then
SendMsg( ply, "You need to indecate a setting: stormfox2_setting [Setting] [Value]")
return
end
local obj = StormFox2.Setting.GetObject(arg)
if not obj then
SendMsg( ply, "Invalid setting: \"" .. tostring( arg ) .. "\"!")
return
end
if not arg2 then
SendMsg( ply, "You need a value for the setting!")
return
end
obj:SetValue( arg2 )
SendMsg( ply, tostring( arg ) .. " = " .. tostring( arg2 ))
end
local function AutoComplete(cmd, args)
args = string.TrimLeft(args)
local a = string.Explode(" ", args or "")
if #a < 2 then
local options = {}
for _, sName in pairs( StormFox2.Setting.GetAllServer() ) do
if string.find(string.lower(sName),string.lower(a[1]), nil, true) then
table.insert(options, "stormfox2_setting " .. sName)
end
end
if #options < 1 then
return {"stormfox2_setting [No Setting Found!]"}
elseif #options < 2 and "stormfox2_setting " .. args == options[1] then
local obj = StormFox2.Setting.GetObject(a[1])
if not obj then
return {"stormfox2_setting [Invalid Setting!]"}
end
return {"stormfox2_setting " .. a[1] .. " [" .. obj.type .. "]"}
end
return options
elseif not a[1] or string.TrimLeft(a[1]) == "" then
return {"stormfox2_setting [Setting] [Value]"}
else
local obj = StormFox2.Setting.GetObject(a[1])
if not obj then
return {"stormfox2_setting [Invalid Setting!]"}
else
return {"stormfox2_setting " .. a[1] .. " [" .. obj.type .. "]"}
end
end
end
concommand.Add("stormfox2_setting", function(ply, _, _, argStr)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox Settings", function()
local a = string.Explode(" ", argStr, false)
SetSetting(a[1],a[2])
end)
end, AutoComplete)
-- Forces the settings to save.
concommand.Add("stormfox2_settings_save", function(ply, _, _, _)
if CLIENT then return end
StormFox2.Permission.EditAccess(ply,"StormFox Settings", function()
SendMsg( ply, "Force saved settings to data/" .. StormFox2.Setting.GetSaveFile())
StormFox2.Setting.ForceSave()
end)
end)
-- Debug commands
if true then return end
concommand.Add("stormfox2_debug_spawnice", function(ply)
if ply and not ply:IsListenServerHost() then return end
SpawnIce()
end, nil, nil)
concommand.Add("stormfox2_debug_removeice", function(ply)
if ply and not ply:IsListenServerHost() then return end
RemoveIce()
end, nil, nil)

View File

@@ -0,0 +1,685 @@
--[[
| 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/
--]]
-- Weather functions
StormFox2.Menu = StormFox2.Menu or {}
local SF_SETWEATHER = 0
local SF_SETTEMP = 1
local SF_SETWIND_A = 2
local SF_SETWIND_F = 3
local SF_SETTIME = 4
local SF_SETTIME_S = 5
local SF_THUNDER = 6
local SF_YEARDAY = 7
if SERVER then
-- Gets called from sh_permission.lua
---Internally used by permissions to relay settings.
---@param ply Player
---@param uID number
---@param var any
---@deprecated
---@server
function StormFox2.Menu.SetWeatherData(ply, uID, var)
if uID == SF_SETWEATHER and type(var) == "table" then
if type(var[1]) ~= "string" or type(var[2])~= "number" then return end
StormFox2.Weather.Set( var[1], var[2] )
elseif uID == SF_SETTEMP and type(var) == "number" then
StormFox2.Temperature.Set( var )
elseif uID == SF_SETWIND_F and type(var) == "number" then
StormFox2.Wind.SetForce( var, 3 )
elseif uID == SF_SETWIND_A and type(var) == "number" then
StormFox2.Wind.SetYaw( var, 3 )
elseif uID == SF_SETTIME and type(var) == "number" then
StormFox2.Time.Set( var )
elseif uID == SF_SETTIME_S and type(var) == "number" then
if not StormFox2.Time.IsPaused() then
StormFox2.Time.Pause()
else
StormFox2.Time.Resume()
end
elseif uID == SF_THUNDER and type(var) == "boolean" then
StormFox2.Thunder.SetEnabled(var, 6)
elseif uID == SF_YEARDAY and type(var) == "number" then
StormFox2.Date.SetYearDay( var )
end
end
return
end
-- Send a request to change the weather
local function SetWeather( uID, var )
net.Start( StormFox2.Net.Permission )
net.WriteUInt(1, 1) -- SF_SERVEREDIT
net.WriteUInt(uID, 4)
net.WriteType(var)
net.SendToServer()
end
-- Menu
local t_col = Color(67,73,83)
local h_col = Color(84,90,103)
local b_col = Color(51,56,62)
local n = 0.7
local p_col = Color(51 * n,56 * n,62 * n)
local rad,cos,sin = math.rad, math.cos, math.sin
local grad = Material("gui/gradient_down")
local function DrawButton(self,w,h)
local hov = self:IsHovered()
local down = self:IsDown() or self._DEPRESSED
surface.SetDrawColor(b_col)
surface.DrawRect(0,0,w,h)
if self._DISABLED then
elseif down then
surface.SetDrawColor(p_col)
elseif hov then
surface.SetDrawColor(h_col)
else
surface.SetDrawColor(t_col)
end
surface.SetMaterial(grad)
surface.DrawTexturedRect(0,0,w,h)
surface.SetDrawColor(p_col)
surface.DrawOutlinedRect(0,0,w,h)
end
local bg_color = Color(27,27,27)
local side_color = Color(44,48,54)
local function OpenMenu( self )
local menu = vgui.Create("DNumberWang")
menu.m_numMin = nil
function menu:SetDraggable() end
local sx = 50 - self:GetWide()
local sy = 24 - self:GetTall()
menu:MakePopup()
menu:SetDraggable(true)
local x, y = self:LocalToScreen(-sx / 2,-sy / 2)
menu:SetPos( x,y )
menu:RequestFocus()
menu:SetSize(50,24)
menu.m_bIsMenuComponent = true
RegisterDermaMenuForClose( menu )
function menu:GetDeleteSelf() return true end
menu:SetValue( self:GetVal() )
menu.b = self
function menu:OnEnter( str )
CloseDermaMenus()
if not str then return end
self.b.p:OnMenu( tonumber( str ) )
end
end
local color_gray = Color(155,155,155)
local function SliderNumber(self)
local p = vgui.Create("DPanel", self)
p:SetTall(18)
p._ta = 30
function p:Paint() end
function p:SetVal(n) self.val = n end
function p:GetVal() return self.val or 0 end
p._aimval = nil
AccessorFunc(p, "_min", "Min", FORCE_NUMBER)
AccessorFunc(p, "_max", "Max", FORCE_NUMBER)
p:SetMax(1)
p:SetMin(0)
function p:GetAP()
return (self._aimval - self:GetMin() ) / ( self:GetMax() - self:GetMin() )
end
function p:GetP()
return (self:GetVal() - self:GetMin() ) / ( self:GetMax() - self:GetMin() )
end
function p:SetP(f)
p:SetVal( -f * self:GetMin() + f * self:GetMax() + self:GetMin() )
end
local slider = vgui.Create("DButton", p)
local button = vgui.Create("DButton", p)
button:SetText("")
button.p = p
slider:SetText("")
function button:SetVal( n ) p:SetVal(n) end
function button:GetVal() return p:GetVal() end
function button:DoClick()
OpenMenu(self)
end
function p:OnMenu( val )
if not val then return end
self:SetVal( val )
self:OnVal( val )
end
function p:DrawText( num ) return num end
function button:Paint(w,h)
if not self:IsEnabled() then return end
surface.SetDrawColor(0, 0, 0, 155)
surface.DrawRect(0, 0, w, h)
local s = p:DrawText( p:GetVal() )
draw.DrawText(s, "DermaDefault", w / 2, 2, color_white, TEXT_ALIGN_CENTER)
end
function slider:Paint(w,h)
local v = math.Clamp(p:GetP(), 0, 1)
local a = p._aimval and math.Clamp(p:GetAP(), 0, 1)
local pos = w * v
-- Background
draw.RoundedBox(30, 0, h / 2 - 3, w, 4, color_black)
-- White
draw.RoundedBox(30, 0, h / 2 - 3, pos, 4, color_white)
if a and v ~= a then
local pos2= w * a
local mi = math.min(pos, pos2)
draw.RoundedBox(30, mi, h / 2 - 3, math.abs(pos - pos2),4, color_gray)
draw.RoundedBox(30, pos2 - 1, 0, 3, h, color_gray)
end
draw.RoundedBox(30, pos - 1, 0, 3, h, color_white)
end
function p:PerformLayout(w, h)
button:SetPos(w - self._ta,0)
button:SetSize(self._ta, h)
if self._ta > 0 then
slider:SetSize(w - self._ta - 5,18)
else
slider:SetSize(w,18)
end
slider:SetPos(0, h / 2 - 9)
end
function slider:OnDepressed()
self._update = true
end
function slider:OnReleased()
self._update = false
local x,y = self:LocalCursorPos()
local f = math.Round(math.Clamp(x / self:GetWide(), 0, 1), 2)
p:SetP( f )
p:OnVal( p:GetVal() )
end
function slider:Think()
if p.Think2 then
p:Think2()
end
if not self._update then return end
local x,y = self:LocalCursorPos()
local f = math.Round(math.Clamp(x / self:GetWide(), 0, 1), 2)
p:SetP( f )
end
function p:SetTextSize( num)
self._ta = num
if num <= 0 then
button:SetEnabled(false)
else
button:SetEnabled(true)
end
self:InvalidateLayout()
end
function p:OnVal( val ) end
p:SetVal(0.6)
return p
end
local bottom_size = 24
local col_ba = Color(0,0,0,155)
local col_dis = Color(125,125,125,125)
local m_cir = Material("stormfox2/hud/hudring2.png")
local m_thunder = Material("stormfox2/hud/w_cloudy_thunder.png")
local padding = 15
local padding_y = 5
local function addW( w_select, v, p )
local b = vgui.Create("DButton",w_select)
b:SetSize(32,32)
b:SetText("")
b:DockMargin(0,0,0,0)
w_select:AddPanel(b)
b.weather = v
b:SetToolTip(v)
function b:OnCursorEntered()
local w = StormFox2.Weather.Get(self.weather)
if not IsValid(w) then return end -- Something bad happen
b:SetToolTip(w:GetName(StormFox2.Time.Get(), StormFox2.Temperature.Get(), StormFox2.Wind.GetForce(), StormFox2.Thunder.IsThundering(), p:GetVal() / 100))
end
function b:Paint(w,h)
DrawButton(self,w,h)
local weather = StormFox2.Weather.Get(b.weather)
local mat = weather.GetSymbol and weather.GetSymbol(_,StormFox2.Temperature.Get())
if mat then
surface.SetDrawColor(255,255,255)
surface.SetMaterial(mat)
surface.DrawTexturedRect(5,5,w - 10,h - 10)
end
end
function b:DoClick()
SetWeather(SF_SETWEATHER, {self.weather, p:GetVal() / 100})
end
end
local function versionGet()
if not StormFox2.Version then return "?" end
return string.format("%.2f", StormFox2.Version)
end
local function Init(self)
self:SetSize(180,432)
self:SetPos(math.min(ScrW() * 0.8, ScrW() - 180), ScrH() / 2 - 200)
self:SetTitle("")
self.btnMaxim:SetVisible( false )
self.btnMinim:SetVisible( false )
function self:Paint(w,h)
surface.SetDrawColor(side_color)
surface.DrawRect(0,0,w,h)
-- Top
local t = "StormFox " .. versionGet()
surface.SetDrawColor(p_col)
surface.DrawRect(0,0,w,24)
surface.SetFont("SF2.W_Button")
local tw,th = surface.GetTextSize(t)
surface.SetTextColor(color_white)
surface.SetTextPos(10,th / 2 - 2)
surface.DrawText(t)
end
self:DockMargin(0,24,0,0)
self:DockPadding(0,24,0,0)
-- Weather
local m_weather = vgui.Create("DPanel", self)
m_weather:SetTall(70)
m_weather:Dock(TOP)
m_weather.Paint = function() end
self.weather = m_weather
local w_button = vgui.Create("DLabel", m_weather)
w_button:SetText("")
w_button:SetTall(28)
function w_button:Paint(w,h)
local t = "Set Weather"
surface.SetTextColor(color_white)
surface.SetFont("SF2.W_Button")
local tw,th = surface.GetTextSize(t)
surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2)
surface.DrawText(t)
end
local w_select = vgui.Create("DHorizontalScroller", m_weather)
w_select:SetOverlap( -4 )
w_select.num = 0
-- Percent & W
local p = SliderNumber( self )
p:SetToolTip('#sf_weatherpercent')
p:SetTextSize(40)
if StormFox2.Weather.GetCurrent() == StormFox2.Weather.Get('Clear') then
p:SetVal(85)
else
p:SetVal(math.Round(math.Clamp(StormFox2.Weather.GetPercent() * 100, 0, 100), 2))
end
function p:OnVal(x)
SetWeather(SF_SETWEATHER, {StormFox2.Weather.GetCurrent().Name, x / 100})
end
function m_weather:PerformLayout(w, h)
w_button:SetWide(w * 0.7)
w_button:SetPos(w * 0.15,5)
-- Calc the wide
local wide = w_select.num * (32 - w_select.m_iOverlap)
-- If weathers won't fit, make it default size and pos
if wide >= w * 0.9 then
w_select:SetSize(w * 0.9,32)
w_select:SetPos(w * 0.05, 32)
else -- Calc calculate the middle
w_select:SetSize(wide,32)
w_select:SetPos(w * 0.05 + (w * 0.9 - wide) / 2 , 32)
end
end
local t = StormFox2.Weather.GetAll()
addW(w_select, "Clear", p) -- Always add clear
if table.HasValue(t, "Cloud") then
addW(w_select, "Cloud", p)
w_select.num = w_select.num + 1
end
if table.HasValue(t, "Rain") then
addW(w_select, "Rain", p)
w_select.num = w_select.num + 1
end
if table.HasValue(t, "Fog") then
addW(w_select, "Fog", p)
w_select.num = w_select.num + 1
end
for k,v in ipairs(t) do
if v == "Clear" or v == "Cloud" or v == "Rain" or v == "Fog" then continue end -- Ignore
addW(w_select, v, p)
w_select.num = w_select.num + 1
end
p:SetMin(1)
p:SetMax(100)
p:Dock(TOP)
p:DockMargin(padding,0,padding,padding_y)
function p:DrawText( s )
return s .. "%"
end
-- Thunder
local tP = vgui.Create("DPanel", self)
tP:Dock(TOP)
tP:SetTall(32)
tP.Paint = empty
local thunder = vgui.Create("DButton", tP)
thunder:NoClipping( true )
thunder:SetSize(33, 32)
thunder:SetText('')
function tP:PerformLayout(w, h)
thunder:SetPos(w / 2 - 16,0)
end
function thunder:Paint(w,h)
local cW = StormFox2.Weather.GetCurrent()
local hasThunder = cW.Name ~= "Clear"
self._DEPRESSED = StormFox2.Thunder.IsThundering()
self._DISABLED = not hasThunder and not self._DEPRESSED
DrawButton(self,w,h)
if not self._DISABLED then
surface.SetDrawColor(color_white)
else
surface.SetDrawColor(col_dis)
end
surface.SetMaterial(m_thunder)
surface.DrawTexturedRect(5,5,w - 10,h - 10)
end
function thunder:DoClick()
local cW = StormFox2.Weather.GetCurrent()
local hasThunder = cW.Name ~= "Clear"
local isth = StormFox2.Thunder.IsThundering()
if not isth and not hasThunder then
return
end
SetWeather(SF_THUNDER, not isth)
end
-- Temperature
local t = vgui.Create("DPanel", self)
t:SetTall(30)
t:Dock(TOP)
t:DockMargin(padding,padding_y,padding,0)
local text = language.GetPhrase("#temperature")
t.text = string.upper(text[1]) .. string.sub(text, 2)
function t:Paint(w,h)
surface.SetFont("SF2.W_Button")
local tw,th = surface.GetTextSize(self.text)
surface.SetTextColor(color_white)
surface.SetTextPos(w / 2 - tw / 2,th / 2 - 2)
surface.DrawText(self.text)
end
local tempslider = SliderNumber(self)
local function Conv( n ) return math.Round(StormFox2.Temperature.Convert(nil,StormFox2.Temperature.GetDisplayType(),n), 1) end
tempslider:DockMargin(padding,0,padding,padding_y)
tempslider:Dock(TOP)
tempslider:SetMin(Conv(-20))
tempslider:SetMax(Conv(40))
tempslider:SetTextSize(40)
function tempslider:OnVal( num )
num = math.Round(StormFox2.Temperature.Convert(StormFox2.Temperature.GetDisplayType(),nil,num), 1)
SetWeather(SF_SETTEMP, num)
end
function tempslider:DrawText( n )
return n .. StormFox2.Temperature.GetDisplaySymbol()
end
tempslider:SetVal( math.Round(StormFox2.Temperature.GetDisplay(),1) )
function tempslider:Think()
tempslider._aimval = math.Round(StormFox2.Temperature.GetDisplay(StormFox2.Data.GetFinal( "Temp", 20 )),1)
tempslider:SetVal( math.Round(StormFox2.Temperature.GetDisplay(),1) )
end
-- Wind Ang
local t = vgui.Create("DPanel", self)
t:DockMargin(padding,padding_y,padding,0)
t:SetTall(30)
t:Dock(TOP)
local text = language.GetPhrase("#sf_wind")
t.text = string.upper(text[1]) .. string.sub(text, 2)
function t:Paint(w,h)
surface.SetFont("SF2.W_Button")
local tw,th = surface.GetTextSize(self.text)
surface.SetTextColor(color_white)
surface.SetTextPos(w / 2 - tw / 2,th / 2 - 2)
surface.DrawText(self.text)
end
local b = vgui.Create("DPanel", self)
function b:Paint() end
b:SetSize(80,80)
b:Dock(TOP)
local w_ang = vgui.Create("DButton", b)
w_ang:SetToolTip('#sf_setang.desc')
w_ang:SetText("")
function b:PerformLayout(w, h)
w_ang:SetSize(h,h)
w_ang:SetPos(w / 2 - h / 2)
end
function w_ang:Paint( w, h )
render.PushFilterMag(TEXFILTER.ANISOTROPIC)
render.PushFilterMin(TEXFILTER.ANISOTROPIC)
surface.SetDrawColor(col_ba)
surface.SetMaterial(m_cir)
surface.DrawTexturedRect(0,0,w,h)
local windang = EyeAngles().y - (StormFox2.Wind.GetYaw() or 0)
local wind = StormFox2.Wind.GetForce() or 0
local t = {{x = w / 2,y = h / 2, u=0.5,v=0.5}}
local l = math.Clamp(wind,0,70) / 3
if l < 1 then
surface.SetDrawColor(155,255,155)
l = 2
else
surface.SetDrawColor(155,155,255)
end
local nn = 90 - l * 5
for i = 0,l - 1 do
local c,s = cos(rad(i * 10 + windang + nn)),sin(rad(i * 10 + windang + nn))
local x = c * w / 2 + w / 2
local y = s * h / 2 + h / 2
table.insert(t,{x = x,y = y, u = (c + 1) / 2, v = (s + 1) / 2})
end
local c,s = cos(rad(l * 10 + windang + nn)),sin(rad(l * 10 + windang + nn))
local x = c * w / 2 + w / 2
local y = s * h / 2 + h / 2
table.insert(t,{x = x,y = y, u=(c + 1) / 2,v = (s + 1) / 2})
--draw.NoTexture()
surface.DrawPoly(t)
surface.SetFont("DermaDefault")
local t = language.GetPhrase("#sf_setang")
local tw,th = surface.GetTextSize(t)
surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2)
surface.DrawText(t)
render.PopFilterMag()
render.PopFilterMin()
end
function w_ang:DoClick()
SetWeather(SF_SETWIND_A, (EyeAngles().y + 180) % 360)
end
-- Wind
local p = vgui.Create("DPanel", self)
p:SetTall(22)
p:Dock(TOP)
p:DockMargin(padding,padding_y,padding,0)
function p:Paint(w,h)
local f = math.Round(StormFox2.Wind.GetForce() or 0, 1)
local bf,desc = StormFox2.Wind.GetBeaufort(f)
local text = f .."m/s : " .. language.GetPhrase(desc)
surface.SetFont("DermaDefault")
surface.SetTextColor(color_white)
local tw,th = surface.GetTextSize(text)
surface.SetTextPos(w / 2 - tw / 2, h / 2 - th / 2)
surface.DrawText(text)
end
local windslide = SliderNumber(self)
windslide:SetToolTip('#sf_setwind')
windslide:Dock(TOP)
windslide:DockMargin(padding,0,padding,0)
windslide:SetMin(0)
windslide:SetMax(70)
windslide:SetTextSize(0)
windslide:SetVal( StormFox2.Wind.GetForce() or 0 )
function windslide:OnVal( num )
SetWeather(SF_SETWIND_F, num)
end
function windslide:Think2()
windslide._aimval = StormFox2.Data.GetFinal( "Wind", 0 )
windslide:SetVal( StormFox2.Wind.GetForce() or 0 )
end
-- Time
local p = vgui.Create("DPanel", self)
p:SetTall(40)
p.Paint = function() end
local t = vgui.Create("SF_TIME", p)
function t:Think()
self:SetValue( StormFox2.Time.Get() )
end
function t:OnNewValue( var )
SetWeather( SF_SETTIME, var )
end
p:Dock(TOP)
local pause = vgui.Create("DButton", p)
pause.state = 1
pause:SetSize(30, 30)
function p:PerformLayout(w, h)
pause:SetPos(10,10)
t:SetPos( 42 ,10)
t:SetWide( w - 20 - 27 )
end
local a = StormFox2.Setting.GetObject("day_length")
local b = StormFox2.Setting.GetObject("night_length")
local r = Material("gui/point.png")
local z = Material("gui/workshop_rocket.png")
function pause:Think()
if StormFox2.Time.IsPaused() then
self.state = 0 -- pause
else
self.state = 1 -- running
end
end
pause:SetText("")
--pause.Paint = DrawButton
--t.bg.Paint = DrawButton
--
--t.ampm.Paint = DrawButton
--function t.ampm:UpdateColours()
-- self:SetTextStyleColor( color_white )
--end
--t.hour.color = color_white
--t.min.color = color_white
local c = Color(0,0,0,225)
function pause:PaintOver(w,h)
local s = 15
if self.state == 0 then
surface.SetMaterial(r)
surface.SetDrawColor(c)
surface.DrawTexturedRectRotated(w / 2 + 2,h / 2,w - s,h - s, 90)
else
surface.SetMaterial(z)
surface.SetDrawColor(c)
surface.DrawTexturedRectRotated(w / 2 - 5,h / 2,w - s * 1.1,h, 0)
surface.DrawTexturedRectRotated(w / 2 + 5,h / 2,w - s * 1.1,h, 0)
end
end
function pause:DoClick()
SetWeather(SF_SETTIME_S, 0)
end
pause:SetPos(20 ,10)
end
-- Caht status
local openChat = false
hook.Add("StartChat","StormFox2.Controller.Disable",function()
openChat = true
end)
hook.Add("FinishChat","StormFox2.Controller.Enable",function()
openChat = false
end)
local mat = Material("gui/workshop_rocket.png")
local c = Color(55,55,55)
---Builds the controller
---@deprecated
---@return userdata panel
---@client
function StormFox2.Menu._OpenController()
if _SF_CONTROLLER then
_SF_CONTROLLER:Remove()
end
if spawnmenu and spawnmenu.SetActiveControlPanel then
spawnmenu.SetActiveControlPanel(nil)
end
local p = vgui.Create("DFrame")
if not p then return end
_SF_CONTROLLER = p
Init(p)
local settings = vgui.Create("DButton", p)
settings:SetSize(31, 24)
settings:SetPos(p:GetWide() - 31 * 2 - 4)
settings:SetIcon('icon16/cog_edit.png')
settings:SetText("")
settings:SetToolTip("#spawnmenu.utilities.server_settings")
function settings:DoClick()
surface.PlaySound("buttons/button14.wav")
RunConsoleCommand("stormfox2_svmenu")
end
function settings:Paint() end
function p:PaintOver(w,h)
if self.enabled then return end
local x,y = 0, h / 2
surface.SetMaterial(mat)
surface.SetDrawColor(HSLToColor(240, 0.3,0.5 + sin(CurTime() * 1.5) / 10))
surface.DrawTexturedRectUV(0,h * 0.4,w,h * 0.2,0.2,-0.2,0.8,1)
draw.DrawText("#sf_holdc", "SF2.W_Button", w / 2, h / 2, color_white, TEXT_ALIGN_CENTER)
end
function p:Think()
local x,y = self:LocalCursorPos(0,0)
local inside = x > 0 and x < self:GetWide() and y > 0 and y < self:GetTall()
if not self.enabled and input.IsKeyDown(KEY_C) and not openChat and not gui.IsConsoleVisible() then
self.enabled = true
self.btnClose:SetDisabled( false )
self:MakePopup()
self:SetSelected()
elseif self.enabled then
if input.IsKeyDown(KEY_C) then return end -- If KEY is down, don't disable
if self:HasHierarchicalFocus() and not self:HasFocus() then return end -- Typing in something. Don't disable.
if inside then return end -- Mouse is inside controller. Don't disable yet.
self.enabled = false
self.btnClose:SetDisabled( true )
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
end
return _SF_CONTROLLER
end
---Opens the controller
---@client
function StormFox2.Menu.OpenController()
net.Start("StormFox2.menu")
net.WriteBool(false)
net.SendToServer()
end
---Closes the controller
---@client
function StormFox2.Menu.CloseController()
if _SF_CONTROLLER then
_SF_CONTROLLER:Remove()
end
end
-- Controller
list.Set( "DesktopWindows", "StormFoxController", {
title = "#sf_wcontoller",
icon = "stormfox2/hud/controller.png",
width = 960,
height = 700,
onewindow = true,
init = function( icon, window )
window:Remove()
surface.PlaySound("buttons/button14.wav")
StormFox2.Menu.OpenController()
end
} )
concommand.Add('stormfox2_controller', StormFox2.Menu.OpenController, nil, "Opens SF controller menu")

View File

@@ -0,0 +1,288 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Use the map-data to set a minimum and maximum fogdistance
---------------------------------------------------------------------------]]
StormFox2.Setting.AddSV("enable_svfog",true,nil, "Effects")
if CLIENT then StormFox2.Setting.AddCL("enable_fog",true, "sf_enable_fog") end
StormFox2.Setting.AddSV("enable_fogz",false,nil, "Effects")
StormFox2.Setting.AddSV("overwrite_fogdistance",-1,nil, "Effects", -1, 800000)
StormFox2.Setting.SetType("overwrite_fogdistance","special_float")
StormFox2.Setting.AddSV("allow_fog_change",engine.ActiveGamemode() == "sandbox",nil, "Effects")
StormFox2.Fog = {}
-- Local functions
local function fogEnabledCheck()
if not StormFox2.Setting.SFEnabled() then return false end
if not StormFox2.Setting.GetCache("enable_svfog", true) then return false end
if not StormFox2.Setting.GetCache("allow_fog_change", false) then return true end
return StormFox2.Setting.GetCache("enable_fog", true)
end
local _fS, _fE, _fD = 0,400000,1
local function fogStart( f )
_fS = f
end
local function fogEnd( f )
_fE = f
end
local function fogDensity( f )
_fD = f
end
local function getFogFill()
if _fS >= 0 then return 0 end
return -_fS / (_fE - _fS) * _fD * 0.1
end
-- Makes it so fog isn't linear
local e = 2.71828
local function fogCalc(b, a, p)
if a == b then return a end
p = e^(-8.40871*p)
local d = b - a
return a + d * p
end
---Returns the start of the fog.
---@return number
---@shared
function StormFox2.Fog.GetStart()
return math.max(0, _fS)
end
---Returns the end of fog.
---@return number
---@shared
function StormFox2.Fog.GetEnd()
return _fE
end
-- Locate / Calculate the default fog-data
local map_distance, map_farZ = -1, -1
local tab = StormFox2.Map.FindClass("env_fog_controller")
if #tab < 1 then
map_distance = 400000
else
-- Set a minimum
map_distance = 6000
for _, data in ipairs(tab) do
map_farZ = math.max(map_farZ, data.farz)
-- Calculate fog-brightness. We can use this to scale the map-distance up to match the fog.
local col_brightness = 1
local density = (tonumber( data.fogmaxdensity or "" ) or 1)
if data.fogcolor then
local fcol = string.Explode(" ", data.fogcolor)
col_brightness = (0.2126 * fcol[1] + 0.7152 * fcol[2] + 0.0722 * fcol[3]) / 255
end
density = density * col_brightness
map_distance = math.max(((data.fogend or 6000) / density), map_distance)
end
-- It is important we don't overshoot farZ
if map_farZ > 0 then
map_distance = math.min(map_distance, map_farZ)
end
end
---Returns the fog-amount. 0 - 1
---@return number
---@shared
function StormFox2.Fog.GetAmount()
return 1 - _fE / map_distance
end
---Returns the fog-distance ( Same as StormFox2.Fog.GetEnd(), but uses the map as a fallback )
---@return number
---@shared
function StormFox2.Fog.GetDistance()
return _fE or map_distance
end
-- Returns the default fog-distance for clear weather.
local function getDefaultDistance()
local ov = StormFox2.Setting.GetCache("overwrite_fogdistance",-1)
if ov > -1 then
return ov
end
return map_distance
end
-- Returns the fog-distance.
local function getAimDistance(bFinal)
local cW = StormFox2.Weather.GetCurrent()
local ov = getDefaultDistance()
if cW.Name == "Clear" then return ov end
local perc = bFinal and StormFox2.Weather.GetFinishPercent() or StormFox2.Weather.GetPercent()
local a = math.min(cW:Get('fogDistance'), ov)
if a == ov then return ov end -- This weathertype doesn't change the fog .. or is higer than default
if not a or perc <= 0 then return ov end -- If weather percent is 0 or under. Return the "clear" distance.
if perc >= 1 then return a end -- If weather is higer or equal to 1, return the base value.
return fogCalc(ov, a, perc)
end
if SERVER then
local loaded, data, f_FogZ = true
---Sets the fogZ distance. Seems buggy atm, use at own rist.
---@param num number
---@param nTimer number
---@server
function StormFox2.Fog.SetZ(num, nTimer)
timer.Remove( "sf_fog_timer" )
if nTimer then
timer.Create("sf_fog_timer", nTimer, 1, function()
StormFox2.Fog.SetZ(num)
end)
return
end
f_FogZ = num
if not loaded then
data = num
return
end
if not num then num = map_farZ end
for k,v in ipairs( StormFox2.Ent.env_fog_controllers or {} ) do
if not IsValid(v) then continue end
v:SetKeyValue("farz", num)
end
end
---Returns the fogz distance.
---@return number
---@server
function StormFox2.Fog.GetZ()
if not StormFox2.Setting.Get("enable_fogz", false) then return map_farZ end
return f_FogZ or (StormFox2.Fog.GetDistance() + 100)
end
hook.Add("StormFox2.PostEntityScan", "StormFox2.Fog.Initz", function()
loaded = true
if data then
StormFox2.Fog.SetZ(data)
data = nil
end
end)
hook.Add("StormFox2.weather.postchange", "StormFox2.Fog.Updater", function( sName ,nPercentage, nDelta )
local old_fE = _fE or map_distance
_fE = getAimDistance(true)
if _fE > 3000 then
_fS = 0
else
_fS = _fE - 3000
end
-- Check fogZ distance
if not StormFox2.Setting.Get("enable_fogz", false) then return end
if old_fE > _fE then -- The fog shriks
StormFox2.Fog.SetZ(_fE * 2 + 100, nDelta)
elseif old_fE < _fE then -- The fog grows
StormFox2.Fog.SetZ(_fE * 2 + 100)
end
end)
timer.Create("StormFox2.Fog.SVUpdate", 2, 0, function()
local cWD = StormFox2.Weather.GetCurrent().Dynamic or {}
if cWD.fogDistance then return end
_fE = getAimDistance(true)
end)
---Returns the fog-color.
---@return Color
---@server
function StormFox2.Fog.GetColor()
return StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor",color_white) ) or color_white
end
return
end
----- Fog render and clientside -----
-- Fog logic and default render
-- Returns the "distance" to outside
local f_outside = 0
local f_indoor = -1
local f_lastDist = map_distance
local function outSideVar()
local env = StormFox2.Environment.Get()
if env.outside then
return f_outside
end
if not env.nearest_outside then
return f_indoor
end
local dis = StormFox2.util.RenderPos():Distance(env.nearest_outside) / 300
if dis > 1 then
return f_indoor
end
return dis
end
hook.Add("Think", "StormFox2.Fog.Updater", function()
-- Figure out the fogdistance we should have
local f_envfar = outSideVar()
local fog_dis = getAimDistance()
local fog_indoor = StormFox2.Mixer.Get("fogIndoorDistance",3000)
if f_envfar == f_indoor then -- Indoors
fog_dis = math.max(fog_dis, fog_indoor)
elseif f_envfar ~= f_outside then
fog_dis = Lerp(f_envfar + 0.1, fog_dis, fog_indoor)
end
_fE = math.Approach(_fE, fog_dis, math.max(10, _fE) * FrameTime())
if _fE > 3000 then
_fS = 0
else
_fS = _fE - 3000
end
end)
local f_Col = color_white
local SkyFog = function(scale)
if _fD <= 0 then return end
if not scale then scale = 1 end
if not fogEnabledCheck() then return end
f_Col = StormFox2.Mixer.Get("fogColor", StormFox2.Mixer.Get("bottomColor") ) or color_white
-- Apply fog
local tD = StormFox2.Thunder.GetLight() / 2055
render.FogMode( 1 )
render.FogStart( StormFox2.Fog.GetStart() * scale )
render.FogEnd( StormFox2.Fog.GetEnd() * scale )
render.FogMaxDensity( (_fD - tD) * 0.999 )
render.FogColor( f_Col.r,f_Col.g,f_Col.b )
return true
end
hook.Add("SetupSkyboxFog","StormFox2.Sky.Fog",SkyFog)
hook.Add("SetupWorldFog","StormFox2.Sky.WorldFog",SkyFog)
-- Returns the fog-color.
function StormFox2.Fog.GetColor()
return f_Col or color_white
end
-- Additional Fog render
local m_fog = Material('stormfox2/effects/fog_sphere')
local l_fogz = 0
hook.Add("StormFox2.2DSkybox.FogLayer", "StormFox2.Fog.RSky", function( viewPos )
if not fogEnabledCheck() then return end
local v = Vector(math.cos( viewPos.y ), math.sin( viewPos.y ), 0)
m_fog:SetVector("$color", Vector(f_Col.r,f_Col.g,f_Col.b) / 255)
m_fog:SetFloat("$alpha", math.Clamp(5000 / _fE, 0, 1))
render.SetMaterial( m_fog )
local tH = math.min(StormFox2.Environment.GetZHeight(), 2100)
if tH ~= l_fogz then
local delta = math.abs(l_fogz, tH) / 2
l_fogz = math.Approach( l_fogz, tH, math.max(delta, 10) * 5 * FrameTime() )
end
local h = 2000 + 6000 * StormFox2.Fog.GetAmount()
render.DrawSphere( Vector(0,0,h - l_fogz * 4) , -30000, 30, 30, color_white)
end)
local mat = Material("color")
local v1 = Vector(1,1,1)
hook.Add("PostDrawOpaqueRenderables", "StormFox2.Sky.FogPDE", function()
if _fS >= 0 or _fD <= 0 then return end
if not fogEnabledCheck() then return end
local a = getFogFill()
mat:SetVector("$color",Vector(f_Col.r / 255,f_Col.g / 255,f_Col.b / 255))
mat:SetFloat("$alpha",a)
render.SetMaterial(mat)
render.DrawScreenQuad()
mat:SetVector("$color",v1)
mat:SetFloat("$alpha",1)
end)

View File

@@ -0,0 +1,298 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Footsteps and logic.
- Overrides default footstepsounds with terrain-sounds
---------------------------------------------------------------------------]]
local NetL = {"npc_zombie", "npc_poisonzombie", "npc_vortigaunt", "npc_antlion", "npc_fastzombie"} -- These entites only play sounds serverside and needs to be networked.
local BL = {"npc_hunter"} -- Tehse entities should not get their sound replaced
local find = string.find
local bAlwaysFootstep = false -- This is set to true on cold maps
local defaultSnowName = "snow.step"
local defaultSnowSnd = {
"stormfox/footstep/footstep_snow0.ogg",
"stormfox/footstep/footstep_snow1.ogg",
"stormfox/footstep/footstep_snow2.ogg",
"stormfox/footstep/footstep_snow3.ogg",
"stormfox/footstep/footstep_snow4.ogg",
"stormfox/footstep/footstep_snow5.ogg",
"stormfox/footstep/footstep_snow6.ogg",
"stormfox/footstep/footstep_snow7.ogg",
"stormfox/footstep/footstep_snow8.ogg",
"stormfox/footstep/footstep_snow9.ogg"
}
if SERVER then
util.AddNetworkString("StormFox2.feetfix")
end
-- We use this to cache the last foot for the players.
local lastFoot = {}
hook.Add("PlayerFootstep", "StormFox2.lastfootprint", function(ply, pos, foot, sound, volume, filter, ...)
lastFoot[ply] = foot
end)
-- Local functions
--local noSpam = {}
local cache = {}
-- Returns the foot from sounddata
local function GetFootstep(tab)
local ent = tab.Entity
if not ent or not IsValid(ent) then return end
if not ent:IsPlayer() and not ent:IsNPC() and not ent:IsNextBot() then return end
--if (noSpam[ent] or 0) > CurTime() then return end
-- Check to see if it is a footstep
local OriginalSnd = tab.OriginalSoundName:lower()
local foot = -1
if cache[OriginalSnd] then
foot = cache[OriginalSnd]
elseif string.match(OriginalSnd, "npc_antlionguard.farstep") or string.match(OriginalSnd, "npc_antlionguard.nearstep") then
foot = lastFoot[ent] or -1
elseif find(OriginalSnd, "stepleft",1,true) or find(OriginalSnd, "gallopleft",1,true) then
foot = 0
cache[OriginalSnd] = 0
elseif find(OriginalSnd, "stepright",1,true) or find(OriginalSnd, "gallopright",1,true) then
foot = 1
cache[OriginalSnd] = 1
elseif find(OriginalSnd, ".footstep",1,true) or find(tab.SoundName:lower(),"^player/footsteps",1) then
foot = lastFoot[ent] or -1
else -- Invalid
return
end
-- No footstep spam
--noSpam[ent] = CurTime() + 0.01
return foot
end
-- TraceHull for the given entity
local function EntTraceTexture(ent,pos) -- Returns the texture the entity is "on"
local mt = ent:GetMoveType()
if mt < 2 or mt > 3 then return end -- Not walking.
local filter = ent
if ent.GetViewEntity then
filter = ent:GetViewEntity()
end
local t = util.TraceHull( {
start = pos + Vector(0,0,30),
endpos = pos + Vector(0,0,-60),
maxs = ent:OBBMaxs(),
mins = ent:OBBMins(),
collisiongroup = ent:GetCollisionGroup(),
filter = filter
} )
if not t.Hit then return end -- flying
if t.Entity and IsValid(t.Entity) and t.HitNonWorld and t.HitTexture == "**studio**" then
return
end
return t.HitTexture
end
-- Returns true if the entity is on replaced texture.
local function IsOnReplacedTex(ent,snd,pos)
if ent._sf2ns and ent._sf2ns > CurTime() then return false, ent._sf2nt or "nil" end
ent._sf2ns = CurTime() + 0.1
local sTexture = EntTraceTexture(ent,pos)
ent._sf2nt = sTexture
if not sTexture then return false,"nil" end
local mat = Material(sTexture)
if not mat then return false, sTexture end
if mat:IsError() and (ent:IsNPC() or string.find(snd,"grass") or string.find(snd,"dirt")) then -- Used by maps
return true, sTexture
end
if StormFox2.Terrain.HasMaterialChanged(mat) then return true, sTexture end
return false,sTexture
end
-- Footstep overwrite and logic
hook.Add("EntityEmitSound", "StormFox2.footstep.detecter", function(data)
if not StormFox2.Terrain then return end
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
-- Only enable if we edit or need footsteps.
if not (bAlwaysFootstep or (cT and cT.footstepLisen)) then return end
-- Check if the server has disabled the footprint logic on their side.
if SERVER and not game.SinglePlayer() and not StormFox2.Setting.GetCache("footprint_enablelogic",true) then return end
-- Check if it is a footstep sound of some sort.
local foot = GetFootstep(data) -- Returns [-1 = invalid, 0 = left, 1 = right]
if not foot then return end
-- Checks to see if the texturem the entity stands on, have been replaced.
local bReplace, sTex = IsOnReplacedTex(data.Entity,data.SoundName:lower(),data.Pos or data.Entity:GetPos())
-- Overwrite the sound if needed.
local changed
if bReplace and cT.footprintSnds then
if cT.footprintSnds[2] then
data.OriginalSoundName = cT.footprintSnds[2] .. (foot == 0 and "left" or "right")
end
if not cT.footprintSnds[1] then
data.SoundName = "ambient/_period.wav"
else
data.SoundName = table.Random(cT.footprintSnds[1])
data.OriginalSoundName = data.SoundName
end
changed = true
end
-- Call footstep hook
hook.Run("StormFox2.terrain.footstep", data.Entity, foot, data.SoundName, sTex, bReplace )
-- Singleplayer and server-sounds fix
if SERVER and (game.SinglePlayer() or table.HasValue(NetL, data.Entity:GetClass())) then
net.Start("StormFox2.feetfix",true)
net.WriteEntity(data.Entity)
net.WriteInt(foot or 1,2)
net.WriteString(data.SoundName)
net.WriteString(sTex)
net.WriteBool(bReplace)
net.Broadcast()
end
-- Call terrain function
if cT.footstepFunc then
cT.footstepFunc(data.Entity, foot, data.SoundName, sTex, bReplace)
end
return changed
end)
-- Singleplayer and entity fix
if CLIENT then
net.Receive("StormFox2.feetfix",function()
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
local ent = net.ReadEntity()
if not IsValid(ent) then return end
local foot = net.ReadInt(2)
local sndName = net.ReadString()
local sTex = net.ReadString()
local bReplace = net.ReadBool()
if cT.footstepFunc then
cT.footstepFunc(ent, foot, sndName, sTex, bReplace)
end
hook.Run("StormFox2.terrain.footstep", ent, foot, sndName, sTex, bReplace)
end)
end
--[[-------------------------------------------------------------------------
Footprint render
---------------------------------------------------------------------------]]
if CLIENT then
local sin,cos,rad,clamp,ceil,min = math.sin,math.cos,math.rad,math.Clamp,math.ceil,math.min
local prints = {}
local footstep_maxlife = 30
local function ET(pos,pos2,mask,filter)
local t = util.TraceLine( {
start = pos,
endpos = pos + pos2,
mask = mask,
filter = filter
} )
if not t then -- tracer failed, this should not happen. Create a fake result.
local t = {}
t.HitPos = pos + pos2
return t
end
t.HitPos = t.HitPos or (pos + pos2)
return t
end
local function AddPrint(ent,foot)
-- Foot calc
local velspeed = ent:GetVelocity():Length()
local y = rad(ent:GetAngles().y)
local fy = y + rad((foot * 2 - 1) * -90)
local l = 5 * ent:GetModelScale()
local ex = Vector(cos(fy) * l + cos(y) * l,sin(fy) * l + sin(y) * l,0)
local pos = ent:GetPos() + ex
-- Find impact
local tr = ET(pos + Vector(0,0,20),Vector(0,0,-40),MASK_SOLID_BRUSHONLY,ent)
if not tr.Hit then return end -- In space?
-- If no bone_angle then angle math
local normal = -tr.HitNormal
-- CalcAng
local yawoff
if ent:IsPlayer() then
yawoff = normal:Angle().y - ent:EyeAngles().y + 180
else
yawoff = normal:Angle().y - ent:GetAngles().y + 180
end
table.insert(prints,{tr.HitPos, normal,foot,ent:GetModelScale() or 1,CurTime() + footstep_maxlife,clamp(velspeed / 300,1,2),yawoff})
-- pos, normal,foot,scale, life, lengh, yawoff
end
-- Footprint logic
local BL = {"npc_hunter","monster_bigmomma","npc_vortigaunt","npc_dog","npc_fastzombie","npc_stalker"} -- Blacklist footprints
local function CanPrint(ent)
local c = ent:GetClass()
for i,v in ipairs(BL) do
if find(c, v,1,true) then return false end
end
if find(ent:GetModel(),"_torso",1,true) then return false end
return true
end
hook.Add("StormFox2.terrain.footstep", "StormFox2.terrain.makefootprint", function(ent, foot, sSnd, sTexture, bReplace )
if foot < 0 then return end -- Invalid foot
if not bReplace and bAlwaysFootstep then -- This is a cold map, check for snow
if not find(sTexture:lower(),"snow",1,true) then return end
elseif bReplace then -- This is terrain
local cT = StormFox2.Terrain.GetCurrent()
if not cT then return end
if not cT.footprints then return end
else -- Invalid
return
end
if not CanPrint(ent) then return end
if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end
if StormFox2.Setting.GetCache("footprint_playeronly",false) and not ent:IsPlayer() then return end
local n_max = StormFox2.Setting.GetCache("footprint_max",200)
if #prints > n_max then
table.remove(prints, 1)
end
AddPrint(ent,foot)
end)
-- Footprint render
local mat = {Material("stormfox2/effects/foot_hq.png"),Material("stormfox2/effects/foot_hql.png"),Material("stormfox2/effects/foot_m.png"),Material("stormfox2/effects/foot_s.png")}
local function getMat(q,foot)
if q == 1 then
if foot == 0 then
return mat[2]
else
return mat[1]
end
end
return mat[q + 1]
end
local DrawQuadEasy = render.DrawQuadEasy
local bC = Color(0,0,0,255)
hook.Add("PreDrawOpaqueRenderables","StormFox2.Terrain.Footprints",function()
if not StormFox2.Setting.GetCache("footprint_enabled",true) then return end
if #prints < 1 then return end
local lp = StormFox2.util.RenderPos()
local del = {}
local footstep_dis = StormFox2.Setting.GetCache("footprint_distance",2000,"The renderdistance for footprints") ^ 2
for k,v in pairs(prints) do
local pos,normal,foot,scale,life,lengh,yawoff = v[1],v[2],v[3],v[4],v[5],v[6],v[7]
local blend = life - CurTime()
if blend <= 0 then
table.insert(del,k)
else
local q = min(ceil(lp:DistToSqr(pos) / footstep_dis),4)
if q >= 4 then continue end
render.SetMaterial(getMat(q,foot))
if foot == 0 and q > 1 then
DrawQuadEasy( pos + Vector(0,0,q / 3 + 1), normal, 6 * scale, 10 * scale * lengh, bC, yawoff )
else
DrawQuadEasy( pos + Vector(0,0,q / 3), normal, -6 * scale, 10 * scale * lengh, bC, yawoff )
end
end
end
for i = #del,1,-1 do
table.remove(prints,del[i])
end
end)
end
-- If the map is cold or has snow, always check for footsteps.
bAlwaysFootstep = StormFox2.Map.IsCold() or StormFox2.Map.HasSnow() -- This is a cold map.
if CLIENT then
StormFox2.Setting.AddCL("footprint_enabled",true) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_max",200) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_distance",2000) -- Add footprint setting
StormFox2.Setting.AddCL("footprint_playeronly",false) -- Add footprint setting
end
StormFox2.Setting.AddSV("footprint_enablelogic",true)

View File

@@ -0,0 +1,122 @@
--[[
| 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/
--]]
StormFox2.Setting.AddSV("edit_tonemap", false, nil, "Effects")
if CLIENT then return end
StormFox2.ToneMap = {}
--SetBloomScale
-- On load data
local function LoadSettings()
-- Locate tonemap
local t = StormFox2.Map.FindClass("env_tonemap_controller")
if #t < 1 then return end -- Unable to locate tonemap within BSP
local targetname = t[1].targetname
if not targetname then return end -- This tonemap can't have any settings
-- Search for logic_auto
local tab = {}
for k, v in ipairs( StormFox2.Map.FindClass("logic_auto") ) do
if not v.onmapspawn then continue end -- No setting?
if not string.match(v.onmapspawn, "^" .. targetname .. ",") then continue end -- This targets tonemap.
for s in string.gmatch(v.raw, '"OnMapSpawn"%s?"' .. targetname .. ',(.-)"') do
local t = string.Explode(",", s)
tab[t[1]] = t[2]
end
end
return tab
end
local DefaultSettings = LoadSettings()
local ent
local changed = false
hook.Add("StormFox2.PostEntityScan", "StormFox2.ToneMapFind", function()
ent = StormFox2.Ent.env_tonemap_controller and StormFox2.Ent.env_tonemap_controller[1]
end)
do
local last = 1
---Sets the tonemaps bloomscale. Use at own rist ask it looks like Soure engine doesn't like it.
---@param num number
---@server
function StormFox2.ToneMap.SetBloomScale( num )
if not ent or not DefaultSettings or not DefaultSettings.SetBloomScale then return end
if last == num then return end
ent:Fire("SetBloomScale",DefaultSettings.SetBloomScale * num)
changed = true
last = num
end
end
do
local last = 1
---Sets the tonemaps exposure scale. Use at own rist ask it looks like Soure engine doesn't like it.
---@param num number
---@server
function StormFox2.ToneMap.SetExposureScale( num )
if not ent or not DefaultSettings then return end
if last == num then return end
ent:Fire("SetAutoExposureMax",(DefaultSettings.SetAutoExposureMax or 1) * num)
ent:Fire("SetAutoExposureMin",(DefaultSettings.SetAutoExposureMin or 0) * num)
changed = true
last = num
end
end
do
local last = 1
---Sets the tonemaps rate-scale. Use at own rist ask it looks like Soure engine doesn't like it.
---@param num number
---@server
function StormFox2.ToneMap.SetTonemapRateScale( num )
if not ent or not DefaultSettings then return end
if last == num then return end
ent:Fire("SetTonemapRate",(DefaultSettings.SetTonemapRate or 0.1) * num)
changed = true
last = num
end
end
---Resets the tonemap settings applied.
---@server
function StormFox2.ToneMap.Reset()
if not changed or not ent then return end
changed = false
StormFox2.ToneMap.SetBloomScale( 1 )
StormFox2.ToneMap.SetExposureScale( 1 )
StormFox2.ToneMap.SetTonemapRateScale( 1 )
end
local function getMaxLight()
local c = StormFox2.Weather.Get("Clear")
return c:Get("mapDayLight",80)
end
local function ToneMapUpdate( lightlvlraw )
if not StormFox2.Setting.SFEnabled() or not StormFox2.Setting.GetCache("edit_tonemap", true) then
StormFox2.ToneMap.Reset()
else
StormFox2.ToneMap.SetExposureScale( lightlvlraw / 100 )
end
end
local last_Raw = 100
-- Toggle tonemap with setting
StormFox2.Setting.Callback("edit_tonemap",function()
ToneMapUpdate(last_Raw)
end,"sf_edit_tonemap")
-- Save the last raw-lightlvl and update the tonemap
hook.Add("StormFox2.lightsystem.new", "StormFox2.ToneMap-Controller", function(lightlvl, lightlvl_raw)
last_Raw = lightlvl_raw
ToneMapUpdate(lightlvl_raw)
end)

View File

@@ -0,0 +1,205 @@
--[[
| 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/
--]]
-- Delete old skybox brushes
if SERVER then
hook.Add( "InitPostEntity", "DeleteBrushNEntity", function()
for i, ent in ipairs( ents.GetAll() ) do
if not IsValid(ent) then continue end
if ent:GetClass() == "func_brush" and (ent:GetName() or "") == "daynight_brush" then
SafeRemoveEntity(ent)
elseif ent:CreatedByMap() and (ent:GetModel() or "") == "models/props/de_port/clouds.mdl" then
ent:SetNoDraw( true )
end
end
end )
end
-- Foliage overwrite
StormFox2.Setting.AddSV("override_foliagesway",true,nil, "Effects")
if StormFox2.Setting.Get("override_foliagesway", true) and CLIENT then
--[[
Foliage_type:
-2 - No treesway
-1 - Tree trunk
0 - Tree / w branches andor leaves
1 - Branches / Leaves
2 - Ground Plant
Bendyness multiplier:
1 - default
mat_height:
0 - height
WaveBonus_speed:
<number>
]]
local default_foliage = {}
default_foliage["models/msc/e_leaves"] = {1}
default_foliage["models/msc/e_leaves2"] = {1}
default_foliage["models/msc/e_bark3"] = {-1}
default_foliage["models/trees/japanese_tree_bark_02"] = {-1, 0.5}
default_foliage["models/trees/japanese_tree_round_02"] = {1}
default_foliage["models/trees/japanese_tree_round_03"] = {1}
default_foliage["models/trees/japanese_tree_round_05"] = {1}
default_foliage["models/props_foliage/tree_deciduous_01a_leaves2"] = {1}
default_foliage["models/msc/e_bigbush3"] = {2,4}
default_foliage["models/props_coalmine/foliage1"] = {2}
default_foliage["models/props_foliage/mall_trees_branches03"] = {2}
default_foliage["models/props_foliage/tree_deciduous_01a_branches"] = {2}
default_foliage["models/props_foliage/bramble01a"] = {2,0.4}
default_foliage["models/props_foliage/leaves_bushes"] = {2}
default_foliage["models/props_foliage/leaves"] = {2}
default_foliage["models/props_foliage/cane_field01"] = {2,nil,0.3}
--default_foliage["models/props_foliage/cattails"] = {2} Not working
--default_foliage["models/props_foliage/trees_farm01"] = {-1,0.8,0.02,1.5} Doesn't look good on some trees
default_foliage["models/props_foliage/cedar01_mip0"] = {0,0.4,0.02,3}
default_foliage["models/props_foliage/coldstream_cedar_bark"] = {-1}
default_foliage["models/props_foliage/coldstream_cedar_branches"] = {0}
default_foliage["models/props_foliage/urban_trees_branches03"] = {0}
default_foliage["models/props_foliage/bush"] = {2}
default_foliage["models/props_foliage/corn_plant01"] = {1,3.4}
default_foliage["models/props_foliage/detail_clusters"] = {2}
default_foliage["models/cliffs/ferns01"] = {0,2,nil,2}
default_foliage["models/props_foliage/rocks_vegetation"] = {0,4,nil,1,2}
default_foliage["models/props_foliage/flower_barrel"] = {0,3,0.07,2}
default_foliage["models/props_foliage/flower_barrel_dead"] = {0,1,0.07,2}
default_foliage["models/props_foliage/flower_barrel_dead"] = {0,1,0.07,2}
default_foliage["models/props/de_inferno/flower_barrel"] = {0,3,0.02,2}
default_foliage["models/props_foliage/grass_01"] = {2,0.5}
default_foliage["models/props_foliage/grass_02"] = {2,0.5}
default_foliage["models/props_foliage/grass_clusters"] = {2}
default_foliage["models/props_foliage/urban_trees_branches02_mip0"] = {-1}
default_foliage["models/props_foliage/hedge_128"] = {2,0.8}
default_foliage["models/props_foliage/foliage1"] = {2}
default_foliage["models/props_foliage/hr_f/hr_medium_tree_color"] = {-1}
default_foliage["models/props_foliage/ivy01"] = {2,0.1}
default_foliage["models/props_foliage/mall_trees_branches01"] = {0,1,nil,2}
default_foliage["models/props_foliage/mall_trees_barks01"] = {-1,1,nil,4}
default_foliage["models/props_foliage/mall_trees_branches02"] = {-1,1,nil,4}
--default_foliage["models/props_foliage/oak_tree01"] = {}
default_foliage["models/props_foliage/potted_plants"] = {0,4,0.055}
default_foliage["models/props_foliage/shrub_03"] = {2}
default_foliage["models/props_foliage/shrub_03_skin2"] = {2}
default_foliage["models/props_foliage/swamp_vegetation01"] = {-1,0.005,0.2}
default_foliage["models/props_foliage/swamp_branches"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_branches01_large"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_barks_large"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_barks"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_branches01"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_barks_still"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_trees_barks_generic"] = {0,0.005,0.2,10}
default_foliage["models/props_foliage/swamp_shrubwall01"] = {2}
default_foliage["models/props_foliage/swamp_trees_branches01_alphatest"] = {0,0.05}
default_foliage["models/props_foliage/swamp_trees_branches01_still"] = {0,0.05}
default_foliage["models/props_foliage/branch_city"] = {-1}
default_foliage["models/props_foliage/arbre01"] = {-1,0.4,0.04,2}
default_foliage["models/props_foliage/arbre01_b"] = {-1,0.05,nil,2}
default_foliage["models/props_foliage/tree_deciduous_01a-lod.mdl"] = {}
default_foliage["models/props_foliage/tree_deciduous_01a_lod"] = {-1}
default_foliage["models/props_foliage/tree_pine_01_branches"] = {-2} -- Looks bad. Remove.
default_foliage["models/props_foliage/pine_tree_large"] = {-1,0.8}
default_foliage["models/props_foliage/pine_tree_large_snow"] = {-1,0.8}
default_foliage["models/props_foliage/branches_farm01"] = {-1,0.2,0.8}
default_foliage["models/props_foliage/urban_trees_branches03_small"] = {2,0.8}
default_foliage["models/props_foliage/urban_trees_barks01_medium"] = {-1}
default_foliage["models/props_foliage/urban_trees_branches03_medium"] = {0,2}
default_foliage["models/props_foliage/urban_trees_barks01_medium"] = {-1,2,0.2}
default_foliage["models/props_foliage/urban_trees_branches02_small"] = {2}
default_foliage["models/props_foliage/urban_trees_barks01_clusters"] = {-1,0.2,0.2}
default_foliage["models/props_foliage/urban_trees_branches01_clusters"] = {0,0.2,0.2}
default_foliage["models/props_foliage/urban_trees_barks01"] = {-1,0.2}
default_foliage["models/props_foliage/urban_trees_barks01_dry"] = {2,nil,10}
default_foliage["models/props_foliage/leaves_large_vines"] = {0}
default_foliage["models/props_foliage/vines01"] = {2,0.3}
default_foliage["models/map_detail/foliage/foliage_01"] = {2,0.5}
default_foliage["models/map_detail/foliage/detailsprites_01"] = {2}
default_foliage["models/nita/ph_resortmadness/pg_jungle_plant"] = {0,1.2}
default_foliage["models/nita/ph_resortmadness/plant_03"] = {-1,0.3}
default_foliage["models/nita/ph_resortmadness/leaf_8"] = {0,2}
default_foliage["models/nita/ph_resortmadness/fern_2"] = {0,2}
default_foliage["models/nita/ph_resortmadness/tx_plant_02"] = {0,4,nil,4}
default_foliage["models/nita/ph_resortmadness/tx_plant_04"] = {0,4,nil,4}
default_foliage["models/nita/ph_resortmadness/orchid"] = {0,4,nil,4}
default_foliage["models/props_foliage/ah_foliage_sheet001"] = {2,0.4}
default_foliage["models/props_foliage/ah_apple_bark001"] = {2,0.4}
default_foliage["statua/nature/furcard1"] = {2,0.1}
default_foliage["models/statua/shared/furcard1"] = {2,0.1}
local max = math.max
local function SetFoliageData(texture,foliage_type,bendyness,mat_height,wave_speed)
if not texture then return end
if not wave_speed then wave_speed = 0 end
if not bendyness then bendyness = 1 end
if not mat_height then mat_height = 0 end
local mat = Material(texture)
if mat:IsError() then return end -- This client don't know what the material this is
-- Enable / Disable the material
if foliage_type < -1 then
mat:SetInt("$treeSway",0)
return
end
mat:SetInt("$treeSway",1) -- 0 is no sway, 1 is classic tree sway, 2 is an alternate, radial tree sway effect.
-- 'Default' settings
mat:SetFloat("$treeswayspeed",2) -- The treesway speed
mat:SetFloat("$treeswayspeedlerpstart",1000) -- Sway starttime
-- Default varables I don't know what do or doesn't have much to do with cl_tree_sway_dir
mat:SetFloat("$treeswayscrumblefalloffexp",3)
mat:SetFloat("$treeswayspeedhighwindmultiplier",0.2)
mat:SetFloat("$treeswaystartradius",0)
mat:SetFloat("$treeswayscrumblefrequency",6.6)
mat:SetFloat("$treeswayspeedlerpend",2500 * bendyness)
-- Special varables
if foliage_type == -1 then --Trunk
mat:SetFloat("$treeSwayStartHeight",mat_height) -- When it starts to sway
mat:SetFloat("$treeswayheight",max(700 - bendyness * 100,0)) -- << How far up before XY starts to matter
mat:SetFloat("$treeswayradius",max(110 - bendyness * 10,0)) -- ?
mat:SetFloat("$treeswayscrumblespeed",3 + (wave_speed or 0)) -- ?
mat:SetFloat("$treeswayscrumblestrength",0.1 * bendyness) -- "Strechyness"
mat:SetFloat("$treeswaystrength",0) -- "Strechyness"
elseif foliage_type == 0 then -- Trees
mat:SetFloat("$treeSwayStartHeight",mat_height) -- When it starts to sway
mat:SetFloat("$treeswayheight",max(700 - bendyness * 100,0)) -- << How far up before XY starts to matter
mat:SetFloat("$treeswayradius",max(110 - bendyness * 10,0)) -- ?
mat:SetFloat("$treeswayscrumblespeed",3 + (wave_speed or 0) ) -- ?
mat:SetFloat("$treeswayscrumblestrength",0.1 * bendyness) -- "Strechyness"
mat:SetFloat("$treeswaystrength",0) -- ?
elseif foliage_type == 1 then -- Leaves
mat:SetFloat("$treeSwayStartHeight",0.5 + mat_height / 2)
mat:SetFloat("$treeswayheight",8)
mat:SetFloat("$treeswayradius",1)
mat:SetFloat("$treeswayscrumblespeed",1 + (wave_speed or 0))
mat:SetFloat("$treeswayscrumblestrength",0.1)
mat:SetFloat("$treeswaystrength",0.06 * bendyness)
else
mat:SetFloat("$treeSwayStartHeight",0.1 + mat_height / 10)
mat:SetFloat("$treeswayheight",8)
mat:SetFloat("$treeswayradius",1)
mat:SetFloat("$treeswayscrumblespeed",wave_speed or 0)
mat:SetFloat("$treeswayscrumblestrength",0)
mat:SetFloat("$treeswaystrength",0.05 * bendyness)
end
mat:SetFloat("treeswaystatic", 0)
end
hook.Add("stormfox2.postinit", "stormfox2.treeswayinit", function()
for texture,data in pairs(default_foliage) do
if not data or #data < 1 then continue end
if data[1] < -1 then
SetFoliageData(texture,-2)
else
SetFoliageData(texture,unpack(data))
end
end
end)
end

View File

@@ -0,0 +1,40 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
vFire support :D
---------------------------------------------------------------------------]]
local vFireList = {}
hook.Add("vFireOnCalculateWind","vFire - StormFox Handshake",function(vFireEnt)
local outside = StormFox2.Wind.IsEntityInWind(vFireEnt)
if outside then
vFireList[vFireEnt] = true
return StormFox2.Wind.GetVector() / 20
end
end)
if CLIENT then return end
local ran = math.random
timer.Create("vFire - StormFox Rain",2,0,function()
local r = StormFox2.Weather.GetRainAmount()
if r <= 0 then table.Empty(vFireList) return end
for ent,_ in pairs(vFireList) do
if IsValid(ent) then
ent:SoftExtinguish(r * ran(130,160))
end
end
table.Empty(vFireList)
end)
timer.Simple(2,function()
if not vFireInstalled then return end
StormFox2.Msg("Gee, vFire, what do you want to do tonight?")
hook.Call("vFire - StormFox Handeshake")
end)

View File

@@ -0,0 +1,223 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Light entities: ( env_projectedtexture , light_dynamic, light, light_spot )
Requirements:
- Named "night" or "1" or "day".
- Not have the name "indoor".
logic_relays support and map lights.
dusk / night_events
dawn / day_events
weather_<type> Called when a weathertype gets applied
weather_onchange Called when a weathertype changes
weather_<type>_off Called when a weathertype gets removed
---------------------------------------------------------------------------]]
-- Special Relays
local special_relays = {}
special_relays.day_relays = {}
special_relays.daytime_relays = {}
special_relays.night_relays = {}
special_relays.nighttime_relays = {}
special_relays.weather_on = {}
special_relays.weather_off = {}
local scanned = false
local startingQ = {}
hook.Add("StormFox2.InitPostEntity", "StormFox2.MapInteractions.SRelays", function()
for _, ent in ipairs( ents.GetAll() ) do
if not ent:IsValid() then continue end
local class = ent:GetClass()
if class == "logic_day_relay" then
if ent:GetTriggerType() == 0 then -- Light change
table.insert(special_relays.day_relays, ent)
else
table.insert(special_relays.daytime_relays, ent)
end
elseif class == "logic_night_relay" then
if ent:GetTriggerType() == 0 then -- Light change
table.insert(special_relays.night_relays, ent)
else
table.insert(special_relays.nighttime_relays, ent)
end
elseif class == "logic_weather_relay" then
table.insert(special_relays.weather_on, ent)
elseif class == "logic_weather_off_relay" then
table.insert(special_relays.weather_off, ent)
end
end
scanned = true
for k, ent in ipairs( startingQ ) do
if not ent:IsValid() then continue end
ent:Trigger()
end
startingQ = {}
end)
local function triggerAll(tab)
if not scanned then -- Wait until all entities are loaded
for _, ent in ipairs(tab) do
if not ent:IsValid() then continue end
table.insert(startingQ, ent)
end
return
end
for _, ent in ipairs(tab) do
if not ent:IsValid() then continue end
ent:Trigger()
end
end
local night_lights = {{}, {}, {}, {}, {}, {}}
local relays = {}
local switch
-- local functions
local function setELight( ent, bTurnOn )
local sOnOff = bTurnOn and "TurnOn" or "TurnOff"
ent:Fire( sOnOff )
end
local function setLights( bTurnOn )
if timer.Exists("StormFox2.mi.lights") then
timer.Remove("StormFox2.mi.lights")
end
local i = 1
timer.Create("StormFox2.mi.lights", 0.5, 6, function()
for _,ent in ipairs(night_lights[i] or {}) do
if not IsValid(ent) then continue end
setELight(ent, bTurnOn)
end
i = i + 1
end)
end
local function SetRelay(fMapLight)
local lights_on = fMapLight < 20
if switch ~= nil and lights_on == switch then return end -- Nothing changed
if lights_on then
StormFox2.Map.CallLogicRelay("night_events")
triggerAll(special_relays.night_relays)
setLights( true )
else
StormFox2.Map.CallLogicRelay("day_events")
triggerAll(special_relays.day_relays)
setLights( false )
end
switch = lights_on
end
local includeNames = {
["1"] = true,
["streetlight"] = true,
["streetlights"] = true
}
local includeSearch = {
["night"] = true,
["day"] = true,
-- ["lake"] = true, Used indoors .. for some reason
["outdoor"] = true
}
local excludeSearch = {
["indoor"] = true,
["ind_"] = true,
["apt_"] = true
}
local function Search(name, tab)
for _, str in ipairs( tab ) do
if string.format(name, str) then return true end
end
return false
end
local t = {"env_projectedtexture", "light_dynamic", "light", "light_spot"}
hook.Add("StormFox2.InitPostEntity", "StormFox2.lightioinit", function()
-- Locate lights on the map
for i,ent in ipairs( ents.GetAll() ) do
local c = ent:GetClass()
if not table.HasValue(t, c) then continue end
local name = ent:GetName()
-- Unnamed entities
if not name then
if c == "light_spot" then
table.insert(night_lights[ 1 + i % 6 ],ent)
end
continue
end
name = name:lower()
-- Check for include
if includeNames[name] then
table.insert(night_lights[ 1 + i % 6 ],ent)
continue
end
-- Check exclude
if Search(name, excludeSearch) then
continue
end
-- Check include
if not Search(name, includeSearch) then
continue
end
table.insert(night_lights[ 1 + i % 6 ],ent)
end
-- Update on launch
timer.Simple(5, function()
SetRelay(StormFox2.Map.GetLight())
end)
end)
-- Call day and night relays
hook.Add("StormFox2.lightsystem.new", "StormFox2.mapinteractions.light", SetRelay)
-- Call day andn ight time-related relays
hook.Add("StormFox2.Time.OnDay", "StormFox2.mapinteractions.day", function()
triggerAll( special_relays.daytime_relays )
end)
hook.Add("StormFox2.Time.OnNight", "StormFox2.mapinteractions.night", function()
triggerAll( special_relays.nighttime_relays )
end)
local function getRelayName( )
local c_weather = StormFox2.Weather.GetCurrent()
local relay = c_weather.Name
if c_weather.LogicRelay then
relay = c_weather.LogicRelay() or relay
end
return relay
end
-- StormFox2.Map.w_CallLogicRelay( name )
local lastWeather
local function checkWRelay()
local relay = getRelayName()
relay = string.lower(relay)
if lastWeather and lastWeather == relay then return end -- Nothing changed
StormFox2.Map.w_CallLogicRelay( relay )
local wP = StormFox2.Data.GetFinal("w_Percentage") or 0
for k,ent in ipairs(special_relays.weather_on) do
if ent:GetRequiredWeather() ~= relay then continue end
if not ent:HasRequredAmount() then continue end
ent:Trigger()
end
for k,ent in ipairs(special_relays.weather_off) do
if ent:GetRequiredWeather() ~= lastWeather then continue end
ent:Trigger()
end
lastWeather = relay
end
hook.Add("StormFox2.weather.postchange", "StormFox2.mapinteractions" , function( sName ,nPercentage )
timer.Simple(1, checkWRelay)
end)
hook.Add("StormFox2.data.change", "StormFox2.mapinteractions.w_logic", function(sKey, nDay)
if sKey ~= "Temp" then return end
timer.Simple(1, checkWRelay)
end)

View File

@@ -0,0 +1,17 @@
--[[
| 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/
--]]
net.Receive(StormFox2.Net.Texture, function(len, ply)
StormFox2.Permission.EditAccess(ply,"StormFox Settings", function()
StormFox2.Map.ModifyMaterialType( net.ReadString(), net.ReadInt( 3 ))
end)
end)

View File

@@ -0,0 +1,901 @@
--[[
| 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/
--]]
StormFox2.WeatherGen = StormFox2.WeatherGen or {}
-- Settings
local auto_weather = StormFox2.Setting.AddSV("auto_weather",true,nil, "Weather")
local hide_forecast = StormFox2.Setting.AddSV("hide_forecast",false,nil, "Weather")
util.AddNetworkString( "StormFox2.weekweather" )
local forecast = {} -- The forecast table
local function SetForecast( tab, unix_time )
forecast = tab
forecast.unix_time = unix_time
if not hide_forecast then return end
net.Start("StormFox2.weekweather")
net.WriteBool( unix_time )
net.WriteTable( tab.temperature or {} )
net.WriteTable( tab.weather or {} )
net.WriteTable( tab.wind or {} )
net.WriteTable( tab.windyaw or {} )
net.Broadcast()
end
---Returns the forecast data
---@return table
---@server
function StormFox2.WeatherGen.GetForecast()
return forecast
end
---Returns true if we're using unix time for the forecast.
---@return boolean
---@server
function StormFox2.WeatherGen.IsUnixTime()
return forecast.unix_time or false
end
-- Open Weather API Settings
local api_MaxCalls = 59
local KEY_INVALID = 0
local KEY_UNKNOWN = 1
local KEY_VALID = 2
local KEY_STATUS = KEY_UNKNOWN
local API_ENABLE = StormFox2.Setting.AddSV("openweathermap_enabled",false,nil,"Weather")
StormFox2.Setting.AddSV("openweathermap_lat",52,nil,"Weather",-180,180) -- Fake setting
StormFox2.Setting.AddSV("openweathermap_lon",-2,nil,"Weather",-180,180) -- Fake setting
if SERVER then
CreateConVar("sf_openweathermap_key", "", {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "Sets the API key")
CreateConVar("sf_openweathermap_real_lat","52.613909" , {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "The real LAT for the API")
CreateConVar("sf_openweathermap_real_lon","-2.005960" , {FCVAR_ARCHIVE, FCVAR_PROTECTED}, "The real LON for the API")
end
local key = StormFox2.Setting.AddSV("openweathermap_key", "", nil,"Weather")
local location = StormFox2.Setting.AddSV("openweathermap_location", "", nil,"Weather") -- Fake setting
local city = StormFox2.Setting.AddSV("openweathermap_city","",nil,"Weather") -- Fake setting
-- Keep them secret and never network them.
key.isSecret = true
location.isSecret = true
city.isSecret = true
local function onSuccessF( body, len, head, code )
KEY_STATUS = KEY_VALID
if not auto_weather:GetValue() then return end
-- Most likly an invalid API-Key.
local t = util.JSONToTable(body) or {}
if code == 401 then
KEY_STATUS = KEY_INVALID
StormFox2.Warning(t.message or "API returned 401! Check your OpenWeatherMap account.")
StormFox2.Setting.Set("openweathermap_enabled", false)
return
end
if t.cod == "404" then return end -- Not found
local timeZone = t.timezone and tonumber(t.timezone) or 0
-- We can set the sunrise and sunset
if t.sys and t.sys.sunrise and t.sys.sunset then
local sunrise = os.date("!%H:%M",t.sys.sunrise + timeZone)
local sunset = os.date("!%H:%M",t.sys.sunset + timeZone)
StormFox2.Setting.Set("sunrise",StormFox2.Time.StringToTime(sunrise))
StormFox2.Setting.Set("sunset",StormFox2.Time.StringToTime(sunset))
end
if t.main then
-- Temperature
local temp = StormFox2.Temperature.Convert("kelvin",nil,tonumber( t.main.temp or t.main.temp_min or t.main.temp_max ))
StormFox2.Temperature.Set( temp, 1 )
-- Weather
local cloudyness = ( t.clouds and t.clouds.all or 0 ) / 110
local rain = 0
if t.rain then
rain = math.max( t.rain["1h"] or 0, t.rain["3h"] or 0, 2) / 8
elseif t.snow then
rain = math.max( t.snow["1h"] or 0, t.snow["3h"] or 0, 2) / 8
end
if rain > 0 then
StormFox2.Weather.Set("Rain", math.Round(rain * .7 + 0.2,2))
elseif cloudyness >= 0.1 then
StormFox2.Weather.Set("Cloud", math.Round(cloudyness, 2))
else
StormFox2.Weather.Set("Clear", 1)
end
-- Thunder
local b_thunder = false
if t.weather and t.weather[1] and t.weather[1].id and (rain > 0 or cloudyness >= 0.3) then
local id = t.weather[1].id
b_thunder = ( id >= 200 and id <= 202 ) or ( id >= 210 and id <= 212 ) or ( id >= 230 and id <= 232 ) or id == 212
end
StormFox2.Thunder.SetEnabled(b_thunder, id == 212 and 12 or 6) -- 212 is heavy thunderstorm
-- Wind
StormFox2.Wind.SetForce( t.wind and t.wind.speed or 0 )
StormFox2.Wind.SetYaw( t.wind and t.wind.deg or 0 )
end
end
local n_NextAllowedCall = 0
local b_BlockNextW = false
local function UpdateLiveWeather( api_key )
if b_BlockNextW then return end
if KEY_STATUS == KEY_INVALID then return end
if n_NextAllowedCall >= CurTime() then
return StormFox2.Warning("API can't be called that often!")
end
n_NextAllowedCall = CurTime() + (60 / api_MaxCalls)
local lat = GetConVar("sf_openweathermap_real_lat"):GetString()
local lon = GetConVar("sf_openweathermap_real_lon"):GetString()
local api_key = api_key or GetConVar("sf_openweathermap_key"):GetString()
http.Fetch("http://api.openweathermap.org/data/2.5/weather?lat=" .. lat .. "&lon=" .. lon .. "&appid=" .. api_key, onSuccessF)
end
local function onSuccessForecast( body, len, head, code )
if KEY_STATUS == KEY_INVALID then return end
local t = util.JSONToTable(body) or {}
if code == 401 then
KEY_STATUS = KEY_INVALID
StormFox2.Warning(t.message or "API returned 401! Check your OpenWeatherMap account.")
StormFox2.Setting.Set("openweathermap_enabled", false)
return
end
if t.cod == "404" then return end -- Not found
if not t.list then return end -- ??
local forecast = {}
forecast.temperature= {}
forecast.weather = {}
forecast.wind = {}
forecast.windyaw = {}
local last_time = -1
local ex_time = 0
for k, v in ipairs( t.list ) do
if not v.main then continue end -- ERR
--local timestr = string.match(v.dt_txt, "(%d+:%d+:%d)")
--local c = string.Explode(":", timestr)
--local h, m, s = c[1] or "0", c[2] or "0", c[3] or "0"
--local time = ( tonumber( h ) or 0 ) * 60 + (tonumber( m ) or 0) + (tonumber( s ) or 0) / 60
--if time < last_time then -- New day
-- ex_time = ex_time + 1440
-- last_time = time
--else
-- last_time = time
--end
-- New time: time + ex_time
--local timeStamp = time + ex_time
local timeStamp = v.dt
if timeStamp > 1440 then
if k%2 == 1 then
continue
end
elseif timeStamp > 2440 then
continue
end
local temp = StormFox2.Temperature.Convert("kelvin",nil,tonumber( v.main.temp or v.main.temp_min or v.main.temp_max ))
local cloudyness = ( t.clouds and t.clouds.all or 0 ) / 110
local rain = 0
local w_type = "Clear"
local w_procent = 0
if v.rain then
rain = math.max( v.rain["1h"] or 0, v.rain["3h"] or 0, 2) / 8
elseif v.snow then
rain = math.max( v.snow["1h"] or 0, v.snow["3h"] or 0, 2) / 8
end
if rain > 0 then
w_procent = math.Round(rain * .7 + 0.2,2)
w_type = "Rain"
elseif cloudyness > 0.1 then
w_procent = math.Round(cloudyness, 2)
w_type = "Cloud"
end
local b_thunder = false
if v.weather and v.weather[1] and v.weather[1].id and (rain > 0 or cloudyness >= 0.3) then
local id = v.weather[1].id
b_thunder = ( id >= 200 and id <= 202 ) or ( id >= 210 and id <= 212 ) or ( id >= 230 and id <= 232 ) or id == 212
end
local wind = v.wind and v.wind.speed or 0
local windyaw = v.wind and v.wind.deg or 0
table.insert(forecast.temperature, {timeStamp, temp})
table.insert(forecast.weather, {timeStamp, {
["sName"] = w_type,
["fAmount"] = w_procent
}})
table.insert(forecast.wind, {timeStamp, wind})
table.insert(forecast.windyaw, {timeStamp, windyaw})
end
SetForecast( forecast, true )
end
local function UpdateLiveFeed( api_key )
if KEY_STATUS == KEY_INVALID then return end
local lat = GetConVar("sf_openweathermap_real_lat"):GetString()
local lon = GetConVar("sf_openweathermap_real_lon"):GetString()
local api_key = api_key or GetConVar("sf_openweathermap_key"):GetString()
http.Fetch("http://api.openweathermap.org/data/2.5/forecast?lat=" .. lat .. "&lon=" .. lon .. "&appid=" .. api_key, onSuccessForecast)
end
local function SetCity( sCityName, callBack )
if KEY_STATUS == KEY_INVALID then return end
if n_NextAllowedCall >= CurTime() then
return StormFox2.Warning("API can't be called that often!")
end
n_NextAllowedCall = CurTime() + (60 / api_MaxCalls)
http.Fetch("http://api.openweathermap.org/data/2.5/weather?q=" .. sCityName .. "&appid=" .. GetConVar("sf_openweathermap_key"):GetString(), function( body, len, head, code )
-- Most likly an invalid API-Key.
local t = util.JSONToTable(body) or {}
if code == 401 then
KEY_STATUS = KEY_INVALID
StormFox2.Warning(t.message or "API returned 401")
StormFox2.Setting.Set("openweathermap_enabled", false)
return
end
if t.cod == 404 or not t.coord then -- City not found
if callBack then callBack( false ) end
return
end
b_BlockNextW = true -- Stop the setting from updating the weather again
local lat = tonumber( t.coord.lat )
RunConsoleCommand( "sf_openweathermap_real_lat", lat )
StormFox2.Setting.Set("openweathermap_lat",math.Round(lat)) -- Fake settings
local lon = tonumber( t.coord.lon )
RunConsoleCommand( "sf_openweathermap_real_lon", lon )
StormFox2.Setting.Set("openweathermap_lon",math.Round(lon)) -- Fake settings
b_BlockNextW = false
onSuccessF( body, len, head, code )
-- We found a city. Make the forecast
timer.Simple(1, UpdateLiveFeed)
if callBack then callBack( true ) end
end)
end
--- Update Value
key:AddCallback( function( sString )
RunConsoleCommand( "sf_openweathermap_key", sString )
key.value = "" -- Silent set it again
KEY_STATUS = KEY_UNKNOWN
UpdateLiveWeather( sString ) -- Try and set the weather
end)
location:AddCallback( function( sString )
local num = tonumber( string.match(sString, "[-%d]+") or "0" ) or 0
if sString:sub(0, 1) == "a" then
RunConsoleCommand( "sf_openweathermap_real_lat", num )
StormFox2.Setting.Set("openweathermap_lat",math.Round(num)) -- Fake settings
else
RunConsoleCommand( "sf_openweathermap_real_lon", num )
StormFox2.Setting.Set("openweathermap_lon",math.Round(num)) -- Fake settings
end
location.value = "" -- Silent set it again
UpdateLiveWeather() -- Set the weather to the given location
timer.Simple(1, UpdateLiveFeed)
end)
city:AddCallback( function( cityName )
if cityName == "" then return end
SetCity( cityName )
city.value = "" -- Silent set it again
end)
-- Enable and disable API
local status = false
local function EnableAPI()
if status then return end
timer.Create("SF_WGEN_API", 5 * 60, 0, function()
if not auto_weather:GetValue() then return end
if not status then return end
UpdateLiveWeather()
end)
timer.Simple(1, UpdateLiveFeed)
end
local function DisableAPI()
if not status then return end
timer.Destroy("SF_WGEN_API")
end
local function IsUsingAPI()
return status
end
-- Weather Gen Settings
local max_days_generate = 7
local min_temp = StormFox2.Setting.AddSV("min_temp",-10,nil,"Weather",-273.15)
local max_temp = StormFox2.Setting.AddSV("max_temp",20,nil, "Weather")
local max_wind = StormFox2.Setting.AddSV("max_wind",50,nil, "Weather")
local night_temp= StormFox2.Setting.AddSV("addnight_temp",-7,nil, "Weather")
local function toStr( num )
local c = tostring( num )
return string.rep("0", 4 - #c) .. c
end
local default
local function SplitSetting( str )
if #str< 20 then return default end -- Invalid, use default
local tab = {}
local min = math.min(100, string.byte(str, 1,1) - 33 ) / 100
local max = math.min(100, string.byte(str, 2,2) - 33 ) / 100
tab.amount_min = math.min(min, max)
tab.amount_max = math.max(min, max)
local min = math.min(1440,tonumber( string.sub(str, 3, 6) ) or 0)
local max = math.min(1440,tonumber( string.sub(str, 7, 10) ) or 0)
tab.start_min = math.min(min, max)
tab.start_max = math.max(min, max)
local min = tonumber( string.sub(str, 11, 14) ) or 0
local max = tonumber( string.sub(str, 15, 18) ) or 0
tab.length_min = math.min(min, max)
tab.length_max = math.max(min, max)
tab.thunder = string.sub(str, 19, 19) == "1"
tab.pr_week = tonumber( string.sub(str, 20) ) or 0
return tab
end
local function CombineSetting( tab )
local c =string.char( 33 + (tab.amount_min or 0) * 100 )
c = c .. string.char( 33 + (tab.amount_max or 0) * 100 )
c = c .. toStr(math.Clamp( math.Round( tab.start_min or 0), 0, 1440 ) )
c = c .. toStr(math.Clamp( math.Round( tab.start_max or 0), 0, 1440 ) )
c = c .. toStr(math.Clamp( math.Round( tab.length_min or 360 ), 180, 9999) )
c = c .. toStr(math.Clamp( math.Round( tab.length_max or 360 ), 180, 9999) )
c = c .. (tab.thunder and "1" or "0")
c = c .. tostring( tab.pr_week or 2 )
return c
end
local default_setting = {}
default_setting["Rain"] = CombineSetting({
["amount_min"] = 0.4,
["amount_max"] = 0.9,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 360,
["length_max"] = 1200,
["thunder"] = true,
["pr_week"] = 3
})
default_setting["Cloud"] = CombineSetting({
["amount_min"] = 0.2,
["amount_max"] = 0.7,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 360,
["length_max"] = 1200,
["pr_week"] = 3
})
default_setting["Clear"] = CombineSetting({
["amount_min"] = 1,
["amount_max"] = 1,
["start_min"] = 0,
["start_max"] = 1440,
["length_min"] = 360,
["length_max"] = 1440,
["pr_week"] = 7
})
-- Morning fog
default_setting["Fog"] = CombineSetting({
["amount_min"] = 0.15,
["amount_max"] = 0.30,
["start_min"] = 360,
["start_max"] = 560,
["length_min"] = 160,
["length_max"] = 360,
["pr_week"] = 1
})
default = CombineSetting({
["amount_min"] = 0.4,
["amount_max"] = 0.9,
["start_min"] = 300,
["start_max"] = 1200,
["length_min"] = 300,
["length_max"] = 1200,
["pr_week"] = 0
})
-- Create settings for weather-types.
local weather_setting = {}
local OnWeatherSettingChange
local function call( newVar, oldVar, sName )
sName = string.match(sName, "wgen_(.+)") or sName
weather_setting[sName] = SplitSetting( newVar )
OnWeatherSettingChange()
end
hook.Add("stormfox2.postloadweather", "StormFox2.WeatherGen.Load", function()
for _, sName in ipairs( StormFox2.Weather.GetAll() ) do
local str = default_setting[sName] or default
local obj = StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather")
obj:AddCallback( call )
weather_setting[sName] = SplitSetting( obj:GetValue() )
end
end)
for _, sName in ipairs( StormFox2.Weather.GetAll() ) do
local str = default_setting[sName] or default
local obj = StormFox2.Setting.AddSV("wgen_" .. sName,str,nil,"Weather")
obj:AddCallback( call, "updateWSetting" )
weather_setting[sName] = SplitSetting( obj:GetValue() )
end
local function SetWeatherSetting(sName, tab)
if CLIENT then return end
StormFox2.Setting.SetValue("wgen_" .. sName, CombineSetting(tab))
end
-- Returns the lowest key that is higer than the inputed key.
-- Second return is the higest key that is lower than the inputed key.
local function getClosestKey( tab, key)
local s, ls
for k, v in ipairs( table.GetKeys(tab) ) do
if v < key then
if not ls or ls < v then
ls = v
end
else
if not s or s > v then
s = v
end
end
end
return s, ls or 0
end
local generator = {} -- Holds all the days
local day = {}
day.__index = day
local function CreateDay()
local t = {}
t._temperature = {}
t._wind = {}
t._windyaw = {}
t._weather = {}
setmetatable(t, day)
table.insert(generator, t)
if #generator > max_days_generate then
table.remove(generator, 1)
end
return t
end
local lastTemp
function day:SetTemperature( nTime, nCelcius )
-- I got no clue why it tries to set values outside, but clamp it here just in case.
nCelcius = math.Clamp(nCelcius, min_temp:GetValue(), max_temp:GetValue())
self._temperature[nTime] = nCelcius
lastTemp = nCelcius
return self
end
function day:GetTemperature( nTime )
return self._temperature[nTime]
end
local lastWind, lastWindYaw
local lastLastWind
function day:SetWind( nTime, nWind, nWindYaw )
self._wind[nTime] = nWind
self._windyaw[nTime] = nWindYaw
lastLastWind = lastWind
lastWind = nWind
lastWindYaw = nWindYaw
return self
end
function day:GetWind( nTime )
return self._wind[nTime], self._windyaw[nTime]
end
function day:SetWeather( sName, nStart, nDuration, nAmount, nThunder )
self._weather[nStart] = {
["sName"] = sName,
["nStart"] = nStart,
["nDuration"] = nDuration,
["fAmount"] = nAmount,
["nThunder"] = nThunder }
self._last = math.max(self._last or 0, nStart + nDuration)
end
function day:GetWeather( nTime )
return self._weather[nTime]
end
function day:GetWeathers()
return self._weather
end
function day:GetLastWeather()
return self._weather[self._last]
end
local function GetLastDay()
return generator[#generator]
end
local weatherWeekCount = {}
local function CanGenerateWeather( sName )
local count = weatherWeekCount[ sName ] or 0
local setting = weather_setting[ sName ]
if not setting then return false end -- Invalid weahter? Ignore this.
local pr_week = setting.pr_week or 0
if pr_week <= 0 then return false end -- Disabled
if pr_week < 1 then -- Floats between 0 and 1 is random.
pr_week = math.random(0, 1 / pr_week) <= 1 and 1 or 0
end
if count >= pr_week then return false end -- This weahter is reached max for this week
return true
end
local function SortList()
local t = {}
for _, sName in pairs(StormFox2.Weather.GetAll()) do
t[sName] = weatherWeekCount[sName] or 0
end
return table.SortByKey(t, true)
end
local function UpdateWeekCount()
weatherWeekCount = {}
for k, day in ipairs( generator ) do
for nTime, weather in pairs( day:GetWeathers() ) do
local sName = weather.sName
weatherWeekCount[sName] = (weatherWeekCount[sName] or 0) + 1
end
end
end
local atemp = math.random(-4, 4)
local nextWeatherOverflow = 0
local function GenerateDay()
local newDay = CreateDay()
UpdateWeekCount()
-- Handle temperature
do
local mi, ma = min_temp:GetValue(), max_temp:GetValue()
local ltemp = lastTemp or math.random(mi, ma)
local aftemp = -math.min(ltemp - mi, 12) -- The closer the temperature is to minimum, the lower min
local ahtemp = math.min(ma - ltemp, 12) -- The closer the temperature is to maximum, the lower max
atemp = atemp + math.Rand(-4, 4) -- How much the temperature goes up or down
atemp = math.Clamp(atemp, aftemp, ahtemp)
-- UnZero
local tempBoost = 7 - math.abs( ltemp + atemp )
if tempBoost > 0 then
if atemp >= 0 then
atemp = math.max(atemp + tempBoost, tempBoost)
else
atemp = math.min(atemp - tempBoost, -tempBoost)
end
end
-- Spikes
if math.random(10) > 8 then
-- Create a spike
if ltemp + atemp >= 0 then
atemp = mi / 2
else
atemp = ma / 2
end
end
-- New temp
local newMidTemp = math.Round(math.Clamp(ltemp + atemp, mi + 4, ma), 1)
-- Make the new temperature
local h = StormFox2.Sun.GetSunRise()
local n_temp = night_temp:GetValue() or -7
local sunDown = StormFox2.Sun.GetSunSet() + math.random(-180, 180) - 180
newDay:SetTemperature( sunDown, newMidTemp )
newDay:SetTemperature( h - 180, math.max(newMidTemp + math.random(n_temp / 2, n_temp), mi) )
lastTemp = newMidTemp -- To make sure night-temp, don't effect the overall temp
end
-- Handle wind
local newWind
do
--lastWind, lastWindYaw
if not lastWind then
lastWind = math.random(5)
lastWindYaw = math.random(360)
end
local buff = math.abs(atemp) - 4 -- Wind tent to increase the more temp changes. Also add a small negative modifier
if math.random(1, 50) >= 49 and buff > 4 then -- Sudden Storm
buff = buff + 10
end
local addforce = math.random(buff / 2, buff - math.abs(lastLastWind or 0))
newWind = math.min(max_wind:GetValue(), math.max(0, lastWind + addforce))
local yawChange = math.min(40, lastWind + addforce * 15)
newDay:SetWind( math.random(180, 1080), newWind, ( lastWindYaw + math.random(-yawChange, yawChange) ) % 360 )
end
-- Handle weather
local i = 3 -- Only generates 2 types of weathers pr day at max
local _last = nextWeatherOverflow -- The next empty-time of the day
for _, sName in ipairs( SortList() ) do
if _last >= 1440 then continue end -- This day is full of weathers. Ignore.
if sName == "Clear" and math.random(0, newWind) < newWind * 0.8 then -- Roll a dice between 0 and windForce. If dice is below 80%, try and find another weahter instead.
if atemp > 0 then -- Warm weather tent to clear up the weather
break
elseif atemp < 0 then -- Colder weather will form weather
continue
end
end
-- Check if weather is enabled, and we haven't reached max.
if not CanGenerateWeather( sName ) then continue end
local setting = weather_setting[sName]
local minS, maxS = setting.start_min, setting.start_max
local minL, maxL = setting.length_min, setting.length_max
if _last >= maxS then continue end -- This weather can't be generated this late.
i = i - 1
if i <= 0 then break end
local start_time = math.random(math.max(minS, _last), maxS)
local length_time = math.random(minL, maxL)
local amount = math.Rand(setting.amount_min, setting.amount_max)
local nThunder
if setting.thunder and amount > 0.5 and math.random(0, 10) > 7 then
nThunder = math.random(4,8)
end
newDay:SetWeather( sName, start_time, length_time, amount, nThunder )
_last = start_time + length_time
end
nextWeatherOverflow = math.max(0, _last - 1440)
end
local function GenerateWeek()
for i = 1, max_days_generate do
GenerateDay()
end
end
local enable = false
local function IsUsingWeatherGen()
return enable
end
local function TimeToIndex( tab )
local c = table.GetKeys( tab )
local a = {}
table.sort(c)
for i = 1, #c do
a[i] = {c[i], tab[c[i]]}
end
return a
end
local wGenList = {}
local function PushDayToList() -- Merges the 7 days into one long line. Its more stable this way
-- empty forecast
wGenList = {}
wGenList._temperature = {}
wGenList._wind = {}
wGenList._windyaw = {}
wGenList._weather = {}
local lastWType = "Clear"
local lastWTime = -100
for i = 1, 4 do
local f = {}
local day = generator[i]
for nTime, var in pairs( day._temperature ) do
wGenList._temperature[ nTime + (i - 1) * 1440 ] = var
end
for nTime, var in pairs( day._wind ) do
local nn = nTime + (i - 1) * 1440
wGenList._wind[ nn ] = var
wGenList._windyaw[ nn ] = day._windyaw[ nTime ]
end
for nTime, var in pairs( day._weather ) do
if var.sName == "Clear" then -- Ignore clear weathers. They're default.
lastWType = "Clear"
continue
end
local nTimeStart = nTime + (i - 1) * 1440
local nTimeMax = nTimeStart + math.Round(math.random(var.nDuration / 4, var.nDuration / 2), 1)
local nTimeMaxEnd = nTimeStart + var.nDuration * 0.75
local nTimeEnd = nTimeStart + var.nDuration
local wObj = StormFox2.Weather.Get(var.sName)
local t = {
["sName"] = var.sName,
["fAmount"] = math.Round(var.fAmount, 2),
["bThunder"] = var.nThunder
}
local useCloud = wObj.Inherit == "Cloud" and math.random(1, 10) >= 5
local startWType = useCloud and "Cloud" or var.sName
if lastWType == var.sName and lastWTime == nTimeStart then -- In case we had the same weather type before, remove the "fading out" part
wGenList._weather[ lastWTime ] = nil
startWType = var.sName
else
wGenList._weather[ nTimeStart ] = {
["sName"] = startWType,
["fAmount"] = 0
}
end
wGenList._weather[ nTimeMax ] = {
["sName"] = startWType,
["fAmount"] = math.Round(var.fAmount, 2)
}
wGenList._weather[ nTimeMaxEnd ] = t
wGenList._weather[ nTimeEnd ] = {
["sName"] = var.sName,
["fAmount"] = 0
}
lastWType = var.sName
lastWTime = var.nTimeEnd
end
end
-- Push it into an index
wGenList.weather = TimeToIndex( wGenList._weather )
wGenList.temperature = TimeToIndex( wGenList._temperature )
wGenList.wind = TimeToIndex( wGenList._wind )
wGenList.windyaw = TimeToIndex( wGenList._windyaw )
if not IsUsingWeatherGen() then return end -- Don't update the forecast. But keep the weather in mind in case it gets enabled.
SetForecast( wGenList )
end
-- In case settings change, update weekweather
local function ClearAndRedo()
timer.Simple(1, function()
weatherWeekCount = {}
generator = {}
weather_index = 0
wind_index = 0
temp_index = 0
-- Generate new Day
GenerateWeek()
-- Make WGen
PushDayToList()
end)
end
min_temp:AddCallback( ClearAndRedo, "weekWeather" )
max_temp:AddCallback( ClearAndRedo, "weekWeather" )
max_wind:AddCallback( ClearAndRedo, "weekWeather" )
night_temp:AddCallback( ClearAndRedo, "weekWeather" )
OnWeatherSettingChange = ClearAndRedo
local lastWeather, lastWind, lastTemp = -1, -1 , -1
local function fkey( x, a, b )
return (x - a) / (b - a)
end
local function findNext( tab, time ) -- First one is time
for i, v in ipairs( tab ) do
if time > v[1] then continue end
return i
end
return 0
end
local weather_index = 0
local wind_index = 0
local temp_index = 0
local function EnableWGenerator(forceCall)
if enable and not forceCall then return end
enable = true
GenerateWeek() -- Generate a week
PushDayToList() -- Push said list to w-table.
-- We need to set the start-weather
-- Set the start temperature
local curTime = math.ceil(StormFox2.Time.Get())
local t_index = findNext( wGenList.temperature, curTime )
if t_index > 0 then
local procentStart = 1
local _start = wGenList.temperature[t_index - 1]
local _end = wGenList.temperature[t_index]
if _start then
procentStart = fkey( curTime, _start[1], _end[1] )
end
local temp = Lerp( procentStart, (_start or _end)[2], _end[2] )
StormFox2.Temperature.Set( math.Round(temp, 2), 0 )
end
-- Set the start wind
local wind_index = findNext( wGenList.wind, curTime )
if wind_index > 0 then
local procentStart = 1
local _start = wGenList.wind[t_index - 1]
local _end = wGenList.wind[t_index]
if _start then
procentStart = fkey( curTime, _start[1], _end[1] )
end
local wind = Lerp( procentStart, (_start or _end)[2], _end[2] )
StormFox2.Wind.SetForce( math.Round(wind, 2), 0 )
local _start = wGenList.windyaw[t_index - 1]
local _end = wGenList.windyaw[t_index]
local windyaw = Lerp( procentStart, (_start or _end)[2], _end[2] )
StormFox2.Wind.SetYaw( math.Round(windyaw, 2), 0 )
end
-- Set the start weather
local weather_index = findNext( wGenList.weather, curTime )
if weather_index > 0 then
local procentStart = 1
local _start = wGenList.weather[weather_index - 1]
local _end = wGenList.weather[weather_index]
if _start then
procentStart = fkey( curTime, _start[1], _end[1] )
else
_start = _end
end
local isClear = _end[2].sName == "Clear" or _end[2].fAmount == 0
local w_type = ( isClear and _start[2] or _end[2] ).sName
local w_procent = ( isClear and _start[2] or _end[2] ).fAmount
StormFox2.Weather.Set( w_type, w_procent * procentStart )
if _end[2].bThunder then
StormFox2.Thunder.SetEnabled(true, _end[2].bThunder)
else
StormFox2.Thunder.SetEnabled(false, 0)
end
end
-- Create a timer to modify the weather, checks every 1.5 seconds
timer.Create("SF_WGEN_DEF", 0.5, 0, function()
local cT = StormFox2.Time.Get()
local e_index = findNext( wGenList.weather, cT )
local i_index = findNext( wGenList.wind, cT )
local t_index = findNext( wGenList.temperature, cT )
if weather_index~= e_index then
weather_index = e_index
local w_data = wGenList.weather[e_index]
if w_data then
local delta = StormFox2.Time.SecondsUntil(w_data[1])
StormFox2.Weather.Set(w_data[2].sName, w_data[2].fAmount, delta )
if w_data[2].bThunder then
StormFox2.Thunder.SetEnabled(true, w_data[2].bThunder)
else
StormFox2.Thunder.SetEnabled(false, 0)
end
end
end
if wind_index~= i_index then
wind_index = i_index
local i_data = wGenList.wind[i_index]
local y_data = wGenList.windyaw[i_index]
if i_data then
local secs = StormFox2.Time.SecondsUntil(i_data[1])
StormFox2.Wind.SetForce(i_data[2], secs)
if y_data then
StormFox2.Wind.SetYaw( y_data[2], secs)
end
end
end
if temp_index~= t_index then
temp_index = t_index
local t_data = wGenList.temperature[t_index]
if t_data then
local delta = StormFox2.Time.SecondsUntil(t_data[1])
StormFox2.Temperature.Set(t_data[2], delta )
end
end
end)
end
local function DisableWGenerator()
if not enable then return end
enable = false
timer.Destroy("SF_WGEN_DEF")
end
hook.Add("StormFox2.Time.NextDay", "StormFox2.WGen.ND", function()
if not enable then return end
-- Generate new Day
GenerateDay()
-- Make WGen
PushDayToList()
weather_index = 0
wind_index = 0
temp_index = 0
end)
-- Logic
local function NewWGenSetting()
if not auto_weather:GetValue() then -- Auto weather is off. Make sure API is off too
DisableAPI()
DisableWGenerator()
return
end
if API_ENABLE:GetValue() == true then
EnableAPI()
DisableWGenerator()
StormFox2.Msg("Using OpenWeatherMap API")
else
DisableAPI()
EnableWGenerator()
StormFox2.Msg("Using WeatherGen")
end
end
API_ENABLE:AddCallback( NewWGenSetting, "API_Enable")
auto_weather:AddCallback( NewWGenSetting, "WGEN_Enable")
hook.Add("stormfox2.postinit", "WGenInit", NewWGenSetting)
NewWGenSetting()
hook.Add("StormFox2.data.initspawn", "StormFox2.Weather.SendForcast",function( ply )
if not hide_forecast then return end
if not forecast then return end -- ?
net.Start("StormFox2.weekweather")
net.WriteBool( forecast.unix_time )
net.WriteTable( forecast.temperature or {} )
net.WriteTable( forecast.weather or {} )
net.WriteTable( forecast.wind or {} )
net.WriteTable( forecast.windyaw or {} )
net.Broadcast()
end)

View File

@@ -0,0 +1,275 @@
--[[
| 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/
--]]
SF_AMB_SND = SF_AMB_SND or {}
SF_AMB_CHANNEL = SF_AMB_CHANNEL or {} -- [snd]{station, target_vol, current_vol}
StormFox2.Ambience = {}
--[[
- Outside Constant
- Near Outside 3D
- Near Window By Distance to nearest
- Roof By Distance to nearest
- Glass Roof (Like window) By Distance to nearest
- Metal Roof By Distance to nearest
]]
--[[ Enums
SF_AMB_CONSTANT = 0
SF_AMB_DISTANCE = 1
SF_AMB_FAKE3D = 2 -- Pans the sound towards the point
SF_AMB_USE3D = 3
]]
SF_AMB_OUTSIDE = 0 -- CONSTANT VOLUME
SF_AMB_NEAR_OUTSIDE = 1 -- DISTANCE VOLUME
SF_AMB_WINDOW = 2 -- DISTANCE VOLUME
SF_AMB_UNDER_WATER = 3 -- CONSTANT VOLUME
SF_AMB_UNDER_WATER_Z = 4 -- Z-DISTANCE VOLUME (The distance to surface)
SF_AMB_ROOF_ANY = 5 -- Z-DISTANCE (SF_AMB_ROOF_CONCRETE and SF_AMB_ROOF_GROUND will be ignored)
SF_AMB_ROOF_GLASS = 6 -- Z-DISTANCE
SF_AMB_ROOF_METAL = 7 -- Z-DISTANCE
SF_AMB_ROOF_WOOD = 8 -- Z-DISTANCE
SF_AMB_ROOF_CONCRETE = 9 -- Z-DISTANCE
SF_AMB_ROOF_GROUND = 10-- Z-DISTANCE (Default roof)
SF_AMB_ROOF_WATER = 11-- Z-DISTANCE
-- Smooth the volume of SF_AMB_CHANNEL
hook.Add("Think", "StormFox2.Ambiences.Smooth", function()
for snd,t in pairs( SF_AMB_CHANNEL ) do
if not IsValid( t[1] ) then -- In case something goes wrong. Delete the channel
SF_AMB_CHANNEL[snd] = nil
continue
end
-- Calc the new volume
local c_vol = t[3]
local newvol = math.Approach( c_vol, t[2], FrameTime() )
if c_vol == newvol then continue end
if newvol <= 0 then
-- Stop the sound and remove channel
t[1]:Stop()
SF_AMB_CHANNEL[snd] = nil
else
if system.HasFocus() then -- We don't want sound playing when gmod is unfocused.
t[1]:SetVolume( newvol )
else
t[1]:SetVolume( 0 )
end
SF_AMB_CHANNEL[snd][3] = newvol
end
end
end)
local AMB_LOAD = {}
-- Handles the sound-channel.
local function RequestChannel( snd )
if AMB_LOAD[snd] then return end -- Already loading, or error
if SF_AMB_CHANNEL[snd] then return end -- Already loaded
AMB_LOAD[snd] = true
sound.PlayFile( snd, "noblock noplay", function( station, errCode, errStr )
if ( IsValid( station ) ) then
SF_AMB_CHANNEL[snd] = {station, 0.1, 0}
station:SetVolume( 0 )
station:EnableLooping( true )
station:Play()
AMB_LOAD[snd] = nil -- Allow it to be loaded again
else
if errCode == 1 then
StormFox2.Warning("Sound Error! [1] Memory error.")
elseif errCode == 2 then
StormFox2.Warning("Sound Error! [2] Unable to locate or open: " .. snd .. ".")
else
StormFox2.Warning("Sound Error! [" .. errCode .. "] " .. errStr .. ".")
end
end
end)
end
local snd_meta = {}
snd_meta.__index = snd_meta
---Creates an ambience sound and returns a sound-object.
---@param snd string
---@param SF_AMB_TYPE number
---@param vol_scale? number
---@param min? number
---@param max? number
---@param playrate? number
---@return table
---@client
function StormFox2.Ambience.CreateAmbienceSnd( snd, SF_AMB_TYPE, vol_scale, min, max, playrate )
local t = {}
t.snd = "sound/" .. snd
t.m_vol = vol_scale or 1
t.min = min or 60
t.max = max or 300
t.SF_AMB_TYPE = SF_AMB_TYPE or SF_AMB_OUTSIDE
t.playbackrate = playrate or 1
setmetatable( t , snd_meta )
return t
end
---Returns the current sound channels / data.
---@return table
---@client
function StormFox2.Ambience.DebugList()
return SF_AMB_CHANNEL
end
-- Sets the scale of the sound
function snd_meta:SetVolume( num )
self.m_vol = math.Clamp(num, 0, 2) -- Just in case
end
-- Doesn't work on sounds with SF_AMB_OUTSIDE or SF_AMB_UNDER_WATER
function snd_meta:SetFadeDistance( min, max )
self.min = min
self.max = max
end
-- Set playback rate.
function snd_meta:SetPlaybackRate( n )
self.playbackrate = n or 1
end
-- Adds ambience for weather
hook.Add("stormfox2.preloadweather", "StormFox2.Amb.Create", function( w_meta )
function w_meta:AddAmbience( amb_object )
if not self.ambience_tab then self.ambience_tab = {} end
table.insert(self.ambience_tab, amb_object)
end
function w_meta:ClearAmbience()
self.ambience_tab = {}
end
hook.Remove("stormfox2.preloadweather", "StormFox2.Amb.Create")
end)
-- Applies the ambience sound
local function check(SF_AMB_TYPE, env)
if SF_AMB_TYPE == SF_AMB_NEAR_OUTSIDE and env.nearest_outside then return env.nearest_outside end
if SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_window then return env.nearest_window end
end
local p_br = {}
-- Forces a sound to play
local fP
---Insers ambience sound and forces it to play.
---@param snd string
---@param nVolume number
---@param playbackSpeed number
---@client
function StormFox2.Ambience.ForcePlay( snd, nVolume, playbackSpeed )
if string.sub(snd, 0, 6) ~= "sound/" then
snd = "sound/" .. snd
end
fP[snd] = nVolume
p_br[snd] = playbackSpeed or 1
end
hook.Add("Think", "StormFox2.Ambiences.Logic", function()
if not StormFox2 or not StormFox2.Weather or not StormFox2.Weather.GetCurrent then return end
local c = StormFox2.Weather.GetCurrent()
local v_pos = StormFox2.util.GetCalcView().pos
local env = StormFox2.Environment.Get()
-- Set all target volume to 0
for _,t2 in pairs( SF_AMB_CHANNEL ) do
t2[2] = 0
end
-- Generate a list of all sounds the client should hear. And set the the volume
local t = {}
if c.ambience_tab and StormFox2.Setting.SFEnabled() then
for _,amb_object in ipairs( c.ambience_tab ) do
local c_vol = t[amb_object.snd] or 0
-- WATER
if env.in_water then -- All sounds gets ignored in water. Exp SF_AMB_INWATER
if amb_object.SF_AMB_TYPE == SF_AMB_INWATER then
if c_vol > amb_object.m_vol then
continue
end
c_vol = amb_object.m_vol
elseif env.outside and amb_object.SF_AMB_TYPE == SF_AMB_UNDER_WATER_Z then
local dis = env.in_water - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
-- OUTSIDE
elseif amb_object.SF_AMB_TYPE == SF_AMB_OUTSIDE and env.outside then
if c_vol > amb_object.m_vol then
continue
end
c_vol = amb_object.m_vol -- Outside is a constant volume
-- ROOFS
elseif amb_object.SF_AMB_TYPE >= SF_AMB_ROOF_ANY and amb_object.SF_AMB_TYPE <= SF_AMB_ROOF_WATER then
if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_ANY and env.roof_z then
if env.roof_type ~= SF_AMB_ROOF_CONCRETE and env.roof_type ~= SF_AMB_ROOF_GROUND then
local dis = env.roof_z - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
elseif env.roof_z and env.roof_type then
if amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GROUND and env.roof_type == SF_DOWNFALL_HIT_GROUND then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_GLASS and env.roof_type == SF_DOWNFALL_HIT_GLASS then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_METAL and env.roof_type == SF_DOWNFALL_HIT_METAL then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WOOD and env.roof_type == SF_DOWNFALL_HIT_WOOD then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_CONCRETE and env.roof_type == SF_DOWNFALL_HIT_CONCRETE then
elseif amb_object.SF_AMB_TYPE == SF_AMB_ROOF_WATER and env.roof_type == SF_DOWNFALL_HIT_WATER then
else
continue
end
local dis = env.roof_z - v_pos.z
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if c_vol > vol then
continue
end
c_vol = vol
end
else
local pos = check( amb_object.SF_AMB_TYPE, env )
if not pos then continue end
local dis = pos:Distance( v_pos )
--if amb_object.SF_AMB_TYPE == SF_AMB_WINDOW and env.nearest_outside then
-- dis = math.max(dis, 250 - env.nearest_outside:Distance(v_pos))
--end
if dis > amb_object.max then continue end -- Too far away
local vol = math.min(1 - ( dis - amb_object.min ) / ( amb_object.max - amb_object.min ) , 1) * amb_object.m_vol
if vol <= 0 then continue end -- Vol too low
if c_vol > vol then
continue
end
c_vol = vol
end
if c_vol > 0 then
t[amb_object.snd] = c_vol
p_br[amb_object.snd] = amb_object.playbackrate
end
end
end
fP = t
hook.Run("StormFox2.Ambiences.OnSound")
-- Set the target volume
for snd, vol in pairs( t ) do
if not SF_AMB_CHANNEL[snd] then -- Request to create the sound channel
RequestChannel( snd )
else
SF_AMB_CHANNEL[snd][2] = vol -- Set the target volume
if IsValid( SF_AMB_CHANNEL[snd][1] ) then
if SF_AMB_CHANNEL[snd][1]:GetState() == 0 then -- Somehow stopped
SF_AMB_CHANNEL[snd][1]:Play()
end
if SF_AMB_CHANNEL[snd][1]:GetPlaybackRate() ~= p_br[snd] then
SF_AMB_CHANNEL[snd][1]:SetPlaybackRate(p_br[snd])
end
end
end
end
end)

View File

@@ -0,0 +1,236 @@
--[[
| 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/
--]]
-- Rain and show particles are a bit large. So we init them here
if not StormFox2.Misc then StormFox2.Misc = {} end
local m_snow = Material("particle/snow")
local m_snow_multi = Material("stormfox2/effects/snow-multi.png")
local m_rain = Material("stormfox2/effects/raindrop.png")
local m_rain_medium = Material("stormfox2/effects/raindrop2.png")
local m_rain_fog = Material("particle/particle_smokegrenade")
local rainsplash_w = Material("effects/splashwake3")
local rainsplash = Material("stormfox2/effects/rain_splash")
local m_noise = Material("particle/particle_noisesphere")
local m_fog = Material("particle/smokesprites_0014")
-- Hit particles
local function MakeRing( vPos, vNormal, L )
local p = StormFox2.DownFall.AddParticle( rainsplash_w, vPos, true )
p:SetAngles(vNormal:Angle())
p:SetStartSize(8)
p:SetEndSize(40)
p:SetDieTime(1)
p:SetEndAlpha(0)
p:SetStartAlpha(math.min(255,25 + math.random(7,10) + L * 0.9))
end
local function MakeSplash( vPos, vNormal, L, Part )
local p = StormFox2.DownFall.AddParticle( rainsplash, vPos, false )
p:SetAngles(vNormal:Angle())
local _,s = Part:GetSize()
p:SetStartSize(s / 10)
p:SetEndSize(s / 2.5)
p:SetDieTime(0.15)
p:SetEndAlpha(0)
p:SetStartAlpha(math.min(105, 30 + L * 0.9))
end
local function MakeSnowflake( vPos, vNormal, L, Part )
local p = StormFox2.DownFall.AddParticle( m_snow, vPos - vNormal, false )
p:SetAngles(vNormal:Angle())
p:SetStartSize(math.min(2,Part:GetSize()))
p:SetEndSize(0)
p:SetDieTime(5)
p:SetEndAlpha(0)
p:SetStartAlpha(math.min(255, 10 + L))
end
local pT = function(self)
local n = math.min(15, StormFox2.Weather.GetLuminance() * 0.75)
if self:GetLifeTime() < self:GetDieTime() * .25 then
self:SetStartAlpha(0)
self:SetEndAlpha( n * 8 )
elseif self:GetLifeTime() < self:GetDieTime() * .5 then
self:SetStartAlpha(n)
self:SetEndAlpha( n )
else
self:SetStartAlpha(n * 2)
self:SetEndAlpha( 0 )
end
self:SetNextThink( CurTime() )
end
local LM = 0
local vector_zero = Vector(0,0,0)
local function MakeMist( vPos, L, Part)
if LM > CurTime() then return end
--LM = CurTime() + 0.1
local w = StormFox2.Wind.GetVector()
local v = Vector(w.x * 8 + math.Rand(-10, 10), w.y * 8 + math.Rand(-10, 10) ,math.Rand(0, 10))
local ss = math.Rand(75,180)
local es = math.Rand(75,180)
local p = StormFox2.DownFall.AddParticle( m_rain_fog, vPos + Vector(0,0,math.max(es,ss) / 2), false )
if not p then return end
p:SetAirResistance(0)
p:SetNextThink( CurTime() )
p:SetDieTime( math.random(10, 15))
p:SetRoll( math.Rand(0,360) )
p:SetStartSize(ss)
p:SetEndSize(es)
p:SetEndAlpha(0)
p:SetStartAlpha(0)
p:SetThinkFunction(pT)
local c = Part:GetColor() or color_white
p:SetColor( c.r, c.g, c.b )
p:SetVelocity(v)
p:SetCollide( true )
p:SetCollideCallback( function( part ) --This is an in-line function
part:SetVelocity(vector_zero)
p:SetLifeTime(25)
end )
end
-- Make big cloud particles size shared, to fix size hitting
local init = function()
local fog_template = StormFox2.DownFall.CreateTemplate(m_fog, false)
StormFox2.Misc.fog_template = fog_template
--fog_template:SetSpeed(0.1)
fog_template:SetSize(250, 250)
function fog_template:OnHit( vPos, vNormal, nHitType, zPart )
if math.random(3) > 1 then return end -- 33% chance to spawn a splash
local L = StormFox2.Weather.GetLuminance() - 10
if nHitType == SF_DOWNFALL_HIT_WATER then
MakeRing( vPos, vNormal, L )
elseif nHitType == SF_DOWNFALL_HIT_GLASS then
MakeSplash( vPos, vNormal, L, zPart )
else -- if nHitType == SF_DOWNFALL_HIT_GROUND then
MakeSplash( vPos, vNormal, L, zPart )
end
end
local rain_template = StormFox2.DownFall.CreateTemplate(m_rain, true)
local rain_template_medium =StormFox2.DownFall.CreateTemplate(m_rain_medium,true)
local rain_template_fog = StormFox2.DownFall.CreateTemplate(m_rain_fog, true)
local snow_template = StormFox2.DownFall.CreateTemplate(m_snow, false, false)
local snow_template_multi = StormFox2.DownFall.CreateTemplate(m_snow_multi, true)
local fog_template = StormFox2.DownFall.CreateTemplate(m_rain, true) -- A "empty" particle that hits the ground, and create a fog particle on-hit.
StormFox2.Misc.rain_template = rain_template
StormFox2.Misc.rain_template_fog = rain_template_fog
StormFox2.Misc.rain_template_medium = rain_template_medium
StormFox2.Misc.snow_template = snow_template
StormFox2.Misc.snow_template_multi = snow_template_multi
StormFox2.Misc.fog_template = fog_template
--rain_template_medium
rain_template_medium:SetFadeIn( true )
rain_template_medium:SetSize(20,40)
rain_template_medium:SetRenderHeight(800)
rain_template_medium:SetAlpha(20)
--rain_template_fog
rain_template_fog:SetFadeIn( true )
rain_template_fog:SetSize(150, 600)
rain_template_fog:SetRandomAngle(0.15)
rain_template_fog:SetSpeed( 0.5 )
snow_template:SetRandomAngle(0.4)
snow_template:SetSpeed( 1 * 0.15)
snow_template:SetSize(5,5)
snow_template_multi:SetFadeIn( true )
snow_template_multi:SetSize(300,300)
--snow_template_multi:SetRenderHeight( 600 )
snow_template_multi:SetRandomAngle(0.3)
-- Think functions:
function rain_template_fog:Think()
local P = StormFox2.Weather.GetPercent()
local fC = StormFox2.Fog.GetColor()
local L = math.min(StormFox2.Weather.GetLuminance(), 100)
local TL = StormFox2.Thunder.GetLight() / 2
local speed = 0.162 * P + 0.324
self:SetColor( Color(fC.r + TL + 15, fC.g + TL + 15, fC.b + TL + 15) )
self:SetAlpha( math.min(255, math.max(0, (P - 0.5) * 525 )) )
self:SetSpeed( speed )
end
-- Particle Explosion
-- Make "rain" explosion at rain particles
function rain_template:OnExplosion( vExPos, nDisPercent, iRange, iMagnetide )
local e_ang = (self:GetPos() - vExPos):Angle():Forward()
local boost = nDisPercent * 5
local p = StormFox2.DownFall.AddParticle( "effects/splash1", vExPos + e_ang * iRange *nDisPercent , false )
p:SetStartSize(math.random(32, 20))
p:SetEndSize(5)
p:SetDieTime(2.5)
p:SetEndAlpha(0)
p:SetStartAlpha(6)
p:SetGravity( physenv.GetGravity() * 2 )
p:SetVelocity( e_ang * iMagnetide * boost)
p:SetAirResistance(3)
p:SetCollide(true)
p:SetRoll(math.random(360))
p:SetCollideCallback(function( part )
part:SetDieTime(0)
end)
end
rain_template_medium.OnExplosion = rain_template.OnExplosion
-- Particle Hit
function snow_template:OnHit( vPos, vNormal, nHitType, zPart )
if math.random(3) > 1 then return end -- 33% chance to spawn a splash
local L = StormFox2.Weather.GetLuminance() - 10
if nHitType == SF_DOWNFALL_HIT_WATER then
MakeRing( vPos, vNormal, L )
else -- if nHitType == SF_DOWNFALL_HIT_GROUND then
MakeSnowflake( vPos, vNormal, L, zPart )
end
end
function rain_template:OnHit( vPos, vNormal, nHitType, zPart )
if math.random(3) > 1 then return end -- 33% chance to spawn a splash
local L = StormFox2.Weather.GetLuminance() - 10
if nHitType == SF_DOWNFALL_HIT_WATER then
MakeRing( vPos, vNormal, L )
elseif nHitType == SF_DOWNFALL_HIT_GLASS then
MakeSplash( vPos, vNormal, L, zPart )
else -- if nHitType == SF_DOWNFALL_HIT_GROUND then
MakeSplash( vPos, vNormal, L, zPart )
end
end
function rain_template_fog:OnHit( vPos, vNormal, nHitType, zPart)
local L = StormFox2.Weather.GetLuminance() - 10
if math.random(1,3)> 2 then return end
MakeMist( vPos, L, zPart)
end
local i = 0
function snow_template_multi:OnHit( vPos, vNormal, nHitType, zPart)
if i < 10 then
i = i + 1
return
end
i = 0
local L = StormFox2.Weather.GetLuminance() - 10
MakeMist( vPos, L, zPart)
end
fog_template:SetSize(512,512)
fog_template:SetSpeed(5)
fog_template:SetAlpha(0)
function fog_template:OnHit( vPos, vNormal, nHitType, zPart)
local L = StormFox2.Weather.GetLuminance() - 10
MakeMist( vPos, L, zPart)
end
end
hook.Add("stormfox2.postlib", "stormfox2.loadParticles", init)
if StormFox2.DownFall and StormFox2.DownFall.CreateTemplate then
init()
end

View File

@@ -0,0 +1,36 @@
--[[
| 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/
--]]
-- Returns an explosion from x position
net.Receive("StormFox2.entity.explosion", function(len)
local pos = net.ReadVector()
local iRadiusOverride = net.ReadInt(16)
local iMagnitude = net.ReadUInt(16)
hook.Run("StormFox2.Entitys.OnExplosion", pos, iRadiusOverride, iMagnitude)
end)
StormFox2.Ent = {}
hook.Add("stormfox2.postlib", "stormfox2.c_ENT",function()
StormFox2.Ent.env_skypaints = true -- Always
StormFox2.Ent.env_fog_controllers = true -- Always
StormFox2.Ent.light_environments = false -- Special
for k,v in ipairs( StormFox2.Map.FindClass("light_environment") ) do
if v.targetname then
StormFox2.Ent.light_environments = true
break
end
end
StormFox2.Ent.shadow_controls = #StormFox2.Map.FindClass("shadow_control") > 0
StormFox2.Ent.env_tonemap_controllers = #StormFox2.Map.FindClass("env_tonemap_controller") > 0
StormFox2.Ent.env_winds = #StormFox2.Map.FindClass("env_wind") > 0
StormFox2.Ent.env_tonemap_controller = #StormFox2.Map.FindClass("env_tonemap_controller") > 0
hook.Remove("stormfox2.postlib", "stormfox2.c_ENT")
end)

View File

@@ -0,0 +1,130 @@
--[[
| 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/
--]]
surface.CreateFont( "SF_Display_H", {
font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = true,
size = 30,
weight = 500,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
} )
surface.CreateFont( "SF_Display_H2", {
font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = true,
size = 20,
weight = 500,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
} )
surface.CreateFont( "SF_Display_H3", {
font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = true,
size = 14,
weight = 500,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
} )
surface.CreateFont( "SF_Menu_H2", {
font = "coolvetica", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = false,
size = 20,
weight = 500,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
} )
surface.CreateFont( "SkyFox-DigitalClock", {
font = "Arial", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = false,
size = 50,
weight = 500,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = false,
} )
surface.CreateFont("SF2.W_Button", {
font = "Tahoma",
size = 15,
weight = 1500,
})
-- Tool
surface.CreateFont( "sf_tool_large", {
font = "Verdana", -- Use the font-name which is shown to you by your operating system Font Viewer, not the file name
extended = false,
size = 30,
weight = 700,
blursize = 0,
scanlines = 0,
antialias = true,
underline = false,
italic = false,
strikeout = false,
symbol = false,
rotary = false,
shadow = false,
additive = false,
outline = true,
} )
surface.CreateFont( "sf_tool_small", {
font = "Verdana",
size = 17,
weight = 1000
} )

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,538 @@
--[[
| 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/
--]]
--[[
CAMI - Common Admin Mod Interface.
Makes admin mods intercompatible and provides an abstract privilege interface
for third party addons.
IMPORTANT: This is a draft script. It is very much WIP.
Follows the specification on this page:
https://docs.google.com/document/d/1QIRVcAgZfAYf1aBl_dNV_ewR6P25wze2KmUVzlbFgMI
Structures:
CAMI_USERGROUP, defines the charactaristics of a usergroup:
{
Name
string
The name of the usergroup
Inherits
string
The name of the usergroup this usergroup inherits from
}
CAMI_PRIVILEGE, defines the charactaristics of a privilege:
{
Name
string
The name of the privilege
MinAccess
string
One of the following three: user/admin/superadmin
Description
string
optional
A text describing the purpose of the privilege
HasAccess
function(
privilege :: CAMI_PRIVILEGE,
actor :: Player,
target :: Player
) :: bool
optional
Function that decides whether a player can execute this privilege,
optionally on another player (target).
}
]]
-- Version number in YearMonthDay format.
local version = 20150902.1
if CAMI and CAMI.Version >= version then return end
CAMI = CAMI or {}
CAMI.Version = version
--[[
usergroups
Contains the registered CAMI_USERGROUP usergroup structures.
Indexed by usergroup name.
]]
local usergroups = CAMI.GetUsergroups and CAMI.GetUsergroups() or {
user = {
Name = "user",
Inherits = "user"
},
admin = {
Name = "admin",
Inherits = "user"
},
superadmin = {
Name = "superadmin",
Inherits = "admin"
}
}
--[[
privileges
Contains the registered CAMI_PRIVILEGE privilege structures.
Indexed by privilege name.
]]
local privileges = CAMI.GetPrivileges and CAMI.GetPrivileges() or {}
--[[
CAMI.RegisterUsergroup
Registers a usergroup with CAMI.
Parameters:
usergroup
CAMI_USERGROUP
(see CAMI_USERGROUP structure)
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.RegisterUsergroup function and the
CAMI.OnUsergroupRegistered hook don't cause an infinite loop
Return value:
CAMI_USERGROUP
The usergroup given as argument.
]]
function CAMI.RegisterUsergroup(usergroup, source)
usergroups[usergroup.Name] = usergroup
hook.Call("CAMI.OnUsergroupRegistered", nil, usergroup, source)
return usergroup
end
--[[
CAMI.UnregisterUsergroup
Unregisters a usergroup from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the usergroup is to be permanently removed.
Parameters:
usergroupName
string
The name of the usergroup.
source
any
Identifier for your own admin mod. Can be anything.
Use this to make sure CAMI.UnregisterUsergroup function and the
CAMI.OnUsergroupUnregistered hook don't cause an infinite loop
Return value:
bool
Whether the unregistering succeeded.
]]
function CAMI.UnregisterUsergroup(usergroupName, source)
if not usergroups[usergroupName] then return false end
local usergroup = usergroups[usergroupName]
usergroups[usergroupName] = nil
hook.Call("CAMI.OnUsergroupUnregistered", nil, usergroup, source)
return true
end
--[[
CAMI.GetUsergroups
Retrieves all registered usergroups.
Return value:
Table of CAMI_USERGROUP, indexed by their names.
]]
function CAMI.GetUsergroups()
return usergroups
end
--[[
CAMI.GetUsergroup
Receives information about a usergroup.
Return value:
CAMI_USERGROUP
Returns nil when the usergroup does not exist.
]]
function CAMI.GetUsergroup(usergroupName)
return usergroups[usergroupName]
end
--[[
CAMI.UsergroupInherits
Returns true when usergroupName1 inherits usergroupName2.
Note that usergroupName1 does not need to be a direct child.
Every usergroup trivially inherits itself.
Parameters:
usergroupName1
string
The name of the usergroup that is queried.
usergroupName2
string
The name of the usergroup of which is queried whether usergroupName1
inherits from.
Return value:
bool
Whether usergroupName1 inherits usergroupName2.
]]
function CAMI.UsergroupInherits(usergroupName1, usergroupName2)
repeat
if usergroupName1 == usergroupName2 then return true end
usergroupName1 = usergroups[usergroupName1] and
usergroups[usergroupName1].Inherits or
usergroupName1
until not usergroups[usergroupName1] or
usergroups[usergroupName1].Inherits == usergroupName1
-- One can only be sure the usergroup inherits from user if the
-- usergroup isn't registered.
return usergroupName1 == usergroupName2 or usergroupName2 == "user"
end
--[[
CAMI.InheritanceRoot
All usergroups must eventually inherit either user, admin or superadmin.
Regardless of what inheritance mechism an admin may or may not have, this
always applies.
This method always returns either user, admin or superadmin, based on what
usergroups eventually inherit.
Parameters:
usergroupName
string
The name of the usergroup of which the root of inheritance is
requested
Return value:
string
The name of the root usergroup (either user, admin or superadmin)
]]
function CAMI.InheritanceRoot(usergroupName)
if not usergroups[usergroupName] then return end
local inherits = usergroups[usergroupName].Inherits
while inherits ~= usergroups[usergroupName].Inherits do
usergroupName = usergroups[usergroupName].Inherits
end
return usergroupName
end
--[[
CAMI.RegisterPrivilege
Registers a privilege with CAMI.
Note: do NOT register all your admin mod's privileges with this function!
This function is for third party addons to register privileges
with admin mods, not for admin mods sharing the privileges amongst one
another.
Parameters:
privilege
CAMI_PRIVILEGE
See CAMI_PRIVILEGE structure.
Return value:
CAMI_PRIVILEGE
The privilege given as argument.
]]
function CAMI.RegisterPrivilege(privilege)
privileges[privilege.Name] = privilege
hook.Call("CAMI.OnPrivilegeRegistered", nil, privilege)
return privilege
end
--[[
CAMI.UnregisterPrivilege
Unregisters a privilege from CAMI. This will call a hook that will notify
all other admin mods of the removal.
Call only when the privilege is to be permanently removed.
Parameters:
privilegeName
string
The name of the privilege.
Return value:
bool
Whether the unregistering succeeded.
]]
function CAMI.UnregisterPrivilege(privilegeName)
if not privileges[privilegeName] then return false end
local privilege = privileges[privilegeName]
privileges[privilegeName] = nil
hook.Call("CAMI.OnPrivilegeUnregistered", nil, privilege)
return true
end
--[[
CAMI.GetPrivileges
Retrieves all registered privileges.
Return value:
Table of CAMI_PRIVILEGE, indexed by their names.
]]
function CAMI.GetPrivileges()
return privileges
end
--[[
CAMI.GetPrivilege
Receives information about a privilege.
Return value:
CAMI_PRIVILEGE when the privilege exists.
nil when the privilege does not exist.
]]
function CAMI.GetPrivilege(privilegeName)
return privileges[privilegeName]
end
--[[
CAMI.PlayerHasAccess
Queries whether a certain player has the right to perform a certain action.
Note: this function does NOT return an immediate result!
The result is in the callback!
Parameters:
actorPly
Player
The player of which is requested whether they have the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string)
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
None, the answer is given in the callback function in order to allow
for the admin mod to perform e.g. a database lookup.
]]
-- Default access handler
local defaultAccessHandler = {["CAMI.PlayerHasAccess"] =
function(_, actorPly, privilegeName, callback, _, extraInfoTbl)
-- The server always has access in the fallback
if not IsValid(actorPly) then return callback(true, "Fallback.") end
local priv = privileges[privilegeName]
local fallback = extraInfoTbl and (
not extraInfoTbl.Fallback and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "user" and true or
extraInfoTbl.Fallback == "admin" and actorPly:IsAdmin() or
extraInfoTbl.Fallback == "superadmin" and actorPly:IsSuperAdmin())
if not priv then return callback(fallback, "Fallback.") end
callback(
priv.MinAccess == "user" or
priv.MinAccess == "admin" and actorPly:IsAdmin() or
priv.MinAccess == "superadmin" and actorPly:IsSuperAdmin()
, "Fallback.")
end,
["CAMI.SteamIDHasAccess"] =
function(_, _, _, callback)
callback(false, "No information available.")
end
}
function CAMI.PlayerHasAccess(actorPly, privilegeName, callback, targetPly,
extraInfoTbl)
hook.Call("CAMI.PlayerHasAccess", defaultAccessHandler, actorPly,
privilegeName, callback, targetPly, extraInfoTbl)
end
--[[
CAMI.GetPlayersWithAccess
Finds the list of currently joined players who have the right to perform a
certain action.
NOTE: this function will NOT return an immediate result!
The result is in the callback!
Parameters:
privilegeName
string
The name of the privilege.
callback
function(players)
This function will be called with the list of players with access.
targetPly
Optional.
The player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
Fallback
string
Either of user/admin/superadmin. When no admin mod replies,
the decision is based on the admin status of the user.
Defaults to admin if not given.
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
]]
function CAMI.GetPlayersWithAccess(privilegeName, callback, targetPly,
extraInfoTbl)
local allowedPlys = {}
local allPlys = player.GetAll()
local countdown = #allPlys
local function onResult(ply, hasAccess, _)
countdown = countdown - 1
if hasAccess then table.insert(allowedPlys, ply) end
if countdown == 0 then callback(allowedPlys) end
end
for _, ply in pairs(allPlys) do
CAMI.PlayerHasAccess(ply, privilegeName,
function(...) onResult(ply, ...) end,
targetPly, extraInfoTbl)
end
end
--[[
CAMI.SteamIDHasAccess
Queries whether a player with a steam ID has the right to perform a certain
action.
Note: the player does not need to be in the server for this to
work.
Note: this function does NOT return an immediate result!
The result is in the callback!
Parameters:
actorSteam
Player
The SteamID of the player of which is requested whether they have
the privilege.
privilegeName
string
The name of the privilege.
callback
function(bool, string)
This function will be called with the answer. The bool signifies the
yes or no answer as to whether the player is allowed. The string
will optionally give a reason.
targetSteam
Optional.
The SteamID of the player on which the privilege is executed.
extraInfoTbl
Optional.
Table containing extra information.
Officially supported members:
IgnoreImmunity
bool
Ignore any immunity mechanisms an admin mod might have.
CommandArguments
table
Extra arguments that were given to the privilege command.
Return value:
None, the answer is given in the callback function in order to allow
for the admin mod to perform e.g. a database lookup.
]]
function CAMI.SteamIDHasAccess(actorSteam, privilegeName, callback,
targetSteam, extraInfoTbl)
hook.Call("CAMI.SteamIDHasAccess", defaultAccessHandler, actorSteam,
privilegeName, callback, targetSteam, extraInfoTbl)
end
--[[
CAMI.SignalUserGroupChanged
Signify that your admin mod has changed the usergroup of a player. This
function communicates to other admin mods what it thinks the usergroup
of a player should be.
Listen to the hook to receive the usergroup changes of other admin mods.
Parameters:
ply
Player
The player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
source
any
Identifier for your own admin mod. Can be anything.
]]
function CAMI.SignalUserGroupChanged(ply, old, new, source)
hook.Call("CAMI.PlayerUsergroupChanged", nil, ply, old, new, source)
end
--[[
CAMI.SignalSteamIDUserGroupChanged
Signify that your admin mod has changed the usergroup of a disconnected
player. This communicates to other admin mods what it thinks the usergroup
of a player should be.
Listen to the hook to receive the usergroup changes of other admin mods.
Parameters:
ply
string
The steam ID of the player for which the usergroup is changed
old
string
The previous usergroup of the player.
new
string
The new usergroup of the player.
source
any
Identifier for your own admin mod. Can be anything.
]]
function CAMI.SignalSteamIDUserGroupChanged(steamId, old, new, source)
hook.Call("CAMI.SteamIDUsergroupChanged", nil, steamId, old, new, source)
end

View File

@@ -0,0 +1,228 @@
--[[
| 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/
--]]
--[[
Unlike SF1, this doesn't support networking.
If timespeed changes, and some lerpvalues like temperature has been applied, we need to keep it syncronised.
That is why we now use Time value instead of CurTime
Data.Set( sKey, zVar, nDelta ) Sets the data. Supports lerping if given delta.
Data.Get( sKey, zDefault ) Returns the data. Returns zDefault if nil.
Data.GetFinal( zKey, zDefault) Returns the data without calculating the lerp.
Data.IsLerping( sKey ) Returns true if the data is currently lerping.
Hooks:
- StormFox2.data.change sKey zVar Called when data changed or started lerping.
- StormFox2.data.lerpstart sKey zVar Called when data started lerping
- StormFox2.data.lerpend sKey zVar Called when data stopped lerping (This will only be called if we check for the variable)
]]
StormFox2.Data = {}
StormFox_DATA = {} -- Var
StormFox_AIMDATA = {} -- Var, start, end
--[[TODO: There are still problems with nil varables.
]]
---Returns the final data, ignoring lerp. Will return zDefault as fallback.
---@param sKey string
---@param zDefault any
---@return any
---@shared
function StormFox2.Data.GetFinal( sKey, zDefault )
if StormFox_AIMDATA[sKey] then
if StormFox_AIMDATA[sKey][1] ~= nil then
return StormFox_AIMDATA[sKey][1]
else
return zDefault
end
end
if StormFox_DATA[sKey] ~= nil then
return StormFox_DATA[sKey]
end
return zDefault
end
local lerpCache = {}
local function calcFraction(start_cur, end_cur)
local n = CurTime()
if n >= end_cur then return 1 end
local d = end_cur - start_cur
return (n - start_cur) / d
end
do
local function isColor(t)
if type(t) ~= "table" then return false end
return t.r and t.g and t.b and true or false
end
local function LerpVar(fraction, from, to)
local t = type(from)
if t ~= type(to) then
--StormFox2.Warning("Can't lerp " .. type(from) .. " to " .. type(to) .. "!")
return to
end
if t == "number" then
return Lerp(fraction, from, to)
elseif t == "string" then
return fraction > .5 and to or from
elseif isColor(from) then
local r = Lerp(fraction, from.r, to.r)
local g = Lerp(fraction, from.g, to.g)
local b = Lerp(fraction, from.b, to.b)
local a = Lerp(fraction, from.a, to.a)
return Color(r,g,b,a)
elseif t == "vector" then
return LerpVector(fraction, from, to)
elseif t == "angle" then
return LerpAngle(fraction, from, to)
elseif t == "boolean" then
if fraction > .5 then
return to
else
return from
end
else
--print("UNKNOWN", t,"TO",to)
end
end
---Returns data. Will return zDefault as fallback.
---@param sKey string
---@param zDefault any
---@return any
---@shared
function StormFox2.Data.Get( sKey, zDefault )
-- Check if lerping
local var1 = StormFox_DATA[sKey]
if not StormFox_AIMDATA[sKey] then
if var1 ~= nil then
return var1
else
return zDefault
end
end
-- Check cache and return
if lerpCache[sKey] ~= nil then return lerpCache[sKey] end
-- Calc
local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3])
local var2 = StormFox_AIMDATA[sKey][1]
if fraction <= 0 then
return var1
elseif fraction < 1 then
lerpCache[sKey] = LerpVar( fraction, var1, var2 )
if not lerpCache[sKey] then
--print("DATA",sKey, zDefault)
--print(debug.traceback())
end
return lerpCache[sKey] or zDefault
else -- Fraction end
StormFox_DATA[sKey] = var2
StormFox_AIMDATA[sKey] = nil
hook.Run("StormFox2.data.lerpend",sKey,var2)
return var2 or zDefault
end
end
local n = 0
-- Reset cache after 4 frames
hook.Add("Think", "StormFox2.resetdatalerp", function()
n = n + 1
if n < 4 then return end
n = 0
lerpCache = {}
end)
end
---Sets data. Will lerp if given delta time. Use StormFox2.Network.Set if you want want to network it.
---@param sKey string
---@param zVar any
---@param nDelta any
---@shared
function StormFox2.Data.Set( sKey, zVar, nDelta )
-- Check if vars are the same
if StormFox_DATA[sKey] ~= nil and not StormFox_AIMDATA[sKey] then
if StormFox_DATA[sKey] == zVar then return end
end
-- If time is paused, there shouldn't be any lerping
if StormFox2.Time and StormFox2.Time.IsPaused and StormFox2.Time.IsPaused() then
nDelta = 0
end
-- Delete old cache
lerpCache[sKey] = nil
-- Set to nil
if not zVar and zVar == nil then
StormFox_DATA[sKey] = nil
StormFox_AIMDATA[sKey] = nil
return
end
-- If delta is 0 or below. (Or no prev data). Set it.
if not nDelta or nDelta <= 0 or StormFox_DATA[sKey] == nil or StormFox2.Time.GetSpeed_RAW() <= 0 then
StormFox_AIMDATA[sKey] = nil
StormFox_DATA[sKey] = zVar
hook.Run("StormFox2.data.change",sKey,zVar)
return
end
-- Get the current lerping value and set that as a start
if StormFox_AIMDATA[sKey] then
StormFox_DATA[sKey] = StormFox2.Data.Get( sKey )
end
StormFox_AIMDATA[sKey] = {zVar, CurTime(), CurTime() + nDelta, StormFox2.Time.GetSpeed_RAW()}
hook.Run("StormFox2.data.lerpstart",sKey,zVar, nDelta)
hook.Run("StormFox2.data.change", sKey, zVar, nDelta)
end
---Returns true if the value is currently lerping.
---@param sKey string
---@return boolean
---@shared
function StormFox2.Data.IsLerping( sKey )
if not StormFox_AIMDATA[sKey] then return false end
-- Check and see if we're done lerping
local fraction = calcFraction(StormFox_AIMDATA[sKey][2],StormFox_AIMDATA[sKey][3])
if fraction < 1 then
return true
end
-- We're done lerping.
StormFox_DATA[sKey] = StormFox2.Data.GetFinal( sKey )
StormFox_AIMDATA[sKey] = nil
lerpCache[sKey] = nil
hook.Run("StormFox2.data.lerpend",sKey,zVar)
return true
end
---Returns a CurTime for when the data is done lerping.
---@param sKey string
---@return number
---@shared
function StormFox2.Data.GetLerpEnd( sKey )
if not StormFox_AIMDATA[sKey] then return 0 end
return StormFox_AIMDATA[sKey][3]
end
-- If time changes, we need to update the lerp values
hook.Add("StormFox2.Time.Changed", "StormFox2.datatimefix", function()
local nT = StormFox2.Time.GetSpeed_RAW()
local c = CurTime()
if nT <= 0.001 then return end
for k,v in pairs( StormFox_AIMDATA ) do
if not v[4] or v[4] == nT then continue end
local now_value = StormFox2.Data.Get( k )
if not StormFox_AIMDATA[k] then continue end -- After checking the value, it is now gone.
if now_value then
StormFox_DATA[k] = now_value
end
local delta_timeamount = (v[3] - c) -- Time left
local delta_time = v[4] / nT -- Time multiplication
StormFox_AIMDATA[k][2] = c
StormFox_AIMDATA[k][3] = c + delta_timeamount * delta_time
StormFox_AIMDATA[k][4] = nT
end
end)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,90 @@
--[[
| 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/
--]]
--[[
Network.Set( sKey, zVar, nDelta ) Same as Data.Set( sKey, zVar, nDelta ) but networks it to all clients.
]]
StormFox2.Network = {}
StormFox_NETWORK = {} -- Var
if SERVER then
local tickets = {}
-- Forces a client to recive the data
function StormFox2.Network.ForceUpdate( ply )
tickets[ply] = true
net.Start(StormFox2.Net.Network)
net.WriteBool(false)
net.WriteTable(StormFox_NETWORK)
net.Send(ply)
hook.Run("StormFox2.data.initspawn", ply)
end
---Same as StormFox2.Data.Set, but networks it to all clients.
---@param sKey string
---@param zVar any
---@param nDelta any
---@server
function StormFox2.Network.Set( sKey, zVar, nDelta )
StormFox2.Data.Set(sKey, zVar, nDelta)
if StormFox_NETWORK[sKey] == zVar then return end
net.Start(StormFox2.Net.Network)
net.WriteBool(true)
net.WriteString(sKey)
net.WriteType(zVar)
net.WriteUInt(nDelta or 0, 16)
net.Broadcast()
StormFox_NETWORK[sKey] = zVar
end
---Force-set the data, ignoring cache.
---@param sKey string
---@param zVar any
---@param nDelta any
---@server
function StormFox2.Network.ForceSet( sKey, zVar, nDelta )
StormFox2.Data.Set(sKey, zVar, nDelta)
net.Start(StormFox2.Net.Network)
net.WriteBool(true)
net.WriteString(sKey)
net.WriteType(zVar)
net.WriteUInt(nDelta or 0, 16)
net.Broadcast()
StormFox_NETWORK[sKey] = zVar
end
net.Receive(StormFox2.Net.Network, function(len, ply)
if tickets[ply] then return end
tickets[ply] = true
net.Start(StormFox2.Net.Network)
net.WriteBool(false)
net.WriteTable(StormFox_NETWORK)
net.Send(ply)
hook.Run("StormFox2.data.initspawn", ply)
end)
else
net.Receive(StormFox2.Net.Network, function(len)
if net.ReadBool() then
local sKey = net.ReadString()
local zVar = net.ReadType()
local nDelta = net.ReadUInt(16)
StormFox2.Data.Set(sKey, zVar, nDelta)
else
StormFox_NETWORK = net.ReadTable()
for k,v in pairs(StormFox_NETWORK) do
StormFox2.Data.Set(k, v)
end
end
end)
-- Ask the server what data we have
hook.Add("StormFox2.InitPostEntity", "StormFox2.network", function()
net.Start(StormFox2.Net.Network)
net.SendToServer()
end)
end

View File

@@ -0,0 +1,164 @@
--[[
| 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/
--]]
StormFox2.Permission = {}
hook.Add("stormfox2.postlib", "stormfox2.privileges", function()
if not CAMI then return end
CAMI.RegisterPrivilege{
Name = "StormFox Settings",
MinAccess = "superadmin"
}
-- Permission to edit StormFox weather and time
CAMI.RegisterPrivilege{
Name = "StormFox WeatherEdit",
MinAccess = "admin"
}
end)
local SF_SERVEREDIT = 0
local SF_WEATHEREDIT= 1
if SERVER then
util.AddNetworkString("StormFox2.menu")
-- "Fake" settings
local commands = {
["cvslist"] = function( var )
StormFox2.Setting.SetCVS( tostring( var ) )
end
}
net.Receive("StormFox2.menu", function(len, ply)
local req = net.ReadBool()
if ply:IsListenServerHost() or game.SinglePlayer() then
net.Start("StormFox2.menu")
net.WriteBool(req)
net.Send( ply )
return
end
CAMI.PlayerHasAccess(ply,req and "StormFox Settings" or "StormFox WeatherEdit",function(b)
if not b then return end
net.Start("StormFox2.menu")
net.WriteBool(req)
net.Send( ply )
end)
end)
local function NoAccess(ply, msg)
if not ply then
MsgC( Color(155,155,255),"[StormFox2] ", color_white, msg )
MsgN()
return
end
net.Start( StormFox2.Net.Permission )
net.WriteString(msg)
net.Send(ply)
end
local function plyRequestSetting(ply, convar, var)
if not CAMI then return end
-- Check if its a stormfox setting
local obj = StormFox2.Setting.GetObject( convar ) or commands[ convar ]
if not obj then
if ply then
NoAccess(ply, "Invalid setting: " .. tostring(convar))
end
return false, "Not SF"
end
-- If singleplayer/host
if game.SinglePlayer() or ply:IsListenServerHost() then
if type(obj) == "function" then
obj( var )
else
obj:SetValue( var )
end
return
end
-- Check CAMI
CAMI.PlayerHasAccess(ply,"StormFox Settings",function(b)
if not b then
NoAccess(ply, "You don't have access to edit the settings!")
return
end
if type(obj) == "function" then
obj( var )
else
obj:SetValue( var )
end
end)
end
local function plyRequestEdit( ply, tID, var)
if not CAMI then return end
-- If singleplayer/host
if game.SinglePlayer() or ply:IsListenServerHost() then
return StormFox2.Menu.SetWeatherData(ply, tID, var)
end
-- Check CAMI
CAMI.PlayerHasAccess(ply,"StormFox WeatherEdit",function(b)
if not b then
NoAccess(ply, "You don't have access to edit the weather!")
return
end
StormFox2.Menu.SetWeatherData(ply, tID, var)
end)
end
net.Receive( StormFox2.Net.Permission, function(len, ply)
local t = net.ReadUInt(1)
if t == SF_SERVEREDIT then
plyRequestSetting(ply, net.ReadString(), net.ReadType())
elseif t == SF_WEATHEREDIT then
plyRequestEdit(ply, net.ReadUInt(4), net.ReadType())
end
end)
---Asks CAMI if the user has access to said permission. Will call and return onSuccess if they do.
---@param ply Player
---@param sPermission string
---@param onSuccess function
---@param ... any
---@return any|nil
---@server
function StormFox2.Permission.EditAccess(ply, sPermission, onSuccess, ...)
if not ply or ply:IsListenServerHost() then -- Console or host
return onSuccess(ply, ... )
end
local a = {...}
CAMI.PlayerHasAccess(ply,sPermission,function(b)
if not b then
NoAccess(ply, "You don't have access to edit the weather.")
return
end
onSuccess(ply, unpack(a) )
end)
end
else
net.Receive(StormFox2.Net.Permission, function(len)
local str = net.ReadString()
chat.AddText(Color(155,155,255),"[StormFox2] ", color_white, str)
end)
net.Receive("StormFox2.menu", function(len)
local n = net.ReadBool()
if n then
StormFox2.Menu._OpenSV()
else
StormFox2.Menu._OpenController()
end
end)
---Asks the server to change a setting.
---@param convar string
---@param var any
---@client
function StormFox2.Permission.RequestSetting( convar, var )
net.Start(StormFox2.Net.Permission)
net.WriteUInt(SF_SERVEREDIT, 1)
net.WriteString( convar )
net.WriteType(var)
net.SendToServer()
end
end

View File

@@ -0,0 +1,22 @@
--[[
| 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/
--]]
-- Allows to reset
if _STORMFOX_POSTENTITY then
timer.Simple(2, function()
hook.Run("StormFox2.InitPostEntity")
end)
end
hook.Add("InitPostEntity", "SF_PostEntity", function()
hook.Run("StormFox2.InitPostEntity")
_STORMFOX_POSTENTITY = true
end)

View File

@@ -0,0 +1,815 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
StormFox Settings
Handle settings and convert convars.
- Hooks: StormFox2.Setting.Change sName, vVarable
---------------------------------------------------------------------------]]
StormFox2.Setting = {}
-- Local functions and var
local NET_ALLSETTINGS = 0
local NET_UPDATE = 1
local settingCallback = {}
local settings = {}
local cache = {}
local ValueToString, StringToValue
do
function StringToValue( str, _type )
_type = _type:lower()
if ( _type == "vector" ) then return Vector( str ) end
if ( _type == "angle" ) then return Angle( str ) end
if ( _type == "float" or _type == "number" ) then return tonumber( str ) end
if ( _type == "int" ) then return math.Round( tonumber( str ) ) end
if ( _type == "bool" or _type == "boolean" ) then return tobool( str ) end
if ( _type == "string" ) then return tostring( str ) end
if ( _type == "entity" ) then return Entity( str ) end
StormFox2.Warning("Unable parse: " .. _type, true)
end
function ValueToString( var, _type )
_type = (_type or type( var )):lower()
if _type == "vector" or _type == "angle" then return string.format( "%.2f %.2f %.2f", var:Unpack() ) end
if _type == "number" or _type == "float" then return util.NiceFloat( var ) end
if _type == "int" then var = math.Round( var ) end
return tostring( var )
end
end
-- Load the settings
local mapFile, defaultFile
if SERVER then
mapFile, defaultFile = "stormfox2/sv_settings/" .. game.GetMap() .. ".json", "stormfox2/sv_settings/default.json"
else
mapFile, defaultFile = "stormfox2/cl_settings/" .. game.GetMap() .. ".json", "stormfox2/cl_settings/default.json"
end
local settingsFile = file.Exists(mapFile, "DATA") and mapFile or defaultFile
local fileData = {}
if file.Exists(settingsFile, "DATA") then
fileData = util.JSONToTable( file.Read(settingsFile, "DATA") or "" ) or {}
end
local blockSaveFile = false
local function saveToFile()
if blockSaveFile then return end
local data = {}
for sName, obj in pairs( settings ) do
if CLIENT and obj:IsServer() then continue end -- If you're the client, ignore server settings
if sName == "mapfile" then continue end
if sName == "mapfile_cl" then continue end
if obj:IsDefault() then continue end
data[sName] = obj:GetString()
end
StormFox2.FileWrite( settingsFile, util.TableToJSON(data, true) )
end
---Returns faæse if we're saving to the default json file.
---@return boolean
---@shared
function StormFox2.Setting.IsUsingMapFile()
return settingsFile == mapFile
end
---Enable/Disable saving to map-specific file.
---@param bool boolean
---@shared
function StormFox2.Setting.UseMapFile( bool )
if bool then
if settingsFile == mapFile then return end
settingsFile = mapFile
-- Settings only save once callback is done. Therefor we force this to save
local ob = blockSaveFile
blockSaveFile = false
saveToFile() -- "Copy" the settings to the file
blockSaveFile = ob
else
if settingsFile == defaultFile then return end
file.Delete(mapFile)
settingsFile = defaultFile
-- Reload the default file
fileData = util.JSONToTable( file.Read(settingsFile, "DATA") or "" ) or {}
blockSaveFile = true
for sName, var in pairs( fileData ) do
local obj = StormFox2.Setting.GetObject( sName )
if not obj then continue end
local newVar = fileData[sName] and StringToValue(fileData[sName], obj.type)
obj:SetValue( newVar )
end
blockSaveFile = false
end
end
---Forces SF2 to save the settings.
---@shared
function StormFox2.Setting.ForceSave()
blockSaveFile = false
saveToFile()
end
---Returns the file we're saving to.
---@return string
function StormFox2.Setting.GetSaveFile()
return settingsFile
end
-- Meta Table
---@class SF2Convar
---@field SetGroup function
---@field GetGroup function
---@field SetDescription function
---@field GetDescription function
local meta = {}
meta.__index = meta
AccessorFunc(meta, "group", "Group")
AccessorFunc(meta, "desc", "Description")
function meta:GetName()
return self.sName
end
function meta:GetValue()
return self.value
end
function meta:GetDescription()
return self.desc
end
function meta:IsSecret()
return self.isSecret or false
end
function meta:SetValue( var )
if self:GetValue() == var then return self end -- Ignore
StormFox2.Setting.Set(self:GetName(), var)
return self
end
function meta:GetDefault()
return self.default
end
function meta:IsDefault()
return self:GetValue() == self:GetDefault()
end
function meta:Revert()
self:SetValue( self:GetDefault() )
end
function meta:IsServer()
return self.server
end
function meta:GetType()
return self.type
end
function meta:SetMenuType( sType, tSortOrter )
StormFox2.Setting.SetType( self:GetName(), sType, tSortOrter )
return self
end
function meta:GetMin()
return self.min
end
function meta:GetMax()
return self.max
end
function meta:GetString()
return ValueToString(self:GetValue(), self:GetType())
end
function meta:IsFuzzyOn()
if not self:GetValue() then return false end
if self.type == "number" then
if self:GetValue() <= ( self:GetMin() or 0 ) then return false end
end
return true
end
function meta:SetFuzzyOn()
if self.type == "boolean" then
self:SetValue( true )
elseif self.type == "number" then
local lowest = ( self:GetMin() or 0 )
self:SetValue( lowest + 1 )
end
return self
end
function meta:SetFuzzyOff()
if self.type == "boolean" then
self:SetValue( false )
elseif self.type == "number" then
local lowest = ( self:GetMin() or 0 )
self:SetValue( lowest )
end
return self
end
function meta:SetFromString( str, type )
self:SetValue( StringToValue( str, type or self.type ) )
return self
end
function meta:AddCallback(fFunc,sID)
StormFox2.Setting.Callback(self:GetName(),fFunc,sID)
end
-- Ties the setting to others. Does require all to be booleans or form of toggles
do
local radioTab = {}
local radioTabDefault = {}
local blockLoop = false
local function callBack(vVar, oldVar, sName, id)
if blockLoop then return end -- Another setting made you change. Don't run.
local obj = settings[sName]
if not obj then StormFox2.Warning("Invalid radio-setting!", true) end
local a = radioTab[sName]
if not a then return end
-- Make sure we turned "on"
if not obj:IsFuzzyOn() then -- We got turned off. Make sure at least one is on
if not blockLoop then -- Turned off, and we didn't get called by others
local default = a[1] -- First one in list. This is to ensure self never get set.
for _, other in ipairs( a ) do
if other:IsFuzzyOn() then return end -- One of the others are on. Ignore.
if radioTabDefault[other:GetName()] then -- I'm the default one
default = other
end
end
-- All other settings are off. Try and switch the default on.
if default:GetName() == sName then -- Tell the settings we can't be turned off
return false
else
default:SetFuzzyOn()
end
end
return
end
blockLoop = true
-- Tell the others we turned on, and they have to turn off
for _, other in ipairs( a ) do
if other:GetName() == obj:GetName() then continue end -- Just to make sure we don't loop around
other:SetFuzzyOff()
end
blockLoop = false
end
local callOthers = true
-- Makes all settings turn off, if one of them are turned on.
function meta:SetRadioAll( ... )
if self:IsServer() and CLIENT then -- Not your job to keep track.
self._radioB = true
for _, other in ipairs( { ... } ) do
other._radioB = true
end
return self
end
local a = {}
-- Make sure the arguments doesn't contain itself and all is from the same realm.
local f = { ... }
for _, other in ipairs( f ) do
if other:GetName() == self:GetName() then continue end -- Don't include self
if other:IsServer() ~= self:IsServer() then -- Make sure same realm
StormFox2.Warning(other:GetName() .. " tried to tie itself to a setting from another realm!")
continue
end
table.insert(a, other)
end
if #a < 1 then StormFox2.Warning(self:GetName() .. " tried to tie itself to nothing!",true) end
-- Tell the other settings to do the same
if callOthers then
callOthers = false
for _, other in ipairs( a ) do
other:SetRadio( self, unpack( a ) )
end
callOthers = true
end
radioTab[self:GetName()] = a
StormFox2.Setting.Callback(self:GetName(),callBack,"radio_setting")
return self
end
function meta:SetRadioDefault() -- Turn on if all others are off
radioTabDefault[self:GetName()] = true
return self
end
function meta:IsRadio()
return (radioTab[self:GetName()] or self._radioB) and true or false
end
-- Tells these settings to turn off, if this setting is turned on
function meta:SetRadio( ... )
if self:IsServer() and CLIENT then -- Not your job to keep track.
self._radioB = true
return self
end
local a = {}
-- Make sure the arguments doesn't contain itself and all is from the same realm.
for _, other in ipairs( { ... } ) do
if other:GetName() == self:GetName() then continue end -- Don't include self
if other:IsServer() ~= self:IsServer() then -- Make sure same realm
StormFox2.Warning(other:GetName() .. " tried to tie itself to a setting from another realm!")
continue
end
table.insert(a, other)
end
if #a < 1 then StormFox2.Warning(self:GetName() .. " tried to tie itself to nothing!",true) end
radioTab[self:GetName()] = a
StormFox2.Setting.Callback(self:GetName(),callBack,"radio_setting")
return self
end
end
local postSettingChace = {}
-- Creates a setting and returns the setting-object
---Creates a server-side setting. Has to be called on the client to show up in the menu.
---@param sName string
---@param vDefaultVar any
---@param sDescription? string
---@param sGroup? string
---@param nMin? number
---@param nMax? number
---@return SF2Convar
---@shared
function StormFox2.Setting.AddSV(sName,vDefaultVar,sDescription,sGroup, nMin, nMax)
if settings[sName] then return settings[sName] end -- Already created
if StormFox2.Map then
vDefaultVar = StormFox2.Map.GetSetting( sName ) or vDefaultVar
end
local t = {}
setmetatable(t, meta)
t.sName = sName
t.type = type(vDefaultVar)
if SERVER then
if fileData[sName] ~= nil then
t.value = StringToValue(fileData[sName], t.type)
end
if t.value == nil then -- Check convar before setting the setting.
local con = GetConVar("sf_" .. sName)
if con then
t.value = StringToValue(con:GetString(), t.type)
end
end
if t.value == nil then -- If all fails, use the default
t.value = vDefaultVar
end
else
t.value = postSettingChace[sName] or vDefaultVar
end
t.default = vDefaultVar
t.server = true
t.min = nMin
t.max = nMax
t:SetGroup( sGroup )
t:SetDescription( sDescription )
settings[sName] = t
return t
end
-- Creates a setting and returns the setting-object
if CLIENT then
---Creates a client-side setting.
---@param sName string
---@param vDefaultVar any
---@param sDescription? string
---@param sGroup? string
---@param nMin? number
---@param nMax? number
---@return SF2Convar
---@client
function StormFox2.Setting.AddCL(sName,vDefaultVar,sDescription,sGroup, nMin, nMax)
if settings[sName] then return settings[sName] end -- Already added
local t = {}
setmetatable(t, meta)
t.sName = sName
t.type = type(vDefaultVar)
if CLIENT then
if fileData[sName] ~= nil then
t.value = StringToValue(fileData[sName], t.type)
end
if t.value == nil then
local con = GetConVar("sf_" .. sName)
if con then
t.value = StringToValue(con:GetString(), t.type)
end
end
if t.value == nil then -- If all fails, use the default
t.value = vDefaultVar
end
end
t.default = vDefaultVar
t.server = false
t.min = nMin
t.max = nMax
t:SetGroup( sGroup )
t:SetDescription( sDescription )
settings[sName] = t
return t
end
end
---Tries to onvert to the given defaultvar to match the setting.
---@param sName string
---@param sString string
---@return any
---@shared
function StormFox2.Setting.StringToType( sName, sString )
local obj = settings[sName]
if not obj then return sString end -- No idea
return StringToValue( sString, obj.type )
end
---Returns a setting and will try to convert to the given defaultvar type. Fallback to vDefaultVar if nil.
---@param sName string
---@param vDefaultVar? any
---@return any
---@shared
function StormFox2.Setting.Get(sName,vDefaultVar)
local obj = settings[sName]
if not obj then return vDefaultVar end
return obj:GetValue()
end
---Returns hte setting object.
---@param sName string
---@return SF2Convar
---@shared
function StormFox2.Setting.GetObject(sName)
return settings[sName]
end
--[[<Shared>-----------------------------------------------------------------
Sets a StormFox setting
---------------------------------------------------------------------------]]
local w_list = {
"openweathermap_key", "openweathermap_real_lat", "openweathermap_real_lon"
}
--value_type["openweathermap_key"] = "string"
--value_type["openweathermap_real_lat"] = "string"
--value_type["openweathermap_real_lon"] = "string"
local function CallBack( sName, newVar, oldVar)
if not settingCallback[sName] then return end
for id, fFunc in pairs( settingCallback[sName] ) do
if isstring(id) or IsValid(id) then
fFunc(newVar, oldVar, sName, id)
else -- Invalid
settingCallback[sName][id] = nil
end
end
end
---Tries to set a setting.
---@param sName string
---@param vVar any
---@param bDontSave boolean
---@return boolean saved
function StormFox2.Setting.Set(sName,vVar, bDontSave)
-- Check if valid
local obj = settings[sName]
if not obj then
StormFox2.Warning("Invalid setting: " .. sName .. "!")
return false
end
-- Check the variable
if obj.type ~= type(vVar) then
if type(vVar) == "string" then -- Try and convert it
vVar = StringToValue( vVar, obj.type )
else
StormFox2.Warning("Invalid variable: " .. sName .. "!")
return false
end
if vVar == nil then return false end -- Failed
end
-- Check min and max
if obj.type == "number" and obj.min then
vVar = math.max(vVar, obj.min)
end
if obj.type == "number" and obj.max then
vVar = math.min(vVar, obj.max)
end
-- Check for duplicates
local oldVar = obj:GetValue()
if oldVar == vVar then return end -- Same value, ignore
-- We need to ask the server to change this setting. This isn't ours
if CLIENT and obj:IsServer() then
if StormFox2.Permission then
StormFox2.Permission.RequestSetting(sName, vVar)
else
StormFox2.Warning("Unable to ask server to change: " .. sName .. "!")
end
return false
end
-- Save the value
-- Make callbacks
local oB = blockSaveFile -- Editing a setting, might change others. Only save after we're done
blockSaveFile = true
local oldVar = obj.value
obj.value = vVar
-- Callback
CallBack(sName, vVar, oldVar)
blockSaveFile = oB -- We're done changing settings, save if we can
if not blockSaveFile and not bDontSave then
if not (sName == "mapfile" or sName == "mapfile_cl") then
saveToFile()
end
end
cache[sName] = nil -- Delete cache
-- Tell all clients about it
if SERVER then
if not obj:IsSecret() then
net.Start( StormFox2.Net.Settings )
net.WriteUInt(NET_UPDATE, 3)
net.WriteString(sName)
net.WriteType(vVar)
net.Broadcast()
end
end
--[[<Shared>------------------------------------------------------------------
Gets called when a StormFox setting changes.
---------------------------------------------------------------------------]]
hook.Run("StormFox2.Setting.Change",sName,vVar, oldVar)
return true
end
-- Server and Clientside NET
if CLIENT then
net.Receive(StormFox2.Net.Settings,function(len)
local _type = net.ReadUInt(3)
if _type == NET_UPDATE then
local sName = net.ReadString()
local var = net.ReadType()
local obj = settings[sName]
if not obj then
StormFox2.Warning("Server tried to set an unknown setting: " .. sName)
return
end
if not obj:IsServer() then
StormFox2.Warning("Server tried to set a clientside setting: " .. sName)
else
local oldVar = obj.value
obj.value = var
cache[sName] = var
-- Callback
CallBack(sName, var, oldVar)
hook.Run("StormFox2.Setting.Change",sName,obj.value,oldVar)
end
elseif _type == NET_ALLSETTINGS then
local tab = net.ReadTable() -- I'm lazy
for sName, vVar in pairs( tab ) do
local obj = settings[sName]
if not obj then -- So this setting "might" be used later. Cache the setting-value and ignore
postSettingChace[sName] = vVar
-- StormFox2.Warning("Server tried to set an unknown setting: " .. sName)
continue
end
if not obj:IsServer() then -- This is a clientside setting. Nope.AVI
StormFox2.Warning("Server tried to set a clientside setting: " .. sName)
else
local oldVar = obj.value
obj.value = vVar
cache[sName] = vVar
-- Callback
CallBack(sName, vVar, oldVar)
hook.Run("StormFox2.Setting.Change",sName,obj.value,oldVar)
end
end
end
end)
else
hook.Add("StormFox2.data.initspawn", "StormFox2.setting.send", function( ply )
net.Start( StormFox2.Net.Settings )
net.WriteUInt(NET_ALLSETTINGS, 3)
for sName, obj in pairs( settings ) do
if not obj then continue end
if obj:IsSecret() then continue end
net.WriteType( sName )
net.WriteType( obj:GetValue() )
end
-- End of table
net.WriteType( nil )
net.Send(ply)
end)
end
---Calls the function when the given setting changes.
---fFunc will be called with: vNewVariable, vOldVariable, ConVarName, sID.
---Unlike convars, server-setings will also be triggered on the clients too.
---@param sName string
---@param fFunc function
---@param sID any
---@shared
function StormFox2.Setting.Callback(sName,fFunc,sID)
if not settingCallback[sName] then settingCallback[sName] = {} end
settingCallback[sName][sID or "default"] = fFunc
end
--hook.Add("StormFox2.Setting.Change", "StormFox2.Setting.Callbacks", function(sName, vVar, oldVar)
--if not settingCallback[sName] then return end
--for id, fFunc in pairs( settingCallback[sName] ) do
-- fFunc(vVar, oldVar, sName, id)
--end
--end)
---Same as StormFox2.Setting.Get, however this will cache the result.
---@param sName string
---@param vDefaultVar any
---@return any
---@shared
function StormFox2.Setting.GetCache(sName,vDefaultVar)
if cache[sName] then return cache[sName] end
local var = StormFox2.Setting.Get(sName,vDefaultVar)
cache[sName] = var
return var
end
---Returns the default setting
---@param sName string
---@return any
---@shared
function StormFox2.Setting.GetDefault(sName)
local obj = settings[sName]
if not obj then return nil end
return obj:GetDefault()
end
---Returns all known settings. Clients will reitrhn both server and client settings.
---@return table
---@shared
function StormFox2.Setting.GetAll()
return table.GetKeys( settings )
end
---Returns all server settings.
---@return table
---@shared
function StormFox2.Setting.GetAllServer()
-- Server only has server settings
if SERVER then return StormFox2.Setting.GetAll() end
-- Make list
local t = {}
for sName,obj in pairs(settings) do
if not obj:IsServer() then continue end
table.insert(t, sName)
end
return t
end
if CLIENT then
---Returns all client settings.
---@return table
function StormFox2.Setting.GetAllClient()
local t = {}
for sName,obj in pairs(settings) do
if obj:IsServer() then continue end
table.insert(t, sName)
end
return t
end
end
-- Returns the valuetype of the setting. This can allow special types like tables .. ect
local type_override = {}
---Returns the settigns variable type.
---@param sName string
---@return string
---@shared
function StormFox2.Setting.GetType( sName )
if type_override[sName] then return type_override[sName] end
local obj = settings[sName]
if not obj then return end
return obj.type
end
--[[ Type:
- number
- string
- float
- boolean
- A table of options { [value] = "description" }
- special_float
Marks below 0 as "off"
- time
- temp / temperature
- Time_toggle
]]
---Sets setting's variable type. Can also use special types like "time".
---@param sName string
---@param sType string
---@param tSortOrter table
---@shared
function StormFox2.Setting.SetType( sName, sType, tSortOrter )
if type(sType) == "nil" then -- Reset it
StormFox2.Warning("Can't make the setting a nil-type!")
end
if type(sType) == "boolean" then
type_override[sName] = "boolean"
elseif type(sType) == "number" then
type_override[sName] = "number"
elseif type(sType) == "table" then -- A table is a list of options
type_override[sName] = {sType, tSortOrter}
else
if sType == "bool" then
type_override[sName] = "boolean"
else
type_override[sName] = string.lower(sType)
end
end
end
-- Resets all stormfox settings to default.
if SERVER then
local obj = StormFox2.Setting.AddSV("mapfile", false)
obj:AddCallback(StormFox2.Setting.UseMapFile)
obj:SetValue( StormFox2.Setting.IsUsingMapFile() )
---Returns all settings back to default.
---@server
function StormFox2.Setting.Reset()
blockSaveFile = true
for sName, obj in pairs(settings) do
if sName == "mapfile" then continue end
obj:Revert()
end
blockSaveFile = false
saveToFile()
StormFox2.Warning("All settings were reset to default values. You should restart!")
cache = {}
end
else
StormFox2.Setting.AddSV("mapfile", false)
local obj = StormFox2.Setting.AddCL("mapfile_cl", false)
obj:AddCallback(StormFox2.Setting.UseMapFile)
obj:SetValue( StormFox2.Setting.IsUsingMapFile() )
---Returns all settings back to default.
---@client
function StormFox2.Setting.Reset()
blockSaveFile = true
for _, sName in ipairs(StormFox2.Setting.GetAllClient()) do
if sName == "mapfile_cl" then continue end
local obj = setting[sName]
if not obj then continue end
obj:Revert()
end
blockSaveFile = false
saveToFile()
StormFox2.Warning("All settings were reset to default values. You should rejoin!")
cache = {}
end
end
-- Gets and sets StormFox server setting
if SERVER then
---Parses a CVS string and applies all settings to SF2.
---@param str string
---@server
function StormFox2.Setting.SetCVS( str )
local t = string.Explode(",", str)
blockSaveFile = true
for i = 1, #t, 2 do
local sName, var = t[i], t[i+1] or nil
if string.len(sName) < 1 or not var then continue end
local obj = StormFox2.Setting.GetObject(sName )
if not obj then
StormFox2.Warning("Invalid setting: " .. sName .. ".")
continue
else
obj:SetValue(var)
end
end
blockSaveFile = false
saveToFile()
StormFox2.Warning("All settings were updated. You should restart!")
end
end
local exlist = {"openweathermap_real_lat", "openweathermap_real_lon", "openweathermap_key"}
---Compiles all server-settigns into a CVS string.
---@return string
---@shared
function StormFox2.Setting.GetCVS()
local c = ""
for sName, obj in pairs(settings) do
if obj:IsSecret() then continue end
if not obj:IsServer() then continue end
c = c .. sName .. "," .. obj:GetString() .. ","
end
return c
end
---Compiles all default server-settings into a CVS string.
---@return string
---@shared
function StormFox2.Setting.GetCVSDefault()
local c = ""
for sName, obj in pairs(settings) do
if obj:IsSecret() then continue end -- Justi n case, so people don't share hidden settings
if not obj:IsServer() then continue end
c = c .. sName .. "," .. ValueToString(obj:GetDefault(), obj:GetType()) .. ","
end
return c
end
-- Disable SF2
StormFox2.Setting.AddSV("enable", true, nil, "Start")
StormFox2.Setting.AddSV("allow_csenable", engine.ActiveGamemode() == "sandbox", nil, "Start")
if CLIENT then
StormFox2.Setting.AddCL("clenable", true, nil, "Start")
end
---Returns true if SF2 is enabled.
---@return boolean
---@shared
function StormFox2.Setting.SFEnabled()
if not StormFox2.Setting.GetCache("enable", true) then return false end
if SERVER or not StormFox2.Setting.GetCache("allow_csenable", false) then return true end
return StormFox2.Setting.GetCache("clenable", true)
end

View File

@@ -0,0 +1,371 @@
--[[
| 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/
--]]
--[[
Terrain control. Changes the ground
Terrain.Create( sName ) Creates a new terrain type and stores it
Terrain.Get( sName ) Returns the terrain.
Terrain.Set( sName ) Sets the terrain. (This should only be done serverside)
Terrain.GetCurrent() Returns the terrain obj or nil.
StormFox2.Terrain.Reset() Resets the terrain to default.
StormFox2.Terrain.HasMaterialChanged( iMaterial ) Returns true if the terrain has changed the material.
Terrain Meta:
:LockUntil( fFunc ) Makes the terrian
:MakeFootprints( sndList, sndName ) Makes footprints. Allows to overwrite footstep sounds.
:AddTextureSwap( material, basetexture, basetextire2 ) Changes a materials textures.
:RenderWindow( width, height ) A function that renders a window-texure. (Weather will trump this)
:RenderWindowRefract( width, height ) A function that renders a window-texure. (Weather will trump this)
:RenderWindow64x64( width, height ) A function that renders a window-texure. (Weather will trump this)
:RenderWindowRefract64x64( width, height ) A function that renders a window-texure. (Weather will trump this)
:Apply Applies the terrain (This won't reset old terrain)
Hooks:
StormFox2.terrain.footstep Entity foot[0 = left,1 = right] sTexture bTerrainTexture
]]
---@class SF2Terrain
local meta = {}
meta.__index = meta
meta.__tostring = function(self) return "SF_TerrainType[" .. (self.Name or "Unknwon") .. "]" end
meta.__eq = function(self, other)
if type(other) ~= "table" then return false end
if not other.Name then return false end
return other.Name == self.Name
end
debug.getregistry()["SFTerrain"] = meta
local terrains = {}
StormFox2.Terrain = {}
--- Creates a new terrain type, stores and returns it.
---@param sName string
---@return SF2Terrain
---@shared
function StormFox2.Terrain.Create( sName )
local t = {}
t.Name = sName
setmetatable(t, meta)
terrains[sName] = t
t.swap = {}
return t
end
local CURRENT_TERRAIN
---Returns the current applies terrain.
---@return SF2Terrain|nil
---@shared
function StormFox2.Terrain.GetCurrent()
return CURRENT_TERRAIN
end
---Returns a terrain by name.
---@param sName string
---@return SF2Terrain|nil
---@shared
function StormFox2.Terrain.Get( sName )
if not sName then return end
return terrains[sName]
end
-- Makes the terrain stay until this function returns true or another terrain overwrites.
---@param fFunc function
---@shared
function meta:LockUntil( fFunc )
self.lock = fFunc
end
---Sets the ground texture. e.i; snow texture.
---@param iTexture string
---@param bOnlyGround boolean
---@shared
function meta:SetGroundTexture( iTexture, bOnlyGround )
self.ground = iTexture
self.only_ground = bOnlyGround
end
-- Adds a texture swap for when this terrain gets applied.
---@param mMaterial Material|string
---@param basetexture string
---@param basetextire2 string
---@shared
function meta:AddTextureSwap( mMaterial, basetexture, basetextire2 )
if type(mMaterial) ~= "IMaterial" then
mMaterial = Material(mMaterial)
end
if not basetexture and not basetextire2 then return end
self.swap[mMaterial] = { basetexture, basetextire2 }
end
---Makes footprints and allows to overwrite default footstep sounds.
---@param bool boolean
---@param sndList? table
---@param sndName? string
---@param OnPrint? function
---@shared
function meta:MakeFootprints( bool, sndList, sndName, OnPrint )
self.footprints = bool
if sndList or sndName then
self.footprintSnds = {sndList, sndName}
end
self.footstepFunc = OnPrint
self.footstepLisen = bool or sndList or sndName or OnPrint
end
---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindow( fFunc )
self.windRender = fFunc
end
---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindowRefract( fFunc )
self.windRenderRef = fFunc
end
---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindow64x64( fFunc )
self.windRender64 = fFunc
end
---A function that renders a window-texure. (Weather will trump this)
---@param fFunc function
---@shared
function meta:RenderWindowRefract64x64( fFunc )
self.windRenderRef64 = fFunc
end
-- Texture handler
_STORMFOX_TEXCHANGES = _STORMFOX_TEXCHANGES or {} -- List of changed materials.
_STORMFOX_TEXORIGINAL = _STORMFOX_TEXORIGINAL or {} -- This is global, just in case.
local footStepLisen = false -- If set to true, will enable footprints.
local function StringTex(iTex)
if not iTex then return end
if type(iTex) == "string" then return iTex end
return iTex:GetName()
end
local function HasChanged( self, materialTexture )
local mat = self:GetName() or "unknown"
local b = materialTexture == "$basetexture2" and 2 or 1
return _STORMFOX_TEXCHANGES[mat] and _STORMFOX_TEXCHANGES[mat][b] or false
end
---Returns true if the material has changed.
---@param iMaterial Material
---@return boolean
---@shared
function StormFox2.Terrain.HasMaterialChanged( iMaterial )
local mat = iMaterial:GetName() or iMaterial
return _STORMFOX_TEXCHANGES[mat] and true or false
end
---Returns the original texture for said material.
---@param iMaterial Material
---@return stirng
---@shared
function StormFox2.Terrain.GetOriginalTexture( iMaterial )
local mat = iMaterial:GetName() or iMaterial
return _STORMFOX_TEXORIGINAL[mat] and _STORMFOX_TEXORIGINAL[mat][1]
end
-- We're going to overwrite SetTexture. As some mods might change the default texture.
local mat_meta = FindMetaTable("IMaterial")
STORMFOX_TEX_APPLY = STORMFOX_TEX_APPLY or mat_meta.SetTexture
function mat_meta:SetTexture(materialTexture, texture)
-- Check if it is basetexutre or basetexture2 we're changing.
if materialTexture ~= "$basetexture" and materialTexture ~= "$basetexture2" then
return STORMFOX_TEX_APPLY( self, materialTexture, texture )
end
-- Overwrite the original texture list.
local mat = self:GetName() or "unknown"
if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end
if materialTexture == "$basetexture" then
_STORMFOX_TEXORIGINAL[mat][1] = StringTex(texture)
else
_STORMFOX_TEXORIGINAL[mat][2] = StringTex(texture)
end
-- If we havn't changed the texture, allow change.
if not HasChanged(self, materialTexture) then
return STORMFOX_TEX_APPLY( self, materialTexture, texture )
end
end
-- Resets the material. Returns false if unable to reset.
local function ResetMaterial( self )
local mat = self:GetName() or "unknown"
if not _STORMFOX_TEXCHANGES[mat] or not _STORMFOX_TEXORIGINAL[mat] then return false end
if _STORMFOX_TEXCHANGES[mat][1] and _STORMFOX_TEXORIGINAL[mat][1] then
STORMFOX_TEX_APPLY( self, "$basetexture", _STORMFOX_TEXORIGINAL[mat][1] )
end
if _STORMFOX_TEXCHANGES[mat][2] and _STORMFOX_TEXORIGINAL[mat][2] then
STORMFOX_TEX_APPLY( self, "$basetexture2", _STORMFOX_TEXORIGINAL[mat][2] )
end
_STORMFOX_TEXCHANGES[mat] = nil
return true
end
-- Set the material
local function SetMat(self, tex1, tex2)
if not tex1 and not tex2 then return end
local mat = self:GetName() or "unknown"
-- Save the default texture
if not _STORMFOX_TEXORIGINAL[mat] then _STORMFOX_TEXORIGINAL[mat] = {} end
if tex1 and not _STORMFOX_TEXORIGINAL[mat][1] then
_STORMFOX_TEXORIGINAL[mat][1] = StringTex(self:GetTexture("$basetexture"))
end
if tex2 and not _STORMFOX_TEXORIGINAL[mat][2] then
_STORMFOX_TEXORIGINAL[mat][2] = StringTex(self:GetTexture("$basetexture2"))
end
-- Set texture
if tex1 then
if CLIENT then
STORMFOX_TEX_APPLY( self, "$basetexture", tex1 )
end
if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end
_STORMFOX_TEXCHANGES[ mat ][ 1 ] = true
end
if tex2 then
if CLIENT then
STORMFOX_TEX_APPLY( self, "$basetexture2", tex2 )
end
if not _STORMFOX_TEXCHANGES[ mat ] then _STORMFOX_TEXCHANGES[ mat ] = {} end
_STORMFOX_TEXCHANGES[ mat ][ 2 ] = true
end
end
-- Resets the terrain to default. Setting bNoUpdate to true on the server, will not notify the clients or relays.
---@param bNoUpdate boolean
---@shared
function StormFox2.Terrain.Reset( bNoUpdate )
--print("Reset")
if SERVER and not bNoUpdate then
StormFox2.Map.CallLogicRelay("terrain_clear")
end
CURRENT_TERRAIN = nil
if SERVER and not bNoUpdate then
net.Start(StormFox2.Net.Terrain)
net.WriteString( "" )
net.Broadcast()
end
if next(_STORMFOX_TEXCHANGES) == nil then return end
for tex,_ in pairs( _STORMFOX_TEXCHANGES ) do
local mat = Material( tex )
if not ResetMaterial( mat ) then
StormFox2.Warning( "Can't reset [" .. tostring( mat ) .. "]." )
end
end
_STORMFOX_TEXCHANGES = {}
end
--- Sets the terrain. (This should only be done serverside)
---@param sName string
---@return boolean
---@shared
function StormFox2.Terrain.Set( sName )
-- Apply terrain.
local t = StormFox2.Terrain.Get( sName )
if not t then
StormFox2.Terrain.Reset()
return false
end
StormFox2.Terrain.Reset( true )
t:Apply()
if SERVER then
StormFox2.Map.CallLogicRelay( "terrain_" .. string.lower(sName) )
end
return true
end
--- Reapplies the current terrain
---@shared
function StormFox2.Terrain.Update()
local terrain = StormFox2.Terrain.GetCurrent()
if not terrain then return end
StormFox2.Terrain.Reset( true )
terrain:Apply()
end
local NO_TYPE = -1
local DIRTGRASS_TYPE = 0
local ROOF_TYPE = 1
local ROAD_TYPE = 2
local PAVEMENT_TYPE = 3
local function checkType(n, bOnlkyG)
if not n then return false end
if n == 0 then return true end
if n == 1 and not bOnlkyG then return true end
return false
end
-- Applies the terrain (This won't reset old terrain)
function meta:Apply()
CURRENT_TERRAIN = self
if SERVER then
net.Start(StormFox2.Net.Terrain)
net.WriteString( CURRENT_TERRAIN.Name )
net.Broadcast()
end
-- Swap materials
if self.swap then
for mat,tab in pairs( self.swap ) do
SetMat( mat, tab[1], tab[2] )
end
end
-- Set ground
if self.ground then
for materialName,tab in pairs( StormFox2.Map.GetTextureTree() ) do
local mat = Material( materialName )
local a,b = checkType(tab[1], self.only_ground), checkType(tab[2], self.only_ground)
SetMat( mat, a and self.ground,b and self.ground)
end
end
footStepLisen = self.footprints or self.footprintSnds
end
-- NET
if SERVER then
net.Receive(StormFox2.Net.Terrain, function(len, ply) -- OI, what terrain?
net.Start(StormFox2.Net.Terrain)
net.WriteString( CURRENT_TERRAIN and CURRENT_TERRAIN.Name or "" )
net.Send(ply)
end)
else
net.Receive(StormFox2.Net.Terrain, function(len)
local sName = net.ReadString()
local b = StormFox2.Terrain.Set( sName )
--"Terrain Recived: ", sName, b)
end)
-- Ask the server
hook.Add("StormFox2.InitPostEntity", "StormFox2.terrain.init", function()
timer.Simple(1, function()
net.Start(StormFox2.Net.Terrain)
net.WriteBit(1)
net.SendToServer()
end)
end)
end
-- A bit ballsy, but lets try.
hook.Add("ShutDown","StormFox2.Terrain.Clear",function()
StormFox2.Msg("Reloading all changed materials ..")
for k, _ in pairs( _STORMFOX_TEXORIGINAL ) do
RunConsoleCommand("mat_reloadmaterial", k)
end
end)

View File

@@ -0,0 +1,256 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Useful functions
---------------------------------------------------------------------------]]
StormFox2.util = {}
local cache = {}
---Returns the OBBMins and OBBMaxs of a model.
---@param sModel string
---@return Vector MinSize
---@return Vector MaxSize
---@shared
function StormFox2.util.GetModelSize(sModel)
if cache[sModel] then return cache[sModel][1],cache[sModel][2] end
if not file.Exists(sModel,"GAME") then
cache[sModel] = {Vector(0,0,0),Vector(0,0,0)}
return cache[sModel]
end
local f = file.Open(sModel,"r", "GAME")
f:Seek(104)
local hullMin = Vector( f:ReadFloat(),f:ReadFloat(),f:ReadFloat())
local hullMax = Vector( f:ReadFloat(),f:ReadFloat(),f:ReadFloat())
f:Close()
cache[sModel] = {hullMin,hullMax}
return hullMin,hullMax
end
if CLIENT then
--[[-----------------------------------------------------------------
Calcview results
---------------------------------------------------------------------------]]
local view = {}
view.pos = Vector(0,0,0)
view.ang = Angle(0,0,0)
view.fov = 0
view.drawviewer = false
local otherPos, otherAng, otherFOV
local a = true
hook.Add("RenderScene", "StormFox2.util.EyeHack", function(pos, ang,fov)
if not a then return end
otherPos, otherAng, otherFOV = pos, ang,fov
a = false
end)
hook.Add("PostRender", "StormFox2.util.EyeHack", function()
local tab = render.GetViewSetup and render.GetViewSetup() or {}
view.pos = tab.origin or otherPos or EyePos()
view.ang = tab.angles or otherAng or EyeAngles()
view.fov = tab.fov or otherFOV or 90
view.drawviewer = LocalPlayer():ShouldDrawLocalPlayer()
a = true
end)
---Returns the last calcview result.
---@return table
---@client
function StormFox2.util.GetCalcView()
return view
end
---Returns the last camera position.
---@return Vector
---@client
function StormFox2.util.RenderPos()
return view.pos or EyePos()
end
---Returns the last camera angle.
---@return Angle
---@client
function StormFox2.util.RenderAngles()
return view.ang or RenderAngles()
end
--[[<Client>-----------------------------------------------------------------
Returns the current viewentity
---------------------------------------------------------------------------]]
local viewEntity
hook.Add("Think", "StormFox2.util.ViewEnt", function()
local lp = LocalPlayer()
if not IsValid(lp) then return end
local p = lp:GetViewEntity() or lp
if p.InVehicle and p:InVehicle() and p == lp then
viewEntity = p:GetVehicle() or p
else
viewEntity = p
end
end)
---Returns the current viewentity.
---@return Entity
---@client
function StormFox2.util.ViewEntity()
return IsValid(viewEntity) and viewEntity or LocalPlayer()
end
end
--[[
Color interpolation suck.
Mixing an orange and blue color can result in a greenish one.
This is not how sky colors work, so we make our own CCT object here that can be mixed instead.
]]
local log,Clamp,pow = math.log, math.Clamp, math.pow
---@class SF2CCT_Color
local meta = {}
function meta.__index( a, b )
return meta[b] or a._col[b]
end
meta.__MetaName = "CCT_Color"
local function CCTToRGB( nKelvin )
kelvin = math.Clamp(nKelvin, 1000, 40000)
local tmp = kelvin / 100
local r, g, b = 0,0,0
if tmp <= 66 then
r = 255
g = 99.4708025861 * log(tmp) - 161.1195681661
else
r = 329.698727446 * pow(tmp - 60, -0.1332047592)
g = 288.1221695283 * pow(tmp - 60, -0.0755148492)
end
if tmp >= 66 then
b = 255
elseif tmp <= 19 then
b = 0
else
b = 138.5177312231 * log(tmp - 10) - 305.0447927307
end
if nKelvin < 1000 then
local f = (nKelvin / 1000)
r = r * f
g = g * f
b = b * f
end
return Color(Clamp(r, 0, 255), Clamp(g, 0, 255), Clamp(b, 0, 255))
end
---Returns a CCT Color object.
---@param kelvin number
---@return SF2CCT_Color
function StormFox2.util.CCTColor( kelvin )
local t = {}
setmetatable(t, meta)
t._kelvin = kelvin
t._col = CCTToRGB( kelvin )
return t
end
function meta:ToRGB()
return self._col
end
function meta:SetKelvin( kelvin )
self._kelvin = kelvin
self._col = CCTToRGB( kelvin )
return self
end
function meta:GetKelvin()
return self._kelvin
end
function meta.__add( a, b )
local t = 0
if type( a ) == "number" then
t = b:GetKelvin() + a
elseif type( b ) == "number" then
t = a:GetKelvin() + b
else
if a.GetKelvin then
t = a:GetKelvin()
end
if b.GetKelvin then
t = t + b:GetKelvin()
end
end
return StormFox2.util.CCTColor( t )
end
function meta.__sub( a, b )
local t = 0
if type( a ) == "number" then
t = a - b:GetKelvin()
elseif type( b ) == "number" then
t = a:GetKelvin() - b
else
if a.GetKelvin then
t = a:GetKelvin()
end
if b.GetKelvin then
t = t - b:GetKelvin()
end
end
return StormFox2.util.CCTColor( t )
end
function meta.__mul( a, b )
local t = 0
if type( a ) == "number" then
t = a * b:GetKelvin()
elseif type( b ) == "number" then
t = a:GetKelvin() * b
else
if a.GetKelvin then
t = a:GetKelvin()
end
if b.GetKelvin then
t = t * b:GetKelvin()
end
end
return StormFox2.util.CCTColor( t )
end
function meta:__div( a, b )
local t = 0
if type( a ) == "number" then
t = a / b:GetKelvin()
elseif type( b ) == "number" then
t = a:GetKelvin() / b
else
if a.GetKelvin then
t = a:GetKelvin()
end
if b.GetKelvin then
t = t / b:GetKelvin()
end
end
return StormFox2.util.CCTColor( t )
end
---Renders a range of colors in the console.
---@param from number
---@param to number
---@param len number
function StormFox2.util.CCTColorDebug( from, to, len )
len = len or 60
from = from or 2200
to = to or 12000
local a = (to - from) / len
Msg(from .. " [")
for i = 1, len do
MsgC(StormFox2.util.CCTColor(from + a * i) , "" )
end
Msg("] " .. to)
MsgN()
end

View File

@@ -0,0 +1,44 @@
--[[
| 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/
--]]
-- Checks the newest version
if SERVER then
-- Checks the workshop page for version number.
local function RunCheck()
http.Fetch(StormFox2.WorkShopURL, function(code)
local lV = tonumber(string.match(code, "Version:(.-)<"))
if not lV then return end -- Unable to locate last version
if StormFox2.Version >= lV then return end -- Up to date
StormFox2.Msg("Version " .. lV .. " is out!")
StormFox2.Network.Set("stormfox_newv", lV)
cookie.Set("sf_nextv", lV)
end)
end
local function RunLogic()
-- Check if a newer version is out
local lV = cookie.GetNumber("sf_nextv", StormFox2.Version)
if cookie.GetNumber("sf_nextvcheck", 0) > os.time() then
if lV > StormFox2.Version then
StormFox2.Msg("Version " .. lV .. " is out!")
StormFox2.Network.Set("stormfox_newv", lV)
end
else
RunCheck()
cookie.Set("sf_nextvcheck", os.time() + 129600) -- Check in 1½ day
end
end
hook.Add("stormfox2.preinit", "stormfox2.checkversion", RunLogic)
end
-- Will return a version-number, if a new version is detected
---@return number
function StormFox2.NewVersion()
return StormFox2.Data.Get("stormfox_newv")
end

View File

@@ -0,0 +1,287 @@
--[[
| 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/
--]]
StormFox2.Weather = {}
local Weathers = {}
-- Diffrent stamps on where the sun are. (Remember, SF2 goes after sunrise/set)
---@class SF_SKY_STAMP : number
SF_SKY_DAY = 0
SF_SKY_SUNRISE = 1
SF_SKY_SUNSET = 2
SF_SKY_CEVIL = 3
SF_SKY_BLUE_HOUR = 4
SF_SKY_NAUTICAL = 5
SF_SKY_ASTRONOMICAL = 6
SF_SKY_NIGHT = 7
---@class SF2_WeatherType
local w_meta = {}
w_meta.__index = w_meta
w_meta.__tostring = function(self) return "SF_WeatherType[" .. (self.Name or "Unknwon") .. "]" end
w_meta.MetaName = "SF-Weather"
debug.getregistry()["SFWeather"] = w_meta
-- function for the generator. Returns true to allow. Function will be called with (day_temperature, time_start, time_duration, percent)
---@deprecated
---@param fFunc function
---@shared
function w_meta:SetRequire(fFunc)
self.Require = fFunc
end
---Runs said function when the weather is applied.
---@param fFunc function
---@shared
function w_meta:SetInit(fFunc)
self.Init = fFunc
end
---Runs said function if the weather-amount change.
---@param fFunc function
---@deprecated
---@shared
function w_meta:SetOnChange(fFunc)
self.OnChange = fFunc
end
---Will always return true
---@return boolean
---@shared
function w_meta:IsValid()
return true
end
---Creates and returns a new weather-type. Will not duplicate weather-types and instead return the one created before.
---@param sName string
---@param sInherit? string
---@return SF2_WeatherType
---@shared
function StormFox2.Weather.Add( sName, sInherit )
if Weathers[sName] then return Weathers[sName] end
local t = {}
t.ID = table.Count(Weathers) + 1
t.Name = sName
setmetatable(t, w_meta)
if sName ~= "Clear" then -- Clear shouldn't inherit itself
t.Inherit = sInherit or "Clear"
end
Weathers[sName] = t
t.Function = {}
t.Static = {}
t.Dynamic = {}
t.SunStamp = {}
return t
end
---Returns a weather-type by name.
---@param sName string
---@return SF2_WeatherType
---@shared
function StormFox2.Weather.Get( sName )
return Weathers[sName]
end
---Returns a list of all weather-types name.
---@return table
---@shared
function StormFox2.Weather.GetAll()
return table.GetKeys( Weathers )
end
---Returns all weathers that can be spawned.
---@deprecated
---@return table
---@shared
function StormFox2.Weather.GetAllSpawnable()
local t = {}
for w, v in pairs( Weathers ) do
if v.spawnable and w ~= "Clear" then -- clear is default
table.insert(t, w)
end
end
return t
end
local keys = {}
local l_e,l_c, c_c = -1,0
---Sets a key-value.
---@param sKey string
---@param zVariable any|function
---@param bStatic boolean
---@shared
function w_meta:Set(sKey,zVariable, bStatic)
keys[sKey] = true
l_c = CurTime()
if type(zVariable) == "function" then
self.Function[sKey] = zVariable
elseif bStatic then
self.Static[sKey] = zVariable
else
self.Dynamic[sKey] = zVariable
end
end
local r_list = {"Terrain", "windRender", "windRenderRef", "windRender64", "windRenderRef64"}
---Returns all keys a weather has.
---@return table
---@shared
function StormFox2.Weather.GetKeys()
if l_c == l_e then
return c_c
end
for k,v in ipairs(r_list) do
keys[v] = nil
end
l_e = l_c
c_c = table.GetKeys(keys)
return c_c
end
--- This function inserts a variable into a table. Using the STAMP as key.
---@param sKey string
---@param zVariable any
---@param stamp SF_SKY_STAMP
---@shared
function w_meta:SetSunStamp(sKey, zVariable, stamp)
keys[sKey] = true
l_c = CurTime()
if not self.SunStamp[sKey] then self.SunStamp[sKey] = {} end
self.SunStamp[sKey][stamp] = zVariable
end
--- Returns a copy of all variables with the given sunstamp, to another given sunstamp.
---@param from_STAMP SF_SKY_STAMP
---@param to_STAMP SF_SKY_STAMP
---@shared
function w_meta:CopySunStamp( from_STAMP, to_STAMP )
for sKey,v in pairs(self.SunStamp) do
if type(v) ~= "table" then continue end
if not v[from_STAMP] then continue end
self.SunStamp[sKey][to_STAMP] = v[from_STAMP] or nil
end
end
do
local in_list = {}
---Returns a variable. If the variable is a function, it will be called with the current stamp.
---Second argument will tell SF it is static and shouldn't be mixed
---@param sKey string
---@param SUNSTAMP SF_SKY_STAMP
---@return any
---@return boolean isStatic
---@shared
function w_meta:Get(sKey, SUNSTAMP )
-- Fallback to day-stamp, if Last Steamp is nil-
if not SUNSTAMP then
SUNSTAMP = StormFox2.Sky.GetLastStamp() or SF_SKY_DAY
end
if self.Function[sKey] then
return self.Function[sKey]( SUNSTAMP )
elseif self.SunStamp[sKey] then
if self.SunStamp[sKey][SUNSTAMP] ~= nil then
return self.SunStamp[sKey][SUNSTAMP]
end
-- This sunstamp isn't set, try and elevate stamp and check
if SUNSTAMP >= SF_SKY_CEVIL then
for i = SF_SKY_CEVIL + 1, SF_SKY_NIGHT do
if self.SunStamp[sKey][i] then
return self.SunStamp[sKey][i]
end
end
else
for i = SF_SKY_CEVIL - 1, SF_SKY_DAY, -1 do
if self.SunStamp[sKey][i] then
return self.SunStamp[sKey][i]
end
end
end
elseif self.Static[sKey] then
return self.Static[sKey], true
elseif self.Dynamic[sKey] then
return self.Dynamic[sKey]
end
if self.Name == "Clear" then return end
-- Check if we inherit
if not self.Inherit then return nil end
if not Weathers[self.Inherit] then return nil end -- Inherit is invalid
if in_list[self.Inherit] == true then -- Loop detected
StormFox2.Warning("WeatherData loop detected! multiple occurences of : " .. self.Inherit)
return
end
in_list[self.Name] = true
local a,b,c,d,e = Weathers[self.Inherit]:Get(sKey, SUNSTAMP)
in_list = {}
return a,b,c,d,e
end
end
---Sets the terrain for the weather. This can also be a function that returns a terrain object.
---@param zTerrain SF2Terrain|function
---@shared
function w_meta:SetTerrain( zTerrain )
self:Set( "Terrain", zTerrain )
end
---A function that renders a window-texure
---@param fFunc function
---@shared
function w_meta:RenderWindow( fFunc )
self._RenderWindow = fFunc
end
---A function that renders a window-texure
---@param fFunc function
---@shared
function w_meta:RenderWindowRefract( fFunc )
self._RenderWindowRefract = fFunc
end
---A function that renders a window-texure
---@param fFunc function
---@shared
function w_meta:RenderWindow64x64( fFunc )
self._RenderWindow64x64 = fFunc
end
---A function that renders a window-texure
---@param fFunc function
---@shared
function w_meta:RenderWindowRefract64x64( fFunc )
self._RenderWindowRefract64x64 = fFunc
end
---Returns the "lightlevel" of the skybox in a range of 0-255.
---@return number
---@shared
function StormFox2.Weather.GetLuminance()
local Col = StormFox2.Mixer.Get("bottomColor") or Color(255,255,255)
return 0.2126 * Col.r + 0.7152 * Col.g + 0.0722 * Col.b
end
-- Load the weathers once lib is done.
hook.Add("stormfox2.postlib", "stormfox2.loadweathers", function()
StormFox2.Setting.AddSV("weather_damage",true,nil, "Weather")
StormFox2.Setting.AddSV("moonsize",20,nil,"Effects", 5, 500)
hook.Run("stormfox2.preloadweather", w_meta)
for _,fil in ipairs(file.Find("stormfox2/weathers/*.lua","LUA")) do
local succ = pcall(include,"stormfox2/weathers/" .. fil)
if SERVER and succ then
AddCSLuaFile("stormfox2/weathers/" .. fil)
end
end
StormFox2.Weather.Loaded = true
hook.Run("stormfox2.postloadweather")
end)

View File

@@ -0,0 +1,570 @@
--[[
| 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/
--]]
StormFox2.Wind = StormFox2.Wind or {}
local min,max,sqrt,abs = math.min,math.max,math.sqrt,math.abs
local function ET(pos,pos2,mask,filter)
local t = util.TraceLine( {
start = pos,
endpos = pos + pos2,
mask = mask,
filter = filter
} )
t.HitPos = t.HitPos or (pos + pos2)
return t,t.HitSky
end
-- Settings
hook.Add("stormfox2.postlib", "stormfox2.windSettings",function()
StormFox2.Setting.AddSV("windmove_players",true,nil,"Weather")
StormFox2.Setting.AddSV("windmove_foliate",true,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props",false,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props_break",true,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props_unweld",true,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props_unfreeze",true,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props_max",100,nil,"Weather")
StormFox2.Setting.AddSV("windmove_props_makedebris",true,nil,"Weather")
hook.Remove("stormfox2.postlib", "stormfox2.windSettings")
end)
if SERVER then
hook.Add("stormfox2.postlib", "stormfox2.svWindInit",function()
if not StormFox2.Ent.env_winds then return end
for _,ent in ipairs( StormFox2.Ent.env_winds ) do
ent:SetKeyValue('windradius',-1) -- Make global
ent:SetKeyValue('maxgustdelay', 20)
ent:SetKeyValue('mingustdelay', 10)
ent:SetKeyValue('gustduration', 5)
end
hook.Remove("stormfox2.postlib", "stormfox2.svWindInit")
end)
---Sets the wind force. Second argument is the lerp-time.
---@param nForce number
---@param nLerpTime? number
---@server
function StormFox2.Wind.SetForce( nForce, nLerpTime )
StormFox2.Network.Set( "Wind", nForce, nLerpTime )
end
---Sets the wind yaw. Second argument is the lerp-time.
---@param nYaw number
---@param nLerpTime? number
---@server
function StormFox2.Wind.SetYaw( nYaw, nLerpTime )
StormFox2.Network.Set( "WindAngle", nYaw, nLerpTime )
end
end
---Returns the wind yaw-direction
---@return number
---@shared
function StormFox2.Wind.GetYaw()
return StormFox2.Data.Get( "WindAngle", 0 )
end
---Returns the wind force.
---@return number
---@shared
function StormFox2.Wind.GetForce()
return StormFox2.Data.Get( "Wind", 0 )
end
-- Beaufort scale and SaffirSimpson hurricane scale
local bfs = {}
bfs[0] = "sf_winddescription.calm"
bfs[0.3] = "sf_winddescription.light_air"
bfs[1.6] = "sf_winddescription.light_breeze"
bfs[3.4] = "sf_winddescription.gentle_breeze"
bfs[5.5] = "sf_winddescription.moderate_breeze"
bfs[8] = "sf_winddescription.fresh_breeze"
bfs[10.8] = "sf_winddescription.strong_breeze"
bfs[13.9] = "sf_winddescription.near_gale"
bfs[17.2] = "sf_winddescription.gale"
bfs[20.8] = "sf_winddescription.strong_gale"
bfs[24.5] = "sf_winddescription.storm"
bfs[28.5] = "sf_winddescription.violent_storm"
bfs[32.7] = "sf_winddescription.hurricane" -- Also known as cat 1
bfs[43] = "sf_winddescription.cat2"
bfs[50] = "sf_winddescription.cat3"
bfs[58] = "sf_winddescription.cat4"
bfs[70] = "sf_winddescription.cat5"
local bfkey = table.GetKeys(bfs)
table.sort(bfkey,function(a,b) return a < b end)
---Returns the current (or given wind in m/s), in a beaufort-scale and description.
---@param ms? number
---@return number
---@return string
---@shared
function StormFox2.Wind.GetBeaufort(ms)
local n = ms or StormFox2.Wind.GetForce()
local Beaufort, Description = 0, "sf_winddescription.calm"
for k,kms in ipairs( bfkey ) do
if kms <= n then
Beaufort, Description = k - 1, bfs[ kms ]
else
break
end
end
return Beaufort, Description
end
-- Spawning env_wind won't work. Therefor we need to use the cl_tree_sway_dir on the client if it's not on the map.
if CLIENT then
local function updateWind()
if StormFox2.Map.HadClass( "env_wind" ) then return end
local nw = math.min(StormFox2.Wind.GetForce() * 0.6, 21)
local ra = math.rad( StormFox2.Data.Get( "WindAngle", 0 ) )
local wx,wy = math.cos(ra) * nw,math.sin(ra) * nw
RunConsoleCommand("cl_tree_sway_dir",wx,wy)
end
hook.Add("StormFox2.Wind.Change","StormFox2.Wind.CLFix",function(windNorm, wind)
if not StormFox2.Setting.GetCache("windmove_foliate", true) then return end
updateWind()
end)
StormFox2.Setting.Callback("windmove_foliate", function(b)
if b then
updateWind()
else
RunConsoleCommand("cl_tree_sway_dir",0,0)
end
end)
else
local function updateWind(nw, ang)
if not StormFox2.Ent.env_winds then return end
local min = nw * .6
local max = nw * .8
local gust = math.min(nw, 5.5)
for _,ent in ipairs( StormFox2.Ent.env_winds ) do
if not IsValid(ent) then continue end
--print(ent, max, min ,gust)
if ang then ent:Fire('SetWindDir', ang) end
ent:SetKeyValue('minwind', min)
ent:SetKeyValue('maxwind', max)
ent:SetKeyValue('gustdirchange', math.max(0, 21 - nw))
ent:SetKeyValue('maxgust', gust)
ent:SetKeyValue('mingust', gust * .8)
end
end
hook.Add("StormFox2.Wind.Change","StormFox2.Wind.SVFix",function(windNorm, wind)
local nw = StormFox2.Wind.GetForce() * 2
local ang = StormFox2.Data.Get( "WindAngle", 0 )
updateWind(nw, ang)
end)
StormFox2.Setting.Callback("windmove_foliate", function(b)
if not StormFox2.Ent.env_winds then return end
local ang = StormFox2.Data.Get( "WindAngle", 0 )
if not b then
updateWind(0, ang)
return
end
local nw = StormFox2.Wind.GetForce() * 2
updateWind(nw, ang)
end)
end
--[[-------------------------------------------------------------------------
Calculate and update the wind direction
---------------------------------------------------------------------------]]
local windNorm = Vector(0,0,-1)
local windVec = Vector(0,0,0)
local wind,windAng = 0,-1
local function calcfunc()
local owind = StormFox2.Data.Get("Wind",0)
local nwind = owind * 0.2
local nang = StormFox2.Data.Get("WindAngle",0)
if nwind == wind and nang == windAng then return end -- Nothing changed
wind = nwind
windAng = nang
windNorm = Angle( 90 - sqrt(wind) * 10 ,windAng,0):Forward()
windVec = windNorm * wind
windNorm:Normalize()
--[[<Shared>-----------------------------------------------------------------
Gets called when the wind changes.
---------------------------------------------------------------------------]]
hook.Run("StormFox2.Wind.Change", windNorm, owind)
end
-- If the wind-data changes, is changing or is done changing. Reclaculate the wind.
timer.Create("StormFox2.Wind.Update", 1, 0, function()
if not StormFox2.Data.IsLerping("Wind") and not StormFox2.Data.IsLerping("WindAngle") then return end
calcfunc()
end)
local function dataCheck(sKey,sVar)
if sKey ~= "Wind" and sKey ~= "WindAngle" then return end
calcfunc()
end
hook.Add("StormFox2.data.change","StormFox2.Wind.Calc",dataCheck)
hook.Add("StormFox2.data.lerpend", "StormFox2.Wind.Calcfinish", dataCheck)
---Returns the wind norm.
---@return Vector
---@shared
function StormFox2.Wind.GetNorm()
return windNorm
end
---Returns the wind vector.
---@return Vector
---@shared
function StormFox2.Wind.GetVector()
return windVec
end
--[[-------------------------------------------------------------------------
Checks if an entity is out in the wind (or rain). Caches the result for 1 second.
---------------------------------------------------------------------------]]
local function IsMaterialEmpty( t )
return t.HitTexture == "TOOLS/TOOLSINVISIBLE" or t.HitTexture == "**empty**" or t.HitTexture == "TOOLS/TOOLSNODRAW"
end
local function ET_II(pos, vec, mask, filter) -- Ignore invisble brushes 'n stuff'
local lastT
for i = 1, 5 do
local t, a = ET(pos, vec, mask, filter)
if not IsMaterialEmpty(t) and t.Hit then return t, a end
lastT = lastT or t
pos = t.HitPos
end
lastT.HitSky = true
return lastT
end
local max_dis = 32400
---Checks to see if the entity is in the wind.
---@param eEnt userdata
---@param bDont_cache? boolean
---@return boolean IsInWind
---@return Vector WindNorm
---@shared
function StormFox2.Wind.IsEntityInWind(eEnt,bDont_cache)
if not IsValid(eEnt) then return end
if not bDont_cache then
if eEnt.sf_wind_var and (eEnt.sf_wind_var[2] or 0) > CurTime() then
return eEnt.sf_wind_var[1],windNorm
else
eEnt.sf_wind_var = {}
end
end
local pos = eEnt:OBBCenter() + eEnt:GetPos()
local tr = ET_II(pos, windNorm * -640000, MASK_SHOT, eEnt)
local hitSky = tr.HitSky
local dis = tr.HitPos:DistToSqr( pos )
if not hitSky and dis >= max_dis then -- So far away. The wind would had gone around. Check if we're outside.
local tr = ET(pos,Vector(0,0,640000),MASK_SHOT,eEnt)
hitSky = tr.HitSky
end
if not bDont_cache then
eEnt.sf_wind_var[1] = hitSky
eEnt.sf_wind_var[2] = CurTime() + 1
end
return hitSky,windNorm
end
-- Wind sounds
if CLIENT then
local windSnd = -1 -- -1 = none, 0 = outside, 0+ Distance to outside
local windGusts = {}
local maxVol = 1.5
local function AddGuest( snd, vol, duration )
if windGusts[snd] then return end
if not duration then duration = SoundDuration( snd ) end
windGusts[snd] = {vol * 0.4, CurTime() + duration - 1}
end
timer.Create("StormFox2.Wind.Snd", 1, 0, function()
windSnd = -1
if StormFox2.Wind.GetForce() <= 0 then return end
local env = StormFox2.Environment.Get()
if not env or (not env.outside and not env.nearest_outside) then return end
if not env.outside and env.nearest_outside then
local view = StormFox2.util.RenderPos()
windSnd = StormFox2.util.RenderPos():Distance(env.nearest_outside)
else
windSnd = 0
end
-- Guests
local vM = (400 - windSnd) / 400
if vM <= 0 then return end
local wForce = StormFox2.Wind.GetForce()
if math.random(50) > 40 then
if wForce > 17 and math.random(1,2) > 1 then
AddGuest("ambient/wind/windgust.wav",math.Rand(0.8, 1) * vM)
elseif wForce > 14 and wForce < 30 then
AddGuest("ambient/wind/wind_med" .. math.random(1,2) .. ".wav", math.min(maxVol, wForce / 30) * vM)
end
end
if wForce > 27 and math.random(50) > 30 then
AddGuest("ambient/wind/windgust_strong.wav",math.min(maxVol, wForce / 30) * vM)
end
end)
-- Cold "empty" wind: ambience/wind1.wav
-- ambient/wind/wind1.wav
-- ambient/wind/wind_rooftop1.wav
-- ambient/wind/wind1.wav
-- StormFox2.Ambience.ForcePlay
hook.Add("StormFox2.Ambiences.OnSound", "StormFox2.Ambiences.Wind", function()
if windSnd < 0 then return end -- No wind
local wForce = StormFox2.Wind.GetForce() * 0.25
local vM = (400 - windSnd) / 400
if vM <= 0 then return end
-- Main loop
StormFox2.Ambience.ForcePlay( "ambient/wind/wind_rooftop1.wav", math.min((wForce - 1) / 35, maxVol) * vM * 0.8, math.min(1.2, 0.9 + wForce / 100) )
-- Wind gusts
for snd,data in pairs(windGusts) do
if data[2] <= CurTime() then
windGusts[snd] = nil
else
StormFox2.Ambience.ForcePlay( snd, 0.2 * data[1] + math.Rand(0, 0.1) )
end
end
end)
else
-- Flag models
local flags = {}
local flag_models = {}
flag_models["models/props_fairgrounds/fairgrounds_flagpole01.mdl"] = 90
flag_models["models/props_street/flagpole_american.mdl"] = 90
flag_models["models/props_street/flagpole_american_tattered.mdl"] = 90
flag_models["models/props_street/flagpole.mdl"] = 90
flag_models["models/mapmodels/flags.mdl"] = 0
flag_models["models/props/de_cbble/cobble_flagpole.mdl"] = 180
flag_models["models/props/de_cbble/cobble_flagpole_2.mdl"] = 225
flag_models["models/props/props_gameplay/capture_flag.mdl"] = 270
flag_models["models/props_medieval/pendant_flag/pendant_flag.mdl"] = 0
flag_models["models/props_moon/parts/moon_flag.mdl"] = 0
local function FlagInit()
-- Check if there are any flags on the map
for _,ent in pairs(ents.GetAll()) do
if not ent:CreatedByMap() then continue end
-- Check the angle
if math.abs(ent:GetAngles():Forward():Dot(Vector(0,0,1))) > 5 then continue end
if not flag_models[ent:GetModel()] then continue end
table.insert(flags,ent)
end
if #flags > 0 then -- Only add the hook if there are flags on the map.
hook.Add("StormFox2.data.change","StormFox2.flagcontroller",function(key,var)
if key == "WindAngle" then
--print("Windang", var)
for _,ent in ipairs(flags) do
if not IsValid(ent) then continue end
local y = flag_models[ent:GetModel()] or 0
ent:SetAngles(Angle(0,var + y,0))
end
elseif key == "Wind" then
--print("Wind", var)
for _,ent in ipairs(flags) do
if not IsValid(ent) then continue end
ent:SetPlaybackRate(math.Clamp(var / 7,0.5,10))
end
end
end)
end
end
hook.Add("StormFox2.PostEntityScan", "StormFox2.Wind.FlagInit", FlagInit)
end
if CLIENT then return end
-- Wind movment
local function windMove(ply, mv, cmd )
if not StormFox2.Setting.GetCache("windmove_players") then return end
if( ply:GetMoveType() != MOVETYPE_WALK ) then return end
local wF = (StormFox2.Wind.GetForce() - 15) / 11
if wF <= 0 then return end
if not StormFox2.Wind.IsEntityInWind(ply) then return end -- Not in wind
-- Calc windforce
local r = math.rad( StormFox2.Wind.GetYaw() - ply:GetAngles().y )
local fS = math.cos( r ) * wF
local sS = math.sin( r ) * wF
if mv:GetForwardSpeed() == 0 and mv:GetSideSpeed() == 0 then -- Not moving
--mv:SetSideSpeed( - sS / 10) Annoying
--mv:SetForwardSpeed( - fS / 10)
else
-- GetForwardMove() returns 10000y. We need to figure out the speed first.
local running, walking = mv:KeyDown( IN_SPEED ), mv:KeyDown( IN_WALK )
local speed = running and ply:GetRunSpeed() or walking and ply:GetSlowWalkSpeed() or ply:GetWalkSpeed()
local forward = math.Clamp(mv:GetForwardSpeed(), -speed, speed)
local side = math.Clamp(mv:GetSideSpeed(), -speed, speed)
if forward~=0 and side~=0 then
forward = forward * .7
side = side * .7
end
-- Now we modify them. We don't want to move back.
if forward > 0 and fS < 0 then
forward = math.max(0, forward / -fS)
elseif forward < 0 and fS > 0 then
forward = math.min(0, forward / fS)
end
if side > 0 and sS > 0 then
side = math.max(0, side / sS)
forward = forward + fS * 20
elseif side < 0 and sS < 0 then
side = math.min(0, side / -sS)
forward = forward + fS * 20
end
-- Apply the new speed
mv:SetForwardSpeed( forward )
cmd:SetForwardMove( forward )
mv:SetSideSpeed( side )
cmd:SetSideMove( side )
end
end
hook.Add("SetupMove", "windtest", windMove)
-- Wind proppush
local move_list = {
["rpg_missile"] = true,
["npc_grenade_frag"] = true,
["npc_grenade_bugbait"] = true, -- Doesn't work
["gmod_hands"] = false,
["gmod_tool"] = false
}
local function CanMoveClass( ent )
if( IsValid( ent:GetParent()) ) then return end
local class = ent:GetClass()
if( move_list[class] == false ) then return false end
return string.match(class,"^prop_") or string.match(class,"^gmod_") or move_list[class]
end
local function ApplyWindEffect( ent, wind, windnorm )
if ent:GetPersistent() then return end
if(wind < 5) then return end
-- Make a toggle
local vol
local phys = ent:GetPhysicsObject()
if(not phys or not IsValid(phys)) then -- No physics
return
end
vol = phys:GetVolume() or 15
-- Check Move
local windPush = windnorm * 1.37 * vol * .66
--windnorm * 5.92 * (vol / 50)
local windRequ = phys:GetInertia()
windRequ = max(windRequ.x,windRequ.y)
if max(abs(windPush.x),abs(windPush.y)) < windRequ then -- Can't move
return
end
local class = ent:GetClass()
if( class != "npc_grenade_frag") then
windPush.x = math.Clamp(windPush.x, -5500, 5500)
windPush.y = math.Clamp(windPush.y, -5500, 5500)
end
-- Unfreeze
if(wind > 20) then
if( StormFox2.Setting.GetCache("windmove_props_unfreeze", true) ) then
if not phys:IsMoveable() then
phys:EnableMotion(true)
end
end
end
-- Unweld
if(wind > 30) then
if( StormFox2.Setting.GetCache("windmove_props_unweld", true) ) then
if constraint.FindConstraint( ent, "Weld" ) and math.random(1, 15) < 2 then
ent:EmitSound("physics/wood/wood_box_break" .. math.random(1,2) .. ".wav")
constraint.RemoveConstraints( ent, "Weld" )
end
end
end
-- Move
phys:Wake()
phys:ApplyForceCenter(Vector(windPush.x, windPush.y, math.max(phys:GetVelocity().z, 0)))
-- Break
if(wind > 40) then
if( StormFox2.Setting.GetCache("windmove_props_break", true) ) then
if not ent:IsVehicle() and (ent._sfnext_dmg or 0) <= CurTime() and ent:GetClass() != "npc_grenade_frag" then
ent._sfnext_dmg = CurTime() + 0.5
ent:TakeDamage(ent:Health() / 10 + 2,game.GetWorld(),game.GetWorld())
end
end
end
end
local move_tab = {}
local current_prop = 0
local function AddEntity( ent )
if( ent._sfwindcan or 0 ) > CurTime() then return end
if( StormFox2.Setting.GetCache("windmove_props_max", 50) <= table.Count(move_tab) ) then return end -- Too many props moving atm
move_tab[ ent ] = CurTime()
--ApplyWindEffect( ent, StormFox2.Wind.GetForce(), StormFox2.Wind.GetNorm() )
end
hook.Add("OnEntityCreated","StormFox.Wind.PropMove",function(ent)
if( not StormFox2.Setting.GetCache("windmove_props", false) ) then return end
if( not IsValid(ent) ) then return end
if( not CanMoveClass( ent ) ) then return end
AddEntity( ent )
end)
local scanID = 0
local function ScanProps()
local t = ents.GetAll()
if( #t < scanID) then
scanID = 0
end
for i = scanID, math.min(#t, scanID + 30) do
local ent = t[i]
if(not IsValid( ent )) then break end
if ent:GetPersistent() then continue end
if not CanMoveClass( ent ) then continue end
if move_tab[ ent ] then continue end -- Already added
if not StormFox2.Wind.IsEntityInWind( ent ) then continue end -- Not in wind
AddEntity( ent )
end
scanID = scanID + 30
end
local next_loop = 0 -- We shouldn't run this on think if there arae too few props
hook.Add("Think","StormFox.Wind.EffectProps",function()
if( not StormFox2.Setting.GetCache("windmove_props", false) ) then return end
if( next_loop > CurTime() ) then return end
next_loop = CurTime() + (game.SinglePlayer() and 0.1 or 0.2)
-- Scan on all entities. This would be slow. But we're only looking at 30 entities at a time.
ScanProps()
local t = table.GetKeys( move_tab )
if(#t < 1) then return end
local wind = StormFox2.Wind.GetForce()
-- Check if there is wind
if( wind < 10) then
table.Empty( move_tab )
return
end
if( current_prop > #t) then
current_prop = 1
end
local wind, c_windnorm = StormFox2.Wind.GetForce(), StormFox2.Wind.GetNorm()
local windnorm = Vector(c_windnorm.x, c_windnorm.y, 0)
local c = CurTime()
for i = current_prop, math.min(#t, current_prop + 30) do
local ent = t[i]
if(not ent) then
break
end
-- Check if valid
if(not IsValid( ent ) or not StormFox2.Wind.IsEntityInWind( ent )) then
move_tab[ent] = nil
continue
end
-- Check if presistence
if ent:GetPersistent() then continue end
-- If the entity has been in the wind for over 10 seconds, try and move on and see if we can pick up something else
if(move_tab[ ent ] < c - 10) then
ent._sfwindcan = c + math.random(20, 30)
if(StormFox2.Setting.GetCache("windmove_props_makedebris", true)) then
ent:SetCollisionGroup( COLLISION_GROUP_DEBRIS )
end
move_tab[ent] = nil
continue
end
ApplyWindEffect( ent, wind, windnorm )
end
current_prop = current_prop + 30
end)

View File

@@ -0,0 +1,37 @@
--[[
| 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/
--]]
-- Adds SF content
if not StormFox2.WorkShopVersion then
local i = 0
local function AddDir(dir,dirlen)
if not dirlen then dirlen = dir:len() end
local files, folders = file.Find(dir .. "/*", "GAME")
for _, fdir in ipairs(folders) do
if fdir ~= ".svn" then
AddDir(dir .. "/" .. fdir)
end
end
for k, v in ipairs(files) do
local fil = dir .. "/" .. v
resource.AddFile(fil)
i = i + 1
end
end
AddDir("materials/stormfox2")
AddDir("sound/stormfox2")
AddDir("models/stormfox2")
StormFox2.Msg("Added " .. i .. " content files.")
-- Add the workshop
else
resource.AddWorkshop(string.match(StormFox2.WorkShopURL, "%d+$"))
StormFox2.Msg("Added content files from workshop.")
end

View File

@@ -0,0 +1,80 @@
--[[
| 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/
--]]
--[[-------------------------------------------------------------------------
Creates or finds map entities
Hook:
StormFox2.PostEntityScan Gets called after StormFox have located the map entities.
Convar:
sf_enable_mapsupport
---------------------------------------------------------------------------]]
StormFox2.Ent = {}
CreateConVar("sf_enable_mapsupport","1",{FCVAR_REPLICATED,FCVAR_ARCHIVE},"StormFox2.setting.mapsupport")
-- Find or creat entities
local function GetOrCreate(str,only_get)
local l = ents.FindByClass(str)
local con = GetConVar("sf_enable_mapsupport")
if #l > 0 then
local s = string.rep(" ",24 - #str)
MsgC( " ", Color(255,255,255), str, s, Color(55,255,55), "Found", Color( 255, 255, 255), "\n" )
return l
end
if not con:GetBool() or only_get then -- Disabled mapsupport or don't create
local s = string.rep(" ",24 - #str)
MsgC( " ", Color(255,255,255), str, s, Color(255,55,55), "Not found", Color( 255, 255, 255), "\n" )
return
end
local ent = ents.Create(str)
ent:Spawn();
ent:Activate();
ent._sfcreated = true
local s = string.rep(" ",24 - #str)
MsgC( " ", Color(255,255,255), str, s, Color(155,155,255), "Created", Color( 255, 255, 255), "\n" )
return {ent}
end
-- We need to use this function, as some entities spawn regardless of what the map has.
local function findEntities()
StormFox2.Msg( "Scanning mapentities ..." )
local tSunlist = ents.FindByClass( "env_sun" )
for i = 1, #tSunlist do -- Remove any env_suns, there should be only one but who knows
tSunlist[ i ]:Fire( "TurnOff" )
end
StormFox2.Ent.env_skypaints = GetOrCreate( "env_skypaint" )
StormFox2.Ent.light_environments = GetOrCreate( "light_environment", true)
StormFox2.Ent.env_fog_controllers = GetOrCreate( "env_fog_controller" )
StormFox2.Ent.shadow_controls = GetOrCreate( "shadow_control", true )
StormFox2.Ent.env_tonemap_controllers = GetOrCreate("env_tonemap_controller", true )
StormFox2.Ent.env_winds = GetOrCreate("env_wind", true ) -- Can't spawn the wind controller without problems
StormFox2.Ent.env_tonemap_controller = GetOrCreate( "env_tonemap_controller", true)
-- Kill TF2 sun
for k,v in ipairs(ents.FindByModel("models/props_skybox/sunnoon.mdl")) do
if v:IsValid() then
v:SetNoDraw( true )
end
end
--[[-------------------------------------------------------------------------
Gets called when StormFox has handled map-entities.
---------------------------------------------------------------------------]]
hook.Run( "StormFox2.PostEntityScan" )
end
-- If this is first run, wait for InitPostEntity.
hook.Add("StormFox2.InitPostEntity","StormFox2.Entities",findEntities)
-- Tell clients about explosions
util.AddNetworkString("StormFox2.entity.explosion")
hook.Add("EntityRemoved","StormFox2.Entitys.Explosion",function(ent)
if ent:GetClass() ~= "env_explosion" then return end
local t = ent:GetKeyValues()
net.Start("StormFox2.entity.explosion")
net.WriteVector(ent:GetPos())
net.WriteUInt(t.iRadiusOverride or t.iMagnitude, 16)
net.WriteUInt(t.iMagnitude, 16)
net.SendPVS(ent:GetPos())
hook.Run("StormFox2.Entitys.OnExplosion", ent:GetPos(), t.iRadiusOverride, t.iMagnitude)
end)

View File

@@ -0,0 +1,139 @@
--[[
| 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/
--]]
--[[
Implemented as described here:
http://flafla2.github.io/2014/08/09/perlinnoise.html
Copied from: https://gist.github.com/SilentSpike/25758d37f8e3872e1636d90ad41fe2ed
]]--
local floor,band,clamp,max = math.floor,bit.band,math.Clamp,math.max
perlin = {}
local p = {}
-- Hash lookup table as defined by Ken Perlin
-- This is a randomly arranged array of all numbers from 0-255 inclusive
local permutation = {151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
}
-- p is used to hash unit cube coordinates to [0, 255]
for i = 0,255 do
-- Convert to 0 based index table
p[i] = permutation[i + 1]
-- Repeat the array to avoid buffer overflow in hash function
p[i + 256] = permutation[i + 1]
end
-- Functions
local dot_product = {
[0x0] = function(x,y,z) return x + y end,
[0x1] = function(x,y,z) return -x + y end,
[0x2] = function(x,y,z) return x - y end,
[0x3] = function(x,y,z) return -x - y end,
[0x4] = function(x,y,z) return x + z end,
[0x5] = function(x,y,z) return -x + z end,
[0x6] = function(x,y,z) return x - z end,
[0x7] = function(x,y,z) return -x - z end,
[0x8] = function(x,y,z) return y + z end,
[0x9] = function(x,y,z) return -y + z end,
[0xA] = function(x,y,z) return y - z end,
[0xB] = function(x,y,z) return -y - z end,
[0xC] = function(x,y,z) return y + x end,
[0xD] = function(x,y,z) return -y + z end,
[0xE] = function(x,y,z) return y - x end,
[0xF] = function(x,y,z) return -y - z end
}
local function grad(hash, x, y, z)
return dot_product[band(hash,0xF)](x,y,z)
end
local function fade(t)
return t * t * t * (t * (t * 6 - 15) + 10)
end
local function lerp(t, a, b)
return a + t * (b - a)
end
function perlin.noise(x, y, z, zoom) -- [-1 , 1]
zoom = zoom or 100
x = x / zoom
y = y and y / zoom or 0
z = z and z / zoom or 0
-- Calculate the "unit cube" that the point asked will be located in
local xi = floor(x) % 256
local yi = floor(y) % 256
local zi = floor(z) % 256
-- Next we calculate the location (from 0 to 1) in that cube
x = x - floor(x)
y = y - floor(y)
z = z - floor(z)
-- We also fade the location to smooth the result
local u = fade(x)
local v = fade(y)
local w = fade(z)
-- Hash all 8 unit cube coordinates surrounding input coordinate
local A = p[xi ] + yi
local AA = p[A ] + zi
local AB = p[A + 1 ] + zi
local AAA = p[ AA ]
local ABA = p[ AB ]
local AAB = p[ AA + 1 ]
local ABB = p[ AB + 1 ]
local B = p[xi + 1] + yi
local BA = p[B ] + zi
local BB = p[B + 1 ] + zi
local BAA = p[ BA ]
local BBA = p[ BB ]
local BAB = p[ BA + 1 ]
local BBB = p[ BB + 1 ]
-- Take the weighted average between all 8 unit cube coordinates
return lerp(w,
lerp(v,
lerp(u,
grad(AAA,x,y,z),
grad(BAA,x-1,y,z)
),
lerp(u,
grad(ABA,x,y-1,z),
grad(BBA,x-1,y-1,z)
)
),
lerp(v,
lerp(u,
grad(AAB,x,y,z-1), grad(BAB,x-1,y,z-1)
),
lerp(u,
grad(ABB,x,y-1,z-1), grad(BBB,x-1,y-1,z-1)
)
)
)
end
function perlin.range(x, y ,z, zoom) -- [0 - 1]
return (1 + perlinnoise(x, y, z, zoom)) / 2
end
function perlin.rangeSub(x, y, z , zoom, n)
return max(0,(perlin.range(x, y ,z, zoom) - n ) / (1 - n))
end

126
lua/stormfox2/sh_cwi.lua Normal file
View File

@@ -0,0 +1,126 @@
--[[
| 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/
--]]
--[[
Commen Weather Interface.
Still WIP!
]]
local Version = 0.02
if CWI and CWI.Version > Version then return end
CWI = {}
CWI.Version = Version
CWI.NotSupported = 12345
CWI.WeatherMod = "StormFox 2"
-- Time
if SERVER then
--[[
Sets the time by using a number between 0 - 1440
720 being midday
]]
function CWI.SetTime( num )
StormFox2.Time.Set( num )
end
--[[
Sets the timespeed
1 = real time
60 = 60x real time speed
]]
CWI.SetTimeSpeed = StormFox2.Time.SetSpeed
end
--[[
Returns the time as a number between 0 - 1440
720 being midday
]]
CWI.GetTime = StormFox2.Time.Get
--[[
Returns the timespeed.
]]
CWI.GetTimeSpeed = StormFox2.Time.GetSpeed
-- Easy day/night variables
CWI.IsDay = function() return StormFox2.Sun.IsUp() end
CWI.IsNight = function() return not StormFox2.Sun.IsUp() end
-- Weather
--[[
Sets the weather
]]
if SERVER then
function CWI.SetWeather( str )
str = string.upper(str[1]) .. string.sub(str, 2):lower()
StormFox2.Weather.Set( str )
end
end
-- Returns the current weather
function CWI.GetWeather()
return StormFox2.Weather.GetCurrent().Name:lower()
end
-- Returns a list of all weather-types
CWI.DefaultWeather = "clear"
function CWI.GetWeathers()
local t = {}
for _, str in ipairs( StormFox2.Weather.GetAll() ) do
table.insert(t, StormFox2.Weather.Get(str).Name:lower())
end
return t
end
-- Downfall
CWI.IsRaining = StormFox2.Weather.IsRaining
CWI.IsSnowing = StormFox2.Weather.IsSnowing
-- Time Functions
function CWI.GetHours( bUse12Hour )
if not bUse12Hour then return math.floor( CWI.GetTime() / 60 ) end
local h = math.floor( CWI.GetTime() / 60 )
local b = ( h < 12 or h == 0 ) and "AM" or "PM"
if h == 0 then
h = 12
elseif h > 12 then
h = h - 12
end
return h, b
end
function CWI.GetMinutes()
return math.floor( CWI.GetTime() % 60 )
end
function CWI.GetSeconds()
return math.floor( CWI.GetTime() % 1 ) * 60
end
function CWI.TimeToString( bUse12Hour )
local h, e = CWI.GetHours( bUse12Hour )
return h .. ":" .. CWI.GetMinutes() .. (e and " " .. e or "")
end
--[[
Hook: CWI.NewWeather, weatherName
]]
local lastW = "Unknown"
hook.Add("StormFox2.weather.postchange", "CWI.CallNewWeather", function( sName )
if lastW == sName then return end
lastW = sName
hook.Run("CWI.NewWeather", sName:lower())
end)
--[[
Hook: CWI.NewDay, dayNumber 0 - 365
]]
hook.Add("StormFox2.Time.NextDay", "CWI.CallNewDay", function( )
hook.Run("CWI.NewDay")
end)
--[[
Hook: CWI.Init
]]
hook.Add("stormfox2.postinit", "SFCWI.Init", function()
hook.Run("CWI.Init")
end)

View File

@@ -0,0 +1,152 @@
--[[
| 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/
--]]
-- SF_TextEntry
-- Supports for temperature conversion and other things
do
local PANEL = {}
AccessorFunc( PANEL, "m_bTemp", "Temperature" )
AccessorFunc( PANEL, "m_sUnity", "Unit" )
local function convFromClient( val )
return StormFox2.Temperature.Convert(StormFox2.Temperature.GetDisplayType(),nil ,tonumber(val))
end
local function convToClient( val )
return StormFox2.Temperature.Convert(nil, StormFox2.Temperature.GetDisplayType() ,tonumber(val))
end
-- Hacky solution to units.
local _oldDTET = vgui.GetControlTable("DTextEntry").DrawTextEntryText
function PANEL:DrawTextEntryText( ... )
if not self:GetUnit() then
return _oldDTET( self, ... )
end
local s = self:GetText()
self:SetText(s .. self:GetUnit())
_oldDTET( self, ... )
self:SetText(s)
end
-- Converts the text displayed and typed unto the temperature unit.
function PANEL:SetTemperature( b )
self.m_bTemp = b
if b then
self:SetUnit( StormFox2.Temperature.GetDisplaySymbol() )
else
self:SetUnit(nil)
end
end
-- Overwrite the values for celcius.
function PANEL:SetValue( strValue )
if ( vgui.GetKeyboardFocus() == self ) then return end
local CaretPos = self:GetCaretPos()
strValue = self.m_bTemp and convToClient(strValue) or strValue
self:SetText( strValue )
self:OnValueChange( strValue )
self:SetCaretPos( CaretPos )
end
function PANEL:UpdateConvarValue()
self:ConVarChanged( self.m_bTemp and convFromClient(self:GetValue()) or self:GetValue() )
end
derma.DefineControl( "SF_TextEntry", "SF TextEntry", PANEL, "TextEntry" )
end
-- SF_TextBox
-- Wraos text
do
local function wrapText(sText, wide)
wide = wide - 10
local tw,th = surface.GetTextSize(language.GetPhrase(sText))
local lines,b = 1, false
local s = ""
for w in string.gmatch(sText, "[^%s,]+") do
local tt = s .. (b and " " or "") .. w
if surface.GetTextSize(tt) >= wide then
s = s .. "\n" .. w
lines = lines + 1
else
s = tt
end
b = true
end
return s, lines, th
end
local PANEL = {}
function PANEL:PerformLayout(w, h)
local text, lines, th = wrapText(self:GetText(), self:GetWide() - self._b:GetWide())
self._d:SetText(text)
self._d:SizeToContents()
local nh = math.max( th, lines * th)
if nh > h then
self:SetTall( math.max( th, lines * th) )
end
end
derma.DefineControl( "SF_TextBox", "SF TextBox", PANEL, "DLabel" )
end
-- SF_Slider
-- Doesn't spam convar
do
local function paintKnob(self,x, y) -- Skin doesn't have x or y pos
local skin = self:GetSkin()
if ( self:GetDisabled() ) then return skin.tex.Input.Slider.H.Disabled( x, y, 15, 15 ) end
if ( self.Depressed ) then
return skin.tex.Input.Slider.H.Down( x, y, 15, 15 )
end
if ( self.Hovered ) then
return skin.tex.Input.Slider.H.Hover( x, y, 15, 15 )
end
skin.tex.Input.Slider.H.Normal( x, y, 15, 15 )
end
local PANEL = {}
AccessorFunc( PANEL, "m_max", "Max" )
AccessorFunc( PANEL, "m_min", "Min" )
AccessorFunc( PANEL, "m_fDecimal", "Decimals" )
AccessorFunc( PANEL, "m_bLiveUpdate", "UpdateLive" )
AccessorFunc( PANEL, "m_fFloatValue", "FloatValue" )
Derma_Install_Convar_Functions( PANEL )
function PANEL:Init()
self:SetMin( 0 )
self:SetMax( 10 )
self:SetDecimals( 2 )
self:SetFloatValue( 1.5 )
self:SetUpdateLive( false )
self:SetText("")
end
function PANEL:SetValue( val )
local val = tonumber( val )
if ( val == nil ) then return end
if ( val == self:GetFloatValue() ) then return end
val = math.Round(val, self:GetDecimals())
self:SetFloatValue( val )
self:OnValueChanged( val )
self:UpdateConVar()
end
function PANEL:Think()
if ( !self:GetActive() ) then
self:ConVarNumberThink()
end
if self._wdown and not self:IsDown() then
self:ConVarChanged( self:GetFloatValue() )
self._wdown = false
elseif self:IsDown() and self._knob then
local w_f = math.Clamp((self:LocalCursorPos() - 7) / (self:GetWide() - 15), 0, 1)
local r = self:GetMax() - self:GetMin()
self:SetValue(self:GetMin() + w_f * r)
self._wdown = true
end
end
function PANEL:Paint( w, h )
derma.SkinHook( "Paint", "Slider", panel, w, h )
local sw = w - 15
paintKnob(self,sw * self.m_fSlideX,0)
end
derma.DefineControl("SF_Slider", "A simple slider", PANEL, "DButton")
end

View File

@@ -0,0 +1,156 @@
--[[
| 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/
--]]
-- Clear weather. This is the default weather
local clear = StormFox2.Weather.Add( "Clear" )
local windy = 8
-- Description
if CLIENT then
function clear:GetName(nTime, nTemp, nWind, bThunder, nFraction )
local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy
if b_windy then
return language.GetPhrase("#sf_weather.clear.windy"), "Windy"
end
return language.GetPhrase("#sf_weather.clear"), "Clear"
end
else
function clear:GetName(nTime, nTemp, nWind, bThunder, nFraction )
local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy
if b_windy then
return "Windy"
end
return "Clear"
end
end
-- Icon
local m1,m2,m3,m4 = (Material("stormfox2/hud/w_clear.png")),(Material("stormfox2/hud/w_clear_night.png")),(Material("stormfox2/hud/w_clear_windy.png")),(Material("stormfox2/hud/w_clear_cold.png"))
function clear.GetSymbol( nTime ) -- What the menu should show
return m1
end
function clear.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
local b_day = StormFox2.Time.IsDay(nTime)
local b_cold = nTemp < -2
local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= windy
if b_windy then
return m3
elseif b_cold then
return m4
elseif b_day then
return m1
else
return m2
end
end
local bCM = string.Explode(" ", StormFox2.Map.GetSetting("fog_color") or "204 255 255")
local bC = Color(tonumber(bCM[1]) or 204, tonumber(bCM[2]) or 255, tonumber(bCM[3]) or 255)
-- Day
clear:SetSunStamp("topColor",Color(91, 127.5, 255), SF_SKY_DAY)
-- clear:SetSunStamp("topColor",StormFox2.util.CCTColor(12000),SF_SKY_DAY)
-- clear:SetSunStamp("bottomColor",bC, SF_SKY_DAY)
clear:SetSunStamp("bottomColor",StormFox2.util.CCTColor(8000),SF_SKY_DAY)
clear:SetSunStamp("fadeBias",0.01, SF_SKY_DAY)
clear:SetSunStamp("duskColor",Color(255, 255, 255), SF_SKY_DAY)
clear:SetSunStamp("duskIntensity",.64, SF_SKY_DAY)
clear:SetSunStamp("duskScale",0.29, SF_SKY_DAY)
clear:SetSunStamp("sunSize",20, SF_SKY_DAY)
clear:SetSunStamp("sunColor",Color(255, 255, 255), SF_SKY_DAY)
clear:SetSunStamp("sunFade",1, SF_SKY_DAY)
clear:SetSunStamp("starFade",0, SF_SKY_DAY)
--clear:SetSunStamp("fogDensity",0.8, SF_SKY_DAY)
-- Night
clear:SetSunStamp("topColor",Color(0,0,0), SF_SKY_NIGHT)
clear:SetSunStamp("bottomColor",Color(0, 1.5, 5.25), SF_SKY_NIGHT)
clear:SetSunStamp("fadeBias",0.12, SF_SKY_NIGHT)
clear:SetSunStamp("duskColor",Color(9, 9, 0), SF_SKY_NIGHT)
clear:SetSunStamp("duskIntensity",0, SF_SKY_NIGHT)
clear:SetSunStamp("duskScale",0, SF_SKY_NIGHT)
clear:SetSunStamp("sunSize",0, SF_SKY_NIGHT)
clear:SetSunStamp("starFade",100, SF_SKY_NIGHT)
clear:SetSunStamp("sunColor",Color(255, 255, 255), SF_SKY_NIGHT)
clear:SetSunStamp("sunFade",0, SF_SKY_NIGHT)
--clear:SetSunStamp("fogDensity",1, SF_SKY_NIGHT)
-- Sunset
-- Old Color(170, 85, 43)
clear:SetSunStamp("topColor",Color(130.5, 106.25, 149), SF_SKY_SUNSET)
--clear:SetSunStamp("bottomColor",Color(204, 98, 5), SF_SKY_SUNSET)
clear:SetSunStamp("bottomColor",StormFox2.util.CCTColor(2000), SF_SKY_SUNSET)
clear:SetSunStamp("fadeBias",1, SF_SKY_SUNSET)
clear:SetSunStamp("duskColor",Color(248, 103, 30), SF_SKY_SUNSET)
clear:SetSunStamp("duskIntensity",1, SF_SKY_SUNSET)
clear:SetSunStamp("duskScale",0.3, SF_SKY_SUNSET)
clear:SetSunStamp("sunSize",30, SF_SKY_SUNSET)
clear:SetSunStamp("sunColor",Color(255, 255, 255), SF_SKY_SUNSET)
clear:SetSunStamp("sunFade",.5, SF_SKY_SUNSET)
clear:SetSunStamp("starFade",30, SF_SKY_SUNSET)
--clear:SetSunStamp("fogDensity",0.8, SF_SKY_SUNSET)
-- Sunrise
clear:SetSunStamp("topColor",Color(130.5, 106.25, 149), SF_SKY_SUNRISE)
--clear:SetSunStamp("bottomColor",Color(204, 98, 5), SF_SKY_SUNRISE)
clear:SetSunStamp("bottomColor",StormFox2.util.CCTColor(2000), SF_SKY_SUNRISE)
clear:SetSunStamp("fadeBias",0.5, SF_SKY_SUNRISE)
clear:SetSunStamp("duskColor",Color(248, 103, 30), SF_SKY_SUNRISE)
clear:SetSunStamp("duskIntensity",0.4, SF_SKY_SUNRISE)
clear:SetSunStamp("duskScale",0.6, SF_SKY_SUNRISE)
clear:SetSunStamp("sunSize",20, SF_SKY_SUNRISE)
clear:SetSunStamp("sunColor",Color(255, 255, 255), SF_SKY_SUNRISE)
clear:SetSunStamp("sunFade",.5, SF_SKY_SUNRISE)
clear:SetSunStamp("starFade",30, SF_SKY_SUNRISE)
clear:SetSunStamp("fogDensity",0.8, SF_SKY_SUNRISE)
-- Cevil
clear:CopySunStamp( SF_SKY_NIGHT, SF_SKY_CEVIL ) -- Copy the night sky
clear:SetSunStamp("fadeBias",0.01, SF_SKY_CEVIL)
clear:SetSunStamp("sunSize",0, SF_SKY_CEVIL)
clear:SetSunStamp("bottomColor",StormFox2.util.CCTColor(0), SF_SKY_CEVIL)
-- Default variables. These don't change.
clear:Set("moonColor", Color( 205, 205, 205 ))
local moonSize = StormFox2.Setting.GetObject("moonsize")
clear:Set("moonSize",moonSize:GetValue())
moonSize:AddCallback(function(var)
clear:Set("moonSize",moonSize:GetValue())
end, "SF_moonSizeUpdate")
clear:Set("moonTexture", "stormfox2/effects/moon/moon.png" )
clear:Set("skyVisibility",100) -- Blocks out the sun/moon
clear:Set("mapDayLight",100)
clear:Set("mapNightLight",0)
clear:Set("clouds",0)
clear:Set("HDRScale",0.7)
clear:Set("fogDistance", 400000)
clear:Set("fogIndoorDistance", 400000)
--clear:Set("fogEnd",90000)
--clear:Set("fogStart",0)
-- Static values
clear:Set("starSpeed", 0.001)
clear:Set("starScale", 2.2)
clear:Set("starTexture", "skybox/starfield")
clear:Set("enableThunder") -- Tells the generator that this weather_type can't have thunder.
-- 2D skyboxes
if SERVER then
local t_day, t_night, t_sunrise, t_sunset
t_day = {"sky_day01_05", "sky_day01_04", "sky_day02_01","sky_day02_03","sky_day02_04","sky_day02_05"}
t_sunrise = {"sky_day01_05", "sky_day01_06", "sky_day01_08"}
t_sunset = {"sky_day02_02", "sky_day02_01"}
t_night = {"sky_day01_09"}
clear:SetSunStamp("skyBox",t_day, SF_SKY_DAY)
clear:SetSunStamp("skyBox",t_sunrise, SF_SKY_SUNRISE)
clear:SetSunStamp("skyBox",t_sunset, SF_SKY_SUNSET)
clear:SetSunStamp("skyBox",t_night, SF_SKY_NIGHT)
end

View File

@@ -0,0 +1,652 @@
--[[
| 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/
--]]
local clamp,max,min,random = math.Clamp,math.max,math.min,math.random
-- Rain and cloud is nearly the same.
local cloudy = StormFox2.Weather.Add( "Cloud" )
local rain = StormFox2.Weather.Add( "Rain", "Cloud" )
-- cloudy.spawnable = true Cloud is not spawnable. Since it is a "default" when it is cloudy
rain.spawnable = true
rain.thunder = function(percent) -- The amount of strikes pr minute
return percent > 0.5 and random(10) > 5 and percent * 3 or 0
end
-- Cloud icon
do
-- Description
if CLIENT then
function cloudy:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if StormFox2.Wind.GetBeaufort(nWind) >= 10 then
return language.GetPhrase('sf_weather.cloud.storm')
elseif bThunder then
return language.GetPhrase('sf_weather.cloud.thunder')
else
return language.GetPhrase('sf_weather.cloud')
end
end
else
function cloudy:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if StormFox2.Wind.GetBeaufort(nWind) >= 10 then
return "Storm"
elseif bThunder then
return "Thunder"
else
return "Cloudy"
end
end
end
-- Icon
local m_def = Material("stormfox2/hud/w_cloudy.png")
local m_night = Material("stormfox2/hud/w_cloudy_night.png")
local m_windy = Material("stormfox2/hud/w_cloudy_windy.png")
local m_thunder = Material("stormfox2/hud/w_cloudy_thunder.png")
function cloudy.GetSymbol( nTime ) -- What the menu should show
return m_def
end
function cloudy.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
local b_day = StormFox2.Time.IsDay(nTime)
local b_cold = nTemp < -2
local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= 7
local b_H = nFraction > 0.5
if bThunder then
return m_thunder
elseif b_windy then
return m_windy
elseif b_H or b_day then
return m_def
else
return m_night
end
end
end
-- Rain icon
do
-- Description
if CLIENT then
function rain:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if StormFox2.Wind.GetBeaufort(nWind) >= 10 then
return language.GetPhrase('sf_weather.cloud.storm'), 'Storm'
elseif bThunder then
return language.GetPhrase('sf_weather.cloud.thunder'), 'Thunder'
elseif nTemp > 0 then
return language.GetPhrase('sf_weather.rain'), 'Raining'
elseif nTemp > -2 then
return language.GetPhrase('sf_weather.rain.sleet'), 'Sleet'
else
return language.GetPhrase('sf_weather.rain.snow'), 'Snowing'
end
end
else
function rain:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if StormFox2.Wind.GetBeaufort(nWind) >= 10 then
return 'Storm', 'Storm'
elseif bThunder then
return 'Thunder', 'Thunder'
elseif nTemp > 0 then
return 'Raining', 'Raining'
elseif nTemp > -2 then
return 'Sleet', 'Sleet'
else
return 'Snowing', 'Snowing'
end
end
end
-- Icon
local m_def = Material("stormfox2/hud/w_raining.png")
local m_def_light = Material("stormfox2/hud/w_raining_light.png")
local m_thunder = Material("stormfox2/hud/w_raining_thunder.png")
local m_windy = Material("stormfox2/hud/w_raining_windy.png")
local m_snow = Material("stormfox2/hud/w_snowing.png")
local m_sleet = Material("stormfox2/hud/w_sleet.png")
function rain.GetSymbol( nTime, nTemp ) -- What the menu should show
if nTemp < -4 then
return m_snow
end
return m_def
end
function rain.GetIcon( _, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
local b_windy = StormFox2.Wind.GetBeaufort(nWind) >= 7
if bThunder then
return m_thunder
elseif b_windy and nTemp > -4 then
return m_windy
elseif nTemp > 0 then
if nFraction > 0.4 then
return m_def
else
return m_def_light
end
elseif nTemp <= -4 then
return m_snow
else
return m_sleet
end
end
function rain.LogicRelay()
if StormFox2.Temperature.Get() < -1 then
return "snow"
end
return "rain"
end
end
-- Sky and default weather variables
do
-- Day --
cloudy:SetSunStamp("topColor",Color(3.0, 2.9, 3.5), SF_SKY_DAY)
cloudy:SetSunStamp("bottomColor",Color(20, 25, 25), SF_SKY_DAY)
cloudy:SetSunStamp("duskColor",Color(3, 2.9, 3.5), SF_SKY_DAY)
cloudy:SetSunStamp("duskScale",1, SF_SKY_DAY)
cloudy:SetSunStamp("HDRScale",0.33, SF_SKY_DAY)
-- Night
cloudy:SetSunStamp("topColor",Color(0.4, 0.2, 0.54), SF_SKY_NIGHT)
cloudy:SetSunStamp("bottomColor",Color(2.25, 2.25,2.25),SF_SKY_NIGHT)
--cloudy:SetSunStamp("bottomColor",Color(14.3* 0.5,14.8* 0.5,15.2* 0.5), SF_SKY_NIGHT)
cloudy:SetSunStamp("duskColor",Color(.4, .2, .54), SF_SKY_NIGHT)
cloudy:SetSunStamp("duskScale",0, SF_SKY_NIGHT)
cloudy:SetSunStamp("HDRScale",0.1, SF_SKY_NIGHT)
-- Sunset/rise
cloudy:SetSunStamp("duskScale",0.26, SF_SKY_SUNSET)
cloudy:SetSunStamp("duskScale",0.26, SF_SKY_SUNRISE)
cloudy:Set("starFade",0)
cloudy:Set("mapDayLight",10)
cloudy:Set("skyVisibility",0)
cloudy:Set("clouds",1)
cloudy:Set("enableThunder", true)
rain:Set("mapDayLight",0)
local cDay, cNight = Color(20, 25, 25), Color(2.25, 2.25,2.25)
local n = 7
local cDayM, cNightM = Color(40 * 2, 50 * 2, 50 * 2), Color(2.25 * n, 2.25 * n,2.25 * n)
rain:Set("bottomColor",function(nStamp)
local temp = StormFox2.Temperature.Get()
if temp >= 0 then
return nStamp == SF_SKY_DAY and cDay or cNight
elseif temp <= -4 then
return nStamp == SF_SKY_DAY and cDayM or cNightM
end
local cRain = nStamp == SF_SKY_DAY and cDay or cNight
local cSnow = nStamp == SF_SKY_DAY and cDayM or cNightM
return StormFox2.Mixer.Blender(temp / 4 + 1, cSnow, cRain)
end)
--rain:SetSunStamp("fogEnd",800,SF_SKY_DAY)
--rain:SetSunStamp("fogEnd",800,SF_SKY_SUNRISE)
--rain:SetSunStamp("fogEnd",2000,SF_SKY_NIGHT)
--rain:SetSunStamp("fogEnd",2000,SF_SKY_BLUE_HOUR)
--rain:Set("fogDensity",1)
--rain:Set("fogStart",0)
rain:Set("fogDistance", function()
local wF = StormFox2.Wind.GetForce()
local temp = clamp(StormFox2.Temperature.Get() / 4 + 1,0,1)
if wF <= 0 then return 6000 end
local tempDist = 2000 + temp * 5200
local multi = max(0, 26 - temp * 8)
return max(tempDist - multi * wF,0)
end)
rain:Set("fogIndoorDistance", 5500)
-- rain:SetSunStamp("fogDistance",2000, SF_SKY_DAY)
-- rain:SetSunStamp("fogDistance",2500, SF_SKY_SUNSET)
-- rain:SetSunStamp("fogDistance",2000, SF_SKY_NIGHT)
-- rain:SetSunStamp("fogDistance",2500, SF_SKY_SUNRISE)
end
-- Window render
local rain_normal_material = Material("stormfox2/effects/window/rain_normal")
local rain_t = StormFox2.Terrain.Create("rain")
do
local raindrops = {}
local raindrops_mat = {(Material("stormfox2/effects/window/raindrop_normal")),(Material("stormfox2/effects/window/raindrop_normal2")),(Material("stormfox2/effects/window/raindrop_normal3"))}
local s = 2
rain:RenderWindowRefract64x64(function(w, h)
if StormFox2.Temperature.Get() < -1 then return false end
local QT = StormFox2.Client.GetQualityNumber()
local P = StormFox2.Weather.GetPercent()
-- Base
surface.SetDrawColor(Color(255,255,255,255 * P))
surface.SetMaterial(rain_normal_material)
local c = (-SysTime() / 1000) % 1
surface.DrawTexturedRectUV(0,0, w, h, 0, c, s, c + s )
-- Create raindrop
if #raindrops < math.Clamp(QT * 10, 5 ,65 * P) and random(100) <= 90 then
local s = random(6,10)
local x,y = random(s, w - s * 2), random(s, h * 0.8)
local sp = random(10, 50)
local lif = CurTime() + random(3,5)
local m = table.Random(raindrops_mat)
table.insert(raindrops, {x,y,s,m,sp,lif})
end
-- Render raindrop
local r = {}
for i,v in ipairs(raindrops) do
local lif = (v[6] - CurTime()) * 10
local a_n = h - v[2] - v[3]
local a = min(25.5,min(a_n,lif)) * 10
if a > 0 then
surface.SetMaterial(v[4])
surface.SetDrawColor(Color(255,255,255,a))
surface.DrawTexturedRect(v[1],v[2],v[3],v[3])
v[2] = v[2] + FrameTime() * v[5]
else
table.insert(r, i)
end
end
-- Remove raindrop
for i = #r,1,-1 do
table.remove(raindrops, r[i])
end
end)
-- Snow window
local mat = Material("stormfox2/effects/window/snow")
local mat2 = Material("stormfox2/effects/blizzard.png","noclamp")
local mat3 = Material("stormfox2/effects/rainstorm.png","noclamp")
local size = 0.5
local function RenderWindow(w, h)
if StormFox2.Temperature.Get() > -2 then
local wi = StormFox2.Wind.GetForce()
local P = StormFox2.Weather.GetPercent()
local lum = max(min(25 + StormFox2.Weather.GetLuminance(), 255),150)
if P * wi < 10 then return false end
-- Storm
local c = Color(lum,lum,lum,math.min(255, wi * 3))
surface.SetDrawColor(c)
surface.SetMaterial(mat3)
for i = 1, math.max(1, wi / 20) do
local cx = CurTime() * -1 % size
local cu = (CurTime() * -(4 + i)) % size
local fx = i / 3 + cx
surface.DrawTexturedRectUV(0,0,w,h, fx, cu, fx + size, size + cu)
end
else
local P = 1 - StormFox2.Weather.GetPercent()
local wi = StormFox2.Wind.GetForce()
local lum = max(min(25 + StormFox2.Weather.GetLuminance(), 255),150)
local c = Color(lum,lum,lum)
local oSF = StormFox2.Environment.GetOutSideFade()
if wi > 5 and oSF < 1 then
c.a = 255 - (oSF * 255)
surface.SetDrawColor(c)
surface.SetMaterial(mat2)
local cu = CurTime() * 3
for i = 1, wi / 20 do
local sz = (i * 3.333) % 3
local sx = i * 3 + (cu * 0.2) % sz
local sy = i * 5 + -cu % (sz * 0.5)
surface.DrawTexturedRectUV(0,0,w,h,sx,sy,sx + sz,sy + sz)
end
c.a = 255
end
surface.SetMaterial(mat)
surface.SetDrawColor(c)
surface.DrawTexturedRect(0,h * 0.12 * P,w,h)
end
end
rain:RenderWindow( RenderWindow )
end
-- Snow Terrain and footsteps
do
local snow = StormFox2.Terrain.Create("snow")
-- Make the snow terrain apply, if temp is low
rain:SetTerrain( function()
if SERVER then
StormFox2.Map.w_CallLogicRelay(rain.LogicRelay())
end
return (StormFox2.Data.GetFinal("Temp") or 0) < -3 and snow or rain_t
end)
-- Make the snow stay, until temp is high or it being replaced.
snow:LockUntil(function()
return StormFox2.Temperature.Get() > -2
end)
-- Footprints
snow:MakeFootprints(true,{
"stormfox2/footstep/footstep_snow0.mp3",
"stormfox2/footstep/footstep_snow1.mp3",
"stormfox2/footstep/footstep_snow2.mp3",
"stormfox2/footstep/footstep_snow3.mp3",
"stormfox2/footstep/footstep_snow4.mp3",
"stormfox2/footstep/footstep_snow5.mp3",
"stormfox2/footstep/footstep_snow6.mp3",
"stormfox2/footstep/footstep_snow7.mp3",
"stormfox2/footstep/footstep_snow8.mp3",
"stormfox2/footstep/footstep_snow9.mp3"
},"snow.step")
snow:SetGroundTexture("nature/snowfloor001a")
snow:AddTextureSwap("models/buggy/buggy001","stormfox2/textures/buggy001-snow")
snow:AddTextureSwap("models/vehicle/musclecar_col","stormfox2/textures/musclecar_col-snow")
-- Other snow textures
-- DOD
if IsMounted("dod") then
snow:AddTextureSwap("models/props_foliage/hedge_128", "models/props_foliage/hedgesnow_128")
snow:AddTextureSwap("models/props_fortifications/hedgehog", "models/props_fortifications/hedgehog_snow")
snow:AddTextureSwap("models/props_fortifications/sandbags", "models/props_fortifications/sandbags_snow")
snow:AddTextureSwap("models/props_fortifications/dragonsteeth", "models/props_fortifications/dragonsteeth_snow")
snow:AddTextureSwap("models/props_normandy/logpile", "models/props_normandy/logpile_snow")
snow:AddTextureSwap("models/props_urban/light_fixture01", "models/props_urban/light_fixture01_snow")
snow:AddTextureSwap("models/props_urban/light_streetlight01", "models/props_urban/light_streetlight01_snow")
snow:AddTextureSwap("models/props_urban/light_fixture01_on", "models/props_urban/light_fixture01_snow_on")
snow:AddTextureSwap("models/props_urban/light_streetlight01_on", "models/props_urban/light_streetlight01_snow_on")
end
-- TF2
if IsMounted("tf") then
snow:AddTextureSwap("models/props_foliage/shrub_03","models/props_foliage/shrub_03_snow")
snow:AddTextureSwap("models/props_swamp/shrub_03","models/props_foliage/shrub_03_snow")
snow:AddTextureSwap("models/props_foliage/shrub_03_skin2","models/props_foliage/shrub_03_snow")
snow:AddTextureSwap("models/props_foliage/grass_02","models/props_foliage/grass_02_snow")
snow:AddTextureSwap("models/props_foliage/grass_02_dark","models/props_foliage/grass_02_snow")
snow:AddTextureSwap("nature/blendgrassground001","nature/blendgrasstosnow001")
snow:AddTextureSwap("nature/blendgrassground002","nature/blendgrasstosnow001")
snow:AddTextureSwap("nature/blendgrassground007","nature/blendgrasstosnow001")
snow:AddTextureSwap("detail/detailsprites_2fort","detail/detailsprites_viaduct_event")
snow:AddTextureSwap("detail/detailsprites_dustbowl","detail/detailsprites_viaduct_event")
snow:AddTextureSwap("detail/detailsprites_trainyard","detail/detailsprites_viaduct_event")
snow:AddTextureSwap("models/props_farm/tree_leaves001","models/props_farm/tree_branches001")
snow:AddTextureSwap("models/props_foliage/tree_pine01","models/props_foliage/tree_pine01_snow")
for _,v in ipairs({"02","05","06","09","10","10a"}) do
snow:AddTextureSwap("models/props_forest/cliff_wall_" .. v,"models/props_forest/cliff_wall_" .. v .. "_snow")
end
snow:AddTextureSwap("models/props_island/island_tree_leaves02","models/props_island/island_tree_roots01")
snow:AddTextureSwap("models/props_forest/train_stop","models/props_forest/train_stop_snow")
end
end
-- Rain particles and sound
if CLIENT then
-- Sound
local rain_light = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_light.ogg", SF_AMB_OUTSIDE, 1 )
local rain_window = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_WINDOW, 0.1 )
local rain_outside = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_outside.ogg", SF_AMB_NEAR_OUTSIDE, 0.1 )
--local rain_underwater = StormFox2.Ambience.CreateAmbienceSnd( "", SF_AMB_UNDER_WATER, 0.1 ) Unused
local rain_watersurf = StormFox2.Ambience.CreateAmbienceSnd( "ambient/water/water_run1.wav", SF_AMB_UNDER_WATER_Z, 0.1 )
local rain_roof_wood = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof.ogg", SF_AMB_ROOF_WOOD, 0.1 )
local rain_roof_metal = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof_metal.ogg", SF_AMB_ROOF_METAL, 0.1 )
local rain_glass = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_ROOF_GLASS, 0.1 )
rain:AddAmbience( rain_light )
rain:AddAmbience( rain_window )
rain:AddAmbience( rain_outside )
rain:AddAmbience( rain_watersurf )
rain:AddAmbience( rain_roof_wood )
rain:AddAmbience( rain_roof_metal )
rain:AddAmbience( rain_glass )
-- Edit watersurf
rain_watersurf:SetFadeDistance(0,100)
rain_watersurf:SetVolume( 0.05 )
rain_watersurf:SetPlaybackRate(2)
-- Edit rain_glass
rain_roof_metal:SetFadeDistance(10,400)
rain_glass:SetFadeDistance(10, 400)
rain_window:SetFadeDistance(100, 200)
-- Edit rain_outside
rain_outside:SetFadeDistance(100, 200)
-- Materials
local m_rain = Material("stormfox2/raindrop.png")
local m_rain2 = Material("stormfox2/effects/raindrop-multi2.png")
local m_rain3 = Material("stormfox2/effects/raindrop-multi3.png")
local m_rain_multi = Material("stormfox2/effects/snow-multi.png","noclamp smooth")
local m_snow = Material("particle/snow")
local m_snow1 = Material("stormfox2/effects/snowflake1.png")
local m_snow2 = Material("stormfox2/effects/snowflake2.png")
local m_snow3 = Material("stormfox2/effects/snowflake3.png")
local t_snow = {m_snow1, m_snow2, m_snow3}
local m_snowmulti = Material("stormfox2/effects/snow-multi.png")
local m_snowmulti2 = Material("stormfox2/effects/snow-multi2.png")
-- Make the distant rain start higer up.
-- Update the rain templates every 10th second
function rain.TickSlow()
local W = StormFox2.Wind.GetForce()
local P = StormFox2.Weather.GetPercent() * (0.5 + W / 30)
local L = StormFox2.Weather.GetLuminance()
local T = StormFox2.Temperature.Get() + 2
local TL = StormFox2.Thunder.GetLight()
local TP = math.Clamp(T / 4,0,1)
rain_outside:SetVolume( P * TP )
rain_light:SetVolume( P * TP )
rain_window:SetVolume( P * 0.3 * TP )
rain_roof_wood:SetVolume( P * 0.3 * TP )
rain_roof_metal:SetVolume( P * 1 * TP )
rain_glass:SetVolume( P * 0.5 * TP )
local P = StormFox2.Weather.GetPercent()
local speed = 0.72 + 0.36 * P
StormFox2.Misc.rain_template:SetSpeed( speed )
StormFox2.Misc.rain_template_medium:SetSpeed( speed )
StormFox2.Misc.rain_template_medium:SetAlpha( L / 5)
end
-- Gets called every tick to add rain.
local multi_dis = 1200
local m2 = Material("particle/particle_smokegrenade1")
local tc = Color(150,150,150)
local snow_col = Color(255,255,255)
function rain.Think()
local P = StormFox2.Weather.GetPercent()
local L = StormFox2.Weather.GetLuminance()
local W = StormFox2.Wind.GetForce()
if StormFox2.DownFall.GetGravity() < 0 then return end -- Rain can't come from the ground.
local T = StormFox2.Temperature.Get() + 2
if T > 0 or T > random(-3, 0) then -- Spawn rain particles
-- Set alpha
local s = 1.22 + 1.56 * P
StormFox2.Misc.rain_template:SetSize( s , 5.22 + 7.56 * P)
StormFox2.Misc.rain_template:SetColor(tc)
StormFox2.Misc.rain_template:SetAlpha(min(100 + 15 * P + L,255))
StormFox2.Misc.rain_template_medium:SetAlpha(min(150 + 15 * P + L,255) /3)
StormFox2.Misc.rain_template_fog:SetAlpha( L )
local rain_distance = min(random(300,900), StormFox2.Fog.GetEnd())
-- Spawn rain particles
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template, 10, 700, 10 + P * 900, 5, vNorm ) or {} ) do
v:SetSize( 1.22 + 1.56 * P * math.Rand(1,2), 5.22 + 7.56 * P )
end
-- Spawn distant rain
if P > 0.15 then
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_medium, 250, 1500, 10 + P * 300, 250, vNorm ) or {} ) do
local d = v:GetDistance()
if d < 700 then
if random()>0.5 then
v:SetMaterial( m_rain2 )
else
v:SetMaterial(m_rain3 )
end
v:SetSize( 250, 250 )
v:SetSpeed( v:GetSpeed() * math.Rand(1,2))
else
v:SetSize( 1.22 + 1.56 * P * math.Rand(1,3) * 10, 5.22 + 7.56 * P * 10 )
end
v:SetAlpha(min(15 + 4 * P + L,255) * 0.2)
end
end
if P > (0.7 - W * 0.4) and L > 5 then -- If it is too dark, make it invis
local max_fog = (90 + P * (20 + (W / 80) * 102)) * 0.5
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_fog, rain_distance, rain_distance * 2 , max_fog, 200, vNorm ) or {} ) do
local d = v:GetDistance()
if not d or d < 500 then
v:SetSize( 225, 500 )
else
v:SetSize( d * .45, d)
end
if random(0,1) >= 0.5 then
v:SetMaterial(m2)
end
end
end
else
-- Spawn snow particles
local force_multi = max(1, (W / 6))
local snow_distance = min(random(140,500), StormFox2.Fog.GetEnd())
local d = min(snow_distance / 500, 1)
local snow_size = math.Rand(3,5) * d * max(0.7,P)
local s = math.Rand(3,5) * d * max(0.7,P)
local snow_speed = 0.15 * force_multi
StormFox2.Misc.snow_template:SetSpeed( snow_speed )
local n = max(min(L * 3, 255), 150)
snow_col.r = n
snow_col.g = n
snow_col.b = n
StormFox2.Misc.snow_template:SetColor(snow_col)
StormFox2.Misc.snow_template_multi:SetColor(snow_col)
local max_normal = 40 * P * (50 - W)
if StormFox2.Environment.GetOutSideFade() < 0.9 then
max_normal = 40 * P
end
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.snow_template, 20, snow_distance, max_normal, 5, vNorm ) or {} ) do
v:SetSize( s, s )
v:SetSpeed( math.Rand(1, 2) * snow_speed)
if snow_speed > 0.15 and random(snow_speed)> 0.15 then
v.hitType = SF_DOWNFALL_HIT_NIL
end
end
-- Spawn snow distant
if P > 0.15 then
local max_multi = 10 * (P - 0.15) * (70 - W)
if StormFox2.Environment.GetOutSideFade() < 0.9 then
max_normal = 10 * (P - 0.15)
end
local snow_distance = min(random(300,900), StormFox2.Fog.GetEnd())
local d = max(snow_distance / 900, 0.5)
local snow_size = math.Rand(0.5,1) * max(0.7,P) * 500 * d
local snow_speed = 0.15 * d * force_multi
StormFox2.Misc.snow_template:SetSpeed( mult_speed )
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.snow_template_multi, 500, snow_distance / 1, max_multi, s, vNorm ) or {} ) do
v:SetSize( snow_size, snow_size )
v:SetSpeed( math.Rand(1, 2) * snow_speed)
v:SetRoll( math.Rand(0, 360))
if random(0,1) == 0 then
v:SetMaterial(m_snowmulti2)
end
end
end
local max_fog = (90 + P * (20 + (W / 80) * 102))
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_fog, snow_distance, snow_distance * 2 , max_fog, 200, vNorm ) or {} ) do
local d = v:GetDistance()
if not d or d < 500 then
v:SetSize( 225, 500 )
else
v:SetSize( d * .45, d)
end
if random(0,1) >= 0 then
v:SetMaterial(m2)
end
end
end
end
-- Render Filter (Screen filter, this is additive)
local blizard = Material("stormfox2/effects/blizzard.png", "noclamp")
local storm = Material("stormfox2/effects/rainstorm.png", "noclamp")
local sx,sy = 0,0
local rx,ry,rx2,ry2 = 0,0,0,0
--surface.SetDrawColor( Color(255,255,255,34 * a) )
--surface.SetMaterial( snowMulti )
--surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH() ,c2,0 + c,2 + c2,2 + c)
--surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH() ,-c2 + c4,0 + c3,1 - c2 + c4,1 + c3)
local up = Vector(0,0,1)
local function setMaterialRoll(mat, roll, u, v)
local matrix = Matrix()
local w = mat:Width()
local h = mat:Height()
matrix:SetAngles(Angle(0,roll,0))
matrix:Translate(Vector(u, v, 0))
mat:SetMatrix("$basetexturetransform", matrix)
end
function rain.DepthFilter(w, h, a)
a = (a - 0.50) * 2
if a <= 0 then return end
local windDir = (-StormFox2.Wind.GetNorm()):Angle()
local rainscale = (StormFox2.Temperature.Get() + 2) / 2
local ad = math.AngleDifference(StormFox2.Wind.GetYaw() + 180, StormFox2.util.GetCalcView().ang.y)
local ada = math.sin(math.rad(ad))
-- 0 = directly into the wind
-- 1 = directly to the side of the wind
-- 0 = not moving at all
-- 1 = max movment
local A = EyeAngles():Forward()
local B = windDir:Forward()
local D = math.abs(A:Dot(B))
local C = 1 - D
local P = StormFox2.Weather.GetPercent()
local W = math.min(1, StormFox2.Wind.GetForce() / 60)
local B2 = windDir:Right()
local D2 = (A:Dot(B2))
if rainscale > -1 then
local WP = math.min(1, P) -- 0 - 1 Wimdy
local wind_x = ada * -C * 4 * WP
local wind_y = -8 * math.max(0.5, WP)
local roll = (windDir.p - 270) * -D2 * 0.8
rx = (rx + FrameTime() * wind_x) % 1
ry = (ry + FrameTime() * wind_y) % 1
setMaterialRoll(storm, 180 - roll + 3, rx, ry)
surface.SetMaterial( storm )
surface.SetDrawColor( Color(255,255,255,154 * a * math.max(0.1, W) * WP * math.max(C,0)) )
local s,s2 = 1.7, 1.8
surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0,0 + s, 0 + s)
-- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), rx,ry, rx + s2,ry + s2)
elseif rainscale < 1 then
local WP = math.min(1, W)
local wind_x = ada * -C * 4 * WP
local wind_y = -8 * math.max(0.5, WP)
local roll = (windDir.p - 270) * -ada
sx = (sx + FrameTime() * wind_x) % 1
sy = (sy + FrameTime() * wind_y) % 1
setMaterialRoll(blizard, 180 - roll + 14, w / 2, h / 2)
surface.SetDrawColor( Color(255,255,255,144 * a * math.max(WP,0.1) * ((P) * 1.15) ) )
surface.SetMaterial( blizard )
surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), sx, sy, 2 + sx, 2 + sy)
surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), -sx, sy, 1 - sx, 1 + sy)
-- surface.SetDrawColor( Color(255,255,255,255 * a * WP * D ) )
-- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0, 1, 1)
-- surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0, 0.6, 0.6)
end
--print(">",w,h,a)
end
-- Render water
local debri = Material("stormfox2/effects/terrain/snow_water")
rain.PreDrawTranslucentRenderables = function( a, b)
local f = 5 + StormFox2.Temperature.Get()
if f > 0 then return end
debri:SetFloat("$alpha",StormFox2.Weather.GetPercent() * 0.3 * math.Clamp(-f, 0, 1))
render.SetMaterial(debri)
StormFox2.Environment.DrawWaterOverlay( b )
end
end
-- 2D skyboxes
if SERVER then
local t_day, t_night, t_sunrise, t_sunset
t_day = {"sky_day03_02", "sky_day03_03", "sky_day03_04"}
t_sunrise = {"sky_day01_01"}
t_sunset = {"sky_day01_06"}
t_night = {"sky_day01_09"}
cloudy:SetSunStamp("skyBox",t_day, SF_SKY_DAY)
cloudy:SetSunStamp("skyBox",t_sunrise, SF_SKY_SUNRISE)
cloudy:SetSunStamp("skyBox",t_sunset, SF_SKY_SUNSET)
cloudy:SetSunStamp("skyBox",t_night, SF_SKY_NIGHT)
end

View File

@@ -0,0 +1,199 @@
--[[
| 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/
--]]
local rad = StormFox2.Weather.Add( "Radioactive", "Rain" )
if CLIENT then
function rad:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return language.GetPhrase('sf_weather.fallout'), "Nuclear fallout"
end
else
function rad:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return "Nuclear fallout", "Nuclear fallout"
end
end
local m_def = Material("stormfox2/hud/w_fallout.png")
function rad.GetSymbol( nTime ) -- What the menu should show
return m_def
end
function rad.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
return m_def
end
-- Day --
rad:SetSunStamp("topColor",Color(3.0, 102.9, 3.5), SF_SKY_DAY)
rad:SetSunStamp("bottomColor",Color(20, 55, 25), SF_SKY_DAY)
rad:SetSunStamp("duskColor",Color(3, 5.9, 3.5), SF_SKY_DAY)
rad:SetSunStamp("duskScale",1, SF_SKY_DAY)
rad:SetSunStamp("HDRScale",0.33, SF_SKY_DAY)
-- Night
rad:SetSunStamp("topColor",Color(0.4, 20.2, 0.54),SF_SKY_NIGHT)
rad:SetSunStamp("bottomColor",Color(2.25, 25,2.25),SF_SKY_NIGHT)
rad:SetSunStamp("duskColor",Color(.4, 1.2, .54), SF_SKY_NIGHT)
rad:SetSunStamp("duskScale",0, SF_SKY_NIGHT)
rad:SetSunStamp("HDRScale",0.1, SF_SKY_NIGHT)
-- Sunset/rise
rad:SetSunStamp("duskScale",0.26, SF_SKY_SUNSET)
rad:SetSunStamp("duskScale",0.26, SF_SKY_SUNRISE)
if CLIENT then
-- Snd
local rain_light = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_light.ogg", SF_AMB_OUTSIDE, 1 )
local rain_window = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_WINDOW, 0.1 )
local rain_outside = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_outside.ogg", SF_AMB_NEAR_OUTSIDE, 0.1 )
local rain_watersurf = StormFox2.Ambience.CreateAmbienceSnd( "ambient/water/water_run1.wav", SF_AMB_UNDER_WATER_Z, 0.1 )
local rain_roof_wood = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof.ogg", SF_AMB_ROOF_WOOD, 0.1 )
local rain_roof_metal = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_roof_metal.ogg", SF_AMB_ROOF_METAL, 0.1 )
local rain_glass = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/rain_glass.ogg", SF_AMB_ROOF_GLASS, 0.1 )
rad:AddAmbience( rain_light )
rad:AddAmbience( rain_window )
rad:AddAmbience( rain_outside )
rad:AddAmbience( rain_watersurf )
rad:AddAmbience( rain_roof_wood )
rad:AddAmbience( rain_roof_metal )
rad:AddAmbience( rain_glass )
-- Edit watersurf
rain_watersurf:SetFadeDistance(0,100)
rain_watersurf:SetVolume( 0.05 )
rain_watersurf:SetPlaybackRate(2)
-- Edit rain_glass
rain_roof_metal:SetFadeDistance(10,400)
rain_glass:SetFadeDistance(10, 400)
rain_window:SetFadeDistance(100, 200)
-- Edit rain_outside
rain_outside:SetFadeDistance(100, 200)
local m_rain = Material("stormfox2/raindrop.png")
local m_rain2 = Material("stormfox2/effects/raindrop-multi2.png")
local m_rain3 = Material("stormfox2/effects/raindrop-multi3.png")
local m_rain_multi = Material("stormfox2/effects/snow-multi.png","noclamp smooth")
function rad.TickSlow()
local P = StormFox2.Weather.GetPercent()
local L = StormFox2.Weather.GetLuminance()
rain_outside:SetVolume( P )
rain_light:SetVolume( P )
rain_window:SetVolume( P * 0.3 )
rain_roof_wood:SetVolume( P * 0.3 )
rain_roof_metal:SetVolume( P * 1 )
rain_glass:SetVolume( P * 0.5 )
local P = StormFox2.Weather.GetPercent()
local speed = 0.72 + 0.36 * P
StormFox2.Misc.rain_template:SetSpeed( speed )
StormFox2.Misc.rain_template_medium:SetSpeed( speed )
StormFox2.Misc.rain_template_medium:SetAlpha( L / 5)
end
local multi_dis = 1200
local c = Color(150,250,150)
function rad.Think()
local P = StormFox2.Weather.GetPercent()
local L = StormFox2.Weather.GetLuminance()
local W = StormFox2.Wind.GetForce()
if StormFox2.DownFall.GetGravity() < 0 then return end -- Rain can't come from the ground.
-- Set alpha
local s = 1.22 + 1.56 * P
StormFox2.Misc.rain_template:SetSize( s , 5.22 + 7.56 * P)
StormFox2.Misc.rain_template:SetColor(c)
StormFox2.Misc.rain_template:SetAlpha(math.min(15 + 4 * P + L,255))
StormFox2.Misc.rain_template_medium:SetAlpha(math.min(15 + 4 * P + L,255) /3)
-- Spawn rain particles
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template, 10, 700, 10 + P * 900, 5, vNorm ) or {} ) do
v:SetSize( 1.22 + 1.56 * P * math.Rand(1,2), 5.22 + 7.56 * P )
end
-- Spawn distant rain
if P > 0.15 then
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_medium, 250, 700, 10 + P * 500, 250, vNorm ) or {} ) do
v:SetColor(c)
local a = math.random(0,2)
if a > 0 then
if a > 1 then
v:SetMaterial( m_rain2 )
else
v:SetMaterial(m_rain3 )
end
v:SetSize( 250, 250 )
v:SetSpeed( v:GetSpeed() * math.Rand(1,2))
else
v:SetSize( 1.22 + 15.6 * P * math.Rand(1,3), 5.22 + 75.6 * P )
end
v:SetAlpha(math.min(15 + 4 * P + L,255) * 0.2)
end
end
if P > (0.5 - W * 0.4) then
local dis = math.random(900 - W * 100 - P * 500,multi_dis)
local d = math.max(dis / multi_dis, 0.5)
local s = math.Rand(0.5,1) * math.max(0.7,P) * 300 * d
--StormFox2.Misc.rain_template_multi:SetAlpha(math.min(15 + 4 * P + L,255) * .2)
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_fog, dis, multi_dis * 2, (90 + P * (250 + W)) / 2, s, vNorm ) or {} ) do
local d = v:GetDistance()
if not d or d < 500 then
v:SetSize( 225, 500 )
else
v:SetSize( d * .45, d)
end
if math.random(0,1) == 1 then
v:SetMaterial(m2)
end
end
end
end
-- Render fallout
local debri = Material("stormfox2/effects/terrain/fallout_water")
local function renderD( a, b)
local P = StormFox2.Weather.GetPercent()
debri:SetFloat("$alpha",StormFox2.Weather.GetPercent())
render.SetMaterial(debri)
StormFox2.Environment.DrawWaterOverlay( b )
end
rad.PreDrawTranslucentRenderables = renderD
else
-- Take dmg in rain, slowly
local nt = 0
function rad.Think()
if not StormFox2.Setting.Get("weather_damage", true) then return end
if nt < CurTime() then
nt = CurTime() + 2
local dmg = DamageInfo()
dmg:SetDamageType( DMG_RADIATION )
dmg:SetDamage(10)
dmg:SetAttacker( Entity(0) )
dmg:SetInflictor( Entity(0) )
local P = StormFox2.Weather.GetPercent() * 5
for i,v in ipairs( player.GetAll() ) do
if v:WaterLevel() > 0 then
dmg:SetDamage((v:WaterLevel() ) * P)
elseif StormFox2.Wind.IsEntityInWind(v) then
dmg:SetDamage(P)
else
continue
end
v:TakeDamageInfo(dmg)
v:EmitSound("player/geiger" .. math.random(1,3) .. ".wav")
end
end
end
end
-- Terrain
local radt = StormFox2.Terrain.Create("radio")
rad:SetTerrain( function(a) return StormFox2.Weather.GetPercent() > 0.5 and radt end )
radt:SetGroundTexture("nature/toxicslime001a")
-- Footsounds
radt:MakeFootprints(true,{
"player/footsteps/gravel1.wav",
"player/footsteps/gravel2.wav",
"player/footsteps/gravel3.wav",
"player/footsteps/gravel4.wav"
},"gravel.step")

View File

@@ -0,0 +1,64 @@
--[[
| 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/
--]]
-- Rain and cloud is nearly the same.
local fog = StormFox2.Weather.Add( "Fog" )
fog:Set("fogDistance", 150)
fog:Set("fogIndoorDistance", 600)
if CLIENT then
function fog.Think()
local p = StormFox2.Weather.GetPercent()
if p < 0.5 then return end
// tTemplate, nMinDistance, nMaxDistance, nAimAmount, traceSize, vNorm, fFunc )
local fc = StormFox2.Fog.GetColor()
local c = Color(fc.r,fc.g,fc.b, 0)
for _,v in ipairs( StormFox2.DownFall.SmartTemplate(StormFox2.Misc.fog_template, 200, 900, 45 * p - 15, 250, vNorm ) or {} ) do
v:SetColor( c )
end
end
function fog:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if nFraction < 0.2 then
return language.GetPhrase('#sf_weather.clear'), 'Clear'
elseif nFraction < 0.6 then
return language.GetPhrase('#sf_weather.fog.low'), 'Haze'
elseif nFraction < 0.8 then
return language.GetPhrase('#sf_weather.fog.medium'), 'Fog'
else
return language.GetPhrase('#sf_weather.fog.high'), 'Thick Fog'
end
end
else
function fog:GetName(nTime, nTemp, nWind, bThunder, nFraction )
if nFraction < 0.2 then
return 'Clear', 'Clear'
elseif nFraction < 0.6 then
return 'Haze', 'Haze'
elseif nFraction < 0.8 then
return 'Fog', 'Fog'
else
return 'Thick Fog', 'Thick Fog'
end
end
end
-- Fog icon
do
-- Icon
local m_def = Material("stormfox2/hud/w_fog.png")
function fog.GetSymbol( nTime ) -- What the menu should show
return m_def
end
function fog.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
return m_def
end
end

View File

@@ -0,0 +1,99 @@
--[[
| 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/
--]]
local lava = StormFox2.Weather.Add( "Lava", "Cloud" )
lava:Set("fogDistance", 2000)
lava:Set("fogIndoorDistance", 3000)
lava:Set("mapDayLight",0)
local s = 10
lava:SetSunStamp("bottomColor",Color(50, 2.5, 2.5), SF_SKY_DAY)
lava:SetSunStamp("bottomColor",Color(2.25, .225,.225),SF_SKY_NIGHT)
if CLIENT then
function lava:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return language.GetPhrase('sf_weather.lava')
end
else
function lava:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return "Lava", "Lava"
end
end
local m_def = Material("stormfox2/hud/w_lava.png")
function lava.GetSymbol( nTime ) -- What the menu should show
return m_def
end
function lava.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
return m_def
end
-- Terrain
local t_lava = StormFox2.Terrain.Create("lava")
local a = function()
local P = StormFox2.Weather.GetPercent()
return P > 0.5 and t_lava
end
lava:SetTerrain( a )
t_lava:SetGroundTexture("stormfox2/effects/terrain/lava_ground", true)
-- Downfall & Snd
if CLIENT then
local lava_snd = StormFox2.Ambience.CreateAmbienceSnd( "stormfox2/amb/lava.ogg", SF_AMB_OUTSIDE, 0.4 )
local m_lava = Material("stormfox2/effects/lava_particle")
lava:AddAmbience( lava_snd )
local p_d = Material("stormfox2/effects/lava_particle2")
local lava_particles = StormFox2.DownFall.CreateTemplate(p_d, true)
lava_particles:SetFadeIn( true )
lava_particles:SetRandomAngle(0.1)
function lava:Think()
local P = StormFox2.Weather.GetPercent()
lava_snd:SetVolume( P * .4 )
local l = math.min(255, StormFox2.Weather.GetLuminance() * 7)
lava_particles:SetColor(Color(l,l,l))
lava_particles:SetSpeed(.3) -- Makes the start position
local dis = math.random(100,1500)
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( lava_particles, 200, dis, P * 800, 5, vNorm ) or {} ) do
local s = math.Rand(10,200)
v:SetSize( s, s )
v:SetSpeed( math.Rand(.05, .15) )
if math.random(1,2) == 1 then
v:SetMaterial(m_lava)
end
end
end
-- Render water debri
local debri = Material("stormfox2/effects/terrain/lava_water")
local function renderD( a, b)
render.SetMaterial(debri)
StormFox2.Environment.DrawWaterOverlay( b )
end
lava.PreDrawTranslucentRenderables = renderD
end
-- Burn
if SERVER then
t_lava:MakeFootprints( false, nil, nil, function(ent, foot, SoundName, sTex, bReplace)
if not ent or not IsValid(ent) then return end
if not bReplace then return end
if ent.Health and ent:Health() <= 1 then return end
if not StormFox2.Setting.Get("weather_damage", true) then return end
if math.random(1, 10) <= 9 then
local burn = DamageInfo()
burn:SetDamage( math.random(5, 10) )
burn:SetDamageType(DMG_BURN)
burn:SetInflictor(game.GetWorld())
burn:SetAttacker(game.GetWorld())
burn:SetDamagePosition( ent:GetPos() )
ent:TakeDamageInfo( burn )
else
ent:Ignite(1, 0)
end
end)
end

View File

@@ -0,0 +1,188 @@
--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local max = math.max
local sand = StormFox2.Weather.Add( "Sandstorm" )
-- Display name
if CLIENT then
function sand:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return language.GetPhrase('sf_weather.sandstorm')
end
else
function sand:GetName(nTime, nTemp, nWind, bThunder, nFraction )
return "Sandstorm"
end
end
-- Icon
local m_def = Material("stormfox2/hud/w_sand.png")
function sand.GetSymbol( nTime ) -- What the menu should show
return m_def
end
function sand.GetIcon( nTime, nTemp, nWind, bThunder, nFraction) -- What symbol the weather should show
return m_def
end
-- Sky
-- Day --
sand:SetSunStamp("bottomColor",Color(255,216,170), SF_SKY_SUNRISE)
-- sand:SetSunStamp("duskColor",Color(3, 2.9, 3.5), SF_SKY_DAY)
-- sand:SetSunStamp("duskScale",1, SF_SKY_DAY)
sand:SetSunStamp("HDRScale",0.33, SF_SKY_DAY)
-- Night
sand:SetSunStamp("bottomColor",Color(255,216,170), SF_SKY_SUNSET)
-- Sunset/rise
-- sand:Set("duskScale",0.26)
sand:Set("starFade",0)
sand:Set("skyVisibility",function(stamp)
local v = (StormFox2.Weather.GetPercent() - 0.5) * 2
return (1 - v) * 70
end)
sand:Set("clouds",function( stamp)
return (StormFox2.Weather.GetPercent() - 0.5) * 2
end)
sand:Set("mapDayLight",70) -- 70% maplight at max
-- Fog
sand:Set("fogIndoorDistance", 5500)
sand:Set("fogDistance", function()
local wF = StormFox2.Wind.GetForce()
if wF <= 0 then return 4000 end
return max(4000 - 55 * wF,0)
end)
-- Terrain
local sand_t = StormFox2.Terrain.Create("sand")
sand:SetTerrain( function()
return StormFox2.Weather.GetPercent() > 0.5 and sand_t
end )
sand_t:SetGroundTexture("nature/sandfloor009a", true)
-- Footprints
sand_t:MakeFootprints(true,{
"player/footsteps/sand1.wav",
"player/footsteps/sand2.wav",
"player/footsteps/sand3.wav",
"player/footsteps/sand4.wav"
},"sand.step")
if CLIENT then
-- Load mats
local t = {}
for i = 1, 16 do
local c = i
if c < 10 then
c = "0" .. c
end
local m = Material("particle/smokesprites_00" .. c)
if m:IsError() then continue end
table.insert(t, m)
end
if #t > 0 then -- Make sure we at least got 1 material
local function makeCloud(vPos, s, vVec)
local p = StormFox2.DownFall.AddParticle( table.Random(t), vPos, false )
p:SetStartSize(s / 4)
p:SetEndSize(50 + s)
p:SetDieTime(math.Rand(2,3))
p:SetEndAlpha(0)
p:SetStartAlpha(math.min(255, 120 + s / 3))
p:SetVelocity(vector_up * math.random(5,15))
local c = StormFox2.Fog.GetColor()
p:SetColor(c.r, c.g, c.b)
p:SetRoll(math.random(360))
end
local limiter = 0
sand_t:MakeFootprints( false, nil, nil, function(ent, foot, SoundName, sTex, bReplace)
if not ent or not IsValid(ent) then return end
if not bReplace then return end
if ent.Health and ent:Health() <= 1 then return end
local s = ent:GetVelocity():Length()
if s < 200 then return end
s = math.min(s, 1500)
if limiter > CurTime() then return end
limiter = CurTime() + 0.2 -- Limit it to about 15 particles
local c = (s - 150)
makeCloud(ent:GetPos(), c / 3)
end)
end
-- Particles
function sand.Think()
local min,random = math.min,math.random
local P = StormFox2.Weather.GetPercent()
local L = StormFox2.Weather.GetLuminance()
local W = StormFox2.Wind.GetForce()
if StormFox2.DownFall.GetGravity() < 0 then return end -- Clouds can't come from the ground.
StormFox2.Misc.rain_template_fog:SetAlpha( L )
-- Set alpha
local s = 1.22 + 1.56 * P
local max_fog = W
local sand_distance = min(random(300,900), StormFox2.Fog.GetEnd())
local fc = StormFox2.Fog.GetColor()
local c = Color(fc.r * 0.95 ,fc.g * 0.95, fc.b * 0.95, 0)
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.fog_template, sand_distance, sand_distance * 2 , 30 + max_fog, 200, vNorm ) or {} ) do
v:SetColor( c )
end
for _,v in ipairs( StormFox2.DownFall.SmartTemplate( StormFox2.Misc.rain_template_fog, sand_distance, sand_distance * 2 , max_fog, 200, vNorm ) or {} ) do
local d = v:GetDistance()
if not d or d < 500 then
v:SetSize( 225, 500 )
else
v:SetSize( d * .45, d)
end
end
end
-- Depth filter
local up = Vector(0,0,1)
local function setMaterialRoll(mat, roll, u, v)
local matrix = Matrix()
local w = mat:Width()
local h = mat:Height()
matrix:SetAngles(Angle(0,roll,0))
matrix:Translate(Vector(u, v, 0))
mat:SetMatrix("$basetexturetransform", matrix)
end
local sx,sy = 0,0
local rx,ry,rx2,ry2 = 0,0,0,0
local mat = Material("stormfox2/effects/rainstorm.png", "noclamp")
function sand.DepthFilter(w, h, a)
a = (a - 0.50) * 2
if a <= 0 then return end
local windDir = (-StormFox2.Wind.GetNorm()):Angle()
local ad = math.AngleDifference(StormFox2.Wind.GetYaw() + 180, StormFox2.util.GetCalcView().ang.y)
local ada = math.sin(math.rad(ad))
-- 0 = directly into the wind
-- 1 = directly to the side of the wind
-- 0 = not moving at all
-- 1 = max movment
local A = EyeAngles():Forward()
local B = windDir:Forward()
local D = math.abs(A:Dot(B))
local C = 1 - D
local P = StormFox2.Weather.GetPercent()
local W = math.min(1, StormFox2.Wind.GetForce() / 60)
local B2 = windDir:Right()
local D2 = (A:Dot(B2))
local WP = math.min(1, P) -- 0 - 1 Wimdy
local wind_x = ada * -C * 4 * WP
local wind_y = -8 * math.max(0.5, WP)
local roll = (windDir.p - 270) * -D2 * 1.4
rx = (rx + FrameTime() * wind_x) % 1
ry = (ry + FrameTime() * wind_y) % 1
setMaterialRoll(mat, 180 - roll + 3, rx, ry)
surface.SetMaterial( mat )
surface.SetDrawColor( Color(255,255,255,154 * a * math.max(0.1, W) * WP * math.max(C,0)) )
local s,s2 = 1.7, 1.8
surface.DrawTexturedRectUV( 0, 0, ScrW(), ScrH(), 0, 0,0 + s, 0 + s)
end
end