Module:BassaridiaForecast

From MicrasWiki
Revision as of 03:47, 26 December 2024 by NewZimiaGov (talk | contribs)
Jump to navigationJump to search

Documentation for this module may be created at Module:BassaridiaForecast/doc

---------------------------------------------------------
-- Module:WeatherForecastTable
---------------------------------------------------------

local p = {}

---------------------------------------------------------
-- 1. Calendar System
---------------------------------------------------------

local function getCurrentDateInfo()
    local startDate = os.time({year = 1999, month = 8, day = 6})
    local secondsInDay = 86400
    local daysPerYear = 183

    local currentTime = os.time()
    local totalDaysElapsed = math.floor((currentTime - startDate) / secondsInDay)

    local yearFraction = totalDaysElapsed / daysPerYear
    local psscYear = math.floor(yearFraction)
    local dayOfYear = math.floor((yearFraction - psscYear) * daysPerYear) + 1

    return {
        psscYear = psscYear,
        dayOfYear = dayOfYear
    }
end

---------------------------------------------------------
-- 2. Determine the Season
--    1..61       => Atosiel
--    62..122     => Thalassiel
--    123..183    => Opsitheiel
---------------------------------------------------------

local function getSeasonName(dayOfYear)
    if dayOfYear <= 61 then
        return "Atosiel"
    elseif dayOfYear <= 122 then
        return "Thalassiel"
    else
        return "Opsitheiel"
    end
end

---------------------------------------------------------
-- 3. Weather Events (10 per climate-season)
---------------------------------------------------------

-- (Same data as before — omitted here only for brevity in this comment,
-- but you must paste the entire "climateEvents" table from your final code.)

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"
        },
        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"
        }
    },
    -- ... plus "Oceanic," "Subpolar Oceanic," "Mediterranean (Hot Summer),"
    -- "Hot Desert," "Cold Steppe," "Hot Steppe," "Subarctic," each with
    -- 10 events per season. (Paste your full data here.)
}

---------------------------------------------------------
-- 4. Temperature Ranges (climateTemperature)
---------------------------------------------------------

-- (Paste your full "climateTemperature" table with hiMin, hiMax, etc.)

local climateTemperature = {
    ["Humid Subtropical"] = {
        Atosiel     = { hiMin=18, hiMax=26, loMin=10, loMax=16, humMin=60, humMax=80, crMin=20, crMax=50, rfMin=1, rfMax=10 },
        Thalassiel  = { hiMin=25, hiMax=34, loMin=19, loMax=24, humMin=65, humMax=90, crMin=30, crMax=70, rfMin=2, rfMax=15 },
        Opsitheiel  = { hiMin=20, hiMax=28, loMin=12, loMax=18, humMin=50, humMax=75, crMin=15, crMax=40, rfMin=1, rfMax=8  }
    },
    -- ... plus the rest for "Oceanic," "Subpolar Oceanic," etc.
}

---------------------------------------------------------
-- 5. Color-Coding Weather Description
---------------------------------------------------------

local function getEventColor(eventText)
    local textLower = eventText:lower()
    -- Each type of event has a logical color:
    if textLower:find("thunder") or textLower:find("storm") then
        return "#FFC0C0"  -- a moderate red/pink for storms
    elseif textLower:find("snow") or textLower:find("sleet") or textLower:find("flurries") then
        return "#CCE6FF"  -- a moderate blue for snowy
    elseif textLower:find("rain") or textLower:find("drizzle") or textLower:find("downpour") then
        return "#CCEEFF"  -- a soft aqua for rain
    elseif textLower:find("dust") or textLower:find("desert") then
        return "#FFFACD"  -- lemon chiffon for dust
    elseif textLower:find("hail") then
        return "#E0FFFF"  -- light cyan for hail
    else
        return "#F8F8F8"   -- default light gray
    end
end

---------------------------------------------------------
-- 6. Map Climate Name -> Background Color
--    According to the table you provided:
--      Cfa (Humid Subtropical) -> #FFE4C4
--      Cfb (Oceanic) -> #CCE5FF
--      Cfc (Subpolar Oceanic) -> #CCFFFF
--      Csa (Mediterranean, hot summer) -> #FFE4C4
--      BSh (Hot Steppe) -> #FFDFAF
--      BWh (Hot Desert) -> #FFD1DC
--      BSk (Cold Steppe) -> #FFECB3
--      Dfc (Subarctic) -> #CAB3FF
--
--  We'll create a function that returns these based on the spelled-out name.
---------------------------------------------------------

