Module:Ship

-- Module:Ship -- Description: Used to query information about ship types and ship upgrade types, including icons. local p = {} local data = mw.loadData("Module:Ship/data") local common = require("Module:Common") local getArgs = require('Module:Arguments').getArgs

local baseStatOverrides = {}

-- Builds content containing a breakdown of a ship stat, given the base ship -- and the equipped upgrades -- Right now we don't actually do anything with the breakdown, but perhaps in the -- future we'll display a tooltip, whatever is permitted by the new unified UI function p.buildStatContent(frame) local frameArgs = getArgs(frame); local dontFormatStat = false local shipType = frameArgs["type"]; local statType = frame.args["stat"] -- Return "-" if the ship type or stat type is nil if not (shipType and statType) then return "-" end local upgrades = { ["hull"] = "", ["sails"] = "", ["helm"] = "", ["lanterns"] = "", ["anchor"] = "" } -- Fill the upgrades table for k,_ in pairs(upgrades) do		upgrades[k] = frameArgs[k] or nil end local adj = collectStatAdjustments(shipType, upgrades, statType, frameArgs) if (adj == nil) then return '-' end -- Special treatment for travel_speed, since it is multiplicative if (statType == "travel_speed") then -- Move base ship type travel_speed stat into a modifier table.insert(adj.modifiers, { value = adj.base, upgradeType = "ship", upgrade = "Base" }) -- Set base to actual base adj.base = 50 local multiplier = 1.0 -- Recalculate final using multiplier for i = 1, #adj.modifiers do			multiplier = multiplier + (adj.modifiers[i].value / 100) end adj.final = adj.base * multiplier -- In addition, don't format the output number dontFormatStat = true end return ""..formatStatWithIcon(statType, adj.final, dontFormatStat).." " end

-- Collects a list of stat adjustments given a shipType and a table of upgrades -- upgrades should be a list of key-value pairs where the key is the upgradeType and -- the value is the name of the upgrade

-- The returned table contains the base, final and a list of modifiers -- each containing a value, an upgrade and an upgradeType function collectStatAdjustments(shipType, upgrades, stat, frameArgs) local result = { } result.base = getShipBaseStatsWithOverrides(shipType, frameArgs)[stat] or 0 result.final = result.base result.modifiers = { } if (upgrades == nil) then return result end -- Loop over all the inputted upgrades for t, u in pairs(upgrades) do		-- Get the modifiers for this upgrade local mod = getStatModifierForUpgrade(t, u, stat) if (mod ~= 0) then -- Insert a new table into the modifiers array containing the value, upgradeType and upgrade name table.insert(result.modifiers, { value = mod, upgradeType = t, upgrade = u }) result.final = result.final + mod end end return result end

function collectAllStatAdjustments(shipType, upgrades, frameArgs) local baseStats = getShipBaseStatsWithOverrides(shipType, frameArgs) local stats = { } -- Inititialize resulting table for k,_ in pairs(data.stats) do		if (k ~= "crew" and k ~= "defenders") then stats[k] = { } stats[k].base = baseStats[k] or 0 stats[k].final = stats[k].base stats[k].modifiers = {} end end -- Loop over all the inputted upgrades for t, u in pairs(upgrades) do		-- Get the modifiers for this upgrade local mods = getAllModifiersForUpgrade(t, u)		if (mods ~= nil) then -- Stat type, stat value for st, sv in pairs(mods) do				-- Check if the stat type is actually contained in the resulting table if (stats[st] ~= nil) then -- Insert a new table into the modifiers array containing the value, upgradeType and upgrade name table.insert(stats[st].modifiers, { value = sv, upgradeType = t, upgrade = u }) stats[st].final = stats[st].final + sv				end end end end mw.logObject(stats) return stats end

-- Fetches the icon for an upgrade, displaying it in a flink-type format. -- The first parameter is required, and is the type of upgrade. -- The second parameter is optional, and is the name of the upgrade. -- If not present, this will be fetched automatically from the infobox parameters using the type of upgrade as the key -- It can be used if the upgrade type doesn't line up with a value passed to the infobox, for example -- with cannon type upgrades, split across the fields guns_port, guns_starboard, etc -- If the parameter "includeStats" is yes or 1, the stats for the upgrade are shown in a table below it function p.buildUpgradeContent(frame) local parentArgs = frame:getParent.args; local upgradeType = frame.args[1] local upgrade = frame.args[2] or parentArgs[upgradeType] local includeStats = (frame.args["includeStats"] == "1" or frame.args["includeStats"] == "yes") return p._buildUpgradeContent(upgradeType, upgrade, includeStats) end

