Module:Township: Difference between revisions

Fixed currencies not showing up in task list
(Various amends to update for v1.1.2, still WIP)
(Fixed currencies not showing up in task list)
(26 intermediate revisions by 4 users not shown)
Line 1: Line 1:
local Shared = require('Module:Shared')
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')
local Icons = require('Module:Icons')
local Items = require('Module:Items')
local Monsters = require('Module:Monsters')
local Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local GameData = require('Module:GameData')
local GameData = require('Module:GameData')
local Constants = require('Module:Constants')
local Modifiers = require('Module:Modifiers')
local Num = require('Module:Number')


local p = {}
local p = {}
Line 19: Line 22:
else
else
return GameData.getEntityByID(Township.buildings, id)
return GameData.getEntityByID(Township.buildings, id)
end
end
-- Gets a Township building by name, e.g. Hunters Cabin
function p._getBuildingByName(name)
-- Check for the special statue case
if name == 'Statues' then
name = 'Statue of Worship'
end
local STATUE_OF = 'Statue of '
if string.sub(name, 1, string.len(STATUE_OF)) == STATUE_OF then
local building = Shared.clone(GameData.getEntityByID(Township.buildings, 'melvorF:Statues'))
building.name = name
return building
else
return GameData.getEntityByName(Township.buildings, name)
end
end
end
end
Line 25: Line 44:
function p._getResourceByID(id)
function p._getResourceByID(id)
return GameData.getEntityByID(Township.resources, id)
return GameData.getEntityByID(Township.resources, id)
end
-- Given a building, find the next building upgrade
function p._getBuildingUpgrade(building)
local function checkFunc(entity)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == building.id
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
if #upgradesTo > 0 then
return upgradesTo[1]
end
return nil
end
end


Line 58: Line 89:
end
end
end
end
end
-- Given a building, groups biomes for which that building has a common cost
function p._getBuildingGroupedCosts(building)
local biomeGroups = {}
for i, biomeID in ipairs(building.biomes) do
local currentBiomeCost = p._getBuildingCostText(building, biomeID)
local found = false
for j, biomeGroup in ipairs(biomeGroups) do
if biomeGroup.cost == currentBiomeCost then
-- Another biome exists with this cost
table.insert(biomeGroup.biomeIDs, biomeID)
found = true
break
end
end
if not found then
table.insert(biomeGroups, { biomeIDs = { biomeID }, cost = currentBiomeCost})
end
end
return biomeGroups
end
-- Given a building, returns a text string repesenting the building costs for all biomes
function p._getBuildingGroupedCostText(building)
local resultPart = {}
local biomeGroups = p._getBuildingGroupedCosts(building)
if Shared.tableCount(biomeGroups) == 1 then
-- If only one entry then simply output the cost
table.insert(resultPart, biomeGroups[1].cost)
else
-- Otherwise, split by biome group
for i, biomeGroup in ipairs(biomeGroups) do
local biomeText = {}
for j, biomeID in ipairs(biomeGroup.biomeIDs) do
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(biomeText, Icons.Icon({biome.name, type='biome', notext=true, nolink=true, alt=biome.name}))
end
table.insert(resultPart, table.concat(biomeText, ', ') .. ': ' .. biomeGroup.cost)
end
end
return table.concat(resultPart, '<br/>')
end
end


-- Given a building and biome ID, returns a string displaying the building's benefits,
-- Given a building and biome ID, returns a string displaying the building's benefits,
-- or nil if no benefits
-- or nil if no benefits
function p._getBuildingBenefits(building, biomeID, includeModifiers, delimiter)
function p._getBuildingBenefitText(building, biomeID, includeModifiers, delimiter)
-- Basic validation of inputs
-- Basic validation of inputs
if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
Line 81: Line 154:
end
end
end
end
 
if providesData ~= nil then
if providesData ~= nil then
local resultPart = {}
local resultPart = {}
Line 89: Line 162:
education = 'Education',
education = 'Education',
storage = 'Storage',
storage = 'Storage',
worship = 'Worship'
worship = 'Worship',
fortification = 'Fortification'
}
}
local resourceText = function(resName, resType, quantity)
local resourceText = function(resName, resType, quantity)
local elemClass = (quantity < 0 and 'text-negative') or 'text-positive'
local elemClass = (quantity < 0 and 'text-negative') or 'text-positive'
local resIcon = Icons.Icon({resName, type=resType, notext=true})
local resIcon = Icons.Icon({resName, type=resType, notext=true})
return resIcon .. '&nbsp;<span class="' .. elemClass .. '">' .. Shared.numStrWithSign(quantity) .. '</span>'
return resIcon .. '&nbsp;<span class="' .. elemClass .. '">' .. Num.numStrWithSign(quantity) .. '</span>'
end
end


Line 109: Line 183:
-- Other stats
-- Other stats
for key, stat in pairs(stats) do
for key, stat in pairs(stats) do
-- TODO Fix always using first biome
local quantity = providesData[key]
local quantity = providesData[key]
if quantity ~= nil and quantity ~= 0 then
if quantity ~= nil and quantity ~= 0 then
table.insert(resultPart, resourceText(stat, 'township', quantity))
table.insert(resultPart, resourceText(stat, 'township', quantity))
end
end
end
-- Modifiers
if includeMods and building.modifiers ~= nil then
table.insert(resultPart, Modifiers.getModifiersText(building.modifiers))
end
end


Line 121: Line 199:
end
end
end
end
end
-- Given a building, groups biomes for which that building has a common benefit/provides
function p._getBuildingGroupedBenefits(building, includeModifiers)
if includeModifiers == nil then
includeModifiers = true
end
local biomeGroups = {}
for i, biomeID in ipairs(building.biomes) do
local currentBiomeBenefit = p._getBuildingBenefitText(building, biomeID, includeModifiers)
local found = false
for j, biomeGroup in ipairs(biomeGroups) do
if biomeGroup.benefit == currentBiomeBenefit then
-- Another biome exists with this cost
table.insert(biomeGroup.biomeIDs, biomeID)
found = true
break
end
end
if not found then
table.insert(biomeGroups, { biomeIDs = { biomeID }, cost = currentBiomeBenefit})
end
end
return biomeGroups
end
-- Given a building, returns a text string repesenting the building benefits for all biomes
function p._getBuildingGroupedBenefitText(building, includeModifiers)
if includeModifiers == nil then
includeModifiers = true
end
local resultPart = {}
local biomeGroups = p._getBuildingGroupedBenefits(building, includeModifiers)
if Shared.tableCount(biomeGroups) == 1 then
-- If only one entry then simply output the cost
table.insert(resultPart, biomeGroups[1].cost)
else
-- Otherwise, split by biome group
for i, biomeGroup in ipairs(biomeGroups) do
local biomeText = {}
for j, biomeID in ipairs(biomeGroup.biomeIDs) do
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(biomeText, Icons.Icon({biome.name, type='biome', notext=true, nolink=true, alt=biome.name}))
end
table.insert(resultPart, table.concat(biomeText, ', ') .. ': ' .. biomeGroup.cost)
end
end
return table.concat(resultPart, '<br/>')
end
end


Line 132: Line 258:
end
end


-- Gets the Township level and population requirements for a tier
-- Gets the Township level or abyssalLevel, population and fortification requirements for a tier
-- Returns {population=X, level=X}
-- Returns {population=X, level=X} for non-abyssal tiers
function p._getTierRequirements(tier)
-- Returns {population=X, abyssalLevel=X, fortification=X} for abyssal tiers
return Township.populationForTier[tier]
function p._getTierRequirements(tier, abyssalTier)
local tierData = Township.populationForTier[tier]
if abyssalTier ~= nil then
local abyssalTierData = Shared.clone(Township.abyssalTierRequirements[abyssalTier + 1])
abyssalTierData.population = tierData.population
return abyssalTierData
else
return tierData
end
end
end