local function getClimateBGColor(climateName)
    -- We'll define a small map from spelled-out to the color in your table.
    -- e.g. if the spelled-out "Humid Subtropical" => "Cfa" => #FFE4C4
    local map = {
        ["Humid Subtropical"]         = "#FFE4C4", -- cfa
        ["Oceanic"]                   = "#CCE5FF", -- cfb
        ["Subpolar Oceanic"]          = "#CCFFFF", -- cfc
        ["Mediterranean (Hot Summer)"]= "#FFE4C4", -- csa
        ["Hot Steppe"]                = "#FFDFAF", -- bsh
        ["Hot Desert"]                = "#FFD1DC", -- bwh
        ["Cold Steppe"]               = "#FFECB3", -- bsk
        ["Subarctic"]                 = "#CAB3FF"  -- dfc
    }
    return map[climateName] or "#F8F8F8"
end

---------------------------------------------------------
-- 7. City-Specific Disaster Profiles
---------------------------------------------------------

local cityDisasterProfiles = {
    ["Vaeringheim"]       = {"flood", "heatwave", "thunderstorm"},
    ["Luminaria"]         = {"flood", "landslide"},
    ["Serena"]            = {"snowstorm", "landslide"},
    ["Pyralis"]           = {"forest fire", "heatwave"},
    ["Symphonara"]        = {"landslide", "flood"},
    ["Aurelia"]           = {"drought", "heatwave"},
    ["Somniumpolis"]      = {"flood"},
    ["Nexa"]              = {"landslide"},
    ["Lunalis Sancta"]    = {"flood"},
    ["Sylvapolis"]        = {"flood"},
    ["Saluria"]           = {"flood"},
    ["Aetherium"]         = {"snowstorm", "blizzard"},
    ["Ferrum Citadel"]    = {"dust storm", "heatwave"},
    ["Acheron"]           = {"flood", "landslide"},
    ["Erythros"]          = {"flood"},
    ["Catonis Atrium"]    = {"landslide"},
    ["Delphica"]          = {"flood"},
    ["Koinonía"]          = {"landslide"},
    ["Aureum"]            = {"drought", "heatwave"},
    ["Skýrophos"]         = {"landslide", "coastal storm"},
    ["Bjornopolis"]       = {"flood"},
    ["Aegirheim"]         = {"snowstorm", "blizzard"},
    ["Norsolyra"]         = {"flood", "dust storm"},
    ["Thorsalon"]         = {"coastal storm", "flood"},
    ["Pelagia"]           = {"dust storm", "drought"},
    ["Myrene"]            = {"flood"},
    ["Thyrea"]            = {"flood", "thunderstorm"},
    ["Ephyra"]            = {"snowstorm"},
    ["Halicarn"]          = {"drought", "landslide"},
    ["Keybir-Aviv"]       = {"flood", "heatwave"},
    ["Tel-Amin"]          = {"drought", "heatwave"},
    ["Diamandis"]         = {"drought", "heatwave"},
    ["Jogi"]              = {"flood"},
    ["Lewisburg"]         = {"flood", "landslide"},
    ["Thermosalem"]       = {"flood"},
    ["Akróstadium"]       = {"dust storm", "landslide"},
    ["Sufriya"]           = {"flood", "dust storm"},
    ["Lykopolis"]         = {"flood"}
}

---------------------------------------------------------
-- 8. Advisory Logic
--    If triggered, color code cell in red
---------------------------------------------------------

