Module:Common

From Melvor Idle
Revision as of 07:18, 10 December 2024 by Slash (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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

-- This module contains common functions which interact with or interpret game data
-- in some form. Functions here may be applicable to various aspects of the game
-- (e.g. monsters, shop, dungeons, pets, items, and so on) and are contained here
-- to avoid loops when requiring modules.

-- Any functions which have no reliance upon, or relevance to game data are better suited
-- for [[Module:Shared]].

-- This module should _never_ require other modules which rely upon [[Module:GameData]]

local p = {}

local Shared = require('Module:Shared')
local GameData = require('Module:GameData')
local Icons = require('Module:Icons')
local Num = require('Module:Number')

-- getSkillName: Given a valid namespaced skill ID, returns that skill's name
function p.getSkillName(skillID)
	local skill = GameData.getSkillData(skillID)
	if skill ~= nil then
		return skill.name
	end
end

-- getSkillID: Given a valid skill name, returns that skill's namespaced ID
function p.getSkillID(skillName)
	for i, skillData in ipairs(GameData.rawData.skillData) do
		if skillData.data.name == skillName then
			return skillData.skillID
		end
	end
end

-- getSkillRecipeKey: Given a skill ID, returns the key under which all recipes
-- for that skill reside. If the returned value is nil, then the skill has
-- no recipes (that is, the skill does not produce any items)
function p.getSkillRecipeKey(skillID)
	-- Convert skillID to local ID if not already
	local ns, localSkillID = Shared.getLocalID(skillID)
	local recipeIDs = {
		["Woodcutting"] = 'trees',
		["Fishing"] = 'fish',
		["Firemaking"] = 'logs',
		["Mining"] = 'rockData',
		["Thieving"] = 'npcs',
		["Agility"] = 'obstacles',
		["Cooking"] = 'recipes',
		["Smithing"] = 'recipes',
		["Farming"] = 'recipes',
		["Summoning"] = 'recipes',
		["Fletching"] = 'recipes',
		["Crafting"] = 'recipes',
		["Runecrafting"] = 'recipes',
		["Herblore"] = 'recipes',
		["Astrology"] = 'recipes',
		["Harvesting"] = 'veinData',
		["Township"] = 'buildings'
	}
	return recipeIDs[localSkillID]
end

-- getEquipmentSlotPage: Given a valid equipment slot ID, returns the page name for that slot's
-- equipment. If the slot is not recognized, then nil is returned
function p.getEquipmentSlotPage(equipSlot)
	if type(equipSlot) == 'string' then
		local slotLinkMap = {
			["Helmet"] = 'Helmets',
			["Platebody"] = 'Platebodies',
			["Platelegs"] = 'Platelegs',
			["Boots"] = 'Boots',
			["Gloves"] = 'Gloves',
			["Cape"] = 'Capes',
			["Amulet"] = 'Amulets',
			["Ring"] = 'Rings',
			["Gem"] = 'Gems (Equipment)',
			["Weapon"] = 'Weapons',
			["Shield"] = 'Shields',
			["Quiver"] = 'Ammunition',
			["Consumable"] = 'Consumables',
			["Passive"] = 'Combat Passive Slot',
			["Summon1"] = 'Summoning',
			["Summon2"] = 'Summoning'
		}
		return slotLinkMap[equipSlot]
	end
end

-- getEquipmentSlotLink: As with getEquipmentSlotPage(), except returns wikitext to link to the
-- relevant page.
function p.getEquipmentSlotLink(equipSlot)
	local pageName = p.getEquipmentSlotPage(equipSlot)
	if pageName ~= nil then
		return '[[' .. pageName .. '|' .. equipSlot .. ']]'
	else
		return equipSlot
	end
end

-- getPurchaseName: Given a purchase from shop data, returns the name of that purchase
function p.getPurchaseName(purch)
	if purch.customName ~= nil then
		return purch.customName
	elseif purch.contains ~= nil then
		local item = nil
		if purch.contains.items ~= nil and not Shared.tableIsEmpty(purch.contains.items) then
			item = GameData.getEntityByID('items', purch.contains.items[1].id)
		elseif purch.contains.itemCharges ~= nil and not Shared.tableIsEmpty(purch.contains.itemCharges) then
			item = GameData.getEntityByID('items', purch.contains.itemCharges.id)
		end
		if item ~= nil then
			return item.name
		end
		if purch.contains.petID ~= nil then
			local pet = GameData.getEntityByID('pets', purch.contains.petID)
			if pet ~= nil then
				return pet.name
			end
		end
	end
	return ''
end

-- getPurchaseType: Given a purchase from shop data, returns the type of that purchase (based on
-- the purchase's contents)
function p.getPurchaseType(purchase)
	if purchase.contains == nil then
		return 'Unknown'
	elseif purchase.contains.petID ~= nil then
		return 'Pet'
	elseif purchase.contains.itemCharges ~= nil then
		return 'Item'
	elseif purchase.contains.modifiers ~= nil or purchase.contains.items == nil or Shared.tableCount(purchase.contains.items) == 0 then
		return 'Upgrade'
	elseif purchase.contains.items ~= nil and Shared.tableCount(purchase.contains.items) > 1 then
		return 'Item Bundle'
	else
		return 'Item'
	end
end

-- getPurchaseIconType: Given a purchase from shop dtaa, returns the icon type to be used within
-- Icons.Icon() to retrieve the purchase's icon
function p.getPurchaseIconType(purchase)
	local purchType = p.getPurchaseType(purchase)
	if purchType == 'Item Bundle' then
		local upgBundles = {
			'melvorAoD:Summoners_Pack_I',
			'melvorAoD:Summoners_Pack_II',
			'melvorAoD:Summoners_Pack_III',
			'melvorAoD:Combat_Supply_I',
			'melvorAoD:Combat_Supply_II',
			'melvorAoD:Combat_Supply_III',
			'melvorItA:Abyssal_Resupply',
			'melvorItA:Blighted_Resupply',
			'melvorItA:Withering_Resupply'
		}
		if Shared.contains(upgBundles, purchase.id) then
			return 'upgrade'
		else
			return 'item'
		end
	else
		return string.lower(purchType)
	end
end

-- getPurchaseIcon: Accepts the same arguments as Icons.Icon(), except the first parameter is a
-- shop purchase rather than the icon/linked page name
function p.getPurchaseIcon(iconArgs)
	local purchase = iconArgs[1]
	local purchaseName = p.getPurchaseName(purchase)
	local iconType = p.getPurchaseIconType(purchase)

	-- Amend iconArgs before passing to Icons.Icon()
	iconArgs[1] = purchaseName
	iconArgs['type'] = iconType

	return Icons.Icon(iconArgs)
end

-- getCostString: Given item & currency costs for something, returns human readable wikitext
-- for those costs. If there are no costs, returns the value specified by valueIfNone instead.
-- Costs are in the format:
-- { items = { ... }, currencies = { ... } }
function p.getCostString(costs, valueIfNone, entryDecorator, entrySeparator)
	local function formatLine(text)
		if entryDecorator == nil then
			return text
		else
			return entryDecorator(text)
		end
	end
	local entrySep = entrySeparator
	if type(entrySeparator) ~= 'string' then
		entrySep = '<br>'
	end

	local costArray = {}
	if type(costs.currencies) == 'table' and not Shared.tableIsEmpty(costs.currencies) then
		for i, currCost in ipairs(costs.currencies) do
			local currID = currCost.id or currCost.currencyID or currCost.currency
			if currCost.min ~= nil then
				-- Cost is a range
				table.insert(costArray, formatLine(Icons._Currency(currID, currCost.min, currCost.max)))
			else
				table.insert(costArray, formatLine(Icons._Currency(currID, (currCost.quantity or currCost.cost))))
			end
		end
	end
	if type(costs.items) == 'table' and not Shared.tableIsEmpty(costs.items) then
		for i, itemCost in ipairs(costs.items) do
			local item = GameData.getEntityByID('items', itemCost.id)
			if item ~= nil then
				table.insert(costArray, formatLine(Icons.Icon({item.name, type='item', qty=itemCost.quantity})))
			end
		end
	end
	if Shared.tableIsEmpty(costArray) then
		return valueIfNone
	else
		return table.concat(costArray, entrySep)
	end
end

-- getRequirementString: Given requirements from something such as a shop purchase or dungeon,
-- returns human readable wikitext for those requirements. If there are no requirements, returns
-- the value specified by valueIfNone instead
function p.getRequirementString(reqs, valueIfNone)
	if reqs == nil or Shared.tableIsEmpty(reqs) then
		return valueIfNone
	end

	local reqArray = {}
	for i, req in ipairs(reqs) do
		if req.type == 'AbyssalLevel' then
			local skillName = p.getSkillName(req.skillID)
			if skillName ~= nil then
				table.insert(reqArray, Icons._SkillReq(skillName, req.level, nil, 'melvorItA:Abyssal'))
			end
		elseif req.type == 'AbyssDepthCompletion' then
			local depth = GameData.getEntityByID('abyssDepths', req.depthID)
			if depth ~= nil then
				local depthStr = 'Complete ' .. Icons.Icon({depth.name, type='combatArea'})
				if req.count > 1 then
					depthStr = depthStr .. ' ' .. Num.formatnum(req.count) .. ' times'
				end
				table.insert(reqArray, depthStr)
			end
		elseif req.type == 'AllSkillLevels' then
			local reqText = 'Level ' .. req.level .. ' in all skills'
			if req.exceptions ~= nil and not Shared.tableIsEmpty(req.exceptions) then
				local exceptSkills = {}
				for i, skillID in ipairs(req.exceptions) do
					local skillName = p.getSkillName(skillID)
					if skillName ~= nil then
						table.insert(exceptSkills, Icons.Icon({skillName, type='skill'}))
					end
				end
				reqText = reqText .. ' except for ' .. table.concat(exceptSkills, ', ')
			end
			table.insert(reqArray, reqText)
		elseif req.type == 'ArchaeologyItemsDonated' then
			table.insert(reqArray, 'Donate ' .. Num.formatnum(req.count) .. ' Artefacts to the [[Museum]]')
		elseif req.type == 'CartographyPOIDiscovery' then
			local map = GameData.getEntityByID(GameData.skillData.Cartography.worldMaps, req.worldMapID)
			if map ~= nil then
				local poiPart = {}
				for j, poiID in ipairs(req.poiIDs) do
					local poi = GameData.getEntityByID(map.pointsOfInterest, poiID)
					if poi ~= nil then
						table.insert(poiPart, Icons.Icon({poi.name, type='poi'}))
					else
						table.insert(poiPart, Shared.printError('Could not find POI with ID ' .. poiID))
					end
				end
				table.insert(reqArray, 'Discover ' .. table.concat(poiPart, ', '))
			end
		elseif req.type == 'Completion' then
			local ns = GameData.getEntityByName('namespaces', req.namespace)
			if ns ~= nil then
				table.insert(reqArray, req.percent .. '% ' .. ns.displayName .. ' Completion')
			end
		elseif req.type == 'DungeonCompletion' then
			local dung = GameData.getEntityByID('dungeons', req.dungeonID)
			if dung ~= nil then
				local dungStr = 'Complete ' .. Icons.Icon({dung.name, type='dungeon'})
				if req.count > 1 then
					dungStr = dungStr .. ' ' .. Num.formatnum(req.count) .. ' times'
				end
				table.insert(reqArray, dungStr)
			end
		elseif req.type == 'ItemFound' then
			local item = GameData.getEntityByID('items', req.itemID)
			if item ~= nil then
				table.insert(reqArray, 'Find ' .. Icons.Icon({item.name, type='item'}))
			end
		elseif req.type == 'MasteryLevel' then
			local skill = GameData.getSkillData(req.skillID)
			local recipeKey = p.getSkillRecipeKey(req.skillID)
			if skill ~= nil then
				local action = GameData.getEntityByID(skill[recipeKey], req.actionID)
				if action ~= nil then
					table.insert(reqArray, Icons._MasteryReq(action.name, req.level, true))
				end
			end
		elseif req.type == 'MonsterKilled' then
			local monster = GameData.getEntityByID('monsters', req.monsterID)
			if monster ~= nil then
				table.insert(reqArray, Icons.Icon({monster.name, type='monster', qty=req.count, notext=true}) .. ' Kills')
			end
		elseif req.type == 'ShopPurchase' then
			local shopPurch = GameData.getEntityByID('shopPurchases', req.purchaseID)
			if shopPurch ~= nil then
				table.insert(reqArray, p.getPurchaseIcon({ shopPurch }) .. ' Purchased')
			end
		elseif req.type == 'SkillLevel' then
			local skillName = p.getSkillName(req.skillID)
			if skillName ~= nil then
				table.insert(reqArray, Icons._SkillReq(skillName, req.level))
			end
		elseif req.type == 'SlayerItem' then
			local item = GameData.getEntityByID('items', req.itemID)
			if item ~= nil then
				table.insert(reqArray, Icons.Icon({item.name, type='item'}) .. ' Equipped')
			end
		elseif req.type == 'SlayerTask' then
			local taskCategory = GameData.getEntityByID('slayerTaskCategories', req.category)
			if taskCategory ~= nil then
				table.insert(reqArray, 'Complete ' .. Num.formatnum(req.count) .. ' ' .. taskCategory.name .. ' or higher Slayer Tasks')
			end
		elseif req.type == 'TownshipBuilding' then
			local tsData = GameData.getSkillData('melvorD:Township')
			if tsData ~= nil and tsData.buildings ~= nil then
				local building = GameData.getEntityByID(tsData.buildings, req.buildingID)
				if building ~= nil then
					table.insert(reqArray, 'Have ' .. Num.formatnum(req.count) .. ' ' .. building.name .. ' actively built in Township')
				end
			end
		elseif req.type == 'TownshipTask' then
			table.insert(reqArray, 'Complete ' .. Num.formatnum(req.count) .. ' Township Tasks')
		else
			table.insert(reqArray, Shared.printError('Unknown requirement: ' .. (req.type or 'nil')))
		end
	end

	if Shared.tableIsEmpty(reqArray) then
		return valueIfNone
	else
		return table.concat(reqArray, '<br/>')
	end
end

return p