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