local function getDisasterAdvisory(cityName, eventText, chanceOfRain, predictedRain, highC)
    local disasterList = cityDisasterProfiles[cityName] or {}
    local textLower = eventText:lower()
    local advisories = {}

    for _, disasterType in ipairs(disasterList) do
        if disasterType == "flood" then
            if chanceOfRain > 60 or textLower:find("heavy downpours") then
                table.insert(advisories, "Flood Advisory")
            end
        elseif disasterType == "heatwave" then
            if highC >= 32 then
                table.insert(advisories, "Heatwave Warning")
            end
        elseif disasterType == "thunderstorm" then
            if textLower:find("thunder") or textLower:find("storm") then
                table.insert(advisories, "Thunderstorm Alert")
            end
        elseif disasterType == "snowstorm" then
            if textLower:find("snow") or textLower:find("sleet") then
                table.insert(advisories, "Snowstorm Warning")
            end
        elseif disasterType == "landslide" then
            if chanceOfRain > 50 or textLower:find("heavy rain") or textLower:find("thunderstorm") then
                table.insert(advisories, "Landslide Risk")
            end
        elseif disasterType == "forest fire" then
            if textLower:find("hot") or chanceOfRain < 10 then
                table.insert(advisories, "Forest Fire Risk")
            end
        elseif disasterType == "drought" then
            if chanceOfRain < 5 then
                table.insert(advisories, "Drought Alert")
            end
        elseif disasterType == "blizzard" then
            if textLower:find("snow") or textLower:find("flurries") then
                table.insert(advisories, "Blizzard Warning")
            end
        elseif disasterType == "dust storm" then
            if textLower:find("dust") then
                table.insert(advisories, "Dust Storm Advisory")
            end
        elseif disasterType == "coastal storm" then
            if textLower:find("storm") then
                table.insert(advisories, "Coastal Storm Alert")
            end
        end
    end

    if #advisories == 0 then
        return "No Advisory"
    else
        return table.concat(advisories, "; ")
    end
end