-- Returns a string containing the Township level and population requirements for a tier
-- Returns a string containing the Township level and population requirements for a tier
function p._getTierText(tier)
function p._getTierText(tier, abyssalTier)
local tierData = p._getTierRequirements(tier)
local realmID = (abyssalTier ~= nil and 'melvorItA:Abyssal' or 'melvorD:Melvor')
local tierData = p._getTierRequirements(tier, abyssalTier)
if tierData ~= nil then
if tierData ~= nil then
local tierText = Icons._SkillReq('Township', tierData.level, false)
local tierText = Icons._SkillReq('Township', tierData.abyssalLevel or tierData.level, false, realmID)
if tierData.population > 0 then
if tierData.population ~= nil and tierData.population > 0 then
tierText = tierText .. '<br/>' .. Icons.Icon({'Population', type='township', notext=true}) .. '&nbsp;' .. Shared.formatnum(tierData.population)
tierText = tierText .. '<br/>' .. Icons.Icon({'Population', type='township', notext=true}) .. '&nbsp;' .. Num.formatnum(tierData.population)
end
if tierData.fortification ~= nil and tierData.fortification > 0 then
tierText = tierText .. '<br/>' .. Icons.Icon({'Fortification', type='township', notext=true}) .. '&nbsp;' .. Num.formatnum(tierData.fortification) .. '%'
end
end
return tierText
return tierText
Line 155: Line 293:
local seasonReqs = {
local seasonReqs = {
["Nightfall"] = Icons.Icon({'Township%23Worship', 'Bane Worship', img='Statue of Bane', type='building'}),
["Nightfall"] = Icons.Icon({'Township%23Worship', 'Bane Worship', img='Statue of Bane', type='building'}),
["SolarEclipse"] = Icons.Icon({'Township%23Worship', 'The Herald Worship', img='Statue of The Herald', type='building'})
["SolarEclipse"] = Icons.Icon({'Township%23Worship', 'The Herald Worship', img='Statue of The Herald', type='building'}),
["Lemon"] = Icons.Icon({'Ancient_Relics', 'Ancient Relics', img='Ancient Relics'}),
["EternalDarkness"] = Icons.Icon({'Township%23Worship', 'Xon Worship', img='Statue of Xon', type='building'}),
}
}


Line 176: Line 316:
table.insert(resultPart, '<br/>Requires ' .. reqs)
table.insert(resultPart, '<br/>Requires ' .. reqs)
end
end
table.insert(resultPart, '\n| ' .. Constants.getModifiersText(season.modifiers))
table.insert(resultPart, '\n| ' .. Modifiers.getModifiersText(season.modifiers))
end
end
table.insert(resultPart, '\n|}')
table.insert(resultPart, '\n|}')
Line 188: Line 328:
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="2"| Requirements')
table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="3"| Requirements')
table.insert(resultPart, '\n|- class="headerRow-1"')
table.insert(resultPart, '\n|- class="headerRow-1"')
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Level', type='skill', nolink=true}))
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Level', type='skill', nolink=true}))
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township%23Population', 'Population', img='Population', type='township'}))
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Population', img='Population', type='township', section='Population' }))
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Forification', img='Fortification', type='township', section='Fortification' }))


for i, biome in ipairs(Township.biomes) do
for i, biome in ipairs(Township.biomes) do
local reqs = p._getTierRequirements(biome.tier)
local reqs = p._getTierRequirements(biome.tier, biome.abyssalTier)
local fortification = reqs.fortification or 0
table.insert(resultPart, '\n|-\n|class="table-img"| ' .. Icons.Icon({biome.name, type='biome', size=50, nolink=true, notext=true}))
table.insert(resultPart, '\n|-\n|class="table-img"| ' .. Icons.Icon({biome.name, type='biome', size=50, nolink=true, notext=true}))
table.insert(resultPart, '\n| ' .. biome.name)
table.insert(resultPart, '\n| ' .. biome.name)
table.insert(resultPart, '\n|style="text-align:right"| ' .. reqs.level)
table.insert(resultPart, '\n|style="text-align:right"| ' .. (reqs.abyssalLevel or reqs.level))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. reqs.population .. '"| ' .. Shared.formatnum(reqs.population))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. reqs.population .. '"| ' .. Num.formatnum(reqs.population))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. fortification .. '"| ' .. Num.formatnum(fortification))
end
end
table.insert(resultPart, '\n|}')
table.insert(resultPart, '\n|}')
Line 208: Line 351:
-- Skips upgraded buildings
-- Skips upgraded buildings
function p.getBuildingBiomeTable(frame)
function p.getBuildingBiomeTable(frame)
-- Setup the table
local tbl = mw.html.create('table')
local ret = {}
:addClass('wikitable sortable stickyHeader')
table.insert(ret, '{| class="wikitable sortable stickyHeader" style="text-align:center"')
:css('text-align', 'center')
table.insert(ret, '\n|- class="headerRow-0"')
 
table.insert(ret, '\n! Building')
local header = mw.html.create('tr'):addClass('headerRow-0')
local level = mw.html.create('tr'):addClass('sorttop')
local pop = mw.html.create('tr'):addClass('sorttop')
local fort = mw.html.create('tr'):addClass('sorttop')


-- Generate the table header, one column per biome
header:tag('th')
:css('z-index', '2')
:wikitext('Building')
level:tag('th')
:wikitext(Icons.Icon({'Township', 'Level', type='skill', nolink=true}))
pop:tag('th')
:wikitext(Icons.Icon({'Township', 'Population', img='Population', type='township', section='Population' }))
fort:tag('th')
:wikitext(Icons.Icon({'Township', 'Fortification', img='Fortification', type='township', section='Fortification' }))
for _, biome in ipairs(Township.biomes) do
for _, biome in ipairs(Township.biomes) do
table.insert(ret, '\n! ' .. Icons.Icon({biome.name, type='biome', notext=true, nolink=true}) .. '<br/>' .. biome.name)
local reqs = p._getTierRequirements(biome.tier, biome.abyssalTier)
header:tag('th')
:wikitext(Icons.Icon({biome.name, type='biome', notext=true, nolink=true}).. '<br/>' .. biome.name)
level:tag('td')
:wikitext(Num.formatnum((reqs.abyssalLevel or reqs.level)))
pop:tag('td')
:wikitext(Num.formatnum(reqs.population))
fort:tag('td')
:wikitext(Num.formatnum((reqs.fortification or 0)))
end
end
tbl:node(header)
tbl:node(level)
tbl:node(pop)
tbl:node(fort)


for _, _building in ipairs(p._sortedBuildings(false)) do
for _, _building in ipairs(p._sortedBuildings(false)) do
Line 231: Line 399:
end
end


-- Build the row
local trow = tbl:tag('tr')
table.insert(ret, '\n|-')
trow:tag('th')
table.insert(ret, '\n!data-sort-value="' .. building.name .. '" style="text-align:left"| ' .. Icons.Icon({building.name, type='building'}))
:css('text-align', 'left')
:attr('data-sort-value', building.name)
:wikitext(Icons.Icon({building.name, type='building'}))
 
for _, biome in ipairs(Township.biomes) do
for _, biome in ipairs(Township.biomes) do
if buildingBiomes[biome.id] then
if buildingBiomes[biome.id] then
-- Buildable
trow:tag('td')
table.insert(ret, '\n|class="table-positive"| ✓')
:addClass('table-positive')
:wikitext('✓')
else
else
-- Invalid biome
trow:tag('td')
table.insert(ret, '\n|style="border:0px"|')
end
end
end
end
end
end
end
end
table.insert(ret, '\n|}')


return table.concat(ret)
return tostring(tbl)
end
end


Line 263: Line 433:
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2"|Building\n!Requirements\n!Type\n!Max Built')
table.insert(resultPart, '\n!colspan="2"|Building\n!Requirements\n!Max Built')
table.insert(resultPart, '\n!Biomes\n!Cost\n!Provides')
table.insert(resultPart, '\n!Biomes\n!Cost\n!Provides')