function p._buildUpgradeContent(upgradeType, upgrade, includeStats, textAfter) if not (common.isNotNilOrEmpty(upgradeType) and common.isNotNilOrEmpty(upgrade)) then return "" end -- We lay out the icon and title in a two column grid. -- This is a bit tidier than a flexbox, and provides more functionality than doing it "manually" local title = mw.html.create("div") :cssText("display:grid; grid-template-columns:25px auto") :wikitext("") :tag("span"):wikitext(""..upgrade..""..(textAfter or "")):done if (includeStats) then local mods = getAllModifiersForUpgrade(upgradeType, upgrade) -- Check if we actually have mods if (mods ~= nil) then -- Create the stat section local tbl = mw.html.create("div"):cssText("display:grid; grid-template-columns:repeat(3, auto);") for k,v in pairs(mods) do				tbl:tag("span"):css("white-space", "nowrap"):wikitext(formatStatWithIcon(k, v)):done end return tostring(title)..tostring(tbl) end end return tostring(title) end

-- Builds a table consisting of the base stats of the ship -- The table has two rows, the first row contains the sail health, combat speed, and crew -- while the second row contains the hull health, travel speed, and defenders -- Each has the icon and the stat beside it function p.buildShipBaseStatsTable(frame) local args = getArgs(frame) local baseStats = getShipBaseStatsWithOverrides(args[1], args) if (baseStats == nil) then return end -- Originally was a table with two rows and three columns, now a grid layout to accomplish the same local tbl = mw.html.create("div"):cssText("display:grid; grid-template-columns:1fr 1.3fr 1fr;") tbl:tag("span"):wikitext(formatStatWithIcon("sail_health", baseStats.sail_health)):done :tag("span"):wikitext(formatStatWithIcon("combat_speed", baseStats.combat_speed)):done :tag("span"):wikitext(formatStatWithIcon("crew", baseStats.min_crew_size)):done :tag("span"):wikitext(formatStatWithIcon("hull_health", baseStats.hull_health)):done :tag("span"):wikitext(formatStatWithIcon("travel_speed", baseStats.travel_speed)):done :tag("span"):wikitext(formatStatWithIcon("defenders", baseStats.defenders)):done :done return tbl end

-- Builds the content for each of the gun/cannon fields -- Takes a single unnamed argument, the field name of the guns function p.buildGunsContent(frame) local args = getArgs(frame) local fieldName = args[1] if (fieldName == nil) then return "" else local fieldValue = args[fieldName] local guns = mw.text.split(fieldValue, ";", true) local output = {} for i = 1, #guns do -- Find offsets of quantity value in e.g. "5x" and capture the integer local qs, qe, qty = string.find(guns[i], "(%d+)x ") -- Default to qty 1, and zero the offsets as not to affect the substring qs = qty ~= nil and qs or 0 qe = qty ~= nil and qe or 0 qty = qty ~= nil and qty or 1 local gun = string.sub(guns[i], qe + 1) local content = p._buildUpgradeContent("cannon", gun, false, " (x"..qty..")") table.insert(output, content) end return table.concat(output) end end

-- Calculates the total crew count, typically (but not always!) the -- amount of enemies that the player faces in deck-to-deck combat. -- active crew + resting crew + defenders + captain function p.getTotalCrewCount(frame) local args = getArgs(frame) function countCrew(list) local count = 0 for i = 1, #list do			count = count + (tonumber(string.match(list[i], "%d*")) or 0) end return count end -- Get resting and active crew counts local activeCrewList = mw.text.split(args["crew"] or "", ";", true) local restingCrewList = mw.text.split(args["resting_crew"] or "", ";", true) local activeCrewCount = countCrew(activeCrewList) local restingCrewCount = countCrew(restingCrewList)

-- Get defender count local defenderCount = getShipBaseStatsWithOverrides(args["type"], args).defenders or 0 -- Total crew count is active + resting + defenders + captain (1) local total = activeCrewCount + restingCrewCount + defenderCount + 1 -- Determine if we should also print the breakdown if (args["breakdown"] == "yes") then local breakdown = {} if (activeCrewCount > 0) then table.insert(breakdown, activeCrewCount.." active") end if (restingCrewCount > 0) then table.insert(breakdown, restingCrewCount.." resting") end if (defenderCount > 0) then table.insert(breakdown, defenderCount.." defenders") end table.insert(breakdown, "1 captain") return total.." ("..table.concat(breakdown, ", ")..")" else return total end end

function p.normalizedColorToRgb(frame) local args = getArgs(frame) return p.normalizedColorStringToRgb(args[1]) end

