Porcopedia
mNo edit summary
(Documenting the module, adding value checking to the display unit infobox function, and removing an unused function.)
Line 400: Line 400:
 
end
 
end
   
  +
--[[
  +
[Local] tableTd:
  +
Helper function that generates a <td> element.
 
  +
Parameters:
  +
- text (string): String containing WikiText to be placed in the <td> tag.
  +
- properties (table [optional]): Optional list of properties that can be used
  +
to alter certain properties in the element.
  +
  +
Returns:
  +
A string containing a <td> element.
  +
]]
 
function l.tableTd(text, properties)
 
function l.tableTd(text, properties)
 
local element = mw.html.create("td"):wikitext(text)
 
local element = mw.html.create("td"):wikitext(text)
Line 410: Line 422:
 
end
 
end
   
  +
--[[
  +
[Local] _tableResourceCostInner
  +
Function that generates a <td> tag for a resource.
  +
Used for _tableResourceCost function.
 
  +
Parameters:
  +
- row (mw.html): Represents a mw.html object, in this case the <tr> for the row.
  +
- resource (string/table): Either a string or a table representing a resource.
  +
  +
Returns:
  +
Nil. Function adds data to the passed row object.
  +
]]
 
function l._tableResourceCostInner(row, resource)
 
function l._tableResourceCostInner(row, resource)
 
if resource == nil then return "<td></td>" end
 
if resource == nil then return "<td></td>" end
Line 429: Line 453:
 
end
 
end
   
  +
--[[
  +
[Local] _tableResourceCost
  +
Function that generates resource costs, used by tableResourceCost function.
  +
  +
Parameters:
  +
- row (mw.html): Represents a mw.html object, in this case the <tr> for the row.
  +
- resource (string/table): Either a string or a table representing a resource.
  +
  +
Returns:
  +
A number indicating how many resources it appended to the row object.
  +
]]
 
function l._tableResourceCost(row, resource)
 
function l._tableResourceCost(row, resource)
 
if resource == nil then return 0 end
 
if resource == nil then return 0 end
Line 444: Line 479:
 
end
 
end
   
  +
--[[
  +
[Local] tableResourceCost
  +
Function that generates HTML code for showing a units resource cost.
 
  +
Parameters:
  +
- row (mw.html): A mw.html object that contains a <tr> element.
  +
- cost: The cost requirements of the unit.
  +
- num_cols: Max number of columns in the table, used to pad the row out if
  +
current unit uses less.
  +
- has_porco_cost: If true, function will show number of required porcos.
  +
- has_mount_cost: If true, function will show mount cost.
 
  +
Returns:
  +
Nil, data is appended to passed row object.
  +
]]
 
function l.tableResourceCost(row, cost, num_cols, has_porco_cost, has_mount_cost)
 
function l.tableResourceCost(row, cost, num_cols, has_porco_cost, has_mount_cost)
 
-- Expected values: Either a table of strings/tables, or a string.
 
-- Expected values: Either a table of strings/tables, or a string.
Line 478: Line 528:
 
end
 
end
   
  +
--[[
  +
[Local] professionGet
  +
Helper function to show a units 'profession'.
 
  +
Parameters:
  +
- p_type (string): A string containing the 'profession type'.
 
  +
Returns:
  +
A string contianing Wikitext.
  +
]]
 
function l.professionGet(p_type)
 
function l.professionGet(p_type)
  +
-- FIXME: This should probably be handled differently..
  +
-- e.g. Abstracted so that the data below is stored in something
  +
-- like a table to allow for easier editing..
 
if p_type == "settler" then return "<span>Settler</span>" end
 
if p_type == "settler" then return "<span>Settler</span>" end
 
if p_type == "basic" then return "<span>Basic Unit</span>" end
 
if p_type == "basic" then return "<span>Basic Unit</span>" end
Line 489: Line 552:
   
 
--[[
 
--[[
  +
[Local] unitProfession
unitProfession: Helper method that generates HTML for the unit infobox showing
+
Helper method that generates a <div> block for the unit infobox showing what
what the type of unit is.
+
the type of unit is.
  +
  +
Parameters:
  +
- profession (string): A string containing the type of profession.
  +
- properties (table): A table containing properties to alter the element
  +
generated. Currently not used.
  +
  +
Returns:
  +
A string containing a <div> element that shows the units profession.
 
]]
 
]]
 
function l.unitProfession(profession, properties)
 
function l.unitProfession(profession, properties)
Line 512: Line 584:
 
end
 
end
   
  +
--[[
  +
[Local] tableUnitProfession
  +
Helper method that generates a <td> block for the units table.
  +
  +
Parameters:
  +
- profession (string): A string containing the type of profession.
  +
- properties (table): A table containing properties to alter the element
  +
generated.
  +
  +
Returns:
  +
A string containing a <td> element that shows the units profession.
  +
]]
 
function l.tableUnitProfession(profession, properties)
 
function l.tableUnitProfession(profession, properties)
 
-- Exepcted values: table of strings, or a singular string.
 
-- Exepcted values: table of strings, or a singular string.
Line 532: Line 616:
 
end
 
end
   
  +
--[[
  +
[Local] unitStatsTableRow
  +
Helper function to generate a <tr> element for unit stats. Used by displayUnitStatsTable.
  +
  +
Parameters:
  +
- html_table (mw.html): A mw_html object that represents the <table>.
  +
- unit (table): A table representing a unit to be shown.
  +
  +
Returns:
  +
Nil, data is appeneded to the html_table object.
  +
]]
 
function l.unitStatsTableRow(html_table, unit)
 
