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