Module:Armor

local p = {} local h = {}

local DataTable = require("Module:Data Table") local Term = require("Module:Term") local utilsArg = require("Module:UtilsArg") local utilsCargo = require("Module:UtilsCargo") local utilsMarkup = require("Module:UtilsMarkup") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable")

local CATEGORY_INVALID_ARGS = require("Module:Constants/category/invalidArgs") local ARMOR_COLUMNS = {"Set", "Body Part", "Armor", "Defense", "Effect(s)", "Sell Price", "Description"} local PAGE_ARMOR_UPGRADES = "Armor Upgrade" local PAGE_ARMOR = "Armor" local PAGE_ARMOR_TOTK = "Armor in Tears of the Kingdom" local UPGRADE_COLUMNS = {"Armor", "Level", "Materials", "Defense", "Sell Price"}

function h.warn(msg, ...) local utilsError = require("Module:UtilsError") msg = string.format(msg, ...) utilsError.warn(msg) end

function h.err(errMsg, warnMsg) local utilsError = require("Module:UtilsError") return utilsError.error(errMsg, warnMsg) end

function p.ArmorData(frame) return h.armorData(frame) end

function p.MaterialArmors(frame) return h.materialArmors(frame) end

function p.ArmorTable(frame) return DataTable.Main(frame, h.storeArmor, function(args) return {		requiredColumns = ARMOR_COLUMNS	} end) end

function p.UpgradeTable(frame) return DataTable.Main(frame, h.storeUpgrades, function(args) return {		requiredColumns = UPGRADE_COLUMNS	} end) end