local row = mw.html.create("tr"):addClass("unit-row")
  +
 
if unit.page ~= nil then
 
row:node(l.tableTd('[[' .. unit.page .. '|' .. (unit.name or "?") .. ']]'))
 
else
 
row:node(l.tableTd(unit.name or "?"))
 
end
  +
 
row:node(l.tableTd(unit.strength or "?"))
 
row:node(l.tableTd(unit.toughness or "?"))
 
row:node(l.tableTd(unit.armor or "?"))
  +
 
row:node(l.tableTd(unit.attack_skill or "?"))
 
row:node(l.tableTd(unit.defense_skill or "?"))
 
row:node(l.tableTd(unit.melee_damage or "?"))
 
row:node(l.tableTd(unit.attack_speed or "?"))
 
row:node(l.tableTd(unit.reach or "?"))
  +
 
if unit.ranged_skill == nil then
 
row:node('<td colspan="4" data-sort-value="0" class="text-muted">n/a</td>')
 
else
 
row:node(l.tableTd(unit.ranged_skill or "?"))
 
row:node(l.tableTd(unit.ranged_damage or "?"))
 
row:node(l.tableTd(unit.ranged_speed or "?"))
 
row:node(l.tableTd(unit.range or "?"))
 
end
  +
 
row:node(l.tableTd(unit.movement or "?"))
 
row:node(l.tableTd(unit.mass or "?"))
 
row:node(l.tableTd(unit.carry_capacity or "?"))
  +
 
html_table:node(tostring(row))
 
end
  +
  +
--[[
  +
  +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  +
  +
Exported Functions Below
  +
  +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  +
  +
]]
  +
  +
--[[
  +
[Exported] displayUnitsTable
  +
Generates HTML code for showing a <table> containing data for a given set of units.
  +
Specifically, this shows the unit, its "profession", and resource cost.
  +
  +
This function is expected to be invoked directly from a wiki page, so its only
  +
parameter is the frame object.
  +
  +
Parameters:
  +
- frame (Frame): The Frame object for the page.
  +
  +
Frame Parameters:
  +
- filter (string [optional]): A string containing key/value pairs to filter the table with.
  +
If this is not set, all units listed in the Units/Data module will be shown.
  +
  +
Returns:
  +
A string containing a <table> object showing the units requested.
  +
]]
 
function p.displayUnitsTable(frame)
 
function p.displayUnitsTable(frame)
   
Line 594: Line 752:
 
end
 
end
   
  +
--[[
function l.unitStatsRangedMeleeStat(melee_stat, ranged_stat)
 
  +
[Exported] displayUnitStatsTable
local stat_text = ""
 
  +
Generates HTML code for showing a <table> containing data for a given set of units.
if ranged_stat ~= nil then
 
  +
Specifically, the unit, and their combat stats.
stat_text = "<b>R. " .. ranged_stat .. "</b><br/>" .. melee_stat
 
  +
else
 
  +
This function is expected to be invoked directly from a wiki page, so its only
stat_text = melee_stat
 
  +
parameter is the frame object.
end
 
return "<td>" .. stat_text .. "</td>"
 
end
 
 
function l.unitStatsTableRow(html_table, unit)
 
local row = mw.html.create("tr"):addClass("unit-row")
 
 
if unit.page ~= nil then
 
row:node(l.tableTd('[[' .. unit.page .. '|' .. (unit.name or "?") .. ']]'))
 
else
 
row:node(l.tableTd(unit.name or "?"))
 
end
 
row:node(l.tableTd(unit.strength or "?"))
 
row:node(l.tableTd(unit.toughness or "?"))
 
row:node(l.tableTd(unit.armor or "?"))
 
 
row:node(l.tableTd(unit.attack_skill or "?"))
 
row:node(l.tableTd(unit.defense_skill or "?"))
 
row:node(l.tableTd(unit.melee_damage or "?"))
 
row:node(l.tableTd(unit.attack_speed or "?"))
 
row:node(l.tableTd(unit.reach or "?"))
 
 
if unit.ranged_skill == nil then
 
row:node('<td colspan="4" data-sort-value="0" class="text-muted">n/a</td>')
 
else
 
row:node(l.tableTd(unit.ranged_skill or "?"))
 
row:node(l.tableTd(unit.ranged_damage or "?"))
 
row:node(l.tableTd(unit.ranged_speed or "?"))
 
row:node(l.tableTd(unit.range or "?"))
 
end
 
 
row:node(l.tableTd(unit.movement or "?"))
 
row:node(l.tableTd(unit.mass or "?"))
 
row:node(l.tableTd(unit.carry_capacity or "?"))
 
 
 
html_table:node(tostring(row))
 
end
 
   
  +
Parameters:
  +
- frame (Frame): The Frame object for the page.
  +
  +
Frame Parameters:
  +
- filter (string [optional]): A string containing key/value pairs to filter the table with.
  +
If this is not set, all units listed in the Units/Data module will be shown.
  +
  +
Returns:
  +
A string containing a <table> object showing the units requested.
  +
]]
 
function p.displayUnitStatsTable(frame)
 
function p.displayUnitStatsTable(frame)
 
local filter = frame.args["filter"]
 
