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 (with daily-random weather). -- 2) weatherForCity(frame) -> returns a single small table -- for a SPECIFIC city (param "city"). -- 3) starsForTonight(frame) -> returns a table of star -- "rise" and "set" times, based on season-based -- night length (very simplified). --------------------------------------------------------- local p = {} --------------------------------------------------------- -- HELPER #1: Current Date Info 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 totalDaysElapsed = math.floor((currentTime - startDate) / secondsInDay) local yearFraction = totalDaysElapsed / daysPerYear local psscYear = math.floor(yearFraction) local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1 return { psscYear = psscYear, dayOfYear = dayOfYear } end --------------------------------------------------------- -- HELPER #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 = { ["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 from your original code) --------------------------------------------------------- local climateEvents = { -- [Truncated for brevity—include your full event lists here!] -- ... } --------------------------------------------------------- -- 5. Disasters: random chance if text has keywords --------------------------------------------------------- local triggeredDisasters = { { keywords = {"seasonal storms","lake%-driven floods"}, hazard = "Seasonal storms and lake-driven floods" }, { keywords = {"river floods","heavy rains"}, hazard = "River floods during heavy rains" }, { keywords = {"snowstorms","higher elevations"}, hazard = "Snowstorms at higher elevations" }, { keywords = {"steam vent eruptions"}, hazard = "Occasional steam vent eruptions" }, -- ... (include ALL from your table) } local function getAdvisory(forecastStr) local textLower = forecastStr:lower() local extras = {} local randomChance = 0.40 -- 40% chance 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 --------------------------------------------------------- -- 6. Conversions and Random Stats (unchanged) --------------------------------------------------------- local function cToF(c) if type(c) == "number" then return math.floor(c * 9/5 + 32 + 0.5) end return c end 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) local wDir = windDirections[math.random(#windDirections)] local wSpd = math.random(0,50) return { high = hi, low = lo, humidity = hum, chanceOfRain= cRain, windDir = wDir, windSpeed = wSpd } end --------------------------------------------------------- -- 7. All City Data (Now Linked to Wiki) --------------------------------------------------------- -- We'll assume the user wants a link to micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#CityName -- so we do e.g.: "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim Vaeringheim]" --------------------------------------------------------- local function cityLink(cityName) -- We'll remove spaces or handle them if needed. -- If cityName has a space, replace with underscore, or just keep #CityName? -- If the anchor is "cityName", we might need an anchor that matches. -- We'll assume your wiki anchor is "#Vaeringheim" etc. local anchor = cityName:gsub(" ", "_") local baseUrl = "https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#" return string.format("[[%s%s|%s]]", baseUrl, anchor, cityName) end local cityData = { {city = "Vaeringheim", climate = "Humid Subtropical"}, {city = "Luminaria", climate = "Oceanic"}, {city = "Serena", climate = "Subpolar Oceanic"}, {city = "Pyralis", climate = "Oceanic"}, {city = "Symphonara", climate = "Oceanic"}, {city = "Aurelia", climate = "Mediterranean (Hot Summer)"}, {city = "Somniumpolis", climate = "Humid Subtropical"}, {city = "Nexa", climate = "Oceanic"}, {city = "Lunalis Sancta", climate = "Oceanic"}, {city = "Sylvapolis", climate = "Humid Subtropical"}, {city = "Saluria", climate = "Oceanic"}, {city = "Aetherium", climate = "Subarctic"}, {city = "Ferrum Citadel", climate = "Hot Desert"}, {city = "Acheron", climate = "Cold Steppe"}, {city = "Erythros", climate = "Oceanic"}, {city = "Catonis Atrium", climate = "Oceanic"}, {city = "Delphica", climate = "Oceanic"}, {city = "Koinonía", climate = "Oceanic"}, {city = "Aureum", climate = "Mediterranean (Hot Summer)"}, {city = "Skýrophos", climate = "Oceanic"}, {city = "Bjornopolis", climate = "Oceanic"}, {city = "Aegirheim", climate = "Subarctic"}, {city = "Norsolyra", climate = "Oceanic"}, {city = "Thorsalon", climate = "Oceanic"}, {city = "Pelagia", climate = "Hot Steppe"}, {city = "Myrene", climate = "Oceanic"}, {city = "Thyrea", climate = "Humid Subtropical"}, {city = "Ephyra", climate = "Subpolar Oceanic"}, {city = "Halicarn", climate = "Mediterranean (Hot Summer)"}, {city = "Keybir-Aviv", climate = "Humid Subtropical"}, {city = "Tel-Amin", climate = "Mediterranean (Hot Summer)"}, {city = "Diamandis", climate = "Mediterranean (Hot Summer)"}, {city = "Jogi", climate = "Oceanic"}, {city = "Lewisburg", climate = "Humid Subtropical"}, {city = "Thermosalem", climate = "Oceanic"}, {city = "Akróstadium", climate = "Cold Steppe"}, {city = "Sufriya", climate = "Humid Subtropical"}, {city = "Lykopolis", climate = "Oceanic"} } --------------------------------------------------------- -- 8. Color-coded cell background --------------------------------------------------------- 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 --------------------------------------------------------- -- 9A: ALL-CITIES FORECAST FUNCTION --------------------------------------------------------- function p.weatherForecast(frame) 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 cityName = entry.city local climateName = entry.climate local climateTbl = climateEvents[climateName] local dateInfo = getCurrentDateInfo() local dayOfYear = dateInfo.dayOfYear local seasonName = getSeasonName(dayOfYear) 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 rowColor = getEventColor(fStr) local stats = getRandomWeatherStats(climateName, seasonName) 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 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(cityName), -- linked version climateName, seasonName, hiF, loF, hum, cRain, wDir, wSpd, rowColor, fStr, advisory )) end table.insert(out, "|}\n") return table.concat(out) end --------------------------------------------------------- -- 9B: SINGLE-CITY FORECAST --------------------------------------------------------- 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 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 if entry.city == cityRequested then foundEntry = entry break end end if not foundEntry then return "Error: City '" .. cityRequested .. "' not found in cityData." end local cityName = 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 rowColor = getEventColor(fStr) local stats = getRandomWeatherStats(climateName, seasonName) 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 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 --------------------------------------------------------- -- 10. Host Stars Logic (Top Image) – "starsForTonight" --------------------------------------------------------- -- Step A: star data (northern hemisphere from your "top image") local starDataNorth = { {name="Agave", starClass="M", latitude=29.5}, {name="Amaäz", starClass="K", latitude=31}, {name="Amáenu", starClass="G", latitude=45}, {name="Amap", starClass="A", latitude=55}, {name="Amazä", starClass="F", latitude=39}, {name="Atämios", starClass="F", latitude=56}, {name="Aprobelle",starClass="A", latitude=59}, {name="Azos", starClass="Galaxy", latitude=60}, -- approx {name="Bebeakaus",starClass="K", latitude=30.5}, {name="Bulhanu", starClass="A", latitude=40}, {name="Crösacío", starClass="K", latitude=17}, {name="Danaß", starClass="A", latitude=67}, {name="Dilëtaz", starClass="G", latitude=36}, {name="Dranamos", starClass="A", latitude=58}, {name="Gaht", starClass="G", latitude=29}, {name="Häpi", starClass="F", latitude=78}, {name="Hazaméos", starClass="A", latitude=72}, {name="Liléigos", starClass="F", latitude=18}, {name="Nyama", starClass="F", latitude=64}, {name="Ocananus", starClass="F", latitude=60.5}, {name="Orebele", starClass="F", latitude=22}, {name="Osiríos", starClass="G", latitude=53}, {name="Pythe", starClass="A", latitude=45}, {name="Sanashalo",starClass="G", latitude=27}, {name="Tä", starClass="F", latitude=39.5}, {name="Vï", starClass="A", latitude=46}, {name="Wedíos", starClass="A", latitude=54}, } -- Step B: how many hours of darkness in each season (rough) local seasonNightLength = { Atosiel = 10, -- 10 hours darkness Thalassiel = 12, -- 12 hours darkness Opsitheiel = 14, -- 14 hours darkness } -- Helper: convert decimal hours to "HH:MM" string local function hourToHM(decimalHour) local hh = math.floor(decimalHour) local mm = math.floor((decimalHour - hh)*60 + 0.5) -- handle wrap-around if hh >= 24 hh = hh % 24 return string.format("%02d:%02d", hh, mm) end function p.starsForTonight(frame) local date = os.date("*t") local dailySeed = (date.year * 1000) + date.yday math.randomseed(dailySeed) local dateInfo = getCurrentDateInfo() local seasonName = getSeasonName(dateInfo.dayOfYear) local nightLen = seasonNightLength[seasonName] or 12 local nightStart = 19.0 -- 19:00 local local queryTime = 18.5 -- 18:30 local time local out = {} table.insert(out, "=== Host Stars Visible Tonight (Top Image) ===\n") table.insert(out, string.format("''Time ~6:30 PM, Bassaridia Vaeringheim – Season: %s''\n\n", seasonName)) table.insert(out, '{| class="wikitable" style="width:100%; text-align:left;"\n') table.insert(out, "! Star Name || Star Class || Approx. Latitude || Rise Time || Set Time || Visible at 18:30?\n") for _, star in ipairs(starDataNorth) do -- pick a random time for starRise local starRise = nightStart + math.random() * (nightLen * 0.3) local starSet = starRise + math.random() * (nightLen * 0.6 + 1.0) -- clamp starSet so it doesn't exceed next morning local maxEnd = nightStart + nightLen + 2.0 -- e.g. by 9:00 next day if starSet > maxEnd then starSet = maxEnd end local riseStr = hourToHM(starRise) local setStr = hourToHM(starSet) local isVis if (starRise <= queryTime) and (queryTime < starSet) then isVis = "Yes" else isVis = "No" end table.insert(out, "|-\n") table.insert(out, string.format( "| %s || %s || %.1f°N || %s || %s || %s\n", star.name, star.starClass, star.latitude, riseStr, setStr, isVis )) end table.insert(out, "|}\n\n") return table.concat(out) end --------------------------------------------------------- -- Return the module table --------------------------------------------------------- return p