Module:Skills/Cartography: Difference between revisions

From Melvor Idle
(Reference the Melvor world map by ID rather than by a numerical index - the order of maps within SkillData.Cartography.worldMaps cannot be guaranteed)
(Add support for 'Items Worn' POI requirements)
 
(10 intermediate revisions by 4 users not shown)
Line 1: Line 1:
--New module for Cartography-related tables
--Unavoidably has some overlap with Module:Skills/Archaeology
--Crucially, stuff that involves both MUST go in Module:Skills/Archaeology
--Because otherwise we get circular references, which are Not Fun.
local p = {}
local p = {}


local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Constants = require('Module:Constants')
local GameData = require('Module:GameData')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Items = require('Module:Items')
local Items = require('Module:Items')
local Common = require('Module:Common')
local Common = require('Module:Common')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Num = require('Module:Number')


local MelvorMap = GameData.getEntityByID(SkillData.Cartography.worldMaps, 'melvorAoD:Melvor')
p.MelvorMap = GameData.getEntityByID(SkillData.Cartography.worldMaps, 'melvorAoD:Melvor')


function p.getPointOfInterestByID(id)
function p.getPointOfInterestByID(id)
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if POI.id == id then
if POI.id == id then
return POI
return POI
Line 26: Line 31:
return nil
return nil
end
end
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if (POI.coords.q == hex.coordinates.q and
if (POI.coords.q == hex.coordinates.q and
    POI.coords.r == hex.coordinates.r) then
    POI.coords.r == hex.coordinates.r) then
Line 36: Line 41:


function p.getPointOfInterestForDigSite(digSiteID)
function p.getPointOfInterestForDigSite(digSiteID)
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if POI.digSiteID ~= nil and POI.digSiteID == digSiteID then
if POI.digSiteID ~= nil and POI.digSiteID == digSiteID then
return POI
return POI
Line 47: Line 52:
name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "%%27", "'")
name = string.gsub(name, "'", "'")
name = string.gsub(name, "'", "'")
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if POI.name == name then
if POI.name == name then
return POI
return POI
Line 57: Line 62:
function p.getPointsOfInterest(checkFunc)
function p.getPointsOfInterest(checkFunc)
local result = {}
local result = {}
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if checkFunc(POI) then
if checkFunc(POI) then
table.insert(result, POI)
table.insert(result, POI)
end
end
return result
end
function p.getMasteryBonuses(checkFunc)
local result = {}
for i, bonus in pairs(p.MelvorMap.masteryBonuses) do
if checkFunc(bonus) then
table.insert(result, bonus)
end
end
end
end
Line 72: Line 87:
end
end
for i, hex in pairs(MelvorMap.hexes) do
for i, hex in pairs(p.MelvorMap.hexes) do
local coords = hex.coordinates
local coords = hex.coordinates
if coords.q == Q and coords.r == R then
if coords.q == Q and coords.r == R then
Line 111: Line 126:
local Hex = p.getHexByAxial(POI.coords)
local Hex = p.getHexByAxial(POI.coords)
local reqTable = {}
local reqTable = {}
local HexReqs = Common.getRequirementString(Hex.requirements, '')
local HexReqs = (Hex ~= nil and Common.getRequirementString(Hex.requirements, '')) or ''
if HexReqs ~= '' then
if HexReqs ~= '' then
table.insert(reqTable, HexReqs)
table.insert(reqTable, HexReqs)
Line 120: Line 135:
table.insert(reqTable, POIReqs)
table.insert(reqTable, POIReqs)
end
end
end
if POI.hidden.itemsWorn ~= nil and not Shared.tableIsEmpty(POI.hidden.itemsWorn) then
return reqTable
local itemsWornStr = 'Items Worn: '
end
for i, itemID in ipairs(POI.hidden.itemsWorn) do
 