local filter = frame.args["filter"]
Line 692: Line 823:
   
 
--[[
 
--[[
displayUnitInfoBox: Shows a infobox representing a unit.
+
[Exported] displayUnitInfoBox
  +
Shows a infobox representing a unit.
Should be passed an argument id=X, where X is the id of the unit being shown.
 
  +
  +
This function is expected to be invoked directly from a wiki page, so its only
  +
parameter is the frame object.
  +
  +
Parameters:
  +
- frame (Frame): The Frame object for the page.
  +
  +
Frame Arguments:
  +
- id (string): The unit id to be shown, this must be set, or an error will be
  +
shown. If an invalid unit is requested, an error will be shown instead.
  +
  +
Returns:
  +
A string containing an infobox for the requested unit.
 
]]
 
]]
 
function p.displayUnitInfobox(frame)
 
function p.displayUnitInfobox(frame)
Line 702: Line 846:
 
 
 
local unit_id = frame.args["id"]
 
local unit_id = frame.args["id"]
  +
 
if unit_id == nil then
  +
return mw.html.create("div"):addClass("infobox"):addClass("infobox-unit"):addClass("invalid"):wikitext("No id passed to displayUnitInfobox invoke.")
 
end
 
 
 
local unit = l.getUnitById(unit_id)
 
local unit = l.getUnitById(unit_id)

Revision as of 05:57, 13 February 2022

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

local p = {} -- Exported functions
local l = {} -- Local functions

local resources = require("Module:Resources")
local resource_html = require("Module:Resources/HTML")

local data = mw.loadData("Module:Units/Data")
local stat_tooltips = mw.loadData("Module:Units/StatTooltips")

local infobox_html = {}

function infobox_html.heading(text, properties)
	local dom_element = mw.html.create("div"):addClass("infobox-heading"):wikitext(text)
	return dom_element
end

function infobox_html.title(text, properties)
	local dom_element = mw.html.create("div"):addClass("infobox-title"):wikitext(text)
	return dom_element
end

function infobox_html.subtitle(text, properties)
	local dom_element = mw.html.create("div"):addClass("infobox-subtitle"):wikitext(text)
	return dom_element
end

--[[
[Local] getUnitById: 
 Gets a unit from the unit list that matches the given id, or nil if not found.

Parameters: 
 - unit_id (string) The unit id to find.
 
Returns:
 The unit from the unit list that maches the given unit_id, or nil if not found.
 
]]
function l.getUnitById(unit_id)
	
	if unit_id == nil or type(unit_id) ~= "string" then
		return nil
	end
	
	for i,unit in ipairs(data) do
		if unit ~= nil and type(unit) == "table" and unit.id ~= nil and unit.id == unit_id then
			return unit
		end
	end
	
	return nil
	
end

--[[
[Local] validProperty: 
 Helper method to see if the variable type matches the given type(s).

Parameters:
 - variable (any): The variable to check.
 - valid_type (string/table): Either a string or table of strings containing accepted types.
 
Returns: 
 true if the variable is a valid type, or false if nil or not a valid type.
]]
function l.validProperty(variable, valid_type)
	
	if variable == nil then return false end
	local variable_type = type(variable)
	
	if type(valid_type) == "string" then
		if variable_type == valid_type then
			return true
		else
			return false
		end
	end
	
	if type(valid_type) == "table" then
		for i, type_option in ipairs(valid_type) do
			if type(type_option) == "string" and variable_type == type_option then
				return true
			end
		end
	end
	
	return false
	
end

--[[
[Local] listHasMount:
 Does the unit list contain a unit with a mount?

Parameters:
 - list (table): A table of units.
 
Returns:
 true if a unit in the table requires a mount, or false if not.

]]
function l.listHasMount(list)
	
	if list == nil or type(list) ~= "table" then
		return false
	end
	
	for i, unit in ipairs(list) do
		if unit == nil then return false end
		if unit.cost == nil or type(unit.cost) ~= "table" then return false end
		if unit.cost.mount ~= nil then
			return true
		end
	end
	
	return false
	
end

--[[
[Local] listHasPorcoCost
 Does the list contain a unit with an additional Porco cost?

Parameters:
 - list (table): A table of units.
 
Returns:
 true if table has a unit that has an additional porco cost, or false if not.
]]
function l.listHasPorcoCost(list)
	
	if list == nil or type(list) ~= "table" then
		return false
	end
	
	for i, unit in ipairs(list) do
		if unit == nil then return false end
		if unit.cost == nil or type(unit.cost) ~= "table" then return false end
		if unit.cost.porcos ~= nil and type(unit.cost.porcos) == "number" and unit.cost.porcos > 1 then
			return true
		end
	end
	
	return false
	
end

--[[
[Local] _resourceCount: 
 Helper method to get the number of resources needed to create the given unit.

Parameters: 
 - cost_item (string/table): Table representing a unit.
 
Returns:
 An integer value that contains the number of resource types needed.
]]
function l._resourceCount(cost_item)
	
	if cost_item == nil then
		return 0
	else
		if type(cost_item) == "string" then
			return 1
		elseif type(cost_item) == "table" then
			local count = 0
			for i,j in ipairs(cost_item) do 
				count = count + 1 
			end
			return count
		else
			return 0
		end
	end
	
end

--[[
[Local] resource_block_list:
 Helper method that generates a block of <div>'s for each resource given.

Parameters:
 - resource (string/table): 
     Either a string for a single resource, or a table of strings/tables.
     
 - properties (table): A table of properties passed to resource_html.resource_block.
 
Returns:
 A string of HTML code for the resource/resources, or an empty string if resource is nil.

]]
function l.resource_block_list(resource, properties)
	if resource == nil then return "" end
	if type(resource) == "table" then
		local return_string = ""
		for i, p_res in ipairs(resource) do
			return_string = return_string .. resource_html.resource_block(p_res, properties)
		end
		return return_string
	else
		return resource_html.resource_block(resource, properties)
	end