Line 277: Line 447:
for j, biomeID in ipairs(building.biomes) do
for j, biomeID in ipairs(building.biomes) do
local biome = biomesByID[biomeID]
local biome = biomesByID[biomeID]
if firstRow then  
if firstRow then
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|class="table-img"' .. rowSpan .. '| ' .. Icons.Icon({buildingName, type='building', notext=true, size=50}))
table.insert(resultPart, '\n|class="table-img"' .. rowSpan .. '| ' .. Icons.Icon({buildingName, type='building', notext=true, size=50}))
table.insert(resultPart, '\n' .. rowSpanOnly .. '| ' .. Icons.getExpansionIcon(building.id) .. Icons.Icon({buildingName, type='building', noicon=true}))
table.insert(resultPart, '\n' .. rowSpanOnly .. '| ' .. Icons.getExpansionIcon(building.id) .. Icons.Icon({buildingName, type='building', noicon=true}))
table.insert(resultPart, '\n|' .. 'data-sort-value="' .. building.tier .. '"' .. rowSpan .. '| ' .. (p._getTierText(building.tier) or ''))
table.insert(resultPart, '\n|' .. 'data-sort-value="' .. building.tier .. '"' .. rowSpan .. '| ' .. (p._getTierText(building.tier, building.abyssalTier) or ''))
table.insert(resultPart, '\n' .. rowSpanOnly .. '| ' .. building.type)
table.insert(resultPart, '\n|style="text-align:right"' .. rowSpan .. '| ' .. building.maxUpgrades)
table.insert(resultPart, '\n|style="text-align:right"' .. rowSpan .. '| ' .. building.maxUpgrades)
firstRow = false
firstRow = false
Line 291: Line 460:
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
local providesText = p._getBuildingBenefits(building, biomeID)
local providesText = p._getBuildingBenefitText(building, biomeID)
if building.modifiers ~= nil then
if building.modifiers ~= nil then
local modText = Constants.getModifiersText(building.modifiers)
local modText = Modifiers.getModifiersText(building.modifiers)
if providesText == nil then
if providesText == nil then
providesText = modText
providesText = modText
Line 310: Line 479:
-- Builds the table of trader items
-- Builds the table of trader items
function p.getTraderTable(frame)
function p.getTraderTable(frame)
-- Get the resources data with associated trader data
local resultPart = {}
 
-- Build the text
-- Build table header
local ret = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
for _, resource in ipairs(p.resources) do
table.insert(resultPart, '\n|- class="headerRow-0"')
if #resource.itemConversions ~= 0 then -- Skips GP
table.insert(resultPart, '\n!colspan="2"| Item\n!Description\n!style="min-width:60px"| Cost\n!Requirements')
local ret_resource = {}
 
for i, tsResource in ipairs(Township.itemConversions.fromTownship) do
-- Header
local res = GameData.getEntityByID(Township.resources, tsResource.resourceID)
table.insert(ret_resource, '\r\n==='..resource.name..'===')
for j, tradeDef in ipairs(tsResource.items) do
table.insert(ret_resource, '\r\n{| class="wikitable sortable stickyHeader"')
local item = Items.getItemByID(tradeDef.itemID)
table.insert(ret_resource, '\r\n|- class="headerRow-0"')
local itemDesc = item.customDescription
table.insert(ret_resource, '\r\n!Item')
if itemDesc == nil then
table.insert(ret_resource, '\r\n!Name')
if item.modifiers ~= nil then
table.insert(ret_resource, '\r\n!Level')
itemDesc = Modifiers.getModifiersText(item.modifiers, false, true)
table.insert(ret_resource, '\r\n!Give To')
table.insert(ret_resource, '\r\n!Take From')
table.insert(ret_resource, '\r\n!Value')
table.insert(ret_resource, '\r\n!Value/Resource')
if resource.id =='melvorF:Food' then
table.insert(ret_resource, '\r\n!Heals')
table.insert(ret_resource, '\r\n!Heals/Resource')
end
-- Each item
for _, item in ipairs(resource.itemConversions) do
-- To indicate the skill level, we need to find the recipe of the item in the target skill
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data
local required_level = nil
local recipes = nil
-- Get the skill based on the item.id or else use the resource's default skill
local skill_overrides = {
['melvorD:Raw_Magic_Fish'] = 'melvorD:Fishing',
['melvorF:Apple'] = 'melvorD:Farming',
}
local skill = skill_overrides[item.id] or p._GetResourceSkill(resource.id)
local skill_namespace, skill_localid = GameData.getLocalID(skill or '')
-- Check for upgraded Crafting items and downgrade them so we can display the crafting level for the base item
-- e.g. converts Black_Dhide_Body_U -> Black_Dhide_Body for the purposes of the lookup
local lookup_id = item.id
if string.match(item.id, '_U$') then
lookup_id = string.sub(item.id, 1, #item.id - 2)
end
-- Find the recipe's level
local recipes = p._FindItemRecipes(lookup_id, skill)
if #recipes == 1 then
required_level = recipes[1].level
end
-- Alright, now that we've found the required recipe and level, we can draw the item's row entry
table.insert(ret_resource, '\r\n|-')
-- Icon
table.insert(ret_resource, '\r\n|style="text-align:center"|'..Icons.Icon({item.name, type='item', size='50', notext=true}))
-- Name
table.insert(ret_resource, '\r\n|style="text-align:left"|'..Icons.getExpansionIcon(item.id)..Icons.Icon({item.name, type='item', noicon=true}))
-- Level
if required_level == nil then
-- Recipe not found, or multiple recipes found
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="0"|N/A')
else
else
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. required_level .. '"|'..Icons.Icon({skill_localid, type="skill", notext=true})..' '..required_level)
itemDesc = ''
end
-- Give To
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.toTownship .. '"|'..Icons.Icon({item.name, type='item', notext=true})..' '..Shared.formatnum(item.toTownship))
-- Take From
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.fromTownship .. '"|'..Icons.Icon({resource.name, type='resource', notext=true})..' '..Shared.formatnum(item.fromTownship))
-- Value
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.sellsFor .. '"|'..Icons.GP(item.sellsFor))
-- Value/Resource
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.sellsFor/item.fromTownship .. '"|'..Icons.GP(Shared.round(item.sellsFor/item.fromTownship, 2, 2)))
if resource.id =='melvorF:Food' then
-- Heals
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.healsFor*10 .. '"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..Shared.formatnum(item.healsFor*10))
-- Heals/Resource
table.insert(ret_resource, '\r\n|style="text-align:center" data-sort-value="' .. item.healsFor*10/item.fromTownship .. '"|'..Icons.Icon({"Hitpoints", type="skill", notext=true})..' '..Shared.round(item.healsFor*10/item.fromTownship, 2, 2))
end
end
end
end
local resQty = math.max(item.sellsFor, 2)
table.insert(ret_resource, '\r\n|}')
local costSort = i * 10000 + resQty
 
table.insert(ret, table.concat(ret_resource))
table.insert(resultPart, '\n|-\n| ' .. Icons.Icon({item.name, type='item', size=50, notext=true}))
table.insert(resultPart, '\n| ' .. Icons.getExpansionIcon(item.id) .. Icons.Icon({item.name, type='item', noicon=true}))
table.insert(resultPart, '\n| ' .. itemDesc)
table.insert(resultPart, '\n|data-sort-value="' .. costSort ..'" style="text-align:right"| ' .. Icons.Icon({res.name, type='resource', qty=resQty, notext=true}))
table.insert(resultPart, '\n| ' .. Shop.getRequirementString(tradeDef.unlockRequirements))
end
end
end
end
return table.concat(ret)
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
end
end


