Module:BassaridiaForecast: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary |
||
Line 1: | Line 1: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- Module: BassaridiaForecast | -- Module: BassaridiaForecast | ||
-- Provides | -- Provides three functions: | ||
-- 1) weatherForecast(frame) | -- 1) weatherForecast(frame) -> returns a single big table | ||
-- | -- for ALL cities (with daily-random weather). | ||
-- 2) weatherForCity(frame) | -- 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). | |||
-- | |||
-- | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
Line 17: | Line 15: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 1 | -- HELPER #1: Current Date Info for PSSC | ||
-- | -- Also used to create a daily random seed. | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getCurrentDateInfo() | local function getCurrentDateInfo() | ||
Line 39: | Line 37: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 2 | -- HELPER #2: Determine Season | ||
-- (Atosiel=1..61, Thalassiel=62..122, Opsitheiel=123..183) | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getSeasonName(dayOfYear) | local function getSeasonName(dayOfYear) | ||
Line 99: | Line 98: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 4. Weather Events by Climate & Season | -- 4. Weather Events by Climate & Season | ||
-- (Unchanged from your original code) | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local climateEvents = { | local climateEvents = { | ||
-- [Truncated for brevity—include your full event lists here!] | |||
-- ... | |||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 5. Disasters: random chance if text has keywords | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local triggeredDisasters = { | local triggeredDisasters = { | ||
Line 419: | Line 113: | ||
{ keywords = {"snowstorms","higher elevations"}, hazard = "Snowstorms at higher elevations" }, | { keywords = {"snowstorms","higher elevations"}, hazard = "Snowstorms at higher elevations" }, | ||
{ keywords = {"steam vent eruptions"}, hazard = "Occasional steam vent eruptions" }, | { keywords = {"steam vent eruptions"}, hazard = "Occasional steam vent eruptions" }, | ||
-- ... (include ALL from your table) | |||
} | } | ||
Line 467: | Line 120: | ||
local extras = {} | local extras = {} | ||
local randomChance = 0.40 | local randomChance = 0.40 -- 40% chance | ||
for _, item in ipairs(triggeredDisasters) do | for _, item in ipairs(triggeredDisasters) do | ||
for _, kw in ipairs(item.keywords) do | for _, kw in ipairs(item.keywords) do | ||
Line 488: | Line 141: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 6. Conversions and Random Stats (unchanged) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function cToF(c) | local function cToF(c) | ||
Line 530: | Line 183: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 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 = { | local cityData = { | ||
{city = " | {city = "Vaeringheim", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Luminaria", climate = "Oceanic"}, | ||
{city = " | {city = "Serena", climate = "Subpolar Oceanic"}, | ||
{city = " | {city = "Pyralis", climate = "Oceanic"}, | ||
{city = " | {city = "Symphonara", climate = "Oceanic"}, | ||
{city = " | {city = "Aurelia", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "Somniumpolis", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Nexa", climate = "Oceanic"}, | ||
{city = " | {city = "Lunalis Sancta", climate = "Oceanic"}, | ||
{city = " | {city = "Sylvapolis", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Saluria", climate = "Oceanic"}, | ||
{city = " | {city = "Aetherium", climate = "Subarctic"}, | ||
{city = " | {city = "Ferrum Citadel", climate = "Hot Desert"}, | ||
{city = " | {city = "Acheron", climate = "Cold Steppe"}, | ||
{city = " | {city = "Erythros", climate = "Oceanic"}, | ||
{city = " | {city = "Catonis Atrium", climate = "Oceanic"}, | ||
{city = " | {city = "Delphica", climate = "Oceanic"}, | ||
{city = " | {city = "Koinonía", climate = "Oceanic"}, | ||
{city = " | {city = "Aureum", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "Skýrophos", climate = "Oceanic"}, | ||
{city = " | {city = "Bjornopolis", climate = "Oceanic"}, | ||
{city = " | {city = "Aegirheim", climate = "Subarctic"}, | ||
{city = " | {city = "Norsolyra", climate = "Oceanic"}, | ||
{city = " | {city = "Thorsalon", climate = "Oceanic"}, | ||
{city = " | {city = "Pelagia", climate = "Hot Steppe"}, | ||
{city = " | {city = "Myrene", climate = "Oceanic"}, | ||
{city = " | {city = "Thyrea", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Ephyra", climate = "Subpolar Oceanic"}, | ||
{city = " | {city = "Halicarn", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "Keybir-Aviv", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Tel-Amin", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "Diamandis", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "Jogi", climate = "Oceanic"}, | ||
{city = " | {city = "Lewisburg", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Thermosalem", climate = "Oceanic"}, | ||
{city = " | {city = "Akróstadium", climate = "Cold Steppe"}, | ||
{city = " | {city = "Sufriya", climate = "Humid Subtropical"}, | ||
{city = " | {city = "Lykopolis", climate = "Oceanic"} | ||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 8. Color-coded cell background | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getEventColor(eventText) | local function getEventColor(eventText) | ||
Line 597: | Line 263: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 9A: ALL-CITIES FORECAST FUNCTION | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
function p.weatherForecast(frame) | function p.weatherForecast(frame) | ||
Line 634: | Line 299: | ||
for _, entry in ipairs(cityData) do | for _, entry in ipairs(cityData) do | ||
local | local cityName = entry.city | ||
local climateName = entry.climate | local climateName = entry.climate | ||
local climateTbl = climateEvents[climateName] | local climateTbl = climateEvents[climateName] | ||
local dateInfo = getCurrentDateInfo() | |||
local dayOfYear = dateInfo.dayOfYear | |||
local seasonName = getSeasonName(dayOfYear) | |||
local seasonTbl = climateTbl and climateTbl[seasonName] | local seasonTbl = climateTbl and climateTbl[seasonName] | ||
local fStr = "No data" | local fStr = "No data" | ||
if seasonTbl and #seasonTbl > 0 then | if seasonTbl and #seasonTbl > 0 then | ||
Line 671: | Line 340: | ||
| %s | | %s | ||
]], | ]], | ||
cityLink, | cityLink(cityName), -- linked version | ||
climateName, | climateName, | ||
seasonName, | seasonName, | ||
Line 691: | Line 360: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 9B: SINGLE-CITY FORECAST | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
function p.weatherForCity(frame) | function p.weatherForCity(frame) | ||
Line 710: | Line 379: | ||
local foundEntry = nil | local foundEntry = nil | ||
for _, entry in ipairs(cityData) do | for _, entry in ipairs(cityData) do | ||
if entry.city == cityRequested then | |||
if entry.city | |||
foundEntry = entry | foundEntry = entry | ||
break | break | ||
Line 722: | Line 389: | ||
end | end | ||
local | local cityName = foundEntry.city | ||
local climateName = foundEntry.climate | local climateName = foundEntry.climate | ||
Line 734: | Line 401: | ||
local rowColor = getEventColor(fStr) | local rowColor = getEventColor(fStr) | ||
local stats = getRandomWeatherStats(climateName, seasonName) | local stats = getRandomWeatherStats(climateName, seasonName) | ||
local hiF = cToF(stats.high) | local hiF = cToF(stats.high) | ||
Line 742: | Line 408: | ||
local wDir = stats.windDir | local wDir = stats.windDir | ||
local wSpd = stats.windSpeed | local wSpd = stats.windSpeed | ||
local advisory = getAdvisory(fStr) | local advisory = getAdvisory(fStr) | ||
Line 763: | Line 428: | ||
local row = string.format( | local row = string.format( | ||
[[ | [[ | ||
| %s | | %s | ||
| %s | | %s | ||
| %d | | %d | ||
| %d | | %d | ||
| %d | | %d | ||
| %d | | %d | ||
| %s | | %s | ||
| %d | | %d | ||
| style="background-color:%s" | %s | | style="background-color:%s" | %s | ||
| %s | | %s | ||
Line 794: | Line 459: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 10. Host Stars Logic (Top Image) – "starsForTonight" | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- Step A: star data (northern hemisphere from your "top image") | |||
local starDataNorth = { | |||
{ name="Agave", starClass="M", | {name="Agave", starClass="M", latitude=29.5}, | ||
{ name="Amaäz", starClass="K", | {name="Amaäz", starClass="K", latitude=31}, | ||
{ name="Amáenu", starClass="G", | {name="Amáenu", starClass="G", latitude=45}, | ||
{ name="Amap", starClass="A", | {name="Amap", starClass="A", latitude=55}, | ||
{ name="Amazä", starClass="F", | {name="Amazä", starClass="F", latitude=39}, | ||
{ name="Atämios", starClass="F", | {name="Atämios", starClass="F", latitude=56}, | ||
{ name="Aprobelle",starClass="A", | {name="Aprobelle",starClass="A", latitude=59}, | ||
{name="Azos", starClass="Galaxy", latitude=60}, -- approx | |||
{ name="Bebeakaus",starClass="K", | {name="Bebeakaus",starClass="K", latitude=30.5}, | ||
{ name="Bulhanu", starClass="A", | {name="Bulhanu", starClass="A", latitude=40}, | ||
{ name="Crösacío", starClass="K", | {name="Crösacío", starClass="K", latitude=17}, | ||
{ name="Danaß", starClass="A", | {name="Danaß", starClass="A", latitude=67}, | ||
{ name="Dilëtaz", starClass="G", | {name="Dilëtaz", starClass="G", latitude=36}, | ||
{ name="Dranamos", starClass="A", | {name="Dranamos", starClass="A", latitude=58}, | ||
{ name="Gaht", starClass="G", | {name="Gaht", starClass="G", latitude=29}, | ||
{ name="Häpi", starClass="F", | {name="Häpi", starClass="F", latitude=78}, | ||
{ name="Hazaméos", starClass="A", | {name="Hazaméos", starClass="A", latitude=72}, | ||
{ name="Liléigos", starClass="F", | {name="Liléigos", starClass="F", latitude=18}, | ||
{ name="Nyama", starClass="F", | {name="Nyama", starClass="F", latitude=64}, | ||
{ name="Ocananus", starClass="F", | {name="Ocananus", starClass="F", latitude=60.5}, | ||
{ name="Orebele", starClass="F", | {name="Orebele", starClass="F", latitude=22}, | ||
{ name="Osiríos", starClass="G", | {name="Osiríos", starClass="G", latitude=53}, | ||
{ name="Pythe", starClass="A", | {name="Pythe", starClass="A", latitude=45}, | ||
{ name="Sanashalo",starClass="G", | {name="Sanashalo",starClass="G", latitude=27}, | ||
{ name="Tä", starClass="F", | {name="Tä", starClass="F", latitude=39.5}, | ||
{ name="Vï", starClass="A", | {name="Vï", starClass="A", latitude=46}, | ||
{ name="Wedíos", starClass="A", | {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) | 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 = {} | local out = {} | ||
table.insert(out, "=== Host Stars Visible Tonight (Top Image) ===\n") | table.insert(out, "=== Host Stars Visible Tonight (Top Image) ===\n") | ||
table.insert(out, "''Time | 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 | end | ||
table.insert(out, "|-\n") | table.insert(out, "|-\n") | ||
table.insert(out, string.format( | table.insert(out, string.format( | ||
"| %s || %s || %.1f°N || %s || %s || %s\n", | |||
| %s | star.name, star.starClass, star.latitude, riseStr, setStr, isVis | ||
| %.1f°N | |||
| %s | |||
| % | |||
star.name, | |||
)) | )) | ||
end | end | ||
table.insert(out, "|}\n") | table.insert(out, "|}\n\n") | ||
return table.concat(out) | return table.concat(out) | ||
end | end |
Revision as of 06:39, 27 December 2024
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