local item = Items.getItemByID(itemID)
function p._getDiscoveryRewards(POI)
if item ~= nil then
local rewardTable = {}
itemsWornStr = itemsWornStr .. Icons.Icon({item.name, type='item', notext=true})
if POI.discoveryRewards ~= nil then
end
if POI.discoveryRewards.gp ~= nil then
table.insert(rewardTable, Icons.GP(POI.discoveryRewards.gp))
end
if POI.discoveryRewards.sc ~= nil then
table.insert(rewardTable, Icons.SC(POI.discoveryRewards.sc))
end
if POI.discoveryRewards.items ~= nil then
for j, reward in pairs(POI.discoveryRewards.items) do
local item = Items.getItemByID(reward.id)
local qty = reward.quantity
table.insert(rewardTable, Icons.Icon({item.name, type='item', qty = qty}))
end
end
table.insert(reqTable, itemsWornStr)
end
end
end
end
return rewardTable
return reqTable
end
end


Line 165: Line 170:
:tag('th'):wikitext('Rewards')
:tag('th'):wikitext('Rewards')
local masteryBonuses = Shared.shallowClone(MelvorMap.masteryBonuses)
local masteryBonuses = Shared.shallowClone(p.MelvorMap.masteryBonuses)
table.sort(masteryBonuses, function(a, b) return a.masteredHexes < b.masteredHexes end)
table.sort(masteryBonuses, function(a, b) return a.masteredHexes < b.masteredHexes end)
for i, bonus in ipairs(masteryBonuses) do
for i, bonus in ipairs(masteryBonuses) do
Line 171: Line 176:
local modText, rewardTextPart = nil, {}
local modText, rewardTextPart = nil, {}
if bonus.modifiers ~= nil and not Shared.tableIsEmpty(bonus.modifiers) then
if bonus.modifiers ~= nil and not Shared.tableIsEmpty(bonus.modifiers) then
modText = Constants.getModifiersText(bonus.modifiers, true, false)
modText = Modifiers.getModifiersText(bonus.modifiers, true, false)
end
end
if bonus.items ~= nil then
if bonus.items ~= nil then
Line 193: Line 198:
local row = mw.html.create('tr')
local row = mw.html.create('tr')
row:tag('td'):wikitext(Shared.formatnum(bonus.masteredHexes))
row:tag('td'):wikitext(Num.formatnum(bonus.masteredHexes))
if modText == nil then
if modText == nil then
row:tag('td')
row:tag('td')
Line 214: Line 219:


function p.getPointOfInterestTable(frame)
function p.getPointOfInterestTable(frame)
local args = ((frame and frame.args ~= nil) and frame.args) or {}
local POIs = {}
local POIs = {}
local POI_Strings = {}
local POI_Strings = {}
local tableStr = {}
local tableStr = {}
local discoveryModifier = args.discoveryModifier
local activeModifier = args.activeModifier
table.insert(tableStr, '{| class="wikitable sortable lighttable"')
table.insert(tableStr, '{| class="wikitable sortable lighttable"')
table.insert(tableStr, '\r\n|-\r\n!colspan="2"|Name!!Type!!X!!Y!!Requirements!!Discovery Rewards!!Discovery Modifiers!!Active Effect')
table.insert(tableStr, '\r\n|-\r\n!colspan="2"|Name!!Type!!X!!Y!!Requirements!!Discovery Rewards!!Discovery Modifiers!!Active Effect')
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
table.insert(POIs, POI)
table.insert(POIs, POI)
end
end
Line 226: Line 235:
for i, POI in pairs(POIs) do
for i, POI in pairs(POIs) do
local X, Y = p.convertAxialToXY(POI.coords)
local meetsCondition = true
table.insert(POI_Strings, POI.name..' ('..X..', '..Y..')')
local poiDiscoveryMods = POI.discoveryModifiers
local poiActiveStats = POI.activeStats
table.insert(tableStr,'\r\n|-\r\n|')
if discoveryModifier ~= nil and discoveryModifier ~= '' then
table.insert(tableStr, Icons.Icon({POI.name, type='poi', notext='true', nolink='true', size='50'}))
if poiDiscoveryMods ~= nil then
table.insert(tableStr, '||id="'..string.gsub(POI.name,' ', '')..'"|')
local modifierText = Modifiers.getModifiersText(poiDiscoveryMods.modifiers)..' (for '..poiDiscoveryMods.moves..' travels)'
--Link Dig Sites
meetsCondition = meetsCondition and string.find(modifierText, discoveryModifier) ~= nil
if POI.type == 'DigSite' then
else
table.insert(tableStr, '[['..POI.name..']]')
meetsCondition = false
else
table.insert(tableStr, POI.name)
end
local POIType = POI.type
if POIType == 'DigSite' then
POIType = 'Dig&nbsp;Site'
else
if POI.activeModifiers ~= nil then
POIType = 'Active'
elseif POI.fastTravel ~= nil then
POIType = 'Port'
end
end
end
end
table.insert(tableStr, '||'..POIType)
table.insert(tableStr, '||'..X)
table.insert(tableStr, '||'..Y)
--Add Requirements
if activeModifier ~= nil and activeModifier ~= '' then
table.insert(tableStr, '\r\n|')
if poiActiveStats ~= nil and poiActiveStats.modifiers ~= nil then
local reqTable= p._getPOIRequirements(POI)
local modifierText = Modifiers.getModifiersText(poiActiveStats.modifiers)
table.insert(tableStr, table.concat(reqTable, '<br/>'))
meetsCondition = meetsCondition and string.find(modifierText, activeModifier) ~= nil
else
--Add Discovery Rewards
meetsCondition = false
table.insert(tableStr, '\r\n|')
end
local rewardTable = p._getDiscoveryRewards(POI)
table.insert(tableStr, table.concat(rewardTable,'<br/>'))
--Add Discovery Modifiers
table.insert(tableStr, '\r\n|')
if POI.discoveryModifiers ~= nil then
table.insert(tableStr, Constants.getModifiersText(POI.discoveryModifiers.modifiers)..' (for '..POI.discoveryModifiers.moves..' travels)')
end
end
--Add Active Modifiers
if meetsCondition then
table.insert(tableStr, '\r\n|')
local X, Y = p.convertAxialToXY(POI.coords)
if POI.activeModifiers ~= nil then
table.insert(POI_Strings, POI.name..' ('..X..', '..Y..')')
table.insert(tableStr, Constants.getModifiersText(POI.activeModifiers))
table.insert(tableStr,'\r\n|-\r\n|')
table.insert(tableStr, Icons.Icon({POI.name, type='poi', notext='true', nolink='true', size='50'}))
table.insert(tableStr, '||id="'..string.gsub(POI.name,' ', '')..'"|')
--Link Dig Sites
if POI.type == 'DigSite' then
table.insert(tableStr, '[['..POI.name..']]')
else
table.insert(tableStr, POI.name)
end
local POIType = POI.type
if POIType == 'DigSite' then
POIType = 'Dig&nbsp;Site'
else
if POI.activeStats ~= nil and not Shared.tableIsEmpty(POI.activeStats) then
POIType = 'Active'
elseif POI.fastTravel ~= nil then
POIType = 'Port'
end
end
table.insert(tableStr, '||'..POIType)
table.insert(tableStr, '||'..X)
table.insert(tableStr, '||'..Y)
--Add Requirements
table.insert(tableStr, '\r\n|')
local reqTable= p._getPOIRequirements(POI)
table.insert(tableStr, table.concat(reqTable, '<br/>'))
--Add Discovery Rewards
table.insert(tableStr, '\r\n|')
table.insert(tableStr, Common.getCostString(POI.discoveryRewards or {}, ''))
--Add Discovery Modifiers
table.insert(tableStr, '\r\n|')
if poiDiscoveryMods ~= nil then
table.insert(tableStr, Modifiers.getModifiersText(poiDiscoveryMods.modifiers)..' (for '..poiDiscoveryMods.moves..' travels)')
end
--Add Active Modifiers
table.insert(tableStr, '\r\n|')
if poiActiveStats ~= nil and poiActiveStats.modifiers ~= nil then
table.insert(tableStr, Modifiers.getModifiersText(poiActiveStats.modifiers))
end
end
end
end
end
Line 282: Line 314:
function p._getDiscoveryRewardsTable(items)
function p._getDiscoveryRewardsTable(items)
local POIs = {}
local POIs = {}
for i, POI in ipairs(MelvorMap.pointsOfInterest) do
for i, POI in ipairs(p.MelvorMap.pointsOfInterest) do
if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
for i, reward in ipairs(POI.discoveryRewards.items) do
for i, reward in ipairs(POI.discoveryRewards.items) do
Line 317: Line 349:
tr:tag('td'):wikitext(Y)
tr:tag('td'):wikitext(Y)
tr:tag('td'):wikitext(table.concat(p._getPOIRequirements(POI), '<br/>'))
tr:tag('td'):wikitext(table.concat(p._getPOIRequirements(POI), '<br/>'))
tr:tag('td'):wikitext(table.concat(p._getDiscoveryRewards(POI), '<br/>'))
tr:tag('td'):wikitext(Common.getCostString(POI.discoveryRewards or {}, ''))
resultTable:node(tr)
resultTable:node(tr)
end
end
Line 327: Line 359:
local items = {}
local items = {}
for itemName in string.gmatch(itemNames, "[^,]*") do
for itemName in string.gmatch(itemNames, "[^,]*") do
item = Items.getItem(itemName)
local item = Items.getItem(itemName)
if item ~= nil then
if item ~= nil then
table.insert(items, item)
table.insert(items, item)
Line 372: Line 404:
function p.testDiscoveryRewards()
function p.testDiscoveryRewards()
local testTable = {}
local testTable = {}
for i, POI in pairs(MelvorMap.pointsOfInterest) do
for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
local item = Items.getItemByID(POI.discoveryRewards.items[1].id)
local item = Items.getItemByID(POI.discoveryRewards.items[1].id)
Line 388: Line 420:
    -- no tile here
    -- no tile here
    else
    else