Line 402: Line 516:
function p.getWorshipTable()
function p.getWorshipTable()
local function getCheckpointCell(checkpoint)
local function getCheckpointCell(checkpoint)
return '\n|-\n!' .. checkpoint .. '%<br/>' .. Shared.formatnum(checkpoint * Township.maxWorship / 100) .. '/' .. Shared.formatnum(Township.maxWorship)
return '\n|-\n!' .. checkpoint .. '%<br/>' .. Num.formatnum(checkpoint * Township.maxWorship / 100) .. '/' .. Num.formatnum(Township.maxWorship)
end
end


Line 418: Line 532:
table.insert(ret, '\n|-\n!Requirements')
table.insert(ret, '\n|-\n!Requirements')
for _, worship in ipairs(worships) do
for _, worship in ipairs(worships) do
table.insert(ret, '\n|style="text-align:center"| ' .. Shop.getRequirementString(worship.unlockRequirements))
local cellStyle = (Shared.tableIsEmpty(worship.unlockRequirements) and 'class="table-na"') or 'style="text-align:center"'
table.insert(ret, '\n|' .. cellStyle ..'| ' .. Shop.getRequirementString(worship.unlockRequirements))
end
 
-- Season multipliers
table.insert(ret, '\n|-\n!Bonus Seasons')
for _, worship in ipairs(worships) do
local bonusPart = {}
local cellStyle = 'style="text-align:center"'
if Shared.tableIsEmpty(worship.seasonMultiplier) then
bonusPart, cellStyle = {'None'}, 'class="table-na"'
end
for i, seasonMult in ipairs(worship.seasonMultiplier) do
local season = GameData.getEntityByID(Township.seasons, seasonMult.seasonID)
if season ~= nil then
table.insert(bonusPart, Icons.Icon({season.name, type='township', nolink=true}) .. ' (' .. seasonMult.multiplier .. 'x)')
end
end
table.insert(ret, '\n|' .. cellStyle .. '| ' .. table.concat(bonusPart, '<br/>'))
end
end


Line 424: Line 556:
table.insert(ret, getCheckpointCell(0))
table.insert(ret, getCheckpointCell(0))
for _, worship in ipairs(worships) do
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.modifiers))
table.insert(ret, '\n| ' .. Modifiers.getModifiersText(worship.modifiers))
end
end


Line 431: Line 563:
table.insert(ret, getCheckpointCell(checkpoint))
table.insert(ret, getCheckpointCell(checkpoint))
for _, worship in ipairs(worships) do
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.checkpoints[i]))
table.insert(ret, '\n| ' .. Modifiers.getModifiersText(worship.checkpoints[i]))
end
end
end
end


-- Total sum
-- Total sum
-- TODO Needs fixing, no function currently for aggregating modifiers
--[==[
table.insert(ret, '\n|-\n!Total')
table.insert(ret, '\n|-\n!Total')
for _, worship in ipairs(worships) do
for _, worship in ipairs(worships) do
Line 454: Line 588:
end
end
end
end
table.insert(ret, '\n|' .. Constants.getModifiersText(modifiers))
table.insert(ret, '\n|' .. Modifiers.getModifiersText(modifiers))
end
end
--]==]
table.insert(ret, '\n|}')
table.insert(ret, '\n|}')


Line 461: Line 596:
end
end


-- TODO Check if functions below this line are still in use
-- Gets a building and prepares all the relevant stats for the building, presented as an infobox
 
function p.getBuildingInfoBox(frame)
-- Returns the recipe for the item of a desired skill.
local name = frame.args ~= nil and frame.args[1] or frame
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data so we instead use this quick function
local building = p._getBuildingByName(name)
function p._FindItemRecipes(itemid, skill)
if building == nil then
return Shared.printError('No building named "' .. name .. '" exists in the data module')
-- No skill? No recipes
if skill == nil then
return {}
end
-- the key name for each skill in the json file
local skill_recipe_keys = {
['melvorD:Woodcutting'] = {recipes='trees', productID='productId'}, -- lowercase "d"
['melvorD:Fishing'] = {recipes='fish', productID='productId'}, -- lowercase "d"
['melvorD:Cooking'] = {recipes='recipes', productID='productID'},
['melvorD:Mining'] = {recipes='rockData', productID='productId'}, -- lowercase "d"
['melvorD:Smithing'] = {recipes='recipes', productID='productID'},
['melvorD:Farming'] = {recipes='recipes', productID='productId'}, -- lowercase "d"
['melvorD:Summoning'] = {recipes='recipes', productID='productID'},
['melvorD:Fletching'] = {recipes='recipes', productID='productID'},
['melvorD:Crafting'] = {recipes='recipes', productID='productID'},
['melvorD:Runecrafting'] = {recipes='recipes', productID='productID'},
['melvorD:Herblore'] = {recipes='recipes', productID='potionIDs'} -- Special case potions I-IV
--[[ Excluded skills:
Attack, Strength, Defence, Magic, Ranged, Prayer, Slayer
Thieving, Agility, Astrology, Firemaking, Township (not items)]]
}
 
local results = {}
local SkillData = GameData.getSkillData(skill)
local recipes = skill_recipe_keys[skill].recipes
local productID = skill_recipe_keys[skill].productID
if SkillData[recipes] ~= nil then
for _, recipe in ipairs(SkillData[recipes]) do
-- Special case for Herblore
if skill == 'melvorD:Herblore' then
-- Iterate over the 4 potion tiers
for _, potionid in ipairs(recipe[productID]) do
if itemid == potionid then
table.insert(results, Shared.clone(recipe))
end
end
-- Base case
else
if itemid == recipe[productID] then
table.insert(results, Shared.clone(recipe))
end
end
end
end
return results
end
 
-- Returns a list of all the Township resources
function p._ResourcesData()
-- Get a sorted list of all the resources
local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder)
resources = Shared.clone(resources)
return resources
end
 
-- Returns a list of all the Township resources along with the Trader's trade ratios
function p._TraderData()
-- Get the list of resources. We get a copy instead of directly using p.resources because we are going to modify the table
local resources = p._ResourcesData()
-- Get the list of tradeable items
-- See township.js -> TownshipResource.buildResourceItemConversions for the calculation of valid items
local function matchNone(item)
return false
end
local function matchFood(item)
return item.type == 'Food' and (not string.match(item.id, '_Perfect')) and item.category ~= 'Farming' and (not item.ignoreCompletion)
end
local function matchLogs(item)
return item.type == 'Logs'
end
local function matchOre(item)
return item.type == 'Ore' and item.id ~= 'melvorTotH:Meteorite_Ore'
end
local function matchCoal(item)
return item.id == 'melvorD:Coal_Ore'
end
local function matchBar(item)
return item.type == 'Bar' and item.id ~= 'melvorTotH:Meteorite_Bar'
end
local function matchHerb(item)
return item.type == 'Herb'
end
local function matchEssence(item)
return item.id == 'melvorD:Rune_Essence' or item.id == 'melvorTotH:Pure_Essence'
end
local function matchLeather(item)
return item.id == 'melvorD:Leather'
end
local function matchPotion(item)
return item.type == 'Potion' and string.match(item.id, '_IV')
end
local function matchClothing(item)
return item.id == 'melvorD:Green_Dragonhide' or item.id == 'melvorD:Blue_Dragonhide' or item.id == 'melvorD:Red_Dragonhide' or item.id == 'melvorD:Black_Dragonhide' or item.id == 'melvorF:Elder_Dragonhide'
end
 
local traderMatchesList = {
['melvorF:GP'] = {traderMatches = matchNone},
['melvorF:Food'] = {traderMatches = matchFood},
['melvorF:Wood'] = {traderMatches = matchLogs},
['melvorF:Stone'] = {traderMatches = matchOre},
['melvorF:Ore'] = {traderMatches = matchOre},
['melvorF:Coal'] = {traderMatches = matchCoal},
['melvorF:Bar'] = {traderMatches = matchBar},
['melvorF:Herbs'] = {traderMatches = matchHerb},
['melvorF:Rune_Essence'] = {traderMatches = matchEssence},
['melvorF:Leather'] = {traderMatches = matchLeather},
['melvorF:Potions'] = {traderMatches = matchPotion},
['melvorF:Planks'] = {traderMatches = matchLogs},
['melvorF:Clothing'] = {traderMatches = matchClothing}
}
 
