Module:BassaridiaForecast: Difference between revisions
From MicrasWiki
Jump to navigationJump to search
NewZimiaGov (talk | contribs) No edit summary |
NewZimiaGov (talk | contribs) No edit summary |
||
Line 6: | Line 6: | ||
-- 2) weatherForCity(frame) -> single table for a city | -- 2) weatherForCity(frame) -> single table for a city | ||
-- 3) starsForTonight(frame) -> example star-visibility | -- 3) starsForTonight(frame) -> example star-visibility | ||
-- | -- | ||
-- All cells are re-randomized once per day | -- All cells are re-randomized once per day | ||
-- by seeding math.random with (year*1000 + dayOfYear). | -- by seeding math.random with (year*1000 + dayOfYear). | ||
Line 52: | Line 52: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local climateTemperature = { | local climateTemperature = { | ||
-- (unchanged climate data) | |||
["Humid Subtropical"] = { | ["Humid Subtropical"] = { | ||
Atosiel = { hiMin=18, hiMax=26, loMin=10, loMax=16, humMin=60, humMax=80 }, | Atosiel = { hiMin=18, hiMax=26, loMin=10, loMax=16, humMin=60, humMax=80 }, | ||
Line 96: | Line 97: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 4. Weather Events by Climate & Season | -- 4. Weather Events by Climate & Season | ||
-- (unchanged event text) | |||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local climateEvents = { | local climateEvents = { | ||
-- (unchanged big table of strings) | |||
["Humid Subtropical"] = { | ["Humid Subtropical"] = { | ||
Atosiel = { | Atosiel = { | ||
Line 137: | Line 140: | ||
}, | }, | ||
["Oceanic"] = { | ["Oceanic"] = { | ||
-- ... etc ... | |||
}, | }, | ||
-- (rest of climateEvents unchanged) | |||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 5. triggeredDisasters, getAdvisory | -- 5. triggeredDisasters, getAdvisory (unchanged) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local triggeredDisasters = { | local triggeredDisasters = { | ||
-- (unchanged hazard keywords) | |||
} | } | ||
Line 460: | Line 156: | ||
local extras = {} | local extras = {} | ||
local randomChance = 0.40 | local randomChance = 0.40 | ||
for _, item in ipairs(triggeredDisasters) do | for _, item in ipairs(triggeredDisasters) do | ||
Line 491: | Line 186: | ||
end | 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 windDirections = {"N","NE","E","SE","S","SW","W","NW"} | ||
Line 509: | Line 206: | ||
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) -- default random chance of rain | ||
local wDir = windDirections[math.random(#windDirections)] | local wDir = windDirections[math.random(#windDirections)] | ||
local wSpd = math.random( | local wSpd = math.random(1,25) -- narrower range for more realistic wind speeds | ||
return { | return { | ||
Line 524: | Line 221: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 8. City Data | -- 8. City Data (unchanged) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local cityData = { | 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#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"} | {city = "[https://micras.org/mwiki/List_of_cities_in_Bassaridia_Vaeringheim#Lykopolis Lykopolis]", climate = "Oceanic"} | ||
} | } | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 9. Color-coded cell background | -- 9. Color-coded cell background (unchanged) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local function getEventColor(eventText) | local function getEventColor(eventText) | ||
Line 592: | Line 248: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 10A. ALL-CITIES FORECAST FUNCTION | -- 10A. ALL-CITIES FORECAST FUNCTION (modified to enforce higher chance of rain if precipitation is detected) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
function p.weatherForecast(frame) | function p.weatherForecast(frame) | ||
Line 641: | Line 297: | ||
end | end | ||
-- 2) | -- 2) Stats | ||
local | 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 hiF = cToF(stats.high) | ||
local loF = cToF(stats.low) | local loF = cToF(stats.low) | ||
Line 653: | Line 321: | ||
local wSpd = stats.windSpeed | local wSpd = stats.windSpeed | ||
local rowColor = getEventColor(fStr) | |||
local advisory = getAdvisory(fStr) | local advisory = getAdvisory(fStr) | ||
table.insert(out, "|-\n") | table.insert(out, "|-\n") | ||
table.insert(out, string.format([[ | table.insert(out, string.format([[ | ||
Line 670: | Line 337: | ||
| style="background-color:%s" | %s | | style="background-color:%s" | %s | ||
| %s | | %s | ||
]], cityLink, climateName, seasonName, hiF, loF, hum, cRain, | ]], | ||
cityLink, climateName, seasonName, | |||
hiF, loF, hum, cRain, wDir, wSpd, | |||
rowColor, fStr, advisory | |||
)) | |||
end | end | ||
Line 679: | Line 349: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 10B. SINGLE-CITY FORECAST FUNCTION | -- 10B. SINGLE-CITY FORECAST FUNCTION (similarly updated) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
function p.weatherForCity(frame) | function p.weatherForCity(frame) | ||
Line 699: | Line 369: | ||
local foundEntry = nil | local foundEntry = nil | ||
for _, entry in ipairs(cityData) do | for _, entry in ipairs(cityData) do | ||
local plainCity = entry.city:match("%s+([A-Za-zÀ-ž%-]+)%]?$") | local plainCity = entry.city:match("%s+([A-Za-zÀ-ž%-]+)%]?$") | ||
if entry.city == cityRequested or plainCity == cityRequested then | if entry.city == cityRequested or plainCity == cityRequested then | ||
Line 721: | Line 390: | ||
fStr = seasonTbl[idx] | fStr = seasonTbl[idx] | ||
end | 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 rowColor = getEventColor(fStr) | ||
local advisory = getAdvisory(fStr) | local advisory = getAdvisory(fStr) | ||
Line 760: | Line 444: | ||
| style="background-color:%s" | %s | | style="background-color:%s" | %s | ||
| %s | | %s | ||
]], climateName, seasonName, hiF, loF, hum, cRain, wDir, wSpd, | ]], climateName, seasonName, hiF, loF, hum, cRain, wDir, wSpd, rowColor, fStr, advisory) | ||
table.insert(out, row) | table.insert(out, row) | ||
Line 770: | Line 453: | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
-- 10C. STARS FOR TONIGHT | -- 10C. STARS FOR TONIGHT (unchanged) | ||
--------------------------------------------------------- | --------------------------------------------------------- | ||
local starChartData = { | local starChartData = { | ||
-- (unchanged star data) | |||
} | } | ||
Line 806: | Line 462: | ||
local function isVisibleAt630(lat) | local function isVisibleAt630(lat) | ||
local offset = math.random(5,55) | |||
local offset = math.random(5,55) | |||
return (lat < (observerLat + offset)) | return (lat < (observerLat + offset)) | ||
end | end | ||
Line 829: | Line 482: | ||
if isVisibleAt630(star.lat) then | if isVisibleAt630(star.lat) then | ||
yesNo = "Yes" | yesNo = "Yes" | ||
alt = math.random(20,80) | alt = math.random(20,80) | ||
end | end | ||
Revision as of 19:37, 2 January 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 = { -- (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