item = p.hex({args={x=x, y=y}})
local item = p.hex({args={x=x, y=y}})
table.insert(testTable, item)
table.insert(testTable, item)
    end
    end

Latest revision as of 21:04, 14 August 2024

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

--New module for Cartography-related tables
--Unavoidably has some overlap with Module:Skills/Archaeology
--Crucially, stuff that involves both MUST go in Module:Skills/Archaeology
--Because otherwise we get circular references, which are Not Fun.
local p = {}

local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local SkillData = GameData.skillData
local Modifiers = require('Module:Modifiers')
local Items = require('Module:Items')
local Common = require('Module:Common')
local Icons = require('Module:Icons')
local Num = require('Module:Number')

p.MelvorMap = GameData.getEntityByID(SkillData.Cartography.worldMaps, 'melvorAoD:Melvor')

function p.getPointOfInterestByID(id)
	
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if POI.id == id then
			return POI
		end
	end
	return nil
end

function p.getPointOfInterestByXY(X, Y)
	local hex = p.getHexByXY(X, Y)
	if hex == nil then
		return nil
	end
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if (POI.coords.q == hex.coordinates.q and
		    POI.coords.r == hex.coordinates.r) then
			return POI
		end
	end
	return nil
end

function p.getPointOfInterestForDigSite(digSiteID)
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if POI.digSiteID ~= nil and POI.digSiteID == digSiteID then
			return POI
		end
	end
	return nil
end

function p.getPointOfInterest(name)
	name = string.gsub(name, "%%27", "'")
	name = string.gsub(name, "&#39;", "'")
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if POI.name == name then
			return POI
		end
	end
	return nil
end

function p.getPointsOfInterest(checkFunc)
	local result = {}
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if checkFunc(POI) then
			table.insert(result, POI)
		end
	end
	return result
end

function p.getMasteryBonuses(checkFunc)
	local result = {}
	for i, bonus in pairs(p.MelvorMap.masteryBonuses) do
		if checkFunc(bonus) then
			table.insert(result, bonus)
		end
	end
	return result
end

function p.getHexByAxial(Q, R)
	--Assume if R is nil that a pair was sent in
	if R == nil then
		R = Q.r
		Q = Q.q
	end
	
	for i, hex in pairs(p.MelvorMap.hexes) do
		local coords = hex.coordinates
		if coords.q == Q and coords.r == R then
			return hex
		end
	end
	return nil
end