for _, resource in ipairs(resources) do
resource.itemConversions = Shared.clone(GameData.getEntities('items', traderMatchesList[resource.id].traderMatches))
end
-- Calculate the trader's conversion ratios
-- See township.js TownshipResource.getBaseConvertToTownshipRatio and TownshipResource.getBaseConvertFromTownshipRatio for the conversion prices
for _, resource in ipairs(resources) do
if resource.id == 'melvorF:Food' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = math.max(math.floor(1000/(item.healsFor*10)), 2)
item.fromTownship = item.healsFor*5*6*5
end
elseif resource.id == 'melvorF:Planks' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = math.max(math.floor(3000/math.max(item.sellsFor, 1)), 2)
item.fromTownship = math.max(math.ceil(item.sellsFor/2)*6, 1);
end
elseif resource.id == 'melvorF:Rune_Essence' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = 5
item.fromTownship = (item.sellsFor+1)*10*6
end
elseif resource.id == 'melvorF:Leather' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = 20
item.fromTownship = 20*6
end
else
for _, item in ipairs(resource.itemConversions) do
        item.toTownship = math.max(math.floor(1000/math.max(item.sellsFor, 1)), 2)
    item.fromTownship = math.max(item.sellsFor*6, 1)
end
end
end
end
return resources
end
p.resources = p._TraderData()


-- Gets the associated skill of a resource by id
local resource_skill = {
['melvorF:GP'] = {skill = nil},
['melvorF:Food'] = {skill = 'melvorD:Cooking'},
['melvorF:Wood'] = {skill = 'melvorD:Woodcutting'},
['melvorF:Stone'] = {skill = 'melvorD:Mining'},
['melvorF:Ore'] = {skill = 'melvorD:Mining'},
['melvorF:Coal'] = {skill = 'melvorD:Mining'},
['melvorF:Bar'] = {skill = 'melvorD:Smithing'},
['melvorF:Herbs'] = {skill = 'melvorD:Farming'},
['melvorF:Rune_Essence'] = {skill = 'melvorD:Mining'},
['melvorF:Leather'] = {skill = nil},
['melvorF:Potions'] = {skill = 'melvorD:Herblore'},
['melvorF:Planks'] = {skill = 'melvorD:Woodcutting'},
['melvorF:Clothing'] = {skill = nil}
}
function p._GetResourceSkill(id)
return resource_skill[id].skill
end
-- Gets a Township building by name, e.g. Hunters Cabin
function p._GetBuildingByName(name)
-- Check for the special statue case
if name == 'Statues' then
name = 'Statue of Worship'
end
local STATUE_OF = 'Statue of '
if string.sub(name, 1, string.len(STATUE_OF)) == STATUE_OF then
local building = Shared.clone(GameData.getEntityByID(Township.buildings, 'melvorF:Statues'))
building.name = name
return building
else
return GameData.getEntityByName(Township.buildings, name)
end
end
-- Gets a building and prepares all the relevant stats for the building
-- TODO Rename, getBuildingInfoBox or something of the sort
function p.GetBuildingTable(frame)
local name = frame.args ~= nil and frame.args[1] or frame
local building = Shared.clone(p._GetBuildingByName(name))
local ret = {}
local ret = {}
-- Header
-- Header
table.insert(ret, '\r\n{| class="wikitable infobox"')
table.insert(ret, '{| class="wikitable infobox"')
-- Name
-- Name
table.insert(ret, '\r\n|-\r\n!'..building.name)
table.insert(ret, '\n|-\n! ' .. Icons.getExpansionIcon(building.id) .. building.name)
-- Icon
-- Icon
table.insert(ret, '\r\n|-\r\n|style="text-align:center"|'..Icons.Icon({building.name, type='building', size='250', notext=true}))
table.insert(ret, '\n|-\n|style="text-align:center"| ' .. Icons.Icon({building.name, type='building', size='250', notext=true}))
-- ID
-- ID
table.insert(ret, '\r\n|-\r\n| <b>Building ID:</b> '..building.id)
table.insert(ret, '\n|-\n| <b>Building ID:</b> ' .. building.id)
-- Type
table.insert(ret, '\r\n|-\r\n| <b>Type:</b> '..building.type)
-- Tier
-- Tier
local tier = p._getTierText(building.tier)
local tier = p._getTierText(building.tier, building.abyssalTier)
table.insert(ret, '\r\n|-\r\n| <b>Requirements:</b><br>'..tier)
table.insert(ret, '\n|-\n| <b>Requirements:</b><br/>' .. tier)


-- Upgrades From
-- Upgrades From
table.insert(ret, '\r\n|-\r\n| <b>Base Cost:</b>')
table.insert(ret, '\n|-\n| <b>Base Cost:</b>')
local upgradesFrom = p._getBuildingDowngrade(building)
local upgradesFrom = p._getBuildingDowngrade(building)
if upgradesFrom ~= nil then
if upgradesFrom ~= nil then
table.insert(ret, '<br>'..Icons.Icon({upgradesFrom.name, type='building'}))
table.insert(ret, '<br/>' .. Icons.Icon({upgradesFrom.name, type='building'}))
end
end
-- Cost
-- Cost
local cost = p._GetBuildingBaseCost(building)
--table.insert(ret, '<br/>' .. p._getBuildingGroupedCostText(building))
table.insert(ret, '<br>'..cost)
local function getGroupedText(building, groupFunc)
local biomeGroups = groupFunc(building)
if Shared.tableCount(biomeGroups) == 1 then
-- If only one entry then simply output the cost
return biomeGroups[1].cost
else
-- Otherwise, split by biome group
local resultPart = {}
table.insert(resultPart, '{| class="wikitable" style="text-align:center; margin: 0.25em 0 0 0"')
for i, biomeGroup in ipairs(biomeGroups) do
local biomeText = {}
for j, biomeID in ipairs(biomeGroup.biomeIDs) do
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(biomeText, Icons.Icon({biome.name, type='biome', notext=true, nolink=true, alt=biome.name}))
end
table.insert(resultPart, '\n|-\n| ' .. table.concat(biomeText, '<br/>'))
table.insert(resultPart, '\n| ' .. biomeGroup.cost)
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
end
 
table.insert(ret, '\n' .. getGroupedText(building, p._getBuildingGroupedCosts))
 
-- Upgrades To
-- Upgrades To
local upgradesTo = p._GetBuildingIDUpgrade(building.id)
local upgradesTo = p._getBuildingUpgrade(building)
if upgradesTo ~= nil then
if upgradesTo ~= nil then
table.insert(ret, '\r\n|-\r\n| <b>Upgrades To:</b>')
table.insert(ret, '\n|-\n| <b>Upgrades To:</b>')
table.insert(ret, '<br>'..Icons.Icon({upgradesTo.name, type='building'}))
table.insert(ret, '<br/>' .. Icons.Icon({upgradesTo.name, type='building'}))
local upgrade_cost = p._GetBuildingBaseCost(upgradesTo)
table.insert(ret, '\n' .. getGroupedText(upgradesTo, p._getBuildingGroupedCosts))
table.insert(ret, '<br>'..upgrade_cost)
end
end


-- Fixed benefits
-- Maximum built
local benefits = p._getBuildingBenefits(building)
local biomeCount = Shared.tableCount(building.biomes)
if benefits ~= nil then
local maxText = Num.formatnum(building.maxUpgrades)
table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..benefits)
if biomeCount > 1 then
maxText = maxText .. ' per biome, ' .. Num.formatnum(biomeCount * building.maxUpgrades) .. ' total'
end
end
 