end

--[[
[Local] listResourceStats: 
 Max number of resources for a unit the unit list contains.
 
Parameters:
 - list (table): A table of units.
 
Returns: 
 A table that specifies if there's an additional porco cost, if a unit requires
 a mount, and the maximum number of resources needed for a single unit.
]]
function l.listResourceStats(list)
	
	if list == nil or type(list) ~= "table" then
		return {
			has_porco_cost = false,
			has_mount = false,
			max_resource_count = 0
		}
	end
	
	local has_porco_cost = false
	local has_mount = false
	local max_resource_count = 0
	
	for i, unit in ipairs(list) do
		local unit_resource_count = 0
		if unit ~= nil then
			if unit.cost ~= nil and type(unit.cost) == "table" then 
				if unit.cost.porcos ~= nil and type(unit.cost.porcos) == "number" then
					unit_resource_count = unit_resource_count + 1
					has_porco_cost = true
				end
				if unit.cost.mount ~= nil then
					has_mount = true
					unit_resource_count = unit_resource_count + 1
				end
				unit_resource_count = unit_resource_count + l._resourceCount(unit.cost.hand)
				unit_resource_count = unit_resource_count + l._resourceCount(unit.cost.offhand)
				unit_resource_count = unit_resource_count + l._resourceCount(unit.cost.chest)
				unit_resource_count = unit_resource_count + l._resourceCount(unit.cost.head)
			end
			if unit_resource_count > max_resource_count then
				max_resource_count = unit_resource_count
			end
	    end
	end
	
	return {
		has_porco_cost = has_porco_cost,
		has_mount = has_mount,
		max_resource_count = max_resource_count
	}
	
end

--[[
[Local] tooltipBlock:
 Helper method to generate HTML code to show a tooltip.
 
Parameters:
 - content (string): String of HTML the tooltip is wrapped around.
 - tooltip (table): Table containing the tooltip to show.
 
Returns:
 A string of HTML containing the tooltip, or the given content if the tooltip
 is nil or not a table.
]]
function l.tooltipBlock(content, tooltip)
	if tooltip == nil or type(tooltip) ~= "table" then
		return content
	end
	return '<div class="advanced-tooltip">' .. content .. '<div style="display:none;"><div class="tooltip-contents"><strong>' .. tooltip[1] .. '</strong><div style="width: 300px; padding: 8px;">' .. tooltip[2] .. '</div></div></div></div>'
end

--[[
[Local] splitString:
 Splits a string.
 
Parameters:
 - input_string (string): The string to split.
 - sep (string [optional]): The separator to use, details to ' ' if not set.
 
Returns:
 The string split, or nil if input_string is not a string.
]]
function l.splitString(input_string, sep)
	if type(input_string) ~= "string" then
		return nil
	end
	if sep == nil then
		sep = "%s"
	end
	local matches = {}
	for str in mw.ustring.gmatch(input_string, "([^"..sep.."]+)") do
		table.insert(matches, str)
	end
	return matches
end

--[[
[Local] splitKeyValueString:
 Splits a key/value string into a table that repesents the string.
 Splits on commans first (,), and then on colons (:).
 
Parameters:
 - input_string (string): The string to split.
 
Returns:
 A table of key value pairs.
]]
function l.splitKeyValueString(input_string)
	local matches = l.splitString(input_string, ",")
	local return_values = {}
	for i, m in ipairs(matches) do
		local split_values = l.splitString(m, ":")
		table.insert(return_values, {
			key = split_values[1],
			value = split_values[2]
		})
	end
	return return_values
end

--[[
[Local] unitMatchesFilter:
 Checks if the given unit matches any of the attributes listed on the filter_list.
 
Parameters:
 - unit (table): A table representing a unit.
 - filter_list: A table of key/value pairs.
 
Returns:
 true if *any* item in the filter list matches, or false if not.
 If the filter list is nil or not a table, true is returned.
]]
function l.unitMatchesFilter(unit, filter_list)
	
	if filter_list == nil or type(filter_list) ~= "table" then
		return true
	end
	
	for i, filter in ipairs(filter_list) do
		if unit[filter["key"]] == nil then
			return false
		else
			if type(unit[filter["key"]]) == "table" then
				local matched = false
				for i, o in ipairs(unit[filter["key"]]) do
					if o == filter["value"] then
						matched = true
					end
				end
				return matched
			else
				if unit[filter["key"]] ~= filter["value"] then
					return false
				end
			end
		end
	end
	
	return true	
	
end

--[[
[Local] getMatchingUnits:
 Gets all units that match the filter passed.
 
Parameters;
 - filter (string): A string of key/value pairs that are used to filter the list,
    expected format is 'key:value,key:value,...'.
    
Returns:
 A table of units that match the given filter.
]]
function l.getMatchingUnits(filter)
	
	if filter == nil or type(filter) ~= "string" then
		return data
	end
	
	local filter_attributes = nil
	local matched_units = {}
	
	filter_attributes = l.splitKeyValueString(filter)
	
	for i, unitRow in ipairs(data) do
		if l.unitMatchesFilter(unitRow, filter_attributes) == true then
			table.insert(matched_units, unitRow)
		end
	end
	
	return matched_units
	
end

--[[
[Local] tableTd:
 Helper function that generates a <td> element.
 
Parameters:
 - text (string): String containing WikiText to be placed in the <td> tag.
 - properties (table [optional]): Optional list of properties that can be used
    to alter certain properties in the element.
    
Returns:
 A string containing a <td> element.
]]
function l.tableTd(text, properties)
	local element = mw.html.create("td"):wikitext(text)
	if properties ~= nil and type(properties) == "table" then
		if properties.min_size == true then
			element:addClass("cell-min")
		end
	end
	return tostring(element)