function p.getHexByXY(X, Y)
	local Q, R = p.convertXYToAxial(X, Y)
	return p.getHexByAxial(Q, R)
end

function p.convertXYToAxial(X, Y)
	--Assume that if Y is nil that a pair was sent in
	if Y == nil then
		Y = X.y
		X = X.x
	end
	local Q = X
	local R = Y - (X - (X % 2)) / 2
	return Q, R
end

function p.convertAxialToXY(Q, R)
	--Assume if r is nil that a pair was sent in
	if R == nil then
		R = Q.r
		Q = Q.q
	end
	local Y = R + (Q - (Q % 2)) / 2
	local X = Q
	return X, Y
end

function p._getPOIRequirements(POI)
	local Hex = p.getHexByAxial(POI.coords)
	local reqTable = {}
	local HexReqs = (Hex ~= nil and Common.getRequirementString(Hex.requirements, '')) or ''
	if HexReqs ~= '' then
		table.insert(reqTable, HexReqs)
	end
	if POI.hidden ~= nil then
		local POIReqs = Common.getRequirementString(POI.hidden.requirements, '')
		if POIReqs ~= '' then
			table.insert(reqTable, POIReqs)
		end
		if POI.hidden.itemsWorn ~= nil and not Shared.tableIsEmpty(POI.hidden.itemsWorn) then
			local itemsWornStr = 'Items Worn: '
			for i, itemID in ipairs(POI.hidden.itemsWorn) do
				local item = Items.getItemByID(itemID)
				if item ~= nil then
					itemsWornStr = itemsWornStr .. Icons.Icon({item.name, type='item', notext=true})
				end
			end
			table.insert(reqTable, itemsWornStr)
		end
	end
	return reqTable
end

function p.sortPOIByCoords(array)
	table.sort(array, function(a, b)
		local aX, aY = p.convertAxialToXY(a.coords)
		local bX, bY = p.convertAxialToXY(b.coords)
		if aX ~= bX then
			return aX < bX
		else
			return aY < bY
		end
	end)
end

function p.getHexMasteryBonusTable(frame)
	-- Build table header
	local resultTable = mw.html.create('table')
	resultTable:addClass('wikitable stickyHeaders')
	resultTable:tag('tr'):addClass('headerRow-0')
		:tag('th'):wikitext('Hexes Mastered')
		:tag('th'):wikitext('Passive Bonuses')
		:tag('th'):wikitext('Rewards')
	
	local masteryBonuses = Shared.shallowClone(p.MelvorMap.masteryBonuses)
	table.sort(masteryBonuses, function(a, b) return a.masteredHexes < b.masteredHexes end)
	for i, bonus in ipairs(masteryBonuses) do
		-- Populate passive bonuses & rewards cell text
		local modText, rewardTextPart = nil, {}
		if bonus.modifiers ~= nil and not Shared.tableIsEmpty(bonus.modifiers) then
			modText = Modifiers.getModifiersText(bonus.modifiers, true, false)
		end
		if bonus.items ~= nil then
			for k, itemDef in ipairs(bonus.items) do
				local item = Items.getItemByID(itemDef.id)
				if item ~= nil then
					local expIcon = Icons.getExpansionIcon(itemDef.id)
					table.insert(rewardTextPart, expIcon .. Icons.Icon({item.name, type='item', qty = itemDef.quantity}))
				end
			end
		end
		if bonus.pets ~= nil then
			for k, petID in ipairs(bonus.pets) do
				local pet = GameData.getEntityByID('pets', petID)
				if pet ~= nil then
					local expIcon = Icons.getExpansionIcon(petID)
					table.insert(rewardTextPart, expIcon .. Icons.Icon({pet.name, type='pet'}))
				end
			end
		end
		
		local row = mw.html.create('tr')
		row:tag('td'):wikitext(Num.formatnum(bonus.masteredHexes))
		if modText == nil then
			row:tag('td')
				:addClass('table-na')
				:wikitext('None')
		else
			row:tag('td'):wikitext(modText)
		end
		if Shared.tableIsEmpty(rewardTextPart) then
			row:tag('td')
				:addClass('table-na')
				:wikitext('None')
		else
			row:tag('td'):wikitext(table.concat(rewardTextPart, '\n'))
		end
		resultTable:node(row)
	end
	return tostring(resultTable)
