--[[ | 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)