table.insert(ret, '\n|-\n| <b>Maximum Built:</b><br/>' .. maxText)
-- Production
--local production = p._GetBuildingBaseProduction(building)
-- Benefits
--if production ~= nil then
local benefits = p._getBuildingGroupedBenefitText(building)
--table.insert(ret, '\r\n|-\r\n| <b>Base Production per '..Icons.Icon({'Workers', type='township', notext=true})..':</b><br>')
if benefits ~= nil and benefits ~= '' then
--table.insert(ret, production)
table.insert(ret, '\n|-\n| <b>Provides:</b><br/>' .. benefits)
--end
 
-- Modifiers
if building.modifiers ~= nil and not Shared.tableIsEmpty(building.modifiers) then
table.insert(ret, '\r\n|-\r\n| <b>Modifiers:</b>\r\n'..Constants.getModifiersText(building.modifiers, true))
end
end


-- Biomes
-- Biomes
table.insert(ret, '\r\n|-\r\n| <b>Biomes:</b>')
table.insert(ret, '\n|-\n| <b>Biomes:</b>')
for _, biomeid in ipairs(building.biomes) do
for _, biomeid in ipairs(building.biomes) do
local biomename = GameData.getEntityByID(Township.biomes, biomeid).name
local biome = GameData.getEntityByID(Township.biomes, biomeid)
table.insert(ret, '<br>'..Icons.Icon({biomename, type='biome', notext=true, nolink=true})..' <span>'..biomename..'</span>')
table.insert(ret, '<br/>' .. Icons.Icon({biome.name, type='biome', nolink=true}))
end
end


-- End
-- End
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
return table.concat(ret)
return table.concat(ret)
end
end


-- Gets a string displaying the base production of a building, or nil if no production
-- Returns an upgrade table of a building
function p._GetBuildingBaseProduction(building)
function p.getBuildingUpgradeTable(frame)
-- TODO Fix always using first biome
local buildingname = frame.args ~= nil and frame.args[1] or frame
local production = Shared.clone(building.provides[1].resources)
local building = p._getBuildingByName(buildingname)
if building == nil then
if #production == 0 then
return Shared.printError('No building named "' .. buildingname .. '" exists in the data module')
return nil
end
local retResources = {}
for _, resource in ipairs(production) do
local retProduction = {}
-- Sourced from township.js -> Township.computeTownResourceGain()
local production = resource.quantity*100*(Township.tickLength/10)
local color = production < 0 and 'red' or 'green'
local resource_data = p._getResourceByID(resource.id)
table.insert(retProduction, '<span style="color:'..color..'">'..Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..Shared.numStrWithSign(production)..'</span>')
if resource_data.requires ~= nil and #resource_data.requires > 0 then
for _, required_resource in ipairs(resource_data.requires) do
local demand = production*required_resource.quantity*100
local required_resource_data = p._getResourceByID(required_resource.id)
table.insert(retProduction, '<span style="color:red">'..Icons.Icon({required_resource_data.name, type='resource', notext=true})..'&nbsp;-'..demand..'</span>')
end
end
end
return table.concat(retResources, '<br>')
end
 
-- Given a building id, find the next building upgrade
function p._GetBuildingIDUpgrade(buildingid)
local function checkFunc(entity)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == buildingid
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
if #upgradesTo > 0 then
return upgradesTo[1]
end
return nil
end
 
-- Given a building, find the base resource cost
function p._GetBuildingBaseCost(building, _join)
local join = _join ~= nil and _join or ', '
local cost = {}
-- TODO Cost can vary by biome, properly handle this rather than
-- always taking costs for the first biome
for _, resource in ipairs(building.cost[1].cost) do
local resource_data = p._getResourceByID(resource.id)
table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..resource.quantity)
end
end
return table.concat(cost, join)
end


-- Returns an upgrade table of a building
function p.GetBuildingUpgradeTable(frame)
local buildingname = frame.args ~= nil and frame.args[1] or frame
local building = p._GetBuildingByName(buildingname)
-- Let's find the base building
-- Let's find the base building
local baseBuilding = building
local baseBuilding = building
Line 797: Line 702:
end
end
end
end
 
-- Let's make a list of all the buildings
-- Let's make a list of all the buildings
-- Return empty string if there is only 1 building in the upgrade chain (i.e. no upgrades/downgrades)
-- Return empty string if there is only 1 building in the upgrade chain (i.e. no upgrades/downgrades)
Line 804: Line 709:
while true do
while true do
table.insert(buildingList, _curBuilding)
table.insert(buildingList, _curBuilding)
_curBuilding = p._GetBuildingIDUpgrade(_curBuilding.id)
_curBuilding = p._getBuildingUpgrade(_curBuilding)
if _curBuilding == nil then
if _curBuilding == nil then
break
break
Line 812: Line 717:
return ''
return ''
end
end
 
local ret = {}
local ret = {}
table.insert(ret, '\r\n== Upgrade Chart ==')
table.insert(ret, '\n== Upgrade Chart ==')
table.insert(ret, '\r\n{| class="wikitable"')
table.insert(ret, '\n{| class="wikitable" style="text-align:center"')
 
-- Name
-- Name
table.insert(ret, '\r\n|- style="text-align:center" \r\n! Name')
table.insert(ret, '\n|-\n!colspan="2"| Name')
for _, building in ipairs(buildingList) do
for _, building in ipairs(buildingList) do
table.insert(ret, '\r\n!'..Icons.Icon({building.name, type='building'}))
table.insert(ret, '\n!' .. Icons.getExpansionIcon(building.id) .. Icons.Icon({building.name, type='building'}))
end
end


-- Tier
-- Tier
table.insert(ret, '\r\n|-\r\n! Requirements')
table.insert(ret, '\n|-\n!colspan="2"| Requirements')
for _, building in ipairs(buildingList) do
for _, building in ipairs(buildingList) do
local tier = p._getTierText(building.tier)
table.insert(ret, '\n|' .. p._getTierText(building.tier, building.abyssalTier))
table.insert(ret, '\r\n|'..tier)
end
end


