Module:Armor/Query

From Zelda Wiki, the Zelda encyclopedia
Jump to navigation Jump to search
This is the main module for the following templates:
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 utilsString = require("Module:UtilsString")
local utilsTable = require("Module:UtilsTable")

local CATEGORY_INVALID_ARGS = "[[Category:"..require("Module:Constants/category/invalidArgs").."]]"

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

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

function p.ArmorList(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Armor List"])
	local categories = err and err.categoryText or ""

	if not args.game then
		return err.categoryText
	end

	local armorData = utilsCargo.query("Armor=Sets, ArmorLevels=Levels", "Sets.armorSet=armorSet, Sets.bodyPart=bodyPart, Levels.armor=armor, Levels.level=level, Levels.defense=defense, Levels.materials=materials, Levels.sellable=sellable, Levels.sellPrice=sellPrice, Levels.description=description", {
		join = "Levels.armor = Sets.armor, Levels.game = Sets.game",
		where = utilsCargo.allOf({
			["Levels.game"] = args.game,
		}),
		limit = 1000,
	})
	categories = categories..utilsCargo.categoryQuerying()
	
	armorData = utilsTable.sortBy(armorData, function(armor)
		local sortKey = armor.armor..(armor.level or "")
		if armor.armorSet and armor.armorSet ~= "" then
			sortKey = armor.armorSet..h.bodyPartSortKey(armor.bodyPart)..sortKey
		end
		return sortKey
	end)
	
	local columns = {"Armor Set [Term]", "Body Part", "Armor [Nowrap][Rowspan][Image:Icon:144x144px][Term]", "Level", string.format("Defense [Defense:%s:-]", args.game), "Sell Price [Rupees]", "Description [Transcript]"}
	local rows = utilsTable.map(armorData, function(armor)
		return {
			cells = {
				armor.armorSet or "N/A",
				armor.bodyPart or "",
				armor.armor or "",
				armor.level or "",
				armor.defense or "",
				armor.sellable == "1" and (armor.sellPrice or "") or "N/A",
				armor.description or "",
			}
		}
	end)
	
	local categories2 = h.annotateTable(rows, columns, args.notes)
	categories = categories..categories2
	
	local dataTable = DataTable.printTable(rows, {
		game = args.game,
		columns = columns,
	}, function (tableArgs)
		for i, row in ipairs(tableArgs.rows) do
			row.cells[1].rowspan = row.cells[3].rowspan
			row.cells[2].rowspan = row.cells[3].rowspan
			row.cells[1].skip = row.cells[3].skip
			row.cells[2].skip = row.cells[3].skip
		end
		return tableArgs
	end)

	return dataTable, categories
end

function p.ArmorLevels(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Armor Levels"])
	local categories = err and err.categoryText or ""
	if err then
		return categories
	end
	
	local data = utilsCargo.query("ArmorLevels", "level, defense, materials, sellPrice, sellable, description", {
		where = utilsCargo.allOf({
			game = args.game,
			armor = args.armor,
		}),
		orderBy = "level",
	})
	if #data == 0 then
		categories = categories..CATEGORY_INVALID_ARGS
		h.warn("No armor named <code>%s</code> is stored at [[Data:Armor/%s]].", args.armor, args.game)
		return categories
	end
	if #data == 1 then
		categories = categories..CATEGORY_INVALID_ARGS
		h.warn("This template is for upgradeable armor only — <code>%s</code> has only one level stored at [[Data:Armor/%s]].", args.armor, args.game)
		return categories
	end
	
	local rows = utilsTable.map(data, function(armor)
		return {
			cells = {
				armor.level or "",
				armor.defense or "",
				armor.level and (armor.materials or "") or "N/A",
				armor.sellable == "1" and (armor.sellPrice or "") or "N/A",
				armor.description or "",
			}
		}
	end)
	local dataTable = DataTable.printTable(rows, {
		game = args.game,
		sortable = false,
		columns = {"Level", string.format("Defense [Defense:%s:-]", args.game), "Materials [Amounts]", "Sell Price [Rupees]", "Description [Transcript]"}
	})

	return dataTable, categories
end

function p.ArmorSetPieces(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Armor Set Pieces"])
	local categories = err and err.categoryText or ""
	if err then
		return categories
	end
	local armorSetPieces = utilsCargo.query("ArmorLevels=Levels, Armor=Sets", "Sets.armor=armor, Sets.armorSet=armorSet, Sets.bodyPart=bodyPart, Levels.level=level, Levels.defense=defense, Levels.materials=materials, Levels.sellable=sellable, Levels.sellPrice=sellPrice", {
		join = "Levels.armor = Sets.armor, Levels.game = Sets.game",
		where = utilsCargo.allOf({
			["Levels.game"] = args.game,
			["Sets.armorSet"] = args.armorSet,
		}),
	})
	categories = categories..utilsCargo.categoryQuerying()
	
	if #armorSetPieces == 0 then
		categories = categories..CATEGORY_INVALID_ARGS
		return h.err("Data not found", string.format("No such armor set <code>%s</code> is stored at [[Data:Armor/%s]].", args.armorSet, args.game)), categories
	end
	
	local isUpgradeable = utilsTable.find(armorSetPieces, "level")
	
	-- Sort armor by slot, then by name, then by level
	armorSetPieces = utilsTable.sortBy(armorSetPieces, function(armor)
		return h.bodyPartSortKey(armor.bodyPart)..armor.armor..(armor.level or "")
	end)
	
	local rows = utilsTable.map(armorSetPieces, function(armor)
		if isUpgradeable then
			return {
				cells = {
					armor.armor or "",
					armor.level or "",
					armor.defense or "",
					armor.level and (armor.materials or "") or "N/A",
					armor.sellable == "1" and (armor.sellPrice or "") or "N/A",
				}
			}
		else 
			return {
				cells = {
					armor.armor or "",
					armor.defense or "",
					armor.sellable == "1" and (armor.sellPrice or "") or "N/A",
				}
			}
		end
	end)
	local columns
	if isUpgradeable then
		columns = {"Armor [Rowspan][Image:Icon:144x144px][Term]", "Level", string.format("Defense [Defense:%s:-]", args.game), "Materials [Amounts]", "Sell Price [Rupees]"}
	else
		columns = {"Armor [Image:Icon:144x144px][Term]", string.format("Defense [Defense:%s:-]", args.game), "Sell Price [Rupees]"}
	end
	
	local dataTable = DataTable.printTable(rows, {
		game = args.game,
		sortable = false,
		columns = columns
	})

	return dataTable, categories
end

function p.MaterialArmors(frame)
	local args, err = utilsArg.parse(frame:getParent().args, p.Templates["Material Armors"])
	local categories = err and err.categoryText or ""
	if err then
		return categories
	end

	local material = args.material
	
	local armorRows = utilsCargo.query("ArmorLevels", "armor, level, materials", {
		where = "game = '"..args.game.."' AND materials LIKE '%"..utilsCargo.escape(material).."%'",
		orderBy = "armor, level"
	})
	categories = categories .. utilsCargo.categoryQuerying()

	-- 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 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+", "")
			if materialName == material then
				totalAmountRequired = totalAmountRequired + tonumber(amount)
				return true
			end
		end
	end)

	if #armorRows == 0 then
		categories = categories..CATEGORY_INVALID_ARGS
		return h.err("Data not found", string.format("No upgrade data exists for material <code>%s</code> on page [[Data:Armor/%s]]", material, args.game)), categories
	end
	
	local tableRows = utilsTable.map(armorRows, function(armorRow)
		return {
			cells = {
				armorRow.armor,
				armorRow.level,
				armorRow.materials,
			}
		}
	end)
	
	local dataTable = DataTable.printTable(tableRows, {
		game = args.game,
		columns = {"Armor [Rowspan][Image:Icon:144x144px][Term]", "Level", "Materials [Amounts]"},
		sortable = false,
	})

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

	local result = dataTable.."<div>"..totalRequired.."</div>"
	return result, categories
end

function h.bodyPartSortKey(bodyPart) 
	if bodyPart == "Head" then
		return "1"
	elseif bodyPart == "Chest" then
		return "2"
	elseif bodyPart == "Legs" then
		return "3"
	else
		return "4"
	end
end

function h.annotateTable(rows, columns, notes)
	if not notes then
		return
	end

	local categories = ""

	local addedNotes = {}
	for i, row in ipairs(rows) do
		columns = DataTable.extractColumnTags(columns)
		local categories = ""
		for j, note in ipairs(notes) do
			local matchesRow = utilsTable.includes(row.cells, note.row)
			local columnIndex = utilsTable.findIndex(columns, function(column)
				return column == note.column
			end)
			if matchesRow and columnIndex then
				row.cells[columnIndex] = row.cells[columnIndex]..note.note
			end
			if not columnIndex then
				h.warn("No such column <code>%s</code>", note.row)
				categories = categories..CATEGORY_INVALID_ARGS
			end
			if matchesRow then
				table.insert(addedNotes, j)
			end
		end
	end

	local unmatchedNotes = utilsTable.difference(utilsTable.keys(notes), addedNotes)
	for i, noteIndex in ipairs(unmatchedNotes) do
		h.warn("No row <code>%s</code> found for note %d", notes[noteIndex].row, noteIndex)
		categories = categories..CATEGORY_INVALID_ARGS
	end
	
	return categories
end

p.Templates = {
	["Armor List"] = {
		format = "block",
		paramOrder = {1, "note", "row", "column"},
		repeatedGroup = {
			name = "notes",
			params = {"note", "row", "column"},
		},
		params = {
			[1] = {
				name = "game",
				inline = true,
				required = true,
				trim = true,
				nilIfEmpty = true,
				type = "string",
				desc = "A game code from [[Data:Franchise]].",
			},
			note = {
				name = "note",
				type = "wikitext",
				desc = "An annotation to add to a table cell.",
			},
			row = {
				type = "string",
				desc = "The name of the armor identifying the row to add the annotation to.",
			},
			column = {
				type = "string",
				desc = "The name of the column identifying the cell to add the annotation to.",
			},
		},
	},
	["Armor Levels"] = {
		params = {
			[1] = {
				name = "game",
				required = true,
				type = "string",
				desc = "A game code from [[Data:Franchise]].",
			},
			[2] = {
				name = "armor",
				required = true,
				type = "wiki-page-name",
				desc = "A wiki page name referring to a particular piece of [[Armor]] as named in <code>game</code>.",
			},
		},
	},
	["Armor Set Pieces"] = {
		params = {
			[1] = {
				name = "game",
				required = true,
				type = "string",
				desc = "A game code from [[Data:Franchise]].",
			},
			[2] = {
				name = "armorSet",
				required = true,
				type = "wiki-page-name",
				desc = "A wiki page name referring to a particular [[Armor Set]] as named in <code>game</code>.",
			},
		},
	},
	["Material Armors"] = {
		params = {
			[1] = {
				name = "game",
				required = true,
				type = "string",
				desc = "A game code from [[Data:Franchise]].",
			},
			[2] = {
				name = "material",
				required = true,
				type = "wiki-page-name",
				desc = "A wiki page name referring to a particular [[Material]] as named in <code>game</code>.",
			},
		},
	},
}

return p