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

901 lines
30 KiB
Lua

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