-- Cost
-- Cost
table.insert(ret, '\r\n|-\r\n! Cost')
local biomeCount = Shared.tableCount(baseBuilding.biomes)
for _, building in ipairs(buildingList) do
table.insert(ret, '\n|-\n!rowspan="' .. biomeCount .. '"| Cost')
local cost = p._GetBuildingBaseCost(building)
local firstBiome = true
table.insert(ret, '\r\n|'..cost)
for _, biomeID in ipairs(baseBuilding.biomes) do
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(ret, (firstBiome and '' or '\n|-') .. '\n! ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
for _, building in ipairs(buildingList) do
local cost = p._getBuildingCostText(building, biomeID)
table.insert(ret, '\n| ' .. cost)
end
firstBiome = false
end
end


-- Optional params
-- Benefits
local benefitText = {}
-- Generate a row
table.insert(benefitText, '\n|-\n!rowspan="' .. biomeCount .. '"| Benefits')
-- textFunc: returns nil if no data for a building, or else returns a string
firstBiome = true
local function BuildOptionalRow(header, textFunc)
local hasText = false
local texts = {}
for _, biomeID in ipairs(baseBuilding.biomes) do
local hasTexts = false
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(benefitText, (firstBiome and '' or '\n|-') .. '\n! ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
for _, building in ipairs(buildingList) do
for _, building in ipairs(buildingList) do
local text = textFunc(building)
local benefit = p._getBuildingBenefitText(building, biomeID, true) or ''
hasTexts = hasTexts == true or text ~= nil
if not hasText and benefit ~= '' then
texts = texts ~= nil and texts or ''
hasText = true
table.insert(texts, text)
end
end
table.insert(benefitText, '\n| ' .. benefit)
if hasTexts == true then
texts = table.concat(texts, '\r\n|')
table.insert(ret, header..texts)
end
end
firstBiome = false
end
if hasText then
-- Only add benefits rows if the building has benefits to display
table.insert(ret, table.concat(benefitText))
end
end
BuildOptionalRow('\r\n|-\r\n! Benefits\r\n|', p._getBuildingBenefits)


-- End
-- End
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
 
return table.concat(ret)
return table.concat(ret)
end
end


-- Returns a row containing a task given a title and a task table
-- Returns a row containing a task given a title and a task table
function p._GetTaskRow(title, task)
function p._getTaskRow(title, task, isDailyTask)
local ret = {}
local ret = {}
 
-- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
-- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
local hasDescription = false
local hasDescription = false
Line 873: Line 786:
end
end
local titlespan = hasDescription == true and 'rowspan="2"|' or ''
local titlespan = hasDescription == true and 'rowspan="2"|' or ''
 
-- Title
-- Title
table.insert(ret, '\r\n|-')
table.insert(ret, '\n|-')
table.insert(ret, '\r\n!'..titlespan..title)
table.insert(ret, '\n!' .. titlespan .. title)
-- Description
-- Description
if hasDescription then
if hasDescription then
table.insert(ret, '\r\n|colspan="2"|'..task.description)
table.insert(ret, '\n|colspan="2"|' .. task.description)
table.insert(ret, '\r\n|-')
table.insert(ret, '\n|-')
end
end
-- Requirements
-- Requirements
table.insert(ret, '\r\n|')
table.insert(ret, '\n|')
local requirements = {}
-- Determines order of requirements output
for _, item in ipairs(task.goals.items) do
local reqOrder = {
local itemname = GameData.getEntityByID('items', item.id).name
["items"] = 10,
table.insert(requirements, Shared.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
["monsters"] = 20,
["monsterWithItems"] = 30,
["skillXP"] = 40,
["buildings"] = 50,
["numPOIs"] = 60,
["numRefinements"] = 70
}
local reqTextPart = {}
 
local function getItemText(itemID)
local item = Items.getItemByID(itemID)
if item == nil then
return Shared.printError('Unknown item: ' .. (itemID or 'nil'))
else
return Icons.Icon({item.name, type='item'})
end
end
end
for _, monster in ipairs(task.goals.monsters) do
local function getMonsterText(monsterID)
local monstername = GameData.getEntityByID('monsters', monster.id).name
local monster = Monsters.getMonsterByID(monsterID)
table.insert(requirements, Shared.formatnum(monster.quantity)..' '..Icons.Icon({monstername, type='monster'}))
if monster == nil then
return Shared.printError('Unknown monster: ' .. (monsterID or 'nil'))
else
return Icons.Icon({Monsters.getMonsterName(monster), type='monster'})
end
end
end
for _, skill in ipairs(task.goals.skillXP) do
 
local skillname = GameData.getSkillData(skill.id).name
for goalType, goalData in pairs(task.goals) do
table.insert(requirements, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
local typeOrder = reqOrder[goalType] or 0
local goalText = nil
if type(goalData) == 'table' then
-- Goal data is a table
for goalIdx, goalObj in ipairs(goalData) do
if goalType == 'items' then
goalText = Num.formatnum(goalObj.quantity) .. ' ' .. getItemText(goalObj.id)
elseif goalType == 'monsters' then
goalText = Num.formatnum(goalObj.quantity) .. ' ' .. getMonsterText(goalObj.id)
elseif goalType == 'monsterWithItems' then
local itemsText = {}
for i, itemID in ipairs(goalObj.itemIDs) do
table.insert(itemsText, getItemText(itemID))
end
goalText = Num.formatnum(goalObj.quantity) .. ' ' .. getMonsterText(goalObj.monsterID) .. ' with ' .. table.concat(itemsText, ', ') .. ' equipped'
elseif goalType == 'skillXP' then
local skillName = GameData.getSkillData(goalObj.id).name
goalText = Num.formatnum(goalObj.quantity) .. ' ' .. Icons.Icon({skillName, type='skill'}) .. ' XP'
elseif goalType == 'buildings' then
local buildingName = p._GetBuildingByID(goalObj.id).name
goalText = Num.formatnum(goalObj.quantity) .. ' ' .. Icons.Icon({buildingName, type='building'})
elseif goalType == 'numPOIs' then
local mapName = GameData.getEntityByID(GameData.skillData.Cartography.worldMaps, goalObj.worldMapID).name
goalText = 'Discover ' .. Num.formatnum(goalObj.quantity) .. ' Points of Interest in ' .. Icons.Icon({'Cartography', type='skill'}) .. ' world map of ' .. mapName
else
goalText = Shared.printError('Unknown goal type: ' .. (goalType or 'nil'))
end
table.insert(reqTextPart, {
["goalOrder"] = typeOrder,
["subOrder"] = goalIdx,
["text"] = goalText
})
end
else
-- Goal data is another value of some type
if goalType == 'numRefinements' then
goalText = 'Refine dig site maps in ' .. Icons.Icon({'Cartography', type='skill'}) .. ' ' .. Num.formatnum(goalData) .. ' times'
else
goalText = Shared.printError('Unknown goal type: ' .. (goalType or 'nil'))
end
table.insert(reqTextPart, {
["goalOrder"] = typeOrder,
["subOrder"] = 0,
["text"] = goalText
})
end
end
end
for _, building in ipairs(task.goals.buildings) do
 
local buildingname = p._GetBuildingByID(building.id).name
table.sort(reqTextPart,
table.insert(requirements, Shared.formatnum(building.quantity)..' '..Icons.Icon({buildingname, type='building'}))
function(a, b)
if a.goalOrder == b.goalOrder then
return a.subOrder < b.subOrder
else
return a.goalOrder < b.goalOrder
end
end
)
 
local requirements = {}
for i, req in ipairs(reqTextPart) do
table.insert(requirements, req.text)
end
end
-- We don't check tasks.requirements (so far it's only used to enumerate the Tutorial tasks so you only see 1 at a time)
-- We don't check tasks.requirements (so far it's only used to enumerate the Tutorial tasks so you only see 1 at a time)
table.insert(ret, table.concat(requirements, '<br>'))
table.insert(ret, table.concat(requirements, '<br/>'))
-- Rewards
-- Rewards
table.insert(ret, '\r\n|')
table.insert(ret, '\n|')
local rewards = {}
local rewards = {}
if task.rewards.gp ~= 0 then
local rewardsVariableQty = {}
table.insert(rewards, Icons.GP(task.rewards.gp))
if task.rewards.currencies ~= nil then
end
for _, currReward in ipairs(task.rewards.currencies) do
if task.rewards.slayerCoins ~= 0 then
if isDailyTask and currReward.id ~= 'melvorD:GP' then
table.insert(rewards, Icons.SC(task.rewards.slayerCoins))
table.insert(rewardsVariableQty, Icons._Currency(currReward.id))
elseif not isDailyTask then
table.insert(rewards, Icons._Currency(currReward.id, currReward.quantity))
end
end
end
end
for _, item in ipairs(task.rewards.items) do
for _, item in ipairs(task.rewards.items) do
local itemname = GameData.getEntityByID('items', item.id).name
local itemname = GameData.getEntityByID('items', item.id).name
table.insert(rewards, Shared.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
table.insert(rewards, Num.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
end
end
for _, skill in ipairs(task.rewards.skillXP) do
for _, skill in ipairs(task.rewards.skillXP) do
local skillname = GameData.getSkillData(skill.id).name
if not (isDailyTask and skill.id == 'melvorD:Township') then
table.insert(rewards, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
local skillname = GameData.getSkillData(skill.id).name
table.insert(rewards, Num.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
end
end
end
for _, townshipResource in ipairs(task.rewards.townshipResources) do
for _, townshipResource in ipairs(task.rewards.townshipResources) do
local resourcename = p._getResourceByID(townshipResource.id).name
local resourcename = p._getResourceByID(townshipResource.id).name
table.insert(rewards, Shared.formatnum(townshipResource.quantity)..' '..Icons.Icon({resourcename, type='resource'}))
table.insert(rewards, Num.formatnum(townshipResource.quantity)..' '..Icons.Icon({resourcename, type='resource'}))
end
if not Shared.tableIsEmpty(rewardsVariableQty) then
table.insert(ret, '[[Township#Casual Tasks|Variable]] ' .. table.concat(rewardsVariableQty, ', ') .. '<br/>')
end
table.insert(ret, table.concat(rewards, '<br/>'))
 
-- Unlock requirements, daily task specific
if isDailyTask then
table.insert(ret, '\n|' .. Shop.getRequirementString(task.requirements))
end
end
table.insert(ret, table.concat(rewards, '<br>'))
return table.concat(ret)
return table.concat(ret)
end
end


-- Returns all the tasks of a given category
-- Returns all the tasks of a given category
function p.GetTaskTable(frame)
-- TODO: Support casual tasks
function p.getTaskTable(frame)
local category = frame.args ~= nil and frame.args[1] or frame
local category = frame.args ~= nil and frame.args[1] or frame
local categoryData = GameData.getEntityByID(Township.taskCategories, category)
local categoryData = GameData.getEntityByName(Township.taskCategories, category)
if categoryData == nil then
local taskData, categoryName, isDailyTask = nil, nil, false
if category == 'Daily' then
isDailyTask = true
taskData = Township.casualTasks
categoryName = 'Casual'
elseif categoryData ~= nil then
taskData = Township.tasks
categoryName = categoryData.name
else
return Shared.printError('Invalid task category specified: ' .. (tostring(category) or 'nil'))
return Shared.printError('Invalid task category specified: ' .. (tostring(category) or 'nil'))
end
end
local categoryname = categoryData.name
 
local taskcount = 0
local taskcount = 0
local ret = {}
local ret = {}
table.insert(ret, '\r\n{| class="wikitable lighttable" style="text-align:left"')
table.insert(ret, '{| class="wikitable lighttable stickyHeader" style="text-align:left"')
table.insert(ret, '\r\n!Task')
table.insert(ret, '\n|- class="headerRow-0"')
table.insert(ret, '\r\n!Requirements')
table.insert(ret, '\n!Task')
table.insert(ret, '\r\n!Rewards')
table.insert(ret, '\n!Requirements')
table.insert(ret, '\n!Rewards')
if isDailyTask then
table.insert(ret, '<br/>(In addition to [[Township#Casual Tasks|Variable]] ' .. Icons._Currency('melvorD:GP') .. ' & ' .. Icons.Icon({'Township', type='skill', notext=true}) .. ' XP)')
end
if isDailyTask then
table.insert(ret, '\n!Unlock Requirements')
end
for _, task in ipairs(Township.tasks) do
for _, task in ipairs(taskData) do
-- Filter out other categories
-- Filter out other categories
if task.category == category then
local categoryID, categoryNS, categoryLocalID = '', '', ''
if categoryData ~= nil then
categoryID = categoryData.id
categoryNS, categoryLocalID = Shared.getLocalID(categoryID)
end
if isDailyTask or task.category == categoryID or task.category == categoryLocalID then
taskcount = taskcount + 1
taskcount = taskcount + 1
local title = categoryname..' '..taskcount
local title = categoryName .. ' ' .. taskcount
table.insert(ret, p._GetTaskRow(title, task))
table.insert(ret, p._getTaskRow(title, task, isDailyTask))
end
end
end
end
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
return table.concat(ret)
return table.concat(ret)
end
end


-- Returns a table containing all the tasks that reference an item or monster
-- Returns a table containing all the tasks that reference an item or monster
-- e.g. p.GetTaskReferenceTable({'Chicken Coop', 'dungeon'})
-- e.g. p.getTaskReferenceTable({'Chicken Coop', 'dungeon'})
-- name = item or monster name
-- name = item or monster name
-- type = 'item' or 'monster' or 'dungeon'
-- type = 'item' or 'monster' or 'dungeon'
function p.GetTaskReferenceTable(frame)
function p.getTaskReferenceTable(frame)
-- Returns a set containing all the desired IDs
-- Returns a set containing all the desired IDs
local function GetReferenceIDs(referenceName, referenceType)
local function GetReferenceIDs(referenceName, referenceType)
Line 966: Line 988:
if referenceType == 'dungeon' then
if referenceType == 'dungeon' then
-- We get the tasks associated with all monsters in the dungeon
-- We get the tasks associated with all monsters in the dungeon
local monsters = GameData.getEntityByName('dungeons', referenceName).monsterIDs
local area = nil
local areaTypes = {'dungeons', 'abyssDepths'}
for _, areaType in ipairs(areaTypes) do
area = GameData.getEntityByName(areaType, referenceName)
if area ~= nil then
break
end
end
local monsters = area.monsterIDs
for _, monster in ipairs(monsters) do
for _, monster in ipairs(monsters) do
IDs[monster] = true
IDs[monster] = true
Line 975: Line 1,005:
end
end
if referenceType == 'monster' then
if referenceType == 'monster' then
IDs[GameData.getEntityByName('monsters', referenceName).id] = true
IDs[Monsters.getMonster(referenceName).id] = true
end
end
return IDs
return IDs
Line 990: Line 1,020:
return referenceType == 'item' and searchItems or searchMonsters
return referenceType == 'item' and searchItems or searchMonsters
end
end
 
local args = frame.args ~= nil and frame.args or frame
local args = frame.args ~= nil and frame.args or frame
local referenceName = Shared.fixPagename(args[1])
local referenceName = Shared.fixPagename(args[1])
Line 1,002: Line 1,032:
return referenceIDs[entry.id] ~= nil
return referenceIDs[entry.id] ~= nil
end
end
for _, searchTable in ipairs(GetSearchTables(task)) do
for _, searchTable in pairs(GetSearchTables(task)) do -- ipairs won't work if first table is nil
-- Check to see if the table contains any of the IDs in referenceIDs
-- Check to see if the table contains any of the IDs in referenceIDs
if searchTable[1] ~= nil then -- Make sure table is not empty
if searchTable[1] ~= nil then -- Make sure table is not empty
Line 1,017: Line 1,047:
return ''
return ''
end
end
 
-- Build the table
-- Build the table
local ret = {}
local ret = {}
table.insert(ret, '==Tasks==')
table.insert(ret, '==Tasks==')
table.insert(ret, '\r\n{| class="wikitable" style="text-align:left"')
table.insert(ret, '\n{| class="wikitable" style="text-align:left"')
table.insert(ret, '\r\n!Task')
table.insert(ret, '\n!Task')
table.insert(ret, '\r\n!Requirements')
table.insert(ret, '\n!Requirements')
table.insert(ret, '\r\n!Rewards')
table.insert(ret, '\n!Rewards')
for _, task in ipairs(tasks) do
for _, task in ipairs(tasks) do
local categoryname = GameData.getEntityByID(Township.taskCategories, task.category).name
-- Some categories have a local ID, resolve this before looking up the task category
local taskNS, taskLocalID = Shared.getLocalID(task.id)
local catID = Shared.getNamespacedID(taskNS,  task.category)
local categoryname = GameData.getEntityByID(Township.taskCategories, catID).name
local title = '[[Township/Tasks#'..categoryname..'|'..categoryname..']]'
local title = '[[Township/Tasks#'..categoryname..'|'..categoryname..']]'
table.insert(ret, p._GetTaskRow(title, task))
table.insert(ret, p._getTaskRow(title, task, false))
end
end
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
return table.concat(ret)
return table.concat(ret)
end
-- TODO Temporary functions, these exist to enable renaming of functions
-- above without causing templates to break until they are migrated to these
-- newly-named functions
function p.GetWorshipTable()
return p.getWorshipTable()
end
end


return p
return p