Module:BassaridiaForecast
From MicrasWiki
Jump to navigationJump to search
Documentation for this module may be created at Module:BassaridiaForecast/doc
--------------------------------------------------------- -- Module: BassaridiaForecast -- Provides three functions: -- 1) weatherForecast(frame) -> returns a single -- big table for ALL cities -- 2) weatherForCity(frame) -> single table for a city -- 3) starsForTonight(frame) -> example star-visibility -- -- All cells are re-randomized once per day -- by seeding math.random with (year*1000 + dayOfYear). --------------------------------------------------------- local p = {} --------------------------------------------------------- -- 1. Determine Today's Date for PSSC -- Also used to create a daily random seed. --------------------------------------------------------- local function getCurrentDateInfo() local startDate = os.time({year=1999, month=8, day=6}) local secondsInDay= 86400 local daysPerYear = 183 local currentTime = os.time() local totalDays = math.floor((currentTime - startDate) / secondsInDay) local fractionYear= totalDays / daysPerYear local psscYear = math.floor(fractionYear) local dayOfYear = math.floor((fractionYear - psscYear) * daysPerYear) + 1 return { psscYear = psscYear, dayOfYear= dayOfYear } end --------------------------------------------------------- -- 2. Determine Season (Atosiel=1..61, Thalassiel=62..122, Opsitheiel=123..183) --------------------------------------------------------- local function getSeasonName(dayOfYear) if dayOfYear <= 61 then return "Atosiel" elseif dayOfYear <= 122 then return "Thalassiel" else return "Opsitheiel" end end --------------------------------------------------------- -- 3. climateTemperature (°C behind scenes, displayed °F) --------------------------------------------------------- local climateTemperature = { -- (unchanged climate data) ["Humid Subtropical"] = { Atosiel = { hiMin=18, hiMax=26, loMin=10, loMax=16, humMin=60, humMax=80 }, Thalassiel = { hiMin=25, hiMax=34, loMin=19, loMax=24, humMin=65, humMax=90 }, Opsitheiel = { hiMin=20, hiMax=28, loMin=12, loMax=18, humMin=50, humMax=75 } }, ["Oceanic"] = { Atosiel = { hiMin=10, hiMax=17, loMin=5, loMax=12, humMin=70, humMax=90 }, Thalassiel = { hiMin=14, hiMax=21, loMin=9, loMax=14, humMin=65, humMax=85 }, Opsitheiel = { hiMin=5, hiMax=12, loMin=1, loMax=6, humMin=70, humMax=90 } }, ["Subpolar Oceanic"] = { Atosiel = { hiMin=3, hiMax=9, loMin=-2, loMax=3, humMin=70, humMax=95 }, Thalassiel = { hiMin=6, hiMax=12, loMin=1, loMax=6, humMin=70, humMax=90 }, Opsitheiel = { hiMin=-1, hiMax=4, loMin=-6, loMax=-1, humMin=75, humMax=95 } }, ["Mediterranean (Hot Summer)"] = { Atosiel = { hiMin=15, hiMax=21, loMin=7, loMax=12, humMin=40, humMax=60 }, Thalassiel = { hiMin=25, hiMax=35, loMin=15, loMax=20, humMin=30, humMax=50 }, Opsitheiel = { hiMin=12, hiMax=18, loMin=5, loMax=10, humMin=45, humMax=65 } }, ["Hot Desert"] = { Atosiel = { hiMin=25, hiMax=35, loMin=12, loMax=18, humMin=10, humMax=30 }, Thalassiel = { hiMin=35, hiMax=45, loMin=25, loMax=30, humMin=5, humMax=20 }, Opsitheiel = { hiMin=22, hiMax=30, loMin=10, loMax=16, humMin=5, humMax=25 } }, ["Cold Steppe"] = { Atosiel = { hiMin=10, hiMax=18, loMin=2, loMax=8, humMin=30, humMax=50 }, Thalassiel = { hiMin=20, hiMax=28, loMin=10, loMax=15, humMin=25, humMax=45 }, Opsitheiel = { hiMin=0, hiMax=7, loMin=-6, loMax=0, humMin=35, humMax=55 } }, ["Hot Steppe"] = { Atosiel = { hiMin=23, hiMax=30, loMin=12, loMax=18, humMin=20, humMax=40 }, Thalassiel = { hiMin=30, hiMax=40, loMin=20, loMax=25, humMin=15, humMax=35 }, Opsitheiel = { hiMin=25, hiMax=33, loMin=15, loMax=20, humMin=15, humMax=40 } }, ["Subarctic"] = { Atosiel = { hiMin=0, hiMax=8, loMin=-10,loMax=-1, humMin=50, humMax=80 }, Thalassiel = { hiMin=5, hiMax=15, loMin=-1, loMax=5, humMin=50, humMax=85 }, Opsitheiel = { hiMin=-5, hiMax=0, loMin=-15,loMax=-5, humMin=60, humMax=90 } } } --------------------------------------------------------- -- 4. Weather Events by Climate & Season -- (unchanged event text) --------------------------------------------------------- local climateEvents = { -- (unchanged big table of strings) ["Humid Subtropical"] = { Atosiel = { "Morning drizzle and warm afternoon sunshine", "Mild thunderstorm building by midday", "Patchy fog at dawn, clearing toward lunch", "Light rain with sunny breaks after noon", "Gentle breezes, blossoming warmth, low humidity", "Scattered clouds with a brief shower by dusk", "Humid sunrise, comfortable high around midday", "Overcast for part of the day, mild temperatures", "Warm breezes carrying faint floral scents", "Partial sun, warm with a slight chance of rain" }, Thalassiel = { "Hot, steamy day with intense midday heat", "Tropical-like humidity, afternoon thunder possible", "Intermittent heavy downpours, muggy evenings", "High humidity and patchy thunderstorms late", "Sun-scorched morning, scattered storms by dusk", "Hazy sunshine, extremely warm all day", "Thick humidity, chance of lightning late evening", "Sticky air, short downpours in isolated spots", "Heat advisory with only brief cooling at night", "Nighttime storms lingering into early morning" }, Opsitheiel = { "Warm daytime, gentle evening breezes", "Occasional rain, otherwise mild temperatures", "Late-season warmth, scattered rain after sunset", "Cooler mornings, returning to warmth by midday", "Sparse cloud cover, tranquil weather overall", "Fog at dawn, warm midday, pleasant night", "Partial sun, comfortable humidity levels", "Evening drizzle with mild breezes", "Patchy haze, moderate warmth throughout the day", "Short-lived shower, then clearing skies" } }, ["Oceanic"] = { -- ... etc ... }, -- (rest of climateEvents unchanged) } --------------------------------------------------------- -- 5. triggeredDisasters, getAdvisory (unchanged) --------------------------------------------------------- local triggeredDisasters = { -- (unchanged hazard keywords) } local function getAdvisory(forecastStr) local textLower = forecastStr:lower() local extras = {} local randomChance = 0.40 for _, item in ipairs(triggeredDisasters) do for _, kw in ipairs(item.keywords) do if textLower:find(kw) then local roll = math.random() if roll <= randomChance then table.insert(extras, item.hazard) end break end end end if #extras > 0 then return table.concat(extras, "; ") else return "No reports" end end --------------------------------------------------------- -- 7. Conversions and Random Stats --------------------------------------------------------- local function cToF(c) if type(c) == "number" then return math.floor(c * 9/5 + 32 + 0.5) end return c end -- Updated function: narrower typical wind speeds (1..25 km/h). -- We'll adjust the code so we can later override chanceOfRain if precipitation is mentioned. local windDirections = {"N","NE","E","SE","S","SW","W","NW"} local function getRandomWeatherStats(climate, season) local data = climateTemperature[climate] and climateTemperature[climate][season] if not data then return { high = "N/A", low = "N/A", humidity = "N/A", chanceOfRain = "N/A", windDir = "N/A", windSpeed = "N/A" } end local hi = math.random(data.hiMin, data.hiMax) local lo = math.random(data.loMin, data.loMax) local hum = math.random(data.humMin, data.humMax) local cRain= math.random(0,100) -- default random chance of rain local wDir = windDirections[math.random(#windDirections)] local wSpd = math.random(1,25) -- narrower range for more realistic wind speeds return { high = hi, low = lo, humidity = hum, chanceOfRain= cRain, windDir = wDir, windSpeed = wSpd } end --------------------------------------------------------- -- 8. City Data (unchanged) --------------------------------------------------------- local cityData = { {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim Vaeringheim]", climate = "Humid Subtropical"}, -- (rest of the city list unchanged) {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lykopolis Lykopolis]", climate = "Oceanic"} } --------------------------------------------------------- -- 9. Color-coded cell background (unchanged) --------------------------------------------------------- local function getEventColor(eventText) local textLower = eventText:lower() if textLower:find("thunder") or textLower:find("storm") then return "#FFD2D2" elseif textLower:find("snow") or textLower:find("sleet") or textLower:find("flurries") then return "#D2ECFF" elseif textLower:find("rain") or textLower:find("drizzle") or textLower:find("downpour") then return "#D2DFFF" elseif textLower:find("dust") or textLower:find("desert") then return "#FFFACD" else return "#F8F8F8" end end --------------------------------------------------------- -- 10A. ALL-CITIES FORECAST FUNCTION (modified to enforce higher chance of rain if precipitation is detected) --------------------------------------------------------- function p.weatherForecast(frame) -- Random seed changes daily local date = os.date("*t") local dailySeed = (date.year * 1000) + date.yday math.randomseed(dailySeed) local dateInfo = getCurrentDateInfo() local dayOfYear = dateInfo.dayOfYear local yearNumber = dateInfo.psscYear local seasonName = getSeasonName(dayOfYear) local out = {} table.insert(out, "== Daily Weather Forecast ==\n") table.insert(out, string.format("''(Day %d of Year %d PSSC, %s)''\n\n", dayOfYear, yearNumber, seasonName ) ) table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"\n') table.insert(out, [[ ! City ! Climate ! Season ! High °F ! Low °F ! Humidity (%) ! Chance of Rain (%) ! Wind Direction ! Wind Speed (km/h) ! Today's Weather ! Natural Disaster Advisory ]]) for _, entry in ipairs(cityData) do local cityLink = entry.city local climateName = entry.climate -- 1) Random weather event local climateTbl = climateEvents[climateName] local seasonTbl = climateTbl and climateTbl[seasonName] local fStr = "No data" if seasonTbl and #seasonTbl > 0 then local idx = math.random(#seasonTbl) fStr = seasonTbl[idx] end -- 2) Stats local stats = getRandomWeatherStats(climateName, seasonName) -- If the daily weather text has precipitation words, override chanceOfRain with a high value: local forecastLower = fStr:lower() if forecastLower:find("rain") or forecastLower:find("drizzle") or forecastLower:find("downpour") or forecastLower:find("shower") or forecastLower:find("snow") or forecastLower:find("storm") or forecastLower:find("thunder") or forecastLower:find("sleet") then stats.chanceOfRain = math.random(70, 99) end local hiF = cToF(stats.high) local loF = cToF(stats.low) local hum = stats.humidity local cRain = stats.chanceOfRain local wDir = stats.windDir local wSpd = stats.windSpeed local rowColor = getEventColor(fStr) local advisory = getAdvisory(fStr) table.insert(out, "|-\n") table.insert(out, string.format([[ | %s | %s | %s | %d | %d | %d | %d | %s | %d | style="background-color:%s" | %s | %s ]], cityLink, climateName, seasonName, hiF, loF, hum, cRain, wDir, wSpd, rowColor, fStr, advisory )) end table.insert(out, "|}\n") return table.concat(out) end --------------------------------------------------------- -- 10B. SINGLE-CITY FORECAST FUNCTION (similarly updated) --------------------------------------------------------- function p.weatherForCity(frame) local cityRequested = frame.args.city if not cityRequested or cityRequested == "" then return "Error: Please specify a city. E.g. {{#invoke:BassaridiaForecast|weatherForCity|city=Vaeringheim}}" end -- daily seed local date = os.date("*t") local dailySeed = (date.year * 1000) + date.yday math.randomseed(dailySeed) local dateInfo = getCurrentDateInfo() local dayOfYear = dateInfo.dayOfYear local yearNumber = dateInfo.psscYear local seasonName = getSeasonName(dayOfYear) local foundEntry = nil for _, entry in ipairs(cityData) do local plainCity = entry.city:match("%s+([A-Za-zÀ-ž%-]+)%]?$") if entry.city == cityRequested or plainCity == cityRequested then foundEntry = entry break end end if not foundEntry then return "Error: City '" .. cityRequested .. "' not found in cityData." end local cityLink = foundEntry.city local climateName= foundEntry.climate local climateTbl = climateEvents[climateName] local seasonTbl = climateTbl and climateTbl[seasonName] local fStr = "No data" if seasonTbl and #seasonTbl > 0 then local idx = math.random(#seasonTbl) fStr = seasonTbl[idx] end local stats = getRandomWeatherStats(climateName, seasonName) -- Override chance of rain if we detect precipitation in fStr local forecastLower = fStr:lower() if forecastLower:find("rain") or forecastLower:find("drizzle") or forecastLower:find("downpour") or forecastLower:find("shower") or forecastLower:find("snow") or forecastLower:find("storm") or forecastLower:find("thunder") or forecastLower:find("sleet") then stats.chanceOfRain = math.random(70, 99) end local hiF = cToF(stats.high) local loF = cToF(stats.low) local hum = stats.humidity local cRain = stats.chanceOfRain local wDir = stats.windDir local wSpd = stats.windSpeed local rowColor = getEventColor(fStr) local advisory = getAdvisory(fStr) local out = {} table.insert(out, '{| class="wikitable" style="width:100%; text-align:left;"\n') table.insert(out, [[ ! Climate ! Season ! High °F ! Low °F ! Humidity (%) ! Chance of Rain (%) ! Wind Dir ! Wind Speed (km/h) ! style="width:20em;" | Today's Weather ! Natural Disaster Advisory ]]) table.insert(out, "|-\n") local row = string.format([[ | %s | %s | %d | %d | %d | %d | %s | %d | style="background-color:%s" | %s | %s ]], climateName, seasonName, hiF, loF, hum, cRain, wDir, wSpd, rowColor, fStr, advisory) table.insert(out, row) table.insert(out, "|}\n\n") return table.concat(out) end --------------------------------------------------------- -- 10C. STARS FOR TONIGHT (unchanged) --------------------------------------------------------- local starChartData = { -- (unchanged star data) } local observerLat = 49 -- Vaeringheim ~49°N local function isVisibleAt630(lat) local offset = math.random(5,55) return (lat < (observerLat + offset)) end function p.starsForTonight(frame) local date = os.date("*t") local dailySeed = (date.year * 1000) + date.yday + 777 math.randomseed(dailySeed) local out = {} table.insert(out, "=== Northern Host Stars Visible at ~6:30 PM ===\n\n") table.insert(out, '{| class="wikitable" style="width:100%; text-align:left;"\n') table.insert(out, "! Star !! Class !! Star-Lat !! Visible? !! Approx. Altitude\n") for _, star in ipairs(starChartData) do local yesNo = "No" local alt = 0 if isVisibleAt630(star.lat) then yesNo = "Yes" alt = math.random(20,80) end table.insert(out, "|-\n") table.insert(out, string.format( "| %s || %s || %.1f°N || %s || %d°\n", star.name, star.starClass, star.lat, yesNo, alt )) end table.insert(out, "|}\n\n") return table.concat(out) end --------------------------------------------------------- -- Return the module table --------------------------------------------------------- return p