end

function p.getPointOfInterestTable(frame)
	local args = ((frame and frame.args ~= nil) and frame.args) or {}
	local POIs = {}
	local POI_Strings = {}
	local tableStr = {}
	local discoveryModifier = args.discoveryModifier
	local activeModifier = args.activeModifier
	
	table.insert(tableStr, '{| class="wikitable sortable lighttable"')
	table.insert(tableStr, '\r\n|-\r\n!colspan="2"|Name!!Type!!X!!Y!!Requirements!!Discovery Rewards!!Discovery Modifiers!!Active Effect')
	
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		table.insert(POIs, POI)
	end
	p.sortPOIByCoords(POIs)
	
	for i, POI in pairs(POIs) do
		local meetsCondition = true
		local poiDiscoveryMods = POI.discoveryModifiers
		local poiActiveStats = POI.activeStats
		
		if discoveryModifier ~= nil and discoveryModifier ~= '' then
			if poiDiscoveryMods ~= nil then
				local modifierText = Modifiers.getModifiersText(poiDiscoveryMods.modifiers)..' (for '..poiDiscoveryMods.moves..' travels)'
				meetsCondition = meetsCondition and string.find(modifierText, discoveryModifier) ~= nil
			else
				meetsCondition = false
			end
		end
		
		if activeModifier ~= nil and activeModifier ~= '' then
			if poiActiveStats ~= nil and poiActiveStats.modifiers ~= nil then
				local modifierText = Modifiers.getModifiersText(poiActiveStats.modifiers)
				meetsCondition = meetsCondition and string.find(modifierText, activeModifier) ~= nil
			else
				meetsCondition = false
			end
		end
		
		if meetsCondition then			
			local X, Y = p.convertAxialToXY(POI.coords)
			table.insert(POI_Strings, POI.name..' ('..X..', '..Y..')')
			
			table.insert(tableStr,'\r\n|-\r\n|')
			table.insert(tableStr, Icons.Icon({POI.name, type='poi', notext='true', nolink='true', size='50'}))
			table.insert(tableStr, '||id="'..string.gsub(POI.name,' ', '')..'"|')
			--Link Dig Sites
			if POI.type == 'DigSite' then
				table.insert(tableStr, '[['..POI.name..']]')
			else
				table.insert(tableStr, POI.name)
			end
			local POIType = POI.type
			if POIType == 'DigSite' then
				POIType = 'Dig&nbsp;Site'
			else
				if POI.activeStats ~= nil and not Shared.tableIsEmpty(POI.activeStats) then
					POIType = 'Active'
				elseif POI.fastTravel ~= nil then
					POIType = 'Port'
				end
			end
			table.insert(tableStr, '||'..POIType)
			table.insert(tableStr, '||'..X)
			table.insert(tableStr, '||'..Y)
			
			--Add Requirements
			table.insert(tableStr, '\r\n|')
			local reqTable= p._getPOIRequirements(POI)
			table.insert(tableStr, table.concat(reqTable, '<br/>'))
			
			--Add Discovery Rewards
			table.insert(tableStr, '\r\n|')
			table.insert(tableStr, Common.getCostString(POI.discoveryRewards or {}, ''))
			
			--Add Discovery Modifiers
			table.insert(tableStr, '\r\n|')
			if poiDiscoveryMods ~= nil then
				table.insert(tableStr, Modifiers.getModifiersText(poiDiscoveryMods.modifiers)..' (for '..poiDiscoveryMods.moves..' travels)')
			end
			
			--Add Active Modifiers
			table.insert(tableStr, '\r\n|')
			if poiActiveStats ~= nil and poiActiveStats.modifiers ~= nil then
				table.insert(tableStr, Modifiers.getModifiersText(poiActiveStats.modifiers))
			end
		end
	end
	
	table.insert(tableStr, '\r\n|}')
	
	return table.concat(tableStr, '')
end