end

--[[
[Local] _tableResourceCostInner
 Function that generates a <td> tag for a resource.
 Used for _tableResourceCost function.
 
Parameters:
 - row (mw.html): Represents a mw.html object, in this case the <tr> for the row.
 - resource (string/table): Either a string or a table representing a resource.

Returns:
 Nil. Function adds data to the passed row object.
]]
function l._tableResourceCostInner(row, resource)
	if resource == nil then return "<td></td>" end
	local qty = nil
	if type(resource) == "table" then
		qty = resource[2]
		resource = resource[1]
	end
	local r_type = resources.getResource(resource)
	if r_type == nil then
		row:node("<td>Unknown resource '" .. resource .. "'</td>")
	else
		if qty == nil or (type(qty) == "number" and qty > 1) == false then
			row:node("<td><div>[[File:" .. r_type.icon .. "|x32px|link=" .. r_type.link .. "]]</div><div>[[" .. r_type.link .. "|" .. r_type.name .. "]]</div></td>")
		else
			row:node('<td><div class="cost-multi"><div>[[File:' .. r_type.icon .. '|x32px|link=' .. r_type.link .. ']]</div><div>x' .. qty .. '</div></div><div>[[' .. r_type.link .. '|' .. r_type.name .. ']]</div></td>')
		end
	end	
end

--[[
[Local] _tableResourceCost
 Function that generates resource costs, used by tableResourceCost function.

Parameters:
 - row (mw.html): Represents a mw.html object, in this case the <tr> for the row.
 - resource (string/table): Either a string or a table representing a resource.

Returns:
 A number indicating how many resources it appended to the row object.
]]
function l._tableResourceCost(row, resource)
	if resource == nil then return 0 end
	if type(resource) == "table" then
		local count = 0
		for i, p_res in ipairs(resource) do
			l._tableResourceCostInner(row, p_res)
			count = count + 1
		end
		return count
	else
		l._tableResourceCostInner(row, resource)
		return 1
	end
end

--[[
[Local] tableResourceCost
 Function that generates HTML code for showing a units resource cost.
 
Parameters:
 - row (mw.html): A mw.html object that contains a <tr> element.
 - cost: The cost requirements of the unit.
 - num_cols: Max number of columns in the table, used to pad the row out if 
    current unit uses less.
 - has_porco_cost: If true, function will show number of required porcos.
 - has_mount_cost: If true, function will show mount cost.
 
Returns:
 Nil, data is appended to passed row object.
]]
function l.tableResourceCost(row, cost, num_cols, has_porco_cost, has_mount_cost)
	-- Expected values: Either a table of strings/tables, or a string.
	-- strings: Shorthand for { resource, 1 }
	-- table: { "resource", { "resource", 2 } }
	local cols_added = 0
	if has_porco_cost then
		if cost.porcos == nil then
			row:node("<td><div>[[File:Unit_porco.png|x32px|link=]]</div><div>Porco</div></td>")
		else
			if type(cost.porcos) == "number" and cost.porcos <= 1 then
				row:node("<td><div>[[File:Unit_porco.png|x32px|link=]]</div><div>Porco</div></td>")
			else
				row:node('<td><div class="cost-multi"><div>[[File:Unit_porco.png|x32px|link=]]</div><div>x' .. cost.porcos .. '</div></div><div>Porco</div></td>')
			end
		end
		cols_added = 1
	end
	if has_mount_cost then
		cols_added = cols_added + 1
		if cost.mount ~= nil then
			l._tableResourceCost(row, cost.mount)
		else
			row:node('<td class="text-muted">n/a</td>')
		end
	end
	cols_added = cols_added + l._tableResourceCost(row, cost.hand)
	cols_added = cols_added + l._tableResourceCost(row, cost.offhand)
	cols_added = cols_added + l._tableResourceCost(row, cost.chest)
	cols_added = cols_added + l._tableResourceCost(row, cost.head)
	if cols_added < num_cols then
		row:node('<td colspan="' .. (num_cols-cols_added) .. '"></td>')
	end
end

--[[
[Local] professionGet
 Helper function to show a units 'profession'.
 
Parameters:
 - p_type (string): A string containing the 'profession type'.
 
Returns:
 A string contianing Wikitext.
]]
function l.professionGet(p_type)
	-- FIXME: This should probably be handled differently..
	-- e.g. Abstracted so that the data below is stored in something
	-- like a table to allow for easier editing..
	if p_type == "settler" then return "<span>Settler</span>" end
	if p_type == "basic" then return "<span>Basic Unit</span>" end
	if p_type == "attacker" then return "[[File:Custom_Sword_Icon.png|link=]]" end
	if p_type == "defender" then return "[[File:Custom_Guard_Icon.png|link=]]" end
	if p_type == "ranged" then return "[[File:Resized_Ranged_Icon.png|link=]]" end
	if p_type == "mounted" then return "[[File:Resized_Mounted_Icon.png|link=]]" end
	return nil	
end

