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