function p._getDiscoveryRewardsTable(items)
	local POIs = {}
	for i, POI in ipairs(p.MelvorMap.pointsOfInterest) do
		if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
			for i, reward in ipairs(POI.discoveryRewards.items) do
				for j, item in ipairs(items) do
					if reward.id == item.id then
						table.insert(POIs, POI)
						break
					end
				end
			end
		end
	end

	if Shared.tableIsEmpty(POIs) then
		return ''
	end
	p.sortPOIByCoords(POIs)

	-- Build the table
	local resultTable = mw.html.create('table')
	resultTable:addClass('wikitable sortable')
	resultTable:tag('tr'):addClass('headerRow-0')
		:tag('th'):wikitext('Point of Interest')
		:tag('th'):wikitext('X')
		:tag('th'):wikitext('Y')
		:tag('th'):wikitext('Requirements')
		:tag('th'):wikitext('Discovery Rewards')

	for _, POI in ipairs(POIs) do
		local X, Y = p.convertAxialToXY(POI.coords)
		local tr = mw.html.create('tr')
		tr:tag('td'):wikitext(Icons.Icon({POI.name, type='poi'}))
		tr:tag('td'):wikitext(X)
		tr:tag('td'):wikitext(Y)
		tr:tag('td'):wikitext(table.concat(p._getPOIRequirements(POI), '<br/>'))
		tr:tag('td'):wikitext(Common.getCostString(POI.discoveryRewards or {}, ''))
		resultTable:node(tr)
	end
	return tostring(resultTable)
end

function p.getDiscoveryRewardsTable(frame)
	local itemNames = frame.args ~= nil and frame.args[1] or frame
	local items = {}
	for itemName in string.gmatch(itemNames, "[^,]*") do
		local item = Items.getItem(itemName)
		if item ~= nil then
			table.insert(items, item)
		end
	end
	if #items == nil then
		return Shared.printError('No items found in game data for: "' .. itemNames .. '"')
	end
	local resultTable = p._getDiscoveryRewardsTable(items)
	if resultTable == '' then
		return ''
	else
		return '==POI Discovery Rewards==\n' .. resultTable
	end
end

function p.hex(frame)
	-- Default behavior, when no coords supplied
	if (frame == nil or frame.args['x'] == nil or frame.args['y'] == nil) then
		return 'Hex'
	end
	local X = tonumber(frame.args['x'])
	local Y = tonumber(frame.args['y'])
	if X == nil or Y == nil then
		return '<invalid XY format>'
	elseif X < 0 or Y < 0 or X > 31 or Y > 29 or (Y == 29 and X % 2 == 0) then
		return '<out of range (' .. X .. ',' .. Y .. ')>'
	end
	--
	local noicon = frame.args['noicon'] or false
	local coords = '<span>(' .. X .. ',' .. Y .. ')</span>'
	local hex = p.getPointOfInterestByXY(X, Y)
	-- nil means just a normal hex
	if hex == nil or hex['id'] == nil or noicon then
		return coords
	end
	-- Valid POI so consider decorations
	local notext = frame.args['notext'] or false
	local nolink = frame.args['nolink'] or false
	local poi_icon = Icons.Icon({hex['name'], type='poi', notext=notext or nil, nolink=nolink or nil, size='25'})
	return '<span>' .. poi_icon .. ' ' .. coords .. '</span>'
end

function p.testDiscoveryRewards()
	local testTable = {}
	for i, POI in pairs(p.MelvorMap.pointsOfInterest) do
		if POI.discoveryRewards ~= nil and POI.discoveryRewards.items ~= nil then
			local item = Items.getItemByID(POI.discoveryRewards.items[1].id)
			table.insert(testTable, p._getDiscoveryRewardsTable({item}))
		end
	end
	return table.concat(testTable)
end

function p.testHex()
	local testTable = {}
	for x = 0, 31 do
		for y = 0, 29 do
		    if y == 29 and x % 2 == 1 then
		    	-- no tile here
		    else
				local item = p.hex({args={x=x, y=y}})
				table.insert(testTable, item)
		    end
		end
	end
	return table.concat(testTable)
end

function p.test()
	return p.getPointOfInterestTable()
end

return p