--[[ 
[Local] unitProfession
 Helper method that generates a <div> block for the unit infobox showing what 
 the type of unit is.
 
Parameters:
 - profession (string): A string containing the type of profession.
 - properties (table): A table containing properties to alter the element
    generated. Currently not used.
    
Returns:
 A string containing a <div> element that shows the units profession.
]]
function l.unitProfession(profession, properties)
	-- Exepcted values: table of strings, or a singular string.
	if profession == nil then return "" end
	local return_str = ""
	if type(profession) == "table" then
		for i, p_type in ipairs(profession) do
			local p_val = l.professionGet(p_type)
			if p_val ~= nil then
				return_str = return_str .. p_val
			end
		end
	else
		local p_val = l.professionGet(profession)
		if p_val ~= nil then
			return_str = p_val
		end
	end
	return '<div>' .. return_str .. '</div>'
end

--[[
[Local] tableUnitProfession
 Helper method that generates a <td> block for the units table.
 
Parameters:
 - profession (string): A string containing the type of profession.
 - properties (table): A table containing properties to alter the element
    generated.
    
Returns:
 A string containing a <td> element that shows the units profession.
]]
function l.tableUnitProfession(profession, properties)
	-- Exepcted values: table of strings, or a singular string.
	if profession == nil then return "<td></td>" end
	local return_str = ""
	if type(profession) == "table" then
		for i, p_type in ipairs(profession) do
			local p_val = l.professionGet(p_type)
			if p_val ~= nil then
				return_str = return_str .. p_val
			end
		end
	else
		local p_val = l.professionGet(profession)
		if p_val ~= nil then
			return_str = p_val
		end
	end
	return l.tableTd(return_str, properties)
end

--[[
[Local] unitStatsTableRow
 Helper function to generate a <tr> element for unit stats. Used by displayUnitStatsTable.
 
Parameters:
 - html_table (mw.html): A mw_html object that represents the <table>.
 - unit (table): A table representing a unit to be shown.
 
Returns:
 Nil, data is appeneded to the html_table object.
]]
function l.unitStatsTableRow(html_table, unit)
	local row = mw.html.create("tr"):addClass("unit-row")
	
	if unit.page ~= nil then
		row:node(l.tableTd('[[' .. unit.page .. '|' .. (unit.name or "?") .. ']]'))
	else
		row:node(l.tableTd(unit.name or "?"))
	end
	
	row:node(l.tableTd(unit.strength or "?"))
	row:node(l.tableTd(unit.toughness or "?"))
	row:node(l.tableTd(unit.armor or "?"))
	
	row:node(l.tableTd(unit.attack_skill or "?"))
	row:node(l.tableTd(unit.defense_skill or "?"))
	row:node(l.tableTd(unit.melee_damage or "?"))
	row:node(l.tableTd(unit.attack_speed or "?"))
	row:node(l.tableTd(unit.reach or "?"))
	
	if unit.ranged_skill == nil then
		row:node('<td colspan="4" data-sort-value="0" class="text-muted">n/a</td>')
	else
		row:node(l.tableTd(unit.ranged_skill or "?"))
		row:node(l.tableTd(unit.ranged_damage or "?"))
		row:node(l.tableTd(unit.ranged_speed or "?"))
		row:node(l.tableTd(unit.range or "?"))
	end
	
	row:node(l.tableTd(unit.movement or "?"))
	row:node(l.tableTd(unit.mass or "?"))
	row:node(l.tableTd(unit.carry_capacity or "?"))
	
	html_table:node(tostring(row))
end

--[[

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

                        Exported Functions Below

 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 
]]

--[[
[Exported] displayUnitsTable
 Generates HTML code for showing a <table> containing data for a given set of units.
 Specifically, this shows the unit, its "profession", and resource cost.
 
 This function is expected to be invoked directly from a wiki page, so its only
 parameter is the frame object.

Parameters:
 - frame (Frame): The Frame object for the page.
 
Frame Parameters:
 - filter (string [optional]): A string containing key/value pairs to filter the table with.
    If this is not set, all units listed in the Units/Data module will be shown.
    
Returns:
 A string containing a <table> object showing the units requested.
]]
function p.displayUnitsTable(frame)

	local html = mw.html.create("table"):addClass("wikitable"):addClass("unit-table")
	
	local filter = frame.args["filter"]
	local units = l.getMatchingUnits(filter)
	local units_stats = l.listResourceStats(units)
	
	if has_proco_count then
		cost_cols = cost_cols + 1
	end
	if has_mount_cost then
		cost_cols = cost_cols + 1
	end
	
	-- Heading
	local heading_1 = mw.html.create("tr")
	heading_1:node('<th colspan="2" rowspan="2">Unit</th>')
	heading_1:node('<th rowspan="2">Type</th>')
	heading_1:node('<th colspan="' .. tostring(units_stats.max_resource_count + 2) .. '">Cost Per Unit</th>')
	html:node(tostring(heading_1))
		
	local heading_2 = mw.html.create("tr")
	if units_stats.max_resource_count > 0 then
		heading_2:node('<th colspan="' .. tostring(units_stats.max_resource_count) .. '">Resource(s)</th>')
	end
	heading_2:node('<th class="cell-min">Cost</th>')
	heading_2:node('<th class="cell-min">Upkeep</th>')
	html:node(tostring(heading_2))
	
	for i, unit in ipairs(units) do
		local row = mw.html.create("tr"):addClass("unit-row")
		
		local portrait_image = unit.portrait or "Icon_unit_empty_big.png"
		local portrait = mw.html.create("td"):addClass("unit-icon"):wikitext("[[File:"..portrait_image.."|x64px|link=]]")
		row:node(tostring(portrait))
		
		if unit.page ~= nil then
			row:node(l.tableTd('[[' .. unit.page .. '|' .. (unit.name or "?") .. ']]', {min_size=true}))
		else
			row:node(l.tableTd(unit.name or "", {min_size=true}))
		end
		
		row:node(l.tableUnitProfession(unit.profession or "", {min_size=true}))
		if unit.recruitable ~= nil and unit.recruitable == false then
			row:node('<td class="text-muted" colspan="' .. tostring(units_stats.max_resource_count + 1) .. '">Not Recruitable</td>')
		else
			if unit.cost ~= nil and type(unit.cost) == "table" then
				l.tableResourceCost(row, unit.cost, units_stats.max_resource_count, units_stats.has_porco_cost, units_stats.has_mount)
				row:node(l.tableTd(unit.cost.coins or "-", {min_size=true}))
			else
				row:node('<td colspan="' .. tostring(units_stats.max_resource_count + 1) .. '"></td>')
			end
		end
		row:node(l.tableTd(unit.upkeep or "", {min_size=true}))
		
		html:node(tostring(row))
	end
	
	return tostring(html)