function p.normalizedColorStringToRgb(str) -- Split by comma local rgb = mw.text.split(str or "", ",", true) if (#rgb ~= 3) then return "0 0 0" end for i = 1, #rgb do		-- Convert to number rgb[i] = tonumber(rgb[i]) or 0 -- Convert normalized value (0.0 - 1.0) to 8 bit integer (0 - 255) rgb[i] = math.floor(rgb[i] * 255) end return table.concat(rgb, " ") end

-- Fetching data

function upgradeTypeToPluralForm(upgradeType) if (common.endsWith(tostring(upgradeType), "s")) then return upgradeType else return upgradeType.."s" end end

-- Check to see if the data module is loaded function checkDataLoaded if (data == nil) then error("Data is not loaded!") return false end return true end

function isUpgradeTypeValid(upgradeType) if (data.upgrades[upgradeType] == nil) then error("No upgrade type found with the name \""..upgradeType.."\". Make sure to use the plural form, or check Module:Ship/data") return false end return true end

function isUpgradeValid(upgradeType, upgrade) if not (checkDataLoaded and upgradeType and upgrade) then return false end if (upgradeType == "" or upgrade == "") then return false end if (isUpgradeTypeValid(upgradeType)) then if (data.upgrades[upgradeType][upgrade] == nil) then error("An upgrade of type \""..upgradeType.."\" with the name \""..upgrade.."\" was not found") return false else return true end end return false end

-- Finds a particular stat modifier for an upgrade -- for example, using "Dragonwing Sails" and "combat_speed" returns 10 -- If a stat mod is not found, 0 is returned function getStatModifierForUpgrade(upgradeType, upgrade, stat) upgradeType = upgradeTypeToPluralForm(upgradeType) if (isUpgradeValid(upgradeType, upgrade) == false) then return 0 end local upgrade = data.upgrades[upgradeType][upgrade] -- Returns 0 if upgrade.stats is nil, or if the stat itself wasn't found -- otherwise return the stat return (upgrade.stats and upgrade.stats[stat]) or 0 end

function getAllModifiersForUpgrade(upgradeType, upgrade) upgradeType = upgradeTypeToPluralForm(upgradeType) if (isUpgradeValid(upgradeType, upgrade) == false) then return 0 end local upgrade = data.upgrades[upgradeType][upgrade] return upgrade.stats or nil end

-- Returns the base stats from the data module. function getShipBaseStats(shipType)

if not (checkDataLoaded and data.ships and data.ships[shipType]) then error("No ship type found with the name \""..(shipType or "").."\"") return nil end return data.ships[shipType]

end

-- Same as the above, but allows overriding specific stats using the arguments -- passed to the template. Overrides can be set in cases where a ship uses unique -- stats that aren't based on the ship type, and should have the same names as -- those found in Module:Ship/data function getShipBaseStatsWithOverrides(shipType, args) local baseStats = getShipBaseStats(shipType) -- Copy baseStats to new table, since it is read only local baseStatsRw = {} for k,v in pairs(baseStats) do		local override = tonumber(args[k]) baseStatsRw[k] = override or v	end return baseStatsRw end

-- Icons

-- Returns the icon for a ship stat function getIconForStat(statType) local icon = data.stats[statType].icon

if (icon == nil) then return "Missing icon.png" else return data.stats[statType].icon end end

-- Returns the icon filename for an upgrade function getIconForUpgrade(upgradeType, name) upgradeType = upgradeTypeToPluralForm(upgradeType) if (isUpgradeValid(upgradeType, name) == false) then return "Missing icon" else return data.upgrades[upgradeType][name].icon end end

-- Stat formatting

-- Formats a ship stat, outputting only the value function formatStat(stat, value) local fmt = data.stats[stat].format; if (fmt == nil) then error("Format string for stat "..stat.." not found!") return tostring(value) else return string.format(fmt, value) end end

-- Formats a ship stat, outputting the value prefixed with the icon function formatStatWithIcon(stat, value, dontFormat) local statInfo = data.stats[stat]; if (statInfo == null) then error("Stat info for stat "..stat.." not found!") return tostring(value) else return " "..(dontFormat == true and value or string.format(statInfo.format, value)) end

end

-- Formats a ship stat, outputting the value prefixed with the icon and suffixed with the stat label function formatStatWithIconAndLabel(stat, value) local statInfo = data.stats[stat]; if (statInfo == null) then error("Stat info for stat "..stat.." not found!") return tostring(value) else return " "..string.format(statInfo.format, value).." "..statInfo.name end end return p