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