end

--[[
[Exported] displayUnitStatsTable
 Generates HTML code for showing a <table> containing data for a given set of units.
 Specifically, the unit, and their combat stats.
 
 This function is expected to be invoked directly from a wiki page, so its only
 parameter is the frame object.

Parameters:
 - frame (Frame): The Frame object for the page.
 
Frame Parameters:
 - filter (string [optional]): A string containing key/value pairs to filter the table with.
    If this is not set, all units listed in the Units/Data module will be shown.
    
Returns:
 A string containing a <table> object showing the units requested.
]]
function p.displayUnitStatsTable(frame)
	local filter = frame.args["filter"]
	
	local html = mw.html.create("table"):addClass("wikitable"):addClass("sortable"):addClass("unit-stats-table")
	
	local units = l.getMatchingUnits(filter)
	
	-- Heading
	local heading = mw.html.create("tr")
	heading:node('<th>Unit Name</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_strength.png|link=]]', stat_tooltips.strength) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_toughness.png|link=]]', stat_tooltips.toughness) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_armor.png|link=]]', stat_tooltips.armor) .. '</th>')
	heading:node('<th colspan="2">' .. l.tooltipBlock('[[File:Icon_WS.png|link=]]', stat_tooltips.melee_skill) .. '</th>')
	heading:node('<th colspan="2">' .. l.tooltipBlock('[[File:Icon_damage.png|link=]]', stat_tooltips.melee_damage) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_reach.png|link=]]', stat_tooltips.melee_reach) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_BS.png|link=]]', stat_tooltips.ranged_skill) .. '</th>')
	heading:node('<th colspan="2">' .. l.tooltipBlock('[[File:Icon_damage.png|link=]]', stat_tooltips.ranged_damage) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_range.png|link=]]', stat_tooltips.ranged_range) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_movement.png|link=]]', stat_tooltips.movement) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_mass.png|link=]]', stat_tooltips.mass) .. '</th>')
	heading:node('<th>' .. l.tooltipBlock('[[File:Icon_carry.png|link=]]', stat_tooltips.carrying_capacity) .. '</th>')
	
	html:node(tostring(heading))
	
	local heading_2 = mw.html.create("tr"):addClass("heading-sort")
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	heading_2:node('<th></th>')
	
	html:node(tostring(heading_2))
	
	for i, unit in ipairs(units) do
		l.unitStatsTableRow(html, unit)
	end
	
	return tostring(html)
end

