Module:Common
From Melvor Idle
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')
-- 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
-- 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
-- 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 purchType = p.getPurchaseType(purchase)
local iconType = nil
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'
}
if Shared.contains(upgBundles, purchase.id) then
iconType = 'upgrade'
else
iconType = 'item'
end
else
iconType = string.lower(purchType)
end
-- 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 = { ... }, gp = 0, sc = 0, rc = 0 }
function p.getCostString(costs, valueIfNone)
local costArray = {}
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, Icons.Icon({item.name, type='item', qty=itemCost.quantity}))
end
end
end
if costs.gp ~= nil and costs.gp > 0 then
table.insert(costArray, Icons.GP(costs.gp))
end
if costs.sc ~= nil and costs.sc > 0 then
table.insert(costArray, Icons.SC(costs.sc))
end
if costs.rc ~= nil and costs.rc > 0 then
table.insert(costArray, Icons.RC(costs.rc))
end
if Shared.tableIsEmpty(costArray) then
return valueIfNone
else
return table.concat(costArray, '<br/>')
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 == '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 ' .. Shared.formatnum(req.count) .. ' Artefacts to the Museum in ' .. Icons.Icon({'Archaeology', type='skill'}))
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 .. ' ' .. Shared.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 == '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
table.insert(reqArray, 'Complete ' .. Shared.formatnum(req.count) .. ' ' .. req.tier .. ' Slayer Tasks')
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 ' .. Shared.formatnum(req.count) .. ' ' .. building.name .. ' actively built in Township')
end
end
elseif req.type == 'TownshipTask' then
table.insert(reqArray, 'Complete ' .. Shared.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
-- 123 => 00:02:03
function p.secondsToHMS(s)
local ONE_DAY = 24 * 60 * 60
if s > ONE_DAY then
return string.format('%d day%s, ', s/ONE_DAY, (math.floor(s/ONE_DAY) > 1) and "s" or "") .. os.date("!%X", s)
else
return os.date("!%X", s)
end
end
-- 123 => 2 minutes, 3 seconds
function p.secondsToHuman(s)
if s < 60 then
if s > 0 or s < 0 then
return string.format('%.1f seconds', s)
else
return string.format('0 seconds', s)
end
end
local days = math.floor(s / (24 * 60 * 60))
local hours = math.floor(s / (60 * 60) % (24))
local minutes = math.floor(s / 60 % 60)
local seconds = math.floor(s % (60))
local output = {}
if days > 0 then
table.insert(output, string.format('%d day%s', days, (days > 1) and "s" or ""))
end
if hours > 0 then
table.insert(output, string.format('%d hour%s', hours, (hours > 1) and "s" or ""))
end
if minutes > 0 then
table.insert(output, string.format('%d minute%s', minutes, (minutes > 1) and "s" or ""))
end
if seconds > 0 then
table.insert(output, string.format('%d second%s', seconds, (seconds > 1) and "s" or ""))
end
return table.concat(output, ", ")
end
function p.testSecondsToHuman(s)
output = {}
for i, s in ipairs(
{-1, 0, 0.5, 5 * 60, 5 * 60 + 20,
6 * 60 * 60, 6 * 60 * 60 + 20, 6 * 60 * 60 + 3 * 60 + 20,
1 * 24 * 60 * 60, 2 * 24 * 60 * 60 + 3 * 60 * 60 + 2 * 60 + 1}) do
table.insert(output, string.format('%s = %s', s, p.secondsToHuman(s)))
end
return table.concat(output, ' || ')
end
function p.prettyPrintTime(frame)
if frame == nil then
return '<invalid frame object>'
end
if frame.args[1] == nil then
return '<missing argument seconds>'
end
local seconds = tonumber(frame.args[1])
if string.lower(frame.args["hms"] or '') == "true" then
return p.secondsToHMS(seconds)
else
return p.secondsToHuman(seconds)
end
end
return p