This commit is contained in:
lifestorm
2024-08-05 18:40:29 +03:00
parent 9f505a0646
commit c6d9b6f580
8044 changed files with 1853472 additions and 21 deletions

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