--[[
[Exported] displayUnitInfoBox
 Shows a infobox representing a unit.
 
 This function is expected to be invoked directly from a wiki page, so its only
 parameter is the frame object.
 
Parameters:
 - frame (Frame): The Frame object for the page.
 
Frame Arguments:
 - id (string): The unit id to be shown, this must be set, or an error will be 
    shown. If an invalid unit is requested, an error will be shown instead.
 
Returns:
 A string containing an infobox for the requested unit.
]]
function p.displayUnitInfobox(frame)
	
	if frame == nil or type(frame) ~= "table" or frame.args == nil or type(frame.args) ~= "table" then
		return ""
	end
	
	local unit_id = frame.args["id"]
	
	if unit_id == nil then
		return mw.html.create("div"):addClass("infobox"):addClass("infobox-unit"):addClass("invalid"):wikitext("No id passed to displayUnitInfobox invoke.")
	end
	
	local unit = l.getUnitById(unit_id)
	
	if unit == nil then
		return mw.html.create("div"):addClass("infobox"):addClass("infobox-unit"):addClass("invalid"):wikitext("Invalid unit_id '" .. unit_id .. "'")
	end
	
	local infobox = mw.html.create("div"):addClass("infobox"):addClass("infobox-unit")
	
	local infobox_header = mw.html.create("div"):addClass("title"):addClass("infoboxname"):addClass("flex"):wikitext('<div class="flex-grow-1">' .. unit.name .. '</div>' .. l.unitProfession(unit.profession))
	infobox:node(infobox_header)
	
	local infobox_content_flex = mw.html.create("div"):addClass("flex"):addClass("flex-horizontal"):addClass("main-stats")
	
	local infobox_image = mw.html.create("div"):addClass("picture"):wikitext('[[File:' .. (unit.portrait or "Icon_unit_empty_big.png")  .. ']]')
	infobox_content_flex:node(infobox_image)
	
	local infobox_content_stats = mw.html.create("div"):addClass("unit-stats"):addClass("flex-grow-1")
	
	local key_stats = mw.html.create("div"):addClass("flex"):addClass("flex-horizontal"):addClass("key-stats")

	key_stats:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_strength.png|x24px|link=]]', stat_tooltips.strength) .. '<span>'.. unit.strength .. '</span><div class="text-muted">Strength</div></div>')
	key_stats:node('<div class="no-wrap stat">[[File:Icon_upkeep.png|x24px|link=|title=Cost]]<span>' .. (unit.upkeep or "?") .. '</span><div class="text-muted">Upkeep</div></div>')
	
	infobox_content_stats:node(key_stats)
	
	local stats_1 = mw.html.create("div"):addClass("flex"):addClass("flex-horizontal")
	
	stats_1:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_toughness.png|link=|Upkeep]]', stat_tooltips.toughness) .. '<span>'.. unit.toughness .. '</span><div class="text-muted">Toughness</div></div>')
	stats_1:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_armor.png|link=]]', stat_tooltips.armor) .. '<span>'.. unit.armor .. '</span><div class="text-muted">Armor</div></div>')
	
	infobox_content_stats:node(stats_1)
	
	local stats_weapons = mw.html.create("div"):addClass("flex"):addClass("flex-horizontal"):addClass("stats-weapons")
	local stats_melee = mw.html.create("div"):addClass("combat-melee"):addClass("flex-grow-1")
	local stats_melee_inner = mw.html.create("div"):addClass("stats-list")
	stats_melee:node(infobox_html.title("Melee"))
	if l.validProperty(unit.attack_skill, "number") then
		stats_melee_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_WS.png|link=]]', stat_tooltips.melee_skill) .. '<span>'.. unit.attack_skill .. '-' .. unit.defense_skill .. '</span></div>')
		stats_melee_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_damage.png|link=]]', stat_tooltips.melee_damage) .. '<span>'.. unit.melee_damage .. '(x' .. unit.attack_speed .. ')</span></div>')
		stats_melee_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_reach.png|link=]]', stat_tooltips.melee_reach) .. '<span>'.. unit.reach .. '</span></div>')
	else
		stats_melee_inner:node('<div class="text-muted">n/a</div>')
	end
	stats_melee:node(stats_melee_inner)
	stats_weapons:node(stats_melee)
	
	local stats_ranged = mw.html.create("div"):addClass("combat-ranged"):addClass("flex-grow-1")
	local stats_ranged_inner = mw.html.create("div"):addClass("stats-list")
	stats_ranged:node(infobox_html.title("Ranged"))
	if l.validProperty(unit.ranged_skill, "number") then
		stats_ranged_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_BS.png|link=]]', stat_tooltips.ranged_skill) .. '<span>'.. unit.ranged_skill .. '</span></div>')
		stats_ranged_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_damage.png|link=]]', stat_tooltips.ranged_damage) .. '<span>'.. unit.ranged_damage .. '(x' .. unit.ranged_speed .. ')</span></div>')
		stats_ranged_inner:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_range.png|link=]]', stat_tooltips.ranged_range) .. '<span>'.. unit.range .. '</span></div>')
	else
		stats_ranged_inner:node('<div class="text-muted">n/a</div>')
	end
	stats_ranged:node(stats_ranged_inner)
	stats_weapons:node(stats_ranged)
	
	
	local stats_2 = mw.html.create("div"):addClass("flex"):addClass("flex-horizontal"):addClass("other-stats")
	stats_2:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_movement.png|link=]]', stat_tooltips.movement) .. '<span>'.. unit.movement .. '</span></div>')
	stats_2:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_mass.png|link=]]', stat_tooltips.mass) .. '<span>'.. unit.mass .. '</span></div>')
	stats_2:node('<div class="no-wrap stat">' .. l.tooltipBlock('[[File:Icon_carry.png|link=]]', stat_tooltips.carrying_capacity) .. '<span>'.. unit.carry_capacity .. '</span></div>')
	
	infobox_content_flex:node(infobox_content_stats)
	
	infobox:node(infobox_content_flex)
	infobox:node(stats_weapons)
	infobox:node(infobox_html.title("Other Stats"))
	infobox:node(stats_2)
	
	if unit.cost ~= nil and type(unit.cost) == "table" then
	
		local cost_element = mw.html.create("div"):addClass("cost-list")
		
		cost_element:node('<div class="resource resource-block"><div class="cost-multi"><div>[[File:Unit_porco.png|x32px|link=|title=Porcos]]</div><div>x' .. (unit.cost.porcos or 1) .. '</div></div><div class="text-muted">Porcos</div></div>')
		
		cost_element:node('<div class="resource resource-block"><div class="cost-multi"><div>[[File:Icon_coin.png|x24px|link=|title=Upkeep]]</div><div>' .. (unit.cost.coins or "?") .. '</div></div><div class="text-muted">Coins</div></div>')
		
		if unit.cost.mount ~= nil then
			has_cost = true
			cost_element:node(l.resource_block_list(unit.cost.mount, {hide_name=false}))
		end
		
		if unit.cost.hand then
			has_cost = true
			cost_element:node(l.resource_block_list(unit.cost.hand, {hide_name=false}))
		end
		
		if unit.cost.offhand then
			has_cost = true
			cost_element:node(l.resource_block_list(unit.cost.offhand, {hide_name=false}))
		end
		
		if unit.cost.chest then
			has_cost = true
			cost_element:node(l.resource_block_list(unit.cost.chest, {hide_name=false}))
		end
		
		if unit.cost.head then
			has_cost = true
			cost_element:node(l.resource_block_list(unit.cost.head, {hide_name=false}))
		end
		
		infobox:node(infobox_html.title("Cost"))
		infobox:node(cost_element)
	
	end
	
	return tostring(infobox)
	
end

return p