---------------------------------------------------------
-- 9. Random Weather Stats (including wind speed)
---------------------------------------------------------

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 {
            highC="N/A", lowC="N/A", humidity="N/A",
            chanceOfRain="N/A", predictedRain="N/A",
            windDir="N/A", windSpeed="N/A"
        }
    end

    local hiC = math.random(data.hiMin, data.hiMax)
    local loC = math.random(data.loMin, data.loMax)
    local hum = math.random(data.humMin, data.humMax)

    local cRain = math.random(data.crMin, data.crMax)
    local pRain = 0
    if cRain > 0 then
        pRain = math.random(data.rfMin, data.rfMax)
    end

    local wDir  = windDirections[math.random(#windDirections)]
    local wSpd  = math.random(0, 50)  -- 0..50 km/h

    return {
        highC = hiC,
        lowC  = loC,
        humidity     = hum,
        chanceOfRain = cRain,
        predictedRain= pRain,
        windDir      = wDir,
        windSpeed    = wSpd
    }
end

---------------------------------------------------------
-- 10. Celsius to Fahrenheit
---------------------------------------------------------

local function cToF(c)
    return math.floor(c * 9/5 + 32 + 0.5)
end

---------------------------------------------------------
-- 11. Full City List
---------------------------------------------------------

local cityData = {
    {city = "Vaeringheim",       climate = "Humid Subtropical"},
    {city = "Luminaria",         climate = "Humid Subtropical"},
    {city = "Serena",            climate = "Subpolar Oceanic"},
    {city = "Pyralis",           climate = "Humid Subtropical"},
    {city = "Symphonara",        climate = "Oceanic"},
    {city = "Aurelia",           climate = "Humid Subtropical"},
    {city = "Somniumpolis",      climate = "Humid Subtropical"},
    {city = "Nexa",              climate = "Humid Subtropical"},
    {city = "Lunalis Sancta",    climate = "Oceanic"},
    {city = "Sylvapolis",        climate = "Humid Subtropical"},

    {city = "Saluria",           climate = "Oceanic"},
    {city = "Aetherium",         climate = "Subarctic"},
    {city = "Ferrum Citadel",    climate = "Hot Desert"},
    {city = "Acheron",           climate = "Cold Steppe"},
    {city = "Erythros",          climate = "Oceanic"},
    {city = "Catonis Atrium",    climate = "Oceanic"},
    {city = "Delphica",          climate = "Oceanic"},
    {city = "Koinonía",          climate = "Oceanic"},
    {city = "Aureum",            climate = "Mediterranean (Hot Summer)"},

    {city = "Skýrophos",         climate = "Oceanic"},
    {city = "Bjornopolis",       climate = "Oceanic"},
    {city = "Aegirheim",         climate = "Subarctic"},
    {city = "Norsolyra",         climate = "Oceanic"},
    {city = "Thorsalon",         climate = "Oceanic"},

    {city = "Pelagia",           climate = "Hot Steppe"},
    {city = "Myrene",            climate = "Oceanic"},
    {city = "Thyrea",            climate = "Humid Subtropical"},
    {city = "Ephyra",            climate = "Subpolar Oceanic"},
    {city = "Halicarn",          climate = "Mediterranean (Hot Summer)"},

    {city = "Keybir-Aviv",       climate = "Humid Subtropical"},
    {city = "Tel-Amin",          climate = "Mediterranean (Hot Summer)"},
    {city = "Diamandis",         climate = "Mediterranean (Hot Summer)"},
    {city = "Jogi",              climate = "Oceanic"},
    {city = "Lewisburg",         climate = "Humid Subtropical"},

    {city = "Thermosalem",       climate = "Oceanic"},
    {city = "Akróstadium",       climate = "Cold Steppe"},
    {city = "Sufriya",           climate = "Humid Subtropical"},
    {city = "Lykopolis",         climate = "Oceanic"}
}

---------------------------------------------------------
-- 12. Main Weather Forecast Table Function
---------------------------------------------------------

function p.weatherForecast(frame)
    local dateInfo  = getCurrentDateInfo()
    local dayOfYear = dateInfo.dayOfYear
    local yearNum   = 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, yearNum, seasonName
    ))

    -- Start table
    table.insert(out, '{| class="wikitable sortable" style="width:100%; text-align:left;"\n')
    table.insert(out,
[[! City !! Climate !! Season !! High °C (°F) !! Low °C (°F) !! Humidity (%) !! Chance of Rain (%) !! Rainfall (mm) !! Wind Dir !! Wind Speed (km/h) !! Today's Weather !! Advisory
]]
    )

    for _, cityEntry in ipairs(cityData) do
        local cityName    = cityEntry.city
        local climateName = cityEntry.climate

        -- (A) Random event pick
        local cTable  = climateEvents[climateName]
        local sTable  = cTable and cTable[seasonName]
        local forecast= "No data"
        if sTable and #sTable > 0 then
            forecast = sTable[math.random(#sTable)]
        end

        -- (B) Weather cell color
        local weatherColor = getEventColor(forecast)

        -- (C) Stats
        local stats = getRandomWeatherStats(climateName, seasonName)
        local hiC   = stats.highC
        local loC   = stats.lowC
        local hum   = stats.humidity
        local cRain = stats.chanceOfRain
        local pRain = stats.predictedRain
        local wDir  = stats.windDir
        local wSpd  = stats.windSpeed
        local hiF   = (type(hiC)=="number") and cToF(hiC) or "N/A"
        local loF   = (type(loC)=="number") and cToF(loC) or "N/A"

        -- (D) Advisory
        local advisory = getDisasterAdvisory(
            cityName, forecast, cRain, pRain,
            (type(hiC)=="number" and hiC or 0)
        )
        -- If there's an advisory, color it red
        local advisoryCell
        if advisory ~= "No Advisory" then
            advisoryCell = 'style="background-color:#FFBABA" | ' .. advisory
        else
            advisoryCell = advisory
        end

        -- (E) Climate cell color
        local climateBG = getClimateBGColor(climateName)
        local climateCell = string.format(
            'style="background-color:%s" | %s',
            climateBG, climateName
        )

        table.insert(out, "|-\n")
        table.insert(out, string.format(
[[| %s || %s || %s || %s (%s) || %s (%s) || %s || %s || %s || %s || %s || style="background-color:%s" | %s || %s
]],
            cityName,
            climateCell,
            seasonName,
            tostring(hiC), tostring(hiF),
            tostring(loC), tostring(loF),
            tostring(hum),
            tostring(cRain),
            tostring(pRain),
            wDir,
            tostring(wSpd),
            weatherColor, forecast,
            advisoryCell
        ))
    end

    table.insert(out, "|}\n")
    return table.concat(out)
end

return p