Module:Skills/Cartography

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, "'", "'")
	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