Module:SouthernStraitCorridorOps

From MicrasWiki
Revision as of 15:44, 2 June 2026 by NewZimiaGov (talk | contribs)
Jump to navigationJump to search

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


-- Module:SouthernStraitCorridorOps
-- Southern Strait-only daily operations wrapper.
-- Shows only Haifan Bassaridian / Southern Strait entries from:
-- 1) WarLeagueDivisionOpsBVR
-- 2) HatchMinistryDailyOps
-- 3) MissionaryDailyOpsBVR
--
-- Also adds:
-- 4) Rare Southern Strait trade-agreement / General Port admission events
-- 5) Extended Southern Strait locality focus lines

local p = {}

--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------

local function yn(value, default)
	local v = tostring(value or ""):lower()
	if v == "" then return default end
	if v == "no" or v == "n" or v == "0" or v == "false" then return false end
	if v == "yes" or v == "y" or v == "1" or v == "true" then return true end
	return default
end

local function makeFrame(args)
	return { args = args or {} }
end

local function safeRequire(moduleName)
	local ok, mod = pcall(require, moduleName)
	if ok and mod then
		return mod, nil
	end
	return nil, "Could not load " .. tostring(moduleName)
end

local function safeCall(moduleName, funcName, args)
	local mod, err = safeRequire(moduleName)
	if not mod then
		return false, "''" .. err .. ".''"
	end
	if type(mod[funcName]) ~= "function" then
		return false, "''" .. tostring(moduleName) .. " has no public function named " .. tostring(funcName) .. ".''"
	end

	local ok, result = pcall(function()
		return mod[funcName](makeFrame(args))
	end)

	if not ok then
		return false, "''Error while invoking " .. tostring(moduleName) .. "." .. tostring(funcName) .. ": " .. tostring(result) .. "''"
	end

	return true, tostring(result or "")
end

local function doyToMonthDom(doy)
	doy = tonumber(doy) or 1
	if doy < 1 then doy = 1 end
	if doy > 183 then doy = 183 end

	if doy <= 61 then
		return 1, doy
	elseif doy <= 122 then
		return 2, doy - 61
	else
		return 3, doy - 122
	end
end

local function dateLabel(args)
	local y = tonumber(args.year or args.psscyear)
	local d = tonumber(args.day or args.dayofyear)
	if y and d then
		local m, dom = doyToMonthDom(d)
		return string.format("%d/%d/%d PSSC", dom, m, y)
	end

	return "current PSSC date"
end

local function section(title, body, collapsible, collapsed)
	local out = {}
	local safeTitle = tostring(title or "Operations")
	local content = tostring(body or "''No output.''")
	if not collapsible then
		table.insert(out, "===" .. safeTitle .. "===")
		table.insert(out, content)
		return table.concat(out, "\n")
	end

	local collapseClass = collapsed and "mw-collapsible mw-collapsed" or "mw-collapsible"
	table.insert(out, '<div class="' .. collapseClass .. '" style="border:1px solid #a2a9b1; margin:0.6em 0; background:#f8f9fa;">')
	table.insert(out, '<div style="font-weight:bold; padding:0.35em 0.6em; background:#eaecf0;">' .. safeTitle .. '</div>')
	table.insert(out, '<div style="padding:0.5em 0.6em;">')
	table.insert(out, content)
	table.insert(out, '</div>')
	table.insert(out, '</div>')

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

local function hr()
	return "\n----\n"
end

local function hashString(s)
	s = tostring(s or "")
	local h = 0
	for i = 1, #s do
		h = (h * 31 + string.byte(s, i)) % 1000003
	end
	return h
end