function h.armorData(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Armor Data"]) if err then return "", err.categoryText end local armor = args.armor or mw.title.getCurrentTitle.subpageText local armorValue = utilsCargo.escape(armor) local armorRows = utilsCargo.query("Armor", "armor, armorSet, bodyPart, level, defense, materials, sellPrice", {		where = string.format("(_pageName = '%s' OR _pageName = '%s' OR _pageName = '%s') AND (armor = '%s' OR armorSet = '%s') AND game= '%s'", PAGE_ARMOR, PAGE_ARMOR_TOTK, PAGE_ARMOR_UPGRADES, armorValue, armorValue, args.game),	}) local armorCount = #(utilsTable.uniqueBy(armorRows, "armor")) if #armorRows == 1 then h.warn("Non-upgradeable Armor does not have enough data to show in a table. The template should be removed from the page.") return "" elseif armorCount == 0 then h.warn("No data exists for Armor or Armor Set  on the %s page.", armor, PAGE_ARMOR) return "" elseif #armorRows == armorCount then return h.armorPieces(armorRows, args.game) -- simply list Armor pieces for non-upgradeable armor sets elseif armorCount > 1 then return h.setLevels(armorRows, args.game) else return h.armorLevels(armorRows, args.game) end end function h.armorLevels(armorRows, game) local rows = {} for i, armor in ipairs(armorRows) do		local row = {} row.cells = { armor.level or " ", armor.defense, armor.materials or "N/A", armor.sellPrice or "N/A" }		table.insert(rows, row) end local dataTable = DataTable.printTable(rows, {		game = game,		sortable = false,		columns = {"Level", string.format("Defense [Defense:%s:-]", game), "Materials [Amounts]", "Sell Price [Rupees]"}	}) return dataTable end function h.setLevels(armorRows, game) armorRows = utilsTable.sortBy(armorRows, h.armorSortKey) local rows = {} for i, armor in ipairs(armorRows) do		local row = {} row.cells = { armor.armor, armor.level or " ", armor.defense, armor.materials or "N/A", armor.sellPrice or "N/A" }		table.insert(rows, row) end local dataTable = DataTable.printTable(rows, {		game = game,		sortable = false,		columns = {"Armor [Rowspan][Term]", "Level", string.format("Defense [Defense:%s:-]", game), "Materials [Amounts]", "Sell Price [Rupees]"}	}) return dataTable end function h.armorPieces(armorRows, game) local rows = {} for i, armor in ipairs(armorRows) do		local row = {} row.cells = { armor.armor, armor.defense, armor.sellPrice or "N/A" }		table.insert(rows, row) end local dataTable = DataTable.printTable(rows, {		game = game,		sortable = false,		columns = {"Armor [Term]", string.format("Defense [Defense:%s:-]", game), "Sell Price [Rupees]"}	}) return dataTable end

function h.materialArmors(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Material Armors"]) if err then return "", err.categoryText end local currentPage = mw.title.getCurrentTitle.subpageText -- subpageText for the benefit of sandboxes local material = args.material or Term.fetchTerm(currentPage, args.game) or currentPage local armorRows = utilsCargo.query("Armor", "armorSet, armor, bodyPart, level, materials", {		where = utilsCargo.allOf({ game = args.game, _pageName = PAGE_ARMOR_UPGRADES }, "materials HOLDS LIKE '%"..utilsCargo.escape(material).."%'")	}) -- Parse materials armorRows = utilsTable.map(armorRows, function(armorRow)		local materials = utilsString.split(armorRow.materials)		armorRow.materialAmounts = {}		for i, armorMaterial in ipairs(materials) do			local amount = string.match(armorMaterial, "^(%d+)%s+")			local materialName = string.gsub(armorMaterial, "^(%d+)%s+", "")			armorRow.materialAmounts[materialName] = tonumber(amount)		end		return armorRow	end) -- Do an exact match on the material name as the SQL query above will return rows containing "Giant Ancient Core" for material "Ancient Row" -- At the same time, compute the total of this material that is required to upgrade everything local totalAmountRequired = 0 armorRows = utilsTable.filter(armorRows, function(armorRow)		local amount = armorRow.materialAmounts[material]		if amount then			totalAmountRequired = totalAmountRequired + amount			return true		end	end)

if #armorRows == 0 then return h.err("Data not found", string.format("No upgrade data exists for material  on page %s for game  ", material, PAGE_ARMOR_UPGRADES, args.game)), "" end

local showArmorSets = #(utilsTable.uniqueBy(armorRows, "armorSet")) > 1 armorRows = utilsTable.sortBy(armorRows, h.armorSortKey)

local tableRows = {} for i, armor in ipairs(armorRows) do		local cells = {armor.armor, armor.level, armor.materials} if showArmorSets then table.insert(cells, 1, armor.armorSet or "N/A") end local row = { cells = cells } table.insert(tableRows, row) end

local columns = {"Armor [Rowspan][Term]", "Level", "Materials [Amounts]"} if showArmorSets then table.insert(columns, 1, "Armor Set [Term]") end local dataTable = DataTable.printTable(tableRows, {		game = args.game,		sortable = false,		columns = columns,	}, showArmorSets and h.postFormat or nil)

local plural = Term.plural(material, args.game) local totalRequired = string.format("Total %s required: %d", plural, totalAmountRequired)

local result = dataTable.." "..totalRequired.." " return result end

function h.postFormat(tableArgs) for i, row in ipairs(tableArgs.rows) do		row.cells[1].rowspan = row.cells[2].rowspan row.cells[1].skip = row.cells[2].skip end return tableArgs end

function h.storeArmor(args, rows) local categories = "" for i, row in ipairs(rows) do		mw.getCurrentFrame:expandTemplate({			title = "Armor/Store",			args = {				game = args.game,				armorSet = h.cell(row["Set"]),				armor = h.cell(row["Armor"]),				bodyPart = h.cell(row["Body Part"]),				level = nil,				defense = h.cell(row["Defense"]),				sellPrice = h.cell(row["Sell Price"]),				effects = h.cell(row["Effect(s)"]),				materials = nil,				description = h.cell(row["Description"])			}		}) end return categories end

function h.cell(cell) if not cell then return cell end cell = utilsMarkup.separateMarkup(cell) if cell == "N/A" then return nil else return cell end end

function h.storeUpgrades(args, rows) local categories = "" local armorByName, armorBySet = h.getBaseArmorData(args.game) for i, row in ipairs(rows) do		local armors, errCategories = h.addBaseData(row, armorBySet, armorByName, args.game) categories = categories..errCategories for i, armor in ipairs(armors) do			mw.getCurrentFrame:expandTemplate({				title = "Armor/Store",				args = armor,			}) end end return categories end function h.addBaseData(data, armorBySet, armorByName, game) local categories = "" local armors = {} local armorName = h.cell(data["Armor"]) local armorPieces = armorBySet[armorName] if armorPieces then for i, armorPiece in ipairs(armorPieces) do			local armor = h.mergeBaseData(data, armorByName[armorPiece]) armor.game = game table.insert(armors, armor) end elseif armorByName[armorName] then local armor = h.mergeBaseData(data, armorByName[armorName]) armor.game = game table.insert(armors, armor) else h.warn("Unknown Armor or Set '%s'", armorName) categories = categories.."" end return armors, categories end function h.mergeBaseData(upgrade, base) local description = base.description return { armor = base.armor, armorSet = base.armorSet, bodyPart = base.bodyPart, effects = base.effects, level = h.cell(upgrade["Level"]), defense = h.cell(upgrade["Defense"]), sellPrice = h.cell(upgrade["Sell Price"]), materials = h.cell(upgrade["Materials"]), description = h.cell(upgrade["Description"]), } end

function h.getBaseArmorData local armors = utilsCargo.query("Armor", "armor, armorSet, bodyPart, effects", {		limit = 1000,		_pageName = ARMOR_PAGE	}) local armorByName = utilsTable.keyBy(armors, "armor") local armorBySet = utilsTable.groupBy(armors, "armorSet") armorBySet = utilsTable.mapValues(armorBySet, utilsTable._map("armor")) return armorByName, armorBySet end

function h.armorSortKey(armor) local bodyPartSortKey = ({		["Head"] = "1",		["Chest"] = "2",		["Legs"] = "3",	})[armor.bodyPart or ""] or ""

local armorSet = armor.armorSet local level = armor.level or "" if armorSet and armorSet ~= "" then return "1"..armorSet..bodyPartSortKey..armor.armor..level else return "0"..armor.armor..bodyPartSortKey..level end end

p.Templates = { ["Armor Data"] = { description = "Displays Armor data", purpose = string.format("Displays Armor data retrieved from the Armor list and Armor upgrade list stored in the Armor Cargo table. For use on individual Armor and Armor Set articles.", PAGE_ARMOR, PAGE_ARMOR_UPGRADES), params = { [1] = {				name = "game", required = true, type = "string", enum = {"BotW", "TotK"}, desc = "A game code.", },			[2] = {				name = "armor", type = "wiki-page-name", desc = "An article name referring to a particular piece of Armor. Defaults to the current page.", },		},		examples = { vertical= true, {"BotW", "Rubber Armor"}, {"BotW", "Rubber Armor (Set)"}, {"BotW", "Well-Worn Outfit"}, {"TotK", "Ember Headdress"}, {"TotK", "Froggy Armor"}, {				desc = "Displays nothing for non-upgradeable individual armor pieces, as there is not enough information to display in a table.", args = {"BotW", "Thunder Helm"}, },			{				desc = "Error: Non-existent Armor", args = {"BotW", "Not an Armor"}, },		},	},	["Armor/Store"] = { purpose = "Used by Template:Data Table/Armor to store armor data.", format = "block", storesData = true, paramOrder = {"game", "armorSet", "armor", "bodyPart", "level", "defense", "sellPrice", "effects", "materials", "description"}, boilerplate = { separateRequiredParams = false, },		params = { game = { type = "string", desc = "A game code from Data:Franchise", trim = true, nilIfEmpty = true, },			armorSet = { type = "wiki-page-name", desc = "If applicable, a wiki page name on the Armor Set that this Armor belongs to.", trim = true, },			armor = { required = true, type = "wiki-page-name", desc = "A wiki article name on a particular piece of Armor.", trim = true, nilIfEmpty = true, },			bodyPart = { required = true, type = "string", enum = {"Head", "Chest", "Legs"}, desc = "The slot that this Armor occupies on Link's body.", trim = true, nilIfEmpty = true, },			level = { type = "number", desc = "The level of the Armor upgrade, as an integer. Blank for base items.", trim = true, nilIfEmpty = true, },			defense = { required = true, type = "number", desc = "The defensive value for the Armor at the specified level.", trim = true, nilIfEmpty = true, },			sellPrice = { type = "number", desc = "An integer – the amount of Rupees that Link can sell the Armor for at the specified level.", trim = true, nilIfEmpty = true, },			effects = { type = "string", desc = "A comma-separated list of the Armor's Effects, if any.", trim = true, nilIfEmpty = true, },			materials = { required = true, type = "string", desc = "A list of the materials required to obtain specified upgrade level.", trim = true, nilIfEmpty = true, },			description = { required = true, type = "content", desc = "A transcript of the item's description on the Inventory screen.", trim = true, nilIfEmpty = true, },		},	},	["Data Table/Armor"] = {}, ["Data Table/Armor Upgrades"] = {}, ["Material Armors"] = { description = "Lists Armor upgrades that require a particular Material.", purpose = string.format("Displays Armor Upgrades requiring a particular Material, for use on individual Material articles. The data is retrieved from the Armor Cargo table data stored on the %s page.", PAGE_ARMOR_UPGRADES), params = { [1] = {				name = "game", required = true, type = "string", enum = {"BotW", "TotK"}, desc = "A game code.", },			[2] = {				name = "material", type = "wiki-page-name", desc = "An article name referring to a particular Material. Defaults to the current page.", },		},		examples = { vertical = true, {"BotW", "Amber"}, {"BotW", "Diamond"}, {"BotW", "Flint"}, {"TotK", "Ice-Breath Lizalfos Tail"}, {"BotW", "Not a Material"}, },	} }

return p