Module:BassaridiaForecast: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary |
||
| (28 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- Module: | -- 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). | ||
-- | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
| Line 15: | Line 14: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 1. PSSC | -- 1. Determine Today's Date for PSSC | ||
-- Also used to create a daily random seed. | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getCurrentDateInfo() | local function getCurrentDateInfo() | ||
local startDate = os.time({year = 1999, month = 8, day = 6}) | local startDate = os.time({year=1999, month=8, day=6}) | ||
local secondsInDay = 86400 | local secondsInDay= 86400 | ||
local daysPerYear = 183 | local daysPerYear = 183 | ||
local currentTime = os.time() | local currentTime = os.time() | ||
local | local totalDays = math.floor((currentTime - startDate) / secondsInDay) | ||
local | local fractionYear= totalDays / daysPerYear | ||
local psscYear = math.floor( | local psscYear = math.floor(fractionYear) | ||
local dayOfYear = math.floor(( | local dayOfYear = math.floor((fractionYear - psscYear) * daysPerYear) + 1 | ||
return { | return { | ||
psscYear = psscYear, | psscYear = psscYear, | ||
dayOfYear = dayOfYear | dayOfYear= dayOfYear | ||
} | } | ||
end | end | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 2. Determine Season (1..61= | -- 2. Determine Season (Atosiel=1..61, Thalassiel=62..122, Opsitheiel=123..183) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getSeasonName(dayOfYear) | local function getSeasonName(dayOfYear) | ||
if dayOfYear <= 61 then | if dayOfYear <= 61 then | ||
| Line 51: | Line 49: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 3. | -- 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 | |||
-- (Revised: several forecast strings now include | |||
-- keywords from triggeredDisasters) | |||
--------------------------------------------------------- | |||
local climateEvents = { | local climateEvents = { | ||
["Humid Subtropical"] = { | ["Humid Subtropical"] = { | ||
| Line 67: | Line 111: | ||
"Overcast for part of the day, mild temperatures", | "Overcast for part of the day, mild temperatures", | ||
"Warm breezes carrying faint floral scents", | "Warm breezes carrying faint floral scents", | ||
"Partial sun, warm with a slight chance of rain" | "Partial sun, warm with a slight chance of rain", | ||
"Early morning seasonal storms and lake-driven floods may develop" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 79: | Line 124: | ||
"Sticky air, short downpours in isolated spots", | "Sticky air, short downpours in isolated spots", | ||
"Heat advisory with only brief cooling at night", | "Heat advisory with only brief cooling at night", | ||
"Nighttime storms lingering into early morning" | "Nighttime storms lingering into early morning", | ||
"Afternoon heavy rains could trigger river floods" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 91: | Line 137: | ||
"Evening drizzle with mild breezes", | "Evening drizzle with mild breezes", | ||
"Patchy haze, moderate warmth throughout the day", | "Patchy haze, moderate warmth throughout the day", | ||
"Short-lived shower, then clearing skies" | "Short-lived shower, then clearing skies", | ||
"Evening cooling may lead to localized mudslides on steep slopes" | |||
} | } | ||
}, | }, | ||
| Line 105: | Line 152: | ||
"Gentle but persistent rain, green foliage thriving", | "Gentle but persistent rain, green foliage thriving", | ||
"Off-and-on showers, brief sunny interludes", | "Off-and-on showers, brief sunny interludes", | ||
"Steady breeze, moderate humidity levels" | "Steady breeze, moderate humidity levels", | ||
"Morning fog with coastal storms and marsh flooding possible" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 117: | Line 165: | ||
"Rain-laden air, occasional breaks of sun", | "Rain-laden air, occasional breaks of sun", | ||
"Maritime winds bringing light spray inland", | "Maritime winds bringing light spray inland", | ||
"Cloudy midday, mild sunshine before dusk" | "Cloudy midday, mild sunshine before dusk", | ||
"Intermittent showers may escalate into flash floods and coastal storms" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 129: | Line 178: | ||
"Morning gloom, occasional pockets of clearing", | "Morning gloom, occasional pockets of clearing", | ||
"Evening rainfall pushing in from the coast", | "Evening rainfall pushing in from the coast", | ||
"Dense cloud cover, breezy and damp" | "Dense cloud cover, breezy and damp", | ||
"Evening drizzle may develop into coastal gales and shoreline flooding" | |||
} | } | ||
}, | }, | ||
| Line 143: | Line 193: | ||
"Chilly breeze, scattered showers off and on", | "Chilly breeze, scattered showers off and on", | ||
"Gray skies, some slushy buildup on paths", | "Gray skies, some slushy buildup on paths", | ||
"Snowmelt water raising local streams" | "Snowmelt water raising local streams", | ||
"Cold drizzle combined with snowstorms and blizzards in higher areas" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 155: | Line 206: | ||
"Surprisingly bright morning, clouding by dusk", | "Surprisingly bright morning, clouding by dusk", | ||
"Short bursts of rain followed by fog", | "Short bursts of rain followed by fog", | ||
"Late-night chill with drizzle or brief sleet" | "Late-night chill with drizzle or brief sleet", | ||
"Misty conditions could prompt snowstorms and river floods" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 167: | Line 219: | ||
"Pelting sleet for an hour, then calm, cold air", | "Pelting sleet for an hour, then calm, cold air", | ||
"Gusty evening, possible snow accumulations", | "Gusty evening, possible snow accumulations", | ||
"Sun rarely visible, persistent winter gloom" | "Sun rarely visible, persistent winter gloom", | ||
"Freezing drizzle may trigger avalanches in higher passes" | |||
} | } | ||
}, | }, | ||
| Line 181: | Line 234: | ||
"Light shower possible at sunrise, drying by noon", | "Light shower possible at sunrise, drying by noon", | ||
"Warming trend, low humidity, pleasant day", | "Warming trend, low humidity, pleasant day", | ||
"Gradual warming under spotless sky" | "Gradual warming under spotless sky", | ||
"Bright skies overshadowed by potential heat advisory and droughts" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 193: | Line 247: | ||
"Cloudless sky, near-record high temps", | "Cloudless sky, near-record high temps", | ||
"Salt-laden breezes if near the coast, scorching inland", | "Salt-laden breezes if near the coast, scorching inland", | ||
"Heatwaves persisting, zero rain expected" | "Heatwaves persisting, zero rain expected", | ||
"Intense midday sun might lead to severe heat waves and dust storms" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 205: | Line 260: | ||
"Clear dawn, mild sunshine, gentle wind late", | "Clear dawn, mild sunshine, gentle wind late", | ||
"Intervals of cloud cover, potential short rain", | "Intervals of cloud cover, potential short rain", | ||
"Moist sea air, moderate daytime warmth" | "Moist sea air, moderate daytime warmth", | ||
"Cooling breezes could reduce temperatures, but localized brushfires and summer wildfires remain possible" | |||
} | } | ||
}, | }, | ||
| Line 219: | Line 275: | ||
"No hint of precipitation, cloudless horizon", | "No hint of precipitation, cloudless horizon", | ||
"Daytime warmth, refreshing if slight breeze arrives", | "Daytime warmth, refreshing if slight breeze arrives", | ||
"Sun-warmed rock surfaces, mild nights" | "Sun-warmed rock surfaces, mild nights", | ||
"Scorching afternoons may be accompanied by dust storms and occasional mirage-like shimmers" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 231: | Line 288: | ||
"Dry conditions, subpar humidity all day", | "Dry conditions, subpar humidity all day", | ||
"Baking desert floor, potential for blowing sand", | "Baking desert floor, potential for blowing sand", | ||
"Intense heat building day after day" | "Intense heat building day after day", | ||
"Extreme heat could combine with blowing sand to trigger potential sandstorms" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 243: | Line 301: | ||
"Gentle warmth, dryness prevalent, no clouds", | "Gentle warmth, dryness prevalent, no clouds", | ||
"Cool breezes shortly after sunset, mild day overall", | "Cool breezes shortly after sunset, mild day overall", | ||
"Hazy horizon, soft morning glow across dunes" | "Hazy horizon, soft morning glow across dunes", | ||
"Evening cooling might still see intense heat with possible dust devils on dry slopes" | |||
} | } | ||
}, | }, | ||
| Line 257: | Line 316: | ||
"Slow temperature rise, slight dryness in mid-afternoon", | "Slow temperature rise, slight dryness in mid-afternoon", | ||
"Brief drizzle, otherwise bright and breezy", | "Brief drizzle, otherwise bright and breezy", | ||
"Day-night difference noticeable, mild but windy" | "Day-night difference noticeable, mild but windy", | ||
"Mild conditions could turn volatile with sudden flash floods and minor landslides" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 269: | Line 329: | ||
"Warm evening, slight nighttime cooldown", | "Warm evening, slight nighttime cooldown", | ||
"Sky remains mostly clear, grassland shimmering", | "Sky remains mostly clear, grassland shimmering", | ||
"Thunderheads visible in distance, might pass by" | "Thunderheads visible in distance, might pass by", | ||
"Afternoon heat may intensify, leading to thunderstorms, river floods, and gusty rain squalls" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 281: | Line 342: | ||
"Biting wind, few clouds, stark horizon", | "Biting wind, few clouds, stark horizon", | ||
"Clearing late morning, crisp, cold nightfall", | "Clearing late morning, crisp, cold nightfall", | ||
"Daytime near freezing, nights well below" | "Daytime near freezing, nights well below", | ||
"Chilly nights might bring frost and the risk of localized landslides on steep slopes" | |||
} | } | ||
}, | }, | ||
| Line 295: | Line 357: | ||
"Sparse vegetation, daily heat climbing gradually", | "Sparse vegetation, daily heat climbing gradually", | ||
"Clear horizon, wind picking up in late evening", | "Clear horizon, wind picking up in late evening", | ||
"Sun exposure high, consistent dryness" | "Sun exposure high, consistent dryness", | ||
"Scorching sun may lead to persistent dryness with a risk of brushfires and dust devils on dry slopes" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 307: | Line 370: | ||
"Rare cloud can appear but quickly vanishes", | "Rare cloud can appear but quickly vanishes", | ||
"Daily highs remain extreme, nights only mild", | "Daily highs remain extreme, nights only mild", | ||
"Lingering warmth through sunset, dryness unwavering" | "Lingering warmth through sunset, dryness unwavering", | ||
"Intense heat may trigger severe dust storms and a potential heat wave" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 319: | Line 383: | ||
"Occasional relief from mild wind, no precipitation", | "Occasional relief from mild wind, no precipitation", | ||
"Hot, tranquil afternoon, moderate wind at dusk", | "Hot, tranquil afternoon, moderate wind at dusk", | ||
"Minimal cloud presence, dryness dominating environment" | "Minimal cloud presence, dryness dominating environment", | ||
"Evening cooling might reduce temperatures, yet localized sandstorms and minor landslides remain a concern" | |||
} | } | ||
}, | }, | ||
| Line 333: | Line 398: | ||
"Icy patches linger, overall chilly but bright", | "Icy patches linger, overall chilly but bright", | ||
"Early thaw, mild midday, returning chill after dusk", | "Early thaw, mild midday, returning chill after dusk", | ||
"Reluctant spring with freezing nights, slow warm days" | "Reluctant spring with freezing nights, slow warm days", | ||
"Rapid thawing could spark river floods and snowstorms with heavy snowfall" | |||
}, | }, | ||
Thalassiel = { | Thalassiel = { | ||
| Line 345: | Line 411: | ||
"Mostly cloudy, occasional glimpses of sun", | "Mostly cloudy, occasional glimpses of sun", | ||
"Brisk wind, earlier nightfall than lower latitudes", | "Brisk wind, earlier nightfall than lower latitudes", | ||
"Daytime highs remain mild, nights quite cold" | "Daytime highs remain mild, nights quite cold", | ||
"Cool days may be interrupted by sudden blizzard-like conditions and heavy rain squalls" | |||
}, | }, | ||
Opsitheiel = { | Opsitheiel = { | ||
| Line 357: | Line 424: | ||
"Mountains see near-constant snow, valleys freeze nightly", | "Mountains see near-constant snow, valleys freeze nightly", | ||
"Rare clear spells, intense wind chill outside", | "Rare clear spells, intense wind chill outside", | ||
"Deep winter mode, swirling snow flurries all day" | "Deep winter mode, swirling snow flurries all day", | ||
"Deep winter chill may result in persistent blizzards and avalanches in higher passes" | |||
} | } | ||
} | } | ||
| Line 363: | Line 431: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 5. Triggered Disasters and Natural Disaster Advisory | ||
-- ( | -- (Each hazard is triggered with a 10% chance) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
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" }, | |||
{ keywords = {"landslides","steep slopes"}, hazard = "Landslides on steep slopes" }, | |||
{ keywords = {"droughts","brushfires"}, hazard = "Droughts and brushfires" }, | |||
{ keywords = {"recurrent flooding in marshes","flooding in marshes"}, hazard = "Recurrent flooding in marshes" }, | |||
{ keywords = {"localized mudslides"}, hazard = "Localized mudslides" }, | |||
{ keywords = {"fog%-related travel hazards"}, hazard = "Fog-related travel hazards" }, | |||
{ keywords = {"swamp flooding"}, hazard = "Swamp flooding" }, | |||
{ keywords = {"river floods"}, hazard = "River floods" }, | |||
{ keywords = {"blizzards"}, hazard = "Blizzards" }, | |||
{ keywords = {"dust storms","heatwaves"}, hazard = "Dust storms and heatwaves" }, | |||
{ keywords = {"flash floods"}, hazard = "Occasional flash floods" }, | |||
{ keywords = {"tar%-pit gas releases"}, hazard = "Tar-pit gas releases" }, | |||
{ keywords = {"landslides on steep slopes"}, hazard = "Landslides on steep slopes" }, | |||
{ keywords = {"coastal storms"}, hazard = "Coastal storms" }, | |||
{ keywords = {"mild landslides"}, hazard = "Mild landslides" }, | |||
{ keywords = {"summer wildfires"}, hazard = "Summer wildfires" }, | |||
{ keywords = {"cliff erosion","sea storms"}, hazard = "Cliff erosion and sea storms" }, | |||
{ keywords = {"flood surges near waterfalls"}, hazard = "Flood surges near waterfalls" }, | |||
{ keywords = {"coastal gales","blizzards"}, hazard = "Coastal gales, blizzards" }, | |||
{ keywords = {"marsh flooding"}, hazard = "Marsh flooding" }, | |||
{ keywords = {"cliff collapses"}, hazard = "Occasional cliff collapses" }, | |||
{ keywords = {"sandstorms"}, hazard = "Sandstorms" }, | |||
{ keywords = {"minor landslides"}, hazard = "Minor landslides" }, | |||
{ keywords = {"lake overflows","marsh flooding"}, hazard = "Lake overflows, marsh flooding" }, | |||
{ keywords = {"avalanches in higher passes"}, hazard = "Avalanches in higher passes" }, | |||
{ keywords = {"shoreline flooding"}, hazard = "Shoreline flooding" }, | |||
{ keywords = {"wildfires in dry spells"}, hazard = "Wildfires in dry spells" }, | |||
{ keywords = {"droughts"}, hazard = "Droughts" }, | |||
{ keywords = {"swamp overflows"}, hazard = "Swamp overflows" }, | |||
{ keywords = {"landslides near hot springs"}, hazard = "Occasional landslides near hot springs" }, | |||
{ keywords = {"dust devils on dry slopes"}, hazard = "Dust devils on dry slopes" }, | |||
{ keywords = {"storm surges from inlet"}, hazard = "Storm surges from inlet" }, | |||
{ keywords = {"mine collapses","gas leaks"}, hazard = "Mine collapses, gas leaks" }, | |||
{ keywords = {"frequent rain squalls", "heavy downpours", "intermittent heavy downpours"}, hazard = "Localized mudslides" }, | |||
{ keywords = {"blizzard%-like conditions"}, hazard = "Blizzards" }, | |||
{ keywords = {"extreme heat", "intense heat", "heat advisory"}, hazard = "Heat wave" }, | |||
{ keywords = {"rain squalls", "persistent dryness", "dry conditions"}, hazard = "Risk of brushfires" }, | |||
{ keywords = {"foggy dawn", "low visibility", "dense fog"}, hazard = "Fog-related hazards" }, | |||
{ keywords = {"heavy snowfall", "gusty winds carrying ice", "deep winter mode"}, hazard = "Possible avalanche" }, | |||
{ keywords = {"strong desert winds", "dust storm", "mirage%-like shimmer"}, hazard = "Sandstorm risk" }, | |||
{ keywords = {"swamp flooding", "marsh flooding"}, hazard = "Potential flood surge" }, | |||
{ keywords = {"tornado", "twister"}, hazard = "Tornado possibility" }, | |||
{ keywords = {"volcano", "volcanic"}, hazard = "Volcanic activity nearby" }, | |||
{ keywords = {"earthquake", "seismic"}, hazard = "Minor earthquake risk" }, | |||
{ keywords = {"hail", "ice storm"}, hazard = "Hail storm risk" }, | |||
{ keywords = {"severe storm", "severe weather"}, hazard = "Severe weather warning" }, | |||
{ keywords = {"tsunami", "coastal surge"}, hazard = "Tsunami risk" }, | |||
} | |||
local function getAdvisory(forecastStr) | |||
local textLower = forecastStr:lower() | |||
local extras = {} | |||
-- Each disaster is triggered with a 10% chance: | |||
local randomChance = 0.10 | |||
for _, item in ipairs(triggeredDisasters) do | |||
for _, kw in ipairs(item.keywords) do | |||
if textLower:find(kw) then | |||
if math.random() <= randomChance then | |||
table.insert(extras, item.hazard) | |||
end | |||
break | |||
end | |||
end | |||
end | |||
-- | -- Fallback: if no hazard was triggered, a 10% chance for a generic advisory | ||
if #extras == 0 and math.random() <= 0.10 then | |||
table.insert(extras, "Unusual atmospheric conditions detected") | |||
end | |||
if #extras > 0 then | |||
return table.concat(extras, "; ") | |||
if | |||
return | |||
else | else | ||
return " | return "No reports" | ||
end | end | ||
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 | |||
local windDirections = {"N", "NE", "E", "SE", "S", "SW", "W", "NW"} | -- Limit wind speeds to 0-15 km/h for Earth-like mild conditions. | ||
local windDirections = {"N","NE","E","SE","S","SW","W","NW"} | |||
local function getRandomWeatherStats(climate, season) | local function getRandomWeatherStats(climate, season) | ||
| Line 411: | Line 545: | ||
local lo = math.random(data.loMin, data.loMax) | local lo = math.random(data.loMin, data.loMax) | ||
local hum = math.random(data.humMin, data.humMax) | local hum = math.random(data.humMin, data.humMax) | ||
local cRain= math.random(0,100) | |||
local cRain = math.random(0,100) | |||
local wDir = windDirections[math.random(#windDirections)] | local wDir = windDirections[math.random(#windDirections)] | ||
local wSpd = math.random(0, | local wSpd = math.random(0,15) | ||
return { | return { | ||
| Line 427: | Line 560: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- | -- 8. City Data | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local cityData = { | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim Vaeringheim]", climate = "Humid Subtropical"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Luminaria Luminaria]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Serena Serena]", climate = "Subpolar Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Pyralis Pyralis]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Symphonara Symphonara]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aurelia Aurelia]", climate = "Mediterranean (Hot Summer)"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis Somniumpolis]", climate = "Humid Subtropical"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Nexa Nexa]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lunalis_Sancta Lunalis Sancta]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sylvapolis Sylvapolis]", climate = "Humid Subtropical"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Saluria Saluria]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aetherium Aetherium]", climate = "Subarctic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ferrum_Citadel Ferrum Citadel]", climate = "Hot Desert"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Acheron Acheron]", climate = "Cold Steppe"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Erythros Erythros]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Catonis_Atrium Catonis Atrium]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Delphica Delphica]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Koinonía Koinonía]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aureum Aureum]", climate = "Mediterranean (Hot Summer)"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Skýrophos Skýrophos]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Bjornopolis Bjornopolis]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aegirheim Aegirheim]", climate = "Subarctic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Norsolyra Norsolyra]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thorsalon Thorsalon]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Pelagia Pelagia]", climate = "Hot Steppe"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Myrene Myrene]", climate = "Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thyrea Thyrea]", climate = "Humid Subtropical"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ephyra Ephyra]", climate = "Subpolar Oceanic"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Halicarn Halicarn]", climate = "Mediterranean (Hot Summer)"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Keybir-Aviv Keybir-Aviv]", climate = "Humid Subtropical"}, | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Tel-Amin Tel-Amin]", climate = "Mediterranean (Hot Summer)"}, | |||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Diamandis Diamandis]", climate = "Mediterranean (Hot Summer)"}, | ||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Jogi Jogi]", climate = "Oceanic"}, | ||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lewisburg Lewisburg]", climate = "Humid Subtropical"}, | |||
{city = " | |||
{city = " | |||
{city = " | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thermosalem Thermosalem]", climate = "Oceanic"}, | |||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Akróstadium Akróstadium]", climate = "Cold Steppe"}, | ||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sufriya Sufriya]", climate = "Humid Subtropical"}, | ||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lykopolis Lykopolis]", climate = "Oceanic"}, | |||
{city = " | |||
{city = " | |||
-- | -- NEW ENTRIES for Bassaridian Normark: | ||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ardclach Ardclach]", climate = "Subarctic"}, -- Dfc | ||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Riddersborg Riddersborg]", climate = "Subpolar Oceanic"}, -- Cfc | |||
{city = " | |||
-- | -- NEW (Valley of Keltia) central steppe cities: | ||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ourid Ourid]", climate = "Cold Steppe"}, | ||
{city = " | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Bashkim Bashkim]", climate = "Cold Steppe"}, | ||
-- Major Cities in | -- NEW: Major Cities in Eastern Caledonia | ||
{city = " | {city = "Skøda", climate = "Subarctic"}, -- Dfc (mountainous, northern/central) | ||
{city = " | {city = "Fanghorn", climate = "Subarctic"}, -- Dfc (mountainous, northern/central) | ||
{city = " | {city = "Eikbu", climate = "Subarctic"}, -- Dfc (mountainous, northern/central) | ||
{city = " | {city = "Galvø", climate = "Subarctic"}, -- Dfc (mountainous, northern/central) | ||
{city = " | {city = "Slevik", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal) | ||
{city = "Sårensby", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal) | |||
{city = "Krlsgorod", climate = "Subarctic"}, -- Dfc (lakeshore/arctic) | |||
{city = "Kaledonija", climate = "Subarctic"}, -- Dfc (lakeshore/arctic) | |||
{city = "Notranskja", climate = "Subarctic"}, -- Dfc | |||
{city = "Hammarfell", climate = "Subarctic"}, -- Dfc | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sjøsborg Sjøsborg]", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal) | |||
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Storesund Storesund]", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal) | |||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 9. | -- 9. 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 | |||
--------------------------------------------------------- | |||
-- 10A. ALL-CITIES FORECAST FUNCTION | |||
-- Overriding chance of rain if precipitation is mentioned | |||
--------------------------------------------------------- | |||
function p.weatherForecast(frame) | function p.weatherForecast(frame) | ||
-- Daily seed | |||
local date = os.date("*t") | |||
local dailySeed = (date.year * 1000) + date.yday | |||
math.randomseed(dailySeed) | |||
local dateInfo = getCurrentDateInfo() | local dateInfo = getCurrentDateInfo() | ||
local dayOfYear = dateInfo.dayOfYear | local dayOfYear = dateInfo.dayOfYear | ||
| Line 500: | Line 664: | ||
local seasonName = getSeasonName(dayOfYear) | local seasonName = getSeasonName(dayOfYear) | ||
local | local out = {} | ||
table.insert( | table.insert(out, "== Daily Weather Forecast ==\n") | ||
table.insert( | 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, [[ | |||
table.insert( | ! City | ||
! Climate | |||
! Season | |||
table.insert( | ! High °F | ||
[[! City | ! Low °F | ||
]] | ! Humidity (%) | ||
! Chance of Rain (%) | |||
! Wind Direction | |||
! Wind Speed (km/h) | |||
! Today's Weather | |||
! Natural Disaster Advisory | |||
]]) | |||
for _, | for _, entry in ipairs(cityData) do | ||
local | local cityLink = entry.city | ||
local climateName = | local climateName = entry.climate | ||
-- 1) Random weather event | -- 1) Random weather event | ||
local | local climateTbl = climateEvents[climateName] | ||
local seasonTbl = | local seasonTbl = climateTbl and climateTbl[seasonName] | ||
local | local fStr = "No data" | ||
if seasonTbl and #seasonTbl > 0 then | if seasonTbl and #seasonTbl > 0 then | ||
local | local idx = math.random(#seasonTbl) | ||
fStr = seasonTbl[idx] | |||
end | end | ||
-- 2) | -- 2) Stats | ||
local | local stats = getRandomWeatherStats(climateName, seasonName) | ||
-- | -- Override chanceOfRain if forecast text indicates precipitation: | ||
local | 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 hiF = ( | local loF = cToF(stats.low) | ||
local | local hum = stats.humidity | ||
local cRain = stats.chanceOfRain | |||
local wDir = stats.windDir | |||
local wSpd = stats.windSpeed | |||
local rowColor = getEventColor(fStr) | |||
table.insert( | local advisory = getAdvisory(fStr) | ||
table.insert( | |||
[[| %s | 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, | |||
seasonName, | rowColor, fStr, advisory | ||
)) | |||
end | |||
table.insert(out, "|}\n") | |||
return table.concat(out) | |||
end | |||
--------------------------------------------------------- | |||
-- 10B. SINGLE-CITY FORECAST FUNCTION | |||
-- Similarly overrides chance of rain if precipitation is found | |||
--------------------------------------------------------- | |||
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 | |||
-- Attempt to match either the full city link or the last plain text | |||
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) | |||
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 = { | |||
{ name="Agave", starClass="M", lat=29.5 }, | |||
{ name="Amaäz", starClass="K", lat=31 }, | |||
{ name="Amáenu", starClass="G", lat=45 }, | |||
{ name="Amap", starClass="A", lat=55 }, | |||
{ name="Amazä", starClass="F", lat=39 }, | |||
{ name="Atämios", starClass="F", lat=56 }, | |||
{ name="Aprobelle",starClass="A", lat=59 }, | |||
{ name="Bebeakaus",starClass="K", lat=30.5 }, | |||
{ name="Bulhanu", starClass="A", lat=40 }, | |||
{ name="Crösacío", starClass="K", lat=17 }, | |||
{ name="Danaß", starClass="A", lat=67 }, | |||
{ name="Dilëtaz", starClass="G", lat=36 }, | |||
{ name="Dranamos", starClass="A", lat=58 }, | |||
{ name="Gaht", starClass="G", lat=29 }, | |||
{ name="Häpi", starClass="F", lat=78 }, | |||
{ name="Hazaméos", starClass="A", lat=72 }, | |||
{ name="Liléigos", starClass="F", lat=18 }, | |||
{ name="Nyama", starClass="F", lat=64 }, | |||
{ name="Ocananus", starClass="F", lat=60.5 }, | |||
{ name="Orebele", starClass="F", lat=22 }, | |||
{ name="Osiríos", starClass="G", lat=53 }, | |||
{ name="Pythe", starClass="A", lat=45 }, | |||
{ name="Sanashalo",starClass="G", lat=27 }, | |||
{ name="Tä", starClass="F", lat=39.5 }, | |||
{ name="Vï", starClass="A", lat=46 }, | |||
{ name="Wedíos", starClass="A", lat=54 }, | |||
} | |||
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 | end | ||
table.insert( | table.insert(out, "|}\n\n") | ||
return table.concat( | |||
return table.concat(out) | |||
end | end | ||
--------------------------------------------------------- | |||
-- Return the module table | |||
--------------------------------------------------------- | |||
return p | return p | ||
Latest revision as of 02:40, 23 October 2025
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 = {
["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
-- (Revised: several forecast strings now include
-- keywords from triggeredDisasters)
---------------------------------------------------------
local climateEvents = {
["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",
"Early morning seasonal storms and lake-driven floods may develop"
},
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",
"Afternoon heavy rains could trigger river floods"
},
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",
"Evening cooling may lead to localized mudslides on steep slopes"
}
},
["Oceanic"] = {
Atosiel = {
"Frequent light rain, cool breezes all day",
"Foggy dawn, mild midmorning sunshine",
"Short sunbreaks among passing showers",
"Consistent drizzle, fresh winds from the sea",
"Cloudy intervals with a few bright spells",
"Late-afternoon clearing, crisp evening air",
"Slow-moving clouds, mild temperatures",
"Gentle but persistent rain, green foliage thriving",
"Off-and-on showers, brief sunny interludes",
"Steady breeze, moderate humidity levels",
"Morning fog with coastal storms and marsh flooding possible"
},
Thalassiel = {
"Light rain off and on, mild temperatures",
"Overcast with comfortable breezes throughout",
"Drizzly morning, partly cloudy afternoon",
"Soft rains, lush vegetation, stable temps",
"Cool, damp start, drying toward evening",
"Frequent cloud cover, gentle summer breeze",
"Moist air with lingering fog near streams",
"Rain-laden air, occasional breaks of sun",
"Maritime winds bringing light spray inland",
"Cloudy midday, mild sunshine before dusk",
"Intermittent showers may escalate into flash floods and coastal storms"
},
Opsitheiel = {
"Overcast skies, cool drizzle through the day",
"Sporadic showers, chillier in the evening",
"Low visibility in morning fog, slow clearing",
"Gray skies, frequent rain squalls, cool wind",
"Chilly nightfall, damp ground all day",
"Heavier showers arriving late afternoon",
"Periodic drizzle, temperatures on the cool side",
"Morning gloom, occasional pockets of clearing",
"Evening rainfall pushing in from the coast",
"Dense cloud cover, breezy and damp",
"Evening drizzle may develop into coastal gales and shoreline flooding"
}
},
["Subpolar Oceanic"] = {
Atosiel = {
"Near-freezing dawn, cold drizzle by midday",
"Mist over melting snow patches, breezy afternoon",
"Overcast skies, mix of rain and sleet",
"Cool, damp air, slight warmup after noon",
"Late-day sleet turning to light rain",
"Mountaintop flurries, valley drizzle",
"Cloudy morning, partial clearing in late afternoon",
"Chilly breeze, scattered showers off and on",
"Gray skies, some slushy buildup on paths",
"Snowmelt water raising local streams",
"Cold drizzle combined with snowstorms and blizzards in higher areas"
},
Thalassiel = {
"Cool, short days with periodic drizzle",
"Heavy low clouds, pockets of bright breaks",
"Gentle rain, evening chill deeper than normal",
"Intermittent showers, damp wind from hills",
"Cloud deck lingering, occasional lighter drizzle",
"Faint sunlight overshadowed by thick cloud",
"Subdued warmth, sporadic mist across ridges",
"Surprisingly bright morning, clouding by dusk",
"Short bursts of rain followed by fog",
"Late-night chill with drizzle or brief sleet",
"Misty conditions could prompt snowstorms and river floods"
},
Opsitheiel = {
"Steady cold rain mixing with wet snow",
"Freezing drizzle at dawn, raw gusty winds",
"Snowfall likely at higher elevations, rain below",
"Cloudy, damp day with potential sleet storms",
"Early morning frost turning to slush midday",
"Occasional heavy showers with flurry bursts",
"Gray gloom, subfreezing nights across valleys",
"Pelting sleet for an hour, then calm, cold air",
"Gusty evening, possible snow accumulations",
"Sun rarely visible, persistent winter gloom",
"Freezing drizzle may trigger avalanches in higher passes"
}
},
["Mediterranean (Hot Summer)"] = {
Atosiel = {
"Mild temperatures, bright sunshine, dry air",
"Morning dew, warming fast under clear skies",
"Cool breezes off the sea, comfortable afternoon",
"Sunny midday with a crisp, light wind",
"Short drizzly spell, then mostly clear",
"Early bloom across hills, moderate warmth",
"Mostly sunny, a few scattered clouds late",
"Light shower possible at sunrise, drying by noon",
"Warming trend, low humidity, pleasant day",
"Gradual warming under spotless sky",
"Bright skies overshadowed by potential heat advisory and droughts"
},
Thalassiel = {
"Very hot midday sun, minimal clouds",
"Scorching days, slight breeze at dusk",
"Parched hillsides, no sign of rainfall",
"High UV index, bright, glaring sunlight",
"Coastal dryness, strong midday glare",
"Overnight relief, but intense heat returns early",
"Dust in the wind from inland hills",
"Cloudless sky, near-record high temps",
"Salt-laden breezes if near the coast, scorching inland",
"Heatwaves persisting, zero rain expected",
"Intense midday sun might lead to severe heat waves and dust storms"
},
Opsitheiel = {
"Cooler spells, brief showery intervals",
"Cloudy periods bringing mild relief from heat",
"Pleasant afternoons, occasional dusk rains",
"Moderate days, crisp mornings, calm nights",
"Sporadic light rainfall, refreshing breezes",
"Cloudbanks drifting inland, mild drizzle at times",
"Patchy overcast, comfortable midday temps",
"Clear dawn, mild sunshine, gentle wind late",
"Intervals of cloud cover, potential short rain",
"Moist sea air, moderate daytime warmth",
"Cooling breezes could reduce temperatures, but localized brushfires and summer wildfires remain possible"
}
},
["Hot Desert"] = {
Atosiel = {
"Hot afternoons, cooler late nights, wide temp swing",
"Dry air, bright sunshine, no clouds in sight",
"Gentle breezes raising light sand by midday",
"Evening chill setting in after hot daytime",
"Minor dust devil scouring the dunes",
"Visibility excellent, stable high pressure",
"Patchy desert haze, strong sun overhead",
"No hint of precipitation, cloudless horizon",
"Daytime warmth, refreshing if slight breeze arrives",
"Sun-warmed rock surfaces, mild nights",
"Scorching afternoons may be accompanied by dust storms and occasional mirage-like shimmers"
},
Thalassiel = {
"Extreme heat from mid-morning onward",
"Blistering midday sun, risk of heat stroke",
"Near-record highs, swirling dust at times",
"Virtually no clouds, scorching open sands",
"Occasional dust storm cutting visibility briefly",
"Mirage-like shimmer across flat terrain",
"Sunset brings partial relief, still quite warm",
"Dry conditions, subpar humidity all day",
"Baking desert floor, potential for blowing sand",
"Intense heat building day after day",
"Extreme heat could combine with blowing sand to trigger potential sandstorms"
},
Opsitheiel = {
"Warm, mostly sunny with cooler nights",
"Sparse winds carrying fine sand sporadically",
"Large day-night temperature gap, no precipitation",
"Stable conditions, sunlit afternoons, brisk evenings",
"Mildly hot days, star-filled clear skies at night",
"Shallow dust clouds after brief winds",
"Occasional gust picking up desert grit",
"Gentle warmth, dryness prevalent, no clouds",
"Cool breezes shortly after sunset, mild day overall",
"Hazy horizon, soft morning glow across dunes",
"Evening cooling might still see intense heat with possible dust devils on dry slopes"
}
},
["Cold Steppe"] = {
Atosiel = {
"Cool mornings, moderate midday warmth, breezy",
"Light rain passing through grasslands midday",
"Variable cloud cover, mild temperature swings",
"Patchy frost at dawn, warming soon after",
"Crisp air, possible small hail if storms appear",
"Sunny afternoon, cooler late evening",
"Gentle breeze, fleeting sun amidst scattered clouds",
"Slow temperature rise, slight dryness in mid-afternoon",
"Brief drizzle, otherwise bright and breezy",
"Day-night difference noticeable, mild but windy",
"Mild conditions could turn volatile with sudden flash floods and minor landslides"
},
Thalassiel = {
"Warm day, potential for gusty thunderstorms",
"Hot midday sun, dryness intensifying by late day",
"Cloud buildup, short but intense showers possible",
"Clear morning, chance of late afternoon storms",
"Long daylight hours, rolling thunder near dusk",
"Slight dryness, patchy heat with limited shade",
"Periodic storms bringing relief from heat",
"Warm evening, slight nighttime cooldown",
"Sky remains mostly clear, grassland shimmering",
"Thunderheads visible in distance, might pass by",
"Afternoon heat may intensify, leading to thunderstorms, river floods, and gusty rain squalls"
},
Opsitheiel = {
"Cool to cold days, frosty nights on open steppe",
"Possible light snowfall, especially overnight",
"Wind-driven chill under gray skies",
"Sparse precipitation, dryness persists, cold air",
"Subfreezing after sunset, sometimes sunny midday",
"Short bursts of sunshine, otherwise chilly conditions",
"Occasional dusting of snow, quickly melting by noon",
"Biting wind, few clouds, stark horizon",
"Clearing late morning, crisp, cold nightfall",
"Daytime near freezing, nights well below",
"Chilly nights might bring frost and the risk of localized landslides on steep slopes"
}
},
["Hot Steppe"] = {
Atosiel = {
"Hot daytime sun, slight breeze, no rain",
"Gusty wind with dust swirling near midday",
"Warm mornings, scorching afternoon, very low humidity",
"Light breeze after sundown, starry overhead",
"Some haze from heated ground, minimal cloud cover",
"Sun intensity rising toward midday peak",
"Dry soil conditions, no sign of moisture",
"Sparse vegetation, daily heat climbing gradually",
"Clear horizon, wind picking up in late evening",
"Sun exposure high, consistent dryness",
"Scorching sun may lead to persistent dryness with a risk of brushfires and dust devils on dry slopes"
},
Thalassiel = {
"Brutally hot midday sun, patchy dust storms",
"Scorching conditions, faint breeze offers minimal relief",
"Occasional swirling wind gusts, drifting dust",
"Heated ground emits rippling mirages at distance",
"Persistent dryness, minimal nighttime cooldown",
"Skies remain cloudless, high evaporation rate",
"Dust-laden air in late afternoon swirl",
"Rare cloud can appear but quickly vanishes",
"Daily highs remain extreme, nights only mild",
"Lingering warmth through sunset, dryness unwavering",
"Intense heat may trigger severe dust storms and a potential heat wave"
},
Opsitheiel = {
"Hot days, moderate evenings, stable dryness",
"Rare short rains if storm fronts approach, mostly none",
"Daytime heat lingering, nights slightly cooler",
"Periodically hazy morning from airborne dust",
"Mild breezes, daytime remains intensely hot",
"Cloudless skies, gentle drop in temps overnight",
"Dust devils possible over parched plains",
"Occasional relief from mild wind, no precipitation",
"Hot, tranquil afternoon, moderate wind at dusk",
"Minimal cloud presence, dryness dominating environment",
"Evening cooling might reduce temperatures, yet localized sandstorms and minor landslides remain a concern"
}
},
["Subarctic"] = {
Atosiel = {
"Snow melting, slushy paths, daytime sunshine",
"Chilly breezes, frequent flurry patches possible",
"Long nights gradually shortening, crisp mornings",
"Mixed precipitation, cold rain at lower altitudes",
"Slush build-up midmorning, partial sun midday",
"Sunny breaks around noon, subzero by late night",
"Sporadic storm with sleet or melting snow",
"Icy patches linger, overall chilly but bright",
"Early thaw, mild midday, returning chill after dusk",
"Reluctant spring with freezing nights, slow warm days",
"Rapid thawing could spark river floods and snowstorms with heavy snowfall"
},
Thalassiel = {
"Short, cool days, moderate sunshine at times",
"Rainy intervals, potential sleet in highest peaks",
"Snow line retreating, green valleys emerging slowly",
"Extended twilight, crisp air after sundown",
"Sudden midafternoon chill despite mid-summer period",
"Drizzle commonly, heavier rainfall occasionally",
"Mountain passes remain cool, breezy conditions",
"Mostly cloudy, occasional glimpses of sun",
"Brisk wind, earlier nightfall than lower latitudes",
"Daytime highs remain mild, nights quite cold",
"Cool days may be interrupted by sudden blizzard-like conditions and heavy rain squalls"
},
Opsitheiel = {
"Early snowfall returning, freezing ground rapidly",
"Prolonged darkness, bitterness sets in quickly",
"Gusty winds carrying ice crystals through valleys",
"Heavy drifts forming, multiple snowfall events",
"Frequent subzero lows, no hint of thaw",
"Blizzard-like conditions with large snow accumulations",
"Icy rivers, widespread frost in daylight",
"Mountains see near-constant snow, valleys freeze nightly",
"Rare clear spells, intense wind chill outside",
"Deep winter mode, swirling snow flurries all day",
"Deep winter chill may result in persistent blizzards and avalanches in higher passes"
}
}
}
---------------------------------------------------------
-- 5. Triggered Disasters and Natural Disaster Advisory
-- (Each hazard is triggered with a 10% chance)
---------------------------------------------------------
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" },
{ keywords = {"landslides","steep slopes"}, hazard = "Landslides on steep slopes" },
{ keywords = {"droughts","brushfires"}, hazard = "Droughts and brushfires" },
{ keywords = {"recurrent flooding in marshes","flooding in marshes"}, hazard = "Recurrent flooding in marshes" },
{ keywords = {"localized mudslides"}, hazard = "Localized mudslides" },
{ keywords = {"fog%-related travel hazards"}, hazard = "Fog-related travel hazards" },
{ keywords = {"swamp flooding"}, hazard = "Swamp flooding" },
{ keywords = {"river floods"}, hazard = "River floods" },
{ keywords = {"blizzards"}, hazard = "Blizzards" },
{ keywords = {"dust storms","heatwaves"}, hazard = "Dust storms and heatwaves" },
{ keywords = {"flash floods"}, hazard = "Occasional flash floods" },
{ keywords = {"tar%-pit gas releases"}, hazard = "Tar-pit gas releases" },
{ keywords = {"landslides on steep slopes"}, hazard = "Landslides on steep slopes" },
{ keywords = {"coastal storms"}, hazard = "Coastal storms" },
{ keywords = {"mild landslides"}, hazard = "Mild landslides" },
{ keywords = {"summer wildfires"}, hazard = "Summer wildfires" },
{ keywords = {"cliff erosion","sea storms"}, hazard = "Cliff erosion and sea storms" },
{ keywords = {"flood surges near waterfalls"}, hazard = "Flood surges near waterfalls" },
{ keywords = {"coastal gales","blizzards"}, hazard = "Coastal gales, blizzards" },
{ keywords = {"marsh flooding"}, hazard = "Marsh flooding" },
{ keywords = {"cliff collapses"}, hazard = "Occasional cliff collapses" },
{ keywords = {"sandstorms"}, hazard = "Sandstorms" },
{ keywords = {"minor landslides"}, hazard = "Minor landslides" },
{ keywords = {"lake overflows","marsh flooding"}, hazard = "Lake overflows, marsh flooding" },
{ keywords = {"avalanches in higher passes"}, hazard = "Avalanches in higher passes" },
{ keywords = {"shoreline flooding"}, hazard = "Shoreline flooding" },
{ keywords = {"wildfires in dry spells"}, hazard = "Wildfires in dry spells" },
{ keywords = {"droughts"}, hazard = "Droughts" },
{ keywords = {"swamp overflows"}, hazard = "Swamp overflows" },
{ keywords = {"landslides near hot springs"}, hazard = "Occasional landslides near hot springs" },
{ keywords = {"dust devils on dry slopes"}, hazard = "Dust devils on dry slopes" },
{ keywords = {"storm surges from inlet"}, hazard = "Storm surges from inlet" },
{ keywords = {"mine collapses","gas leaks"}, hazard = "Mine collapses, gas leaks" },
{ keywords = {"frequent rain squalls", "heavy downpours", "intermittent heavy downpours"}, hazard = "Localized mudslides" },
{ keywords = {"blizzard%-like conditions"}, hazard = "Blizzards" },
{ keywords = {"extreme heat", "intense heat", "heat advisory"}, hazard = "Heat wave" },
{ keywords = {"rain squalls", "persistent dryness", "dry conditions"}, hazard = "Risk of brushfires" },
{ keywords = {"foggy dawn", "low visibility", "dense fog"}, hazard = "Fog-related hazards" },
{ keywords = {"heavy snowfall", "gusty winds carrying ice", "deep winter mode"}, hazard = "Possible avalanche" },
{ keywords = {"strong desert winds", "dust storm", "mirage%-like shimmer"}, hazard = "Sandstorm risk" },
{ keywords = {"swamp flooding", "marsh flooding"}, hazard = "Potential flood surge" },
{ keywords = {"tornado", "twister"}, hazard = "Tornado possibility" },
{ keywords = {"volcano", "volcanic"}, hazard = "Volcanic activity nearby" },
{ keywords = {"earthquake", "seismic"}, hazard = "Minor earthquake risk" },
{ keywords = {"hail", "ice storm"}, hazard = "Hail storm risk" },
{ keywords = {"severe storm", "severe weather"}, hazard = "Severe weather warning" },
{ keywords = {"tsunami", "coastal surge"}, hazard = "Tsunami risk" },
}
local function getAdvisory(forecastStr)
local textLower = forecastStr:lower()
local extras = {}
-- Each disaster is triggered with a 10% chance:
local randomChance = 0.10
for _, item in ipairs(triggeredDisasters) do
for _, kw in ipairs(item.keywords) do
if textLower:find(kw) then
if math.random() <= randomChance then
table.insert(extras, item.hazard)
end
break
end
end
end
-- Fallback: if no hazard was triggered, a 10% chance for a generic advisory
if #extras == 0 and math.random() <= 0.10 then
table.insert(extras, "Unusual atmospheric conditions detected")
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
-- Limit wind speeds to 0-15 km/h for Earth-like mild conditions.
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,15)
return {
high = hi,
low = lo,
humidity = hum,
chanceOfRain= cRain,
windDir = wDir,
windSpeed = wSpd
}
end
---------------------------------------------------------
-- 8. City Data
---------------------------------------------------------
local cityData = {
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Vaeringheim Vaeringheim]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Luminaria Luminaria]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Serena Serena]", climate = "Subpolar Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Pyralis Pyralis]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Symphonara Symphonara]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aurelia Aurelia]", climate = "Mediterranean (Hot Summer)"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Somniumpolis Somniumpolis]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Nexa Nexa]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lunalis_Sancta Lunalis Sancta]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sylvapolis Sylvapolis]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Saluria Saluria]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aetherium Aetherium]", climate = "Subarctic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ferrum_Citadel Ferrum Citadel]", climate = "Hot Desert"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Acheron Acheron]", climate = "Cold Steppe"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Erythros Erythros]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Catonis_Atrium Catonis Atrium]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Delphica Delphica]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Koinonía Koinonía]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aureum Aureum]", climate = "Mediterranean (Hot Summer)"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Skýrophos Skýrophos]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Bjornopolis Bjornopolis]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Aegirheim Aegirheim]", climate = "Subarctic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Norsolyra Norsolyra]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thorsalon Thorsalon]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Pelagia Pelagia]", climate = "Hot Steppe"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Myrene Myrene]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thyrea Thyrea]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ephyra Ephyra]", climate = "Subpolar Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Halicarn Halicarn]", climate = "Mediterranean (Hot Summer)"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Keybir-Aviv Keybir-Aviv]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Tel-Amin Tel-Amin]", climate = "Mediterranean (Hot Summer)"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Diamandis Diamandis]", climate = "Mediterranean (Hot Summer)"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Jogi Jogi]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lewisburg Lewisburg]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Thermosalem Thermosalem]", climate = "Oceanic"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Akróstadium Akróstadium]", climate = "Cold Steppe"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sufriya Sufriya]", climate = "Humid Subtropical"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lykopolis Lykopolis]", climate = "Oceanic"},
-- NEW ENTRIES for Bassaridian Normark:
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ardclach Ardclach]", climate = "Subarctic"}, -- Dfc
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Riddersborg Riddersborg]", climate = "Subpolar Oceanic"}, -- Cfc
-- NEW (Valley of Keltia) central steppe cities:
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Ourid Ourid]", climate = "Cold Steppe"},
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Bashkim Bashkim]", climate = "Cold Steppe"},
-- NEW: Major Cities in Eastern Caledonia
{city = "Skøda", climate = "Subarctic"}, -- Dfc (mountainous, northern/central)
{city = "Fanghorn", climate = "Subarctic"}, -- Dfc (mountainous, northern/central)
{city = "Eikbu", climate = "Subarctic"}, -- Dfc (mountainous, northern/central)
{city = "Galvø", climate = "Subarctic"}, -- Dfc (mountainous, northern/central)
{city = "Slevik", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal)
{city = "Sårensby", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal)
{city = "Krlsgorod", climate = "Subarctic"}, -- Dfc (lakeshore/arctic)
{city = "Kaledonija", climate = "Subarctic"}, -- Dfc (lakeshore/arctic)
{city = "Notranskja", climate = "Subarctic"}, -- Dfc
{city = "Hammarfell", climate = "Subarctic"}, -- Dfc
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Sjøsborg Sjøsborg]", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal)
{city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Storesund Storesund]", climate = "Subpolar Oceanic"}, -- Cfc (arctic/coastal)
}
---------------------------------------------------------
-- 9. 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
---------------------------------------------------------
-- 10A. ALL-CITIES FORECAST FUNCTION
-- Overriding chance of rain if precipitation is mentioned
---------------------------------------------------------
function p.weatherForecast(frame)
-- 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 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)
-- Override chanceOfRain if forecast text indicates precipitation:
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 overrides chance of rain if precipitation is found
---------------------------------------------------------
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
-- Attempt to match either the full city link or the last plain text
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)
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 = {
{ name="Agave", starClass="M", lat=29.5 },
{ name="Amaäz", starClass="K", lat=31 },
{ name="Amáenu", starClass="G", lat=45 },
{ name="Amap", starClass="A", lat=55 },
{ name="Amazä", starClass="F", lat=39 },
{ name="Atämios", starClass="F", lat=56 },
{ name="Aprobelle",starClass="A", lat=59 },
{ name="Bebeakaus",starClass="K", lat=30.5 },
{ name="Bulhanu", starClass="A", lat=40 },
{ name="Crösacío", starClass="K", lat=17 },
{ name="Danaß", starClass="A", lat=67 },
{ name="Dilëtaz", starClass="G", lat=36 },
{ name="Dranamos", starClass="A", lat=58 },
{ name="Gaht", starClass="G", lat=29 },
{ name="Häpi", starClass="F", lat=78 },
{ name="Hazaméos", starClass="A", lat=72 },
{ name="Liléigos", starClass="F", lat=18 },
{ name="Nyama", starClass="F", lat=64 },
{ name="Ocananus", starClass="F", lat=60.5 },
{ name="Orebele", starClass="F", lat=22 },
{ name="Osiríos", starClass="G", lat=53 },
{ name="Pythe", starClass="A", lat=45 },
{ name="Sanashalo",starClass="G", lat=27 },
{ name="Tä", starClass="F", lat=39.5 },
{ name="Vï", starClass="A", lat=46 },
{ name="Wedíos", starClass="A", lat=54 },
}
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