local function pick(list, seed, salt)
	if not list or #list == 0 then return nil end
	local idx = (hashString(tostring(seed or "") .. ":" .. tostring(salt or "")) % #list) + 1
	return list[idx]
end

local function pctFromSeed(seed, salt)
	return hashString(tostring(seed or "") .. ":" .. tostring(salt or "")) % 100
end

local function flagLabel(file, label)
	if file and tostring(file) ~= "" then
		return "{{flagicon image|" .. file .. "|200px}} " .. tostring(label or "")
	end
	return tostring(label or "")
end

--------------------------------------------------------------------------------
-- Extended Southern Strait / Eastern Mykonos / Snowholme locality list
--------------------------------------------------------------------------------

local southernTradeCities = {
	{
		code = "MIR",
		city = "[[Mirovia]]",
		region = "[[Eastern Mykonos]] Desert",
		category = "Agriculture",
		company = "[[Mirovian Cistern Growers' Syndicate]]",
		resource = "cistern melons, dryland cucumbers, seed oil, and preserved desert fruit"
	},
	{
		code = "SAR",
		city = "[[Saruq Thyrsa]]",
		region = "Southern Strait coast",
		category = "Agriculture",
		company = "[[Saruq Thyrsa Reedcutters' Guild]]",
		resource = "coastal reed, cane fiber, packing straw, ferry mats, and cargo-padding material"
	},
	{
		code = "AYS",
		city = "[[Ayllu Nysara]]",
		region = "Eastern foothills of the [[Snowholme Range]]",
		category = "Agriculture",
		company = "[[Nysaran Terrace Farmers' Ayllu]]",
		resource = "terrace beans, cold-weather tubers, sheep wool, and highland fodder"
	},
	{
		code = "THQ",
		city = "[[Thal Qencha]]",
		region = "Eastern foothills of the [[Snowholme Range]]",
		category = "Agriculture",
		company = "[[Thal Qencha Springkeepers' Cooperative]]",
		resource = "spring-fed watercress, trout ponds, smoked fish, and cold-storage greens"
	},
	{
		code = "RUK",
		city = "[[Rumi-Kefaya]]",
		region = "[[Eastern Mykonos]] Desert",
		category = "Mining / Manufacturing / Energy",
		company = "[[Rumi-Kefaya Stone and Lime Works]]",
		resource = "basalt, limestone, paving stone, railway ballast, drainage tile, and lime mortar"
	},
	{
		code = "HAY",
		city = "[[Hayr Chaskal]]",
		region = "Southern Strait coast",
		category = "Mining / Manufacturing / Energy",
		company = "[[Hayr Chaskal Glass and Signal Works]]",
		resource = "beacon glass, telegraph insulators, lighthouse lenses, lamp glass, and weatherproof signal equipment"
	},
	{
		code = "QSE",
		city = "[[Qhapaq Selen]]",
		region = "Eastern foothills of the [[Snowholme Range]]",
		category = "Services",
		company = "[[Qhapaq Selen Route Registry Office]]",
		resource = "night-road registry, caravan lodging, pass permits, route insurance, guide licensing, and pilgrimage-office services"
	}
}

--------------------------------------------------------------------------------
-- Cult sign-off list for trade agreements
--------------------------------------------------------------------------------

local tradeSigningCults = {
	{
		code = "UMB",
		flag = "UmbOracle.png",
		label = "[[Noctis (Reformed Stripping Path)#Order of the Umbral Oracle|Order of the Umbral Oracle]]"
	},
	{
		code = "GGS",
		flag = "GoldenShadows.png",
		label = "[[Chrysos (Reformed Stripping Path)#Guild of Golden Shadows|Guild of Golden Shadows]]"
	},
	{
		code = "AZS",
		flag = "AzureSentinel.png",
		label = "[[Thalassa (Reformed Stripping Path)#Azure Sentinel Sect|Azure Sentinel Sect]]"
	},
	{
		code = "HAR",
		flag = "Harmonysanctum.png",
		label = "[[Micras (Reformed Stripping Path)#Harmony Sanctum|Harmony Sanctum]]"
	},
	{
		code = "VIT",
		flag = "Vitalis.png",
		label = "[[Plateau (Reformed Stripping Path)#Sanctum Vitalis|Sanctum Vitalis]]"
	},
	{
		code = "AUR",
		flag = "Auroramystica.png",
		label = "[[Eos (Reformed Stripping Path)#Order of Aurora Mystica|Order of Aurora Mystica]]"
	},
	{
		code = "DEL",
		flag = "SanctumDel.png",
		label = "[[Sisera (Reformed Stripping Path)#Sanctum Delphica|Sanctum Delphica]]"
	},
	{
		code = "CAT",
		flag = "Catonis.png",
		label = "[[Cato (Reformed Stripping Path)#Rex Catonis|Rex Catonis]]"
	},
	{
		code = "CON",
		flag = "Concordia.png",
		label = "[[Lukedu (Reformed Stripping Path)#Accord Concordia|Accord Concordia]]"
	},
	{
		code = "STY",
		flag = "StygianVe.png",
		label = "[[Styx (Reformed Stripping Path)#Mystery of the Stygian Veil|Mystery of the Stygian Veil]]"
	}
}

local tradeOutcomes = {
	"Trade agreement countersigned; company cleared for addition to the appropriate [[General Port of Lake Morovia]] ledger pending ordinary page update.",
	"Provisional General Port admission recommended; company papers transmitted to the Merchant General's audit desk for category placement.",
	"Conditional trade window approved; first-cycle output, route papers, and stipend-voucher handling placed under Port review.",
	"Company charter accepted for corridor listing; local cult sign-off attached to the General Port admission file.",
	"Trade agreement filed without public ceremony; company admitted through ordinary registry channels to avoid inflaming corridor tensions."
}

--------------------------------------------------------------------------------
-- Rare Southern Strait trade-agreement / General Port admission events
--------------------------------------------------------------------------------

local function tradeAgreementEvent(args)
	local chance = tonumber(args.tradechance or args.tradeChance) or 8
	if chance < 0 then chance = 0 end
	if chance > 100 then chance = 100 end

	local showNoEvent = yn(args.showTradeNoEvent or args.showtradenoevent, false)

	local year = tonumber(args.year or args.psscyear) or 0
	local day  = tonumber(args.day or args.dayofyear) or 0
	local seed = tostring(args.seed or "") .. ":SS-TRADE:" .. tostring(year) .. ":" .. tostring(day)

	local roll = pctFromSeed(seed, "roll") + 1
	if roll > chance then
		if showNoEvent then
			return "''No new Southern Strait trade-agreement sign-off recorded for " .. dateLabel(args) .. ".''"
		end
		return nil
	end

	local city = pick(southernTradeCities, seed, "city")
	local cult = pick(tradeSigningCults, seed, "cult")
	local outcome = pick(tradeOutcomes, seed, "outcome")
	if not city or not cult then return nil end

	local seq = (pctFromSeed(seed, "seq") % 20) + 1
	local tradeId
	if year > 0 and day > 0 then
		tradeId = string.format("GP-SS-%03d.%03d-%s-%02d", year, day, city.code or "GEN", seq)
	else
		tradeId = string.format("GP-SS-%s-%02d", city.code or "GEN", seq)
	end

	local signCode = string.format("%s-TA%02d", cult.code or "CUL", seq)

	return table.concat({
		"{| class=\"wikitable sortable\"",
		"! Date",
		"! Trade-ID",
		"! Settlement",
		"! Company",
		"! General Port category",
		"! Resource focus",
		"! Cult sign-off",
		"! Outcome",
		"|-",
		"| " .. dateLabel(args),
		"| " .. tradeId,
		"| " .. city.city .. "<br><small>" .. city.region .. "</small>",
		"| " .. city.company,
		"| " .. city.category,
		"| " .. city.resource,
		"| " .. flagLabel(cult.flag, cult.label) .. "<br><small>Sign-off code: '''" .. signCode .. "'''</small>",
		"| " .. outcome,
		"|}"
	}, "\n")
end

--------------------------------------------------------------------------------
-- Locality focus lines for all Southern Strait sections
--------------------------------------------------------------------------------

local function corridorLocalityFocus(args, channel)
	local year = tonumber(args.year or args.psscyear) or 0
	local day  = tonumber(args.day or args.dayofyear) or 0
	local seed = tostring(args.seed or "") .. ":SS-LOCALITY:" .. tostring(channel or "") .. ":" .. tostring(year) .. ":" .. tostring(day)

	local city = pick(southernTradeCities, seed, "locality")
	if not city then return "" end

	local channelText = {
		war = "War League corridor activity",
		hatch = "Hatch Ministry privateering and registry activity",
		missionary = "Temple Bank missionary activity"
	}

	return "''Southern Strait locality focus: " .. city.city .. " (" .. city.region .. ") — " ..
		(channelText[channel] or "Corridor activity") ..
		" is treated as extending through this settlement's trade routes, inspection points, service offices, or inland approaches for this reporting cycle.''\n\n"
end

--------------------------------------------------------------------------------
-- Main Southern Strait-only view
--------------------------------------------------------------------------------

function p.makeTable(frame)
	local args = (frame and frame.args) or {}

	local showIntro  = yn(args.intro, false)
	local showTitle  = yn(args.showtitle or args.titleline, false)
	local showLegend = yn(args.legend or args.showlegend, false)

	-- Collapsible section controls.
	-- collapsible=no disables collapsible boxes.
	-- collapsed=no keeps them open by default.
	local collapseSections = yn(args.collapsible, true)
	local startCollapsed = yn(args.collapsed, true)

	local includeWar     = yn(args.war or args.warleague, true)
	local includeHatch   = yn(args.hatch or args.hatchministry, true)
	local includeMission = yn(args.missionary or args.templebank, true)
	local includeTrade   = yn(args.trade or args.tradeagreements or args.tradeagreement, true)

	-- localities=no hides the extended locality focus lines.
	local showLocalities = yn(args.localities or args.showlocalities, true)

	local year = args.year or args.psscyear
	local day  = args.day or args.dayofyear

	local title = args.title or "Southern Strait Corridor Daily Operations"
	local label = dateLabel(args)

	local out = {}

	if showTitle then
		table.insert(out, "==" .. title .. "==")
	end

	if showIntro then
		table.insert(out, string.format(
			"''Southern Strait-only operational display for %s, limited to Haifan Bassaridian / former Maritime Markets corridor entries.''",
			label
		))
		table.insert(out, "")
		table.insert(out, "This display combines War League deployments, Hatch Ministry privateer activity, Temple Bank missionary operations, rare General Port trade-agreement sign-offs, and extended Southern Strait locality focus lines while excluding unrelated regional rows.")
	end

	----------------------------------------------------------------------------
	-- War League: Southern Strait-only
	----------------------------------------------------------------------------

	if includeWar then
		local warArgs = {
			year = year,
			day = day,
			-- rows = number of Southern Strait rows to display.
			-- scanrows = number of original War League rows to scan while preserving
			-- original Op-ID/type data from the source War League generation.
			rows = args.warrows or args.rows or "24",
			scanrows = args.warscanrows or args.scanrows or "160",

			showlegend = showLegend and "yes" or "no",
			flagsize = args.flagsize or "20",
			zone = "SouthernStrait",
		}

		if args.division and tostring(args.division) ~= "" then
			warArgs.division = args.division
		elseif args.wardivision and tostring(args.wardivision) ~= "" then
			warArgs.division = args.wardivision
		end

		if args.seed and tostring(args.seed) ~= "" then
			warArgs.seed = args.seed
		end

		local ok, body = safeCall("Module:WarLeagueDivisionOpsBVR", "makeTable", warArgs)
		if showLocalities then
			body = corridorLocalityFocus(args, "war") .. body
		end
		table.insert(out, hr())
		table.insert(out, section("War League Southern Strait deployments", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Hatch Ministry: Southern Strait-only
	----------------------------------------------------------------------------

	if includeHatch then
		local hatchArgs = {
			year = year,
			day = day,
			legend = showLegend and "yes" or "no",
			zone = "SouthernStrait",
		}

		if args.captain and tostring(args.captain) ~= "" then
			hatchArgs.captain = args.captain
		end

		local ok, body = safeCall("Module:HatchMinistryDailyOps", "dailyReport", hatchArgs)
		if showLocalities then
			body = corridorLocalityFocus(args, "hatch") .. body
		end
		table.insert(out, hr())
		table.insert(out, section("Hatch Ministry Southern Strait privateer operations", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Temple Bank / Missionary Operations: Haifan only
	----------------------------------------------------------------------------

	if includeMission then
		local missionaryArgs = {
			year = year,
			day = day,
			region = "Haifan",
			showlegend = showLegend and "yes" or "no",
			includeThreat = args.includeThreat or args.includethreat or "no",
			showEcon = args.showEcon or args.showecon or "no",
		}

		if args.missionarylimit and tostring(args.missionarylimit) ~= "" then
			missionaryArgs.limit = args.missionarylimit
		elseif args.limit and tostring(args.limit) ~= "" then
			missionaryArgs.limit = args.limit
		end

		local ok, body = safeCall("Module:MissionaryDailyOpsBVR", "makeTable", missionaryArgs)
		if showLocalities then
			body = corridorLocalityFocus(args, "missionary") .. body
		end
		table.insert(out, hr())
		table.insert(out, section("Temple Bank Southern Strait missionary operations", body, collapseSections, startCollapsed))
	end

	----------------------------------------------------------------------------
	-- Rare trade-agreement / General Port admission events
	----------------------------------------------------------------------------

	if includeTrade then
		local tradeBody = tradeAgreementEvent(args)
		if tradeBody and tostring(tradeBody) ~= "" then
			table.insert(out, hr())
			table.insert(out, section("Southern Strait trade-agreement sign-offs", tradeBody, collapseSections, startCollapsed))
		end
	end

	----------------------------------------------------------------------------
	-- Combined legend
	----------------------------------------------------------------------------

	if showLegend then
		local legendBody = table.concat({
			"* '''War League Southern Strait deployments''' show Haifan Bassaridian / former Maritime Markets security rows only.",
			"* '''Hatch Ministry Southern Strait privateer operations''' show Haifan home-port privateers only.",
			"* '''Temple Bank Southern Strait missionary operations''' show Haifan Bassaridian missionary rows only.",
			"* '''Southern Strait locality focus''' lines assign one extended Southern Strait, Eastern Mykonos Desert, or eastern Snowholme foothill settlement to each War League, Hatch Ministry, and Temple Bank reporting section.",
			"* '''Southern Strait trade-agreement sign-offs''' are rare or uncommon events allowing companies in Southern Strait, Eastern Mykonos Desert, and eastern Snowholme foothill settlements to be admitted to the appropriate [[General Port of Lake Morovia]] ledger after cult sign-off."
		}, "\n")
		table.insert(out, hr())
		table.insert(out, section("Combined legend", legendBody, collapseSections, startCollapsed))
	end

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

--------------------------------------------------------------------------------
-- Shortcut views
--------------------------------------------------------------------------------

function p.southernStrait(frame)
	local args = (frame and frame.args) or {}
	args.title = args.title or "Southern Strait Corridor Daily Operations"
	return p.makeTable({ args = args })
end

function p.warOnly(frame)
	local args = (frame and frame.args) or {}
	args.hatch = "no"
	args.missionary = "no"
	args.trade = "no"
	args.war = "yes"
	args.title = args.title or "War League Southern Strait Daily Operations"
	return p.makeTable({ args = args })
end

function p.hatchOnly(frame)
	local args = (frame and frame.args) or {}
	args.war = "no"
	args.missionary = "no"
	args.trade = "no"
	args.hatch = "yes"
	args.title = args.title or "Hatch Ministry Southern Strait Daily Operations"
	return p.makeTable({ args = args })
end

function p.missionaryOnly(frame)
	local args = (frame and frame.args) or {}
	args.war = "no"
	args.hatch = "no"
	args.trade = "no"
	args.missionary = "yes"
	args.title = args.title or "Temple Bank Southern Strait Missionary Daily Operations"
	return p.makeTable({ args = args })
end

return p