Module:Township: Difference between revisions

Fix township building sticky header and add cell borders
(Resource icons and stats are now accessed via Icons.Icon. Added GetBuildingTable)
(Fix township building sticky header and add cell borders)
 
(46 intermediate revisions by 3 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 GameData = require('Module:GameData')
local GameData = require('Module:GameData')
local Constants = require('Module:Constants')
local Constants = require('Module:Constants')
local Num = require('Module:Number')


local p = {}


local Township = GameData.getSkillData('melvorD:Township')
p.Township = Township


local Township = GameData.getSkillData('melvorD:Township')
-- Gets a Township building by ID, e.g. melvorF:Hunters_Cabin
function p._getBuildingByID(id)
-- Check for the special statue case
if id == 'melvorF:Statues' then
local building = Shared.clone(GameData.getEntityByID(Township.buildings, id))
building.name = 'Statue of Worship'
return building
else
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


local p = {}
-- Gets a resource from id
function p._getResourceByID(id)
return GameData.getEntityByID(Township.resources, id)
end


-- Returns the namespace name (eventually we should use an icon)
-- Given a building, find the next building upgrade
function p.PLACEHOLDER_NAMESPACE_ICON(namespace)
function p._getBuildingUpgrade(building)
local namespaces = {
local function checkFunc(entity)
melvorD = 'Demo',
return entity.upgradesFrom ~= nil and entity.upgradesFrom == building.id
melvorF = 'Full',
end
melvorTotH = 'TotH'
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
}
if #upgradesTo > 0 then
return namespaces[namespace]
return upgradesTo[1]
end
return nil
end
end


-- Returns the recipe for the item of a desired skill.
-- Given a building, find the building's downgrade
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data so we instead use this quick function
function p._getBuildingDowngrade(building)
function p._FindItemRecipes(itemid, skill)
if building.upgradesFrom ~= nil then
return p._getBuildingByID(building.upgradesFrom)
-- No skill? No recipes
if skill == nil then
return {}
end
end
return nil
-- the key name for each skill in the json file
end
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 = {}
-- Given a building and biome ID, returns the cost of constructing the building
-- within that biome as a human readable text string. Returns nil if the building
local SkillData = GameData.getSkillData(skill)
-- cannot be built within that biome.
local recipes = skill_recipe_keys[skill].recipes
function p._getBuildingCostText(building, biomeID, delimiter)
local productID = skill_recipe_keys[skill].productID
-- Basic validation of inputs
if type(building) == 'table' and building.cost ~= nil and biomeID ~= nil then
if SkillData[recipes] ~= nil then
local delim = delimiter
for _, recipe in ipairs(SkillData[recipes]) do
if delim == nil then
-- Special case for Herblore
delim = ', '
if skill == 'melvorD:Herblore' then
end
-- Iterate over the 4 potion tiers
for i, costDef in ipairs(building.cost) do
for _, potionid in ipairs(recipe[productID]) do
if costDef.biomeID == biomeID then
if itemid == potionid then
local resultPart = {}
table.insert(results, Shared.clone(recipe))
for j, cost in ipairs(costDef.cost) do
local resData = p._getResourceByID(cost.id)
if resData ~= nil then
table.insert(resultPart, Icons.Icon({resData.name, type='resource', notext=true, nolink=true, qty=cost.quantity}))
end
end
end
end
-- Base case
return table.concat(resultPart, delim)
else
end
if itemid == recipe[productID] then
end
table.insert(results, Shared.clone(recipe))
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
end
if not found then
table.insert(biomeGroups, { biomeIDs = { biomeID }, cost = currentBiomeCost})
end
end
end
end
return biomeGroups
return results
end
end


-- Returns a list of all the Township resources
-- Given a building, returns a text string repesenting the building costs for all biomes
function p._ResourcesData()
function p._getBuildingGroupedCostText(building)
local resultPart = {}
-- Get a sorted list of all the resources
local biomeGroups = p._getBuildingGroupedCosts(building)
local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder[1].ids)
if Shared.tableCount(biomeGroups) == 1 then
resources = Shared.clone(resources)
-- If only one entry then simply output the cost
table.insert(resultPart, biomeGroups[1].cost)
return resources
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


-- Returns a list of all the Township resources along with the Trader's trade ratios
-- Given a building and biome ID, returns a string displaying the building's benefits,
function p._TraderData()
-- or nil if no benefits
-- Get the list of resources
function p._getBuildingBenefitText(building, biomeID, includeModifiers, delimiter)
local resources = p._ResourcesData()
-- Basic validation of inputs
if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
-- Get the list of tradeable items
local delim = delimiter
-- See township.js -> TownshipResource.buildResourceItemConversions for the calculation of valid items
if delim == nil then
local function matchNone(item)
delim = ', '
return false
end
local includeMods = includeModifiers
if includeMods == nil then
includeMods = false
end
 
local providesData = nil
for i, provides in ipairs(building.provides) do
if provides.biomeID == biomeID then
providesData = provides
break
end
end
 
if providesData ~= nil then
local resultPart = {}
local stats = {
population = 'Population',
happiness = 'Happiness',
education = 'Education',
storage = 'Storage',
worship = 'Worship'
}
local resourceText = function(resName, resType, quantity)
local elemClass = (quantity < 0 and 'text-negative') or 'text-positive'
local resIcon = Icons.Icon({resName, type=resType, notext=true})
return resIcon .. '&nbsp;<span class="' .. elemClass .. '">' .. Shared.numStrWithSign(quantity) .. '</span>'
end
 
-- Resources
if providesData.resources ~= nil then
for i, resource in ipairs(providesData.resources) do
local resData = p._getResourceByID(resource.id)
if resData ~= nil and resource.quantity ~= 0 then
table.insert(resultPart, resourceText(resData.name, 'resource', resource.quantity))
end
end
end
 
-- Other stats
for key, stat in pairs(stats) do
local quantity = providesData[key]
if quantity ~= nil and quantity ~= 0 then
table.insert(resultPart, resourceText(stat, 'township', quantity))
end
end
 
-- Modifiers
if includeMods and building.modifiers ~= nil then
table.insert(resultPart, Constants.getModifiersText(building.modifiers))
end
 
if not Shared.tableIsEmpty(resultPart) then
return table.concat(resultPart, delim)
end
end
end
end
local function matchFood(item)
end
return item.type == 'Food' and (not string.match(item.id, '_Perfect')) and item.category ~= 'Farming' and (not item.ignoreCompletion)
 
-- 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
end
local function matchLogs(item)
local biomeGroups = {}
return item.type == 'Logs'
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
end
local function matchOre(item)
return biomeGroups
return item.type == 'Ore' and item.id ~= 'melvorTotH:Meteorite_Ore'
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
end
local function matchCoal(item)
local resultPart = {}
return item.id == 'melvorD:Coal_Ore'
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
end
local function matchBar(item)
return table.concat(resultPart, '<br/>')
return item.type == 'Bar' and item.id ~= 'melvorTotH:Meteorite_Bar'
end
 
-- Returns a sorted list of all Township buildings
function p._sortedBuildings(keepUnsorted)
local ku = true
if keepUnsorted ~= nil then
ku = keepUnsorted
end
end
local function matchHerb(item)
return GameData.sortByOrderTable(Township.buildings, Township.buildingDisplayOrder, ku)
return item.type == 'Herb'
end
 
-- Gets the Township level and population requirements for a tier
-- Returns {population=X, level=X}
function p._getTierRequirements(tier)
return Township.populationForTier[tier]
end
 
-- Returns a string containing the Township level and population requirements for a tier
function p._getTierText(tier)
local tierData = p._getTierRequirements(tier)
if tierData ~= nil then
local tierText = Icons._SkillReq('Township', tierData.level, false)
if tierData.population > 0 then
tierText = tierText .. '<br/>' .. Icons.Icon({'Population', type='township', notext=true}) .. '&nbsp;' .. Shared.formatnum(tierData.population)
end
return tierText
end
end
local function matchEssence(item)
end
return item.id == 'melvorD:Rune_Essence' or item.id == 'melvorTotH:Pure_Essence'
 
-- Generates a table of all seasons, their type/requirements, and modifiers
function p.getSeasonTable(frame)
-- Manual data specifying the worship requirement for those rare seasons
local seasonReqs = {
["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'}),
["Lemon"] = Icons.Icon({'Ancient_Relics', 'Ancient Relics', img='Ancient Relics'})
}
 
local seasons = Shared.shallowClone(Township.seasons)
table.sort(seasons, function(a, b) return a.order < b.order end)
 
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2" | Season\n!Type\n!Modifiers')
 
for i, season in ipairs(seasons) do
local ns, localSeasonID = Shared.getLocalID(season.id)
local reqs = seasonReqs[localSeasonID]
table.insert(resultPart, '\n|-')
table.insert(resultPart, '\n|class="table-img"| ' .. Icons.Icon({season.name, type='township', size=50, nolink=true, notext=true}))
table.insert(resultPart, '\n| ' .. Icons.Icon({season.name, type='township', nolink=true, noicon=true}))
table.insert(resultPart, '\n| ' .. (season.order <= 3 and 'Regular' or 'Rare'))
if reqs ~= nil then
table.insert(resultPart, '<br/>Requires ' .. reqs)
end
table.insert(resultPart, '\n| ' .. Constants.getModifiersText(season.modifiers))
end
end
local function matchLeather(item)
table.insert(resultPart, '\n|}')
return item.id == 'melvorD:Leather'
 
return table.concat(resultPart)
end
 
-- Generates a table listing all biomes and their associated requirements
function p.getBiomeTable(frame)
local resultPart = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="2"| Requirements')
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', 'Population', img='Population', type='township', section='Population' }))
 
for i, biome in ipairs(Township.biomes) do
local reqs = p._getTierRequirements(biome.tier)
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|style="text-align:right"| ' .. reqs.level)
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. reqs.population .. '"| ' .. Shared.formatnum(reqs.population))
end
end
local function matchPotion(item)
table.insert(resultPart, '\n|}')
return item.type == 'Potion' and string.match(item.id, '_IV')
 
return table.concat(resultPart)
end
 
-- Generates a table showing which buildings can be built in which biomes
-- Skips upgraded buildings
function p.getBuildingBiomeTable(frame)
local tbl = mw.html.create('table')
:addClass('wikitable sortable stickyHeader')
:css('text-align', 'center')
 
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')
 
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' }))
for _, biome in ipairs(Township.biomes) do
local reqs = p._getTierRequirements(biome.tier)
header:tag('th')
:wikitext(Icons.Icon({biome.name, type='biome', notext=true, nolink=true}).. '<br/>' .. biome.name)
level:tag('td')
:wikitext(Num.formatnum(reqs.level))
pop:tag('td')
:wikitext(Num.formatnum(reqs.population))
end
end
local function matchClothing(item)
local valid_tiers = {'Leather', 'Hard Leather', 'Dragonhide', 'Elderwood', 'Revenant', 'Carrion'}
tbl:node(header)
for _, tier in ipairs(valid_tiers) do
tbl:node(level)
if item.tier == tier then
tbl:node(pop)
return true
 
for _, _building in ipairs(p._sortedBuildings(false)) do
-- Fix melvorF:Statues
local building = p._getBuildingByID(_building.id)
-- Skip upgraded buildings
if p._getBuildingDowngrade(building) == nil then
-- Populate the biome habitability data
local buildingBiomes = {}
-- Set all valid biomes to true
for _, biomeid in ipairs(building.biomes) do
buildingBiomes[biomeid] = true
end
 
local trow = tbl:tag('tr')
trow:tag('th')
:css('text-align', 'left')
:attr('data-sort-value', building.name)
:wikitext(Icons.Icon({building.name, type='building'}))
 
for _, biome in ipairs(Township.biomes) do
if buildingBiomes[biome.id] then
trow:tag('td')
:addClass('table-positive')
:wikitext('✓')
else
trow:tag('td')
end
end
end
end
end
return false
end
end


local traderMatchesList = {
return tostring(tbl)
['melvorF:GP'] = {traderMatches = matchNone},
end
['melvorF:Food'] = {traderMatches = matchFood},
 
['melvorF:Wood'] = {traderMatches = matchLogs},
-- Generates a table contaning each building plus their relevant information
['melvorF:Stone'] = {traderMatches = matchOre},
function p.getBuildingTable(frame)
['melvorF:Ore'] = {traderMatches = matchOre},
local resultPart = {}
['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
-- Change structure of biomes data for ease of use later
resource.itemConversions = Shared.clone(GameData.getEntities('items', traderMatchesList[resource.id].traderMatches))
local biomesByID = {}
for i, biome in ipairs(Township.biomes) do
biomesByID[biome.id] = biome
end
end
 
-- Calculate the trader's conversion ratios
-- Generate table header
-- See township.js TownshipResource.getBaseConvertToTownshipRatio and TownshipResource.getBaseConvertFromTownshipRatio for the conversion prices
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
for _, resource in ipairs(resources) do
table.insert(resultPart, '\n|- class="headerRow-0"')
if resource.id == 'melvorF:Food' then
table.insert(resultPart, '\n!colspan="2"|Building\n!Requirements\n!Max Built')
for _, item in ipairs(resource.itemConversions) do
table.insert(resultPart, '\n!Biomes\n!Cost\n!Provides')
item.toTownship = math.max(math.floor(1000/(item.healsFor*10)), 2)
 
item.fromTownship = item.healsFor*5*6
local buildings = p._sortedBuildings(false)
 
for i, building in ipairs(buildings) do
-- Number of rows per building is dictated by number of biomes
local buildingName = (building.id == 'melvorF:Statues' and 'Statue of Worship') or building.name
local firstRow = true
local rowCount = Shared.tableCount(building.biomes)
local rowSpan = (rowCount > 1 and ' rowspan="' .. rowCount .. '"') or ''
local rowSpanOnly = (rowCount > 1 and '|' .. rowSpan) or ''
for j, biomeID in ipairs(building.biomes) do
local biome = biomesByID[biomeID]
if firstRow then
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' .. 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|style="text-align:right"' .. rowSpan .. '| ' .. building.maxUpgrades)
firstRow = false
else
table.insert(resultPart, '\n|-')
end
end
elseif resource.id == 'melvorF:Planks' then
-- This section generates by biome rows
for _, item in ipairs(resource.itemConversions) do
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
item.toTownship = math.max(math.floor(3000/math.max(item.sellsFor, 1)), 2)
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
item.fromTownship = math.max(math.ceil(item.sellsFor/2)*6, 1);
local providesText = p._getBuildingBenefitText(building, biomeID)
end
if building.modifiers ~= nil then
elseif resource.id == 'melvorF:Rune_Essence' then
local modText = Constants.getModifiersText(building.modifiers)
for _, item in ipairs(resource.itemConversions) do
if providesText == nil then
item.toTownship = 5
providesText = modText
item.fromTownship = (item.sellsFor+1)*10*6
else
end
providesText = providesText .. '<br/>' .. modText
elseif resource.id == 'melvorF:Leather' then
end
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
table.insert(resultPart, '\n| ' .. (providesText or ''))
end
end
end
end
return resources
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
end
end


-- 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 = {}
local resources = p._TraderData()
 
-- Build table header
-- Build the text
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
local ret = {}
table.insert(resultPart, '\n|- class="headerRow-0"')
for _, resource in ipairs(resources) do
table.insert(resultPart, '\n!colspan="2"| Item\n!Description\n!style="min-width:60px"| Cost\n!Requirements')
if #resource.itemConversions ~= 0 then -- Skips GP
 
local ret_resource = {}
for i, tsResource in ipairs(Township.itemConversions.fromTownship) do
local res = GameData.getEntityByID(Township.resources, tsResource.resourceID)
-- Header
for j, tradeDef in ipairs(tsResource.items) do
table.insert(ret_resource, '\r\n==='..resource.name..'===')
local item = Items.getItemByID(tradeDef.itemID)
table.insert(ret_resource, '\r\n{| class="wikitable sortable stickyHeader"')
local itemDesc = item.customDescription
table.insert(ret_resource, '\r\n|- class="headerRow-0"')
if itemDesc == nil then
table.insert(ret_resource, '\r\n!Item')
if item.modifiers ~= nil then
table.insert(ret_resource, '\r\n!Name')
itemDesc = Constants.getModifiersText(item.modifiers, false, true)
table.insert(ret_resource, '\r\n!DLC')
table.insert(ret_resource, '\r\n!Level')
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.Icon({item.name, type='item', noicon=true}))
-- DLC
local item_namespace, item_localid = GameData.getLocalID(item.id)
table.insert(ret_resource, '\r\n|style="text-align:center"|'..p.PLACEHOLDER_NAMESPACE_ICON(item_namespace))
-- 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


-- Gets the associated skill of a resource by id
-- Generates a table showing all the worship options
local resource_skill = {
function p.getWorshipTable()
['melvorF:GP'] = {skill = nil},
local function getCheckpointCell(checkpoint)
['melvorF:Food'] = {skill = 'melvorD:Cooking'},
return '\n|-\n!' .. checkpoint .. '%<br/>' .. Shared.formatnum(checkpoint * Township.maxWorship / 100) .. '/' .. Shared.formatnum(Township.maxWorship)
['melvorF:Wood'] = {skill = 'melvorD:Woodcutting'},
end
['melvorF:Stone'] = {skill = 'melvorD:Mining'},
 
['melvorF:Ore'] = {skill = 'melvorD:Mining'},
local worships = GameData.getEntities(Township.worships, function(w) return not w.isHidden end)
['melvorF:Coal'] = {skill = 'melvorD:Mining'},
local ret = {}
['melvorF:Bar'] = {skill = 'melvorD:Smithing'},
 
['melvorF:Herbs'] = {skill = 'melvorD:Farming'},
table.insert(ret, '{| class="wikitable stickyHeader"')
['melvorF:Rune_Essence'] = {skill = 'melvorD:Mining'},
table.insert(ret, '\n!' .. Icons.Icon({'Worship', type='township', nolink=true}))
['melvorF:Leather'] = {skill = nil},
-- Names
['melvorF:Potions'] = {skill = 'melvorD:Herblore'},
for _, worship in ipairs(worships) do
['melvorF:Planks'] = {skill = 'melvorD:Woodcutting'},
table.insert(ret, '\n!' .. Icons.Icon({worship.name, type='monster', size=50}) .. Icons.Icon({'Statue of ' .. worship.name, type='building', size=50, notext=true}))
['melvorF:Clothing'] = {skill = 'melvorD:Crafting'}
end
}
 
function p._GetResourceSkill(id)
-- Requirements
return resource_skill[id].skill
table.insert(ret, '\n|-\n!Requirements')
end
for _, worship in ipairs(worships) do
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
 
-- Base modifiers
table.insert(ret, getCheckpointCell(0))
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.modifiers))
end


-- Gets a Township building by ID, e.g. melvorF:Hunters_Cabin
-- Checkpoint modifiers
function p._GetBuildingByID(id)
for i, checkpoint in ipairs(Township.worshipCheckpoints) do
return GameData.getEntityByID(Township.buildings, id)
table.insert(ret, getCheckpointCell(checkpoint))
end
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.checkpoints[i]))
end
end


-- Gets a Township building by name, e.g. Hunters Cabin
-- Total sum
function p._GetBuildingByName(name)
table.insert(ret, '\n|-\n!Total')
return GameData.getEntityByName(Township.buildings, name)
for _, worship in ipairs(worships) do
end
local modifiers = Shared.clone(worship.modifiers)
for _, checkpoint in ipairs(worship.checkpoints) do
for modifier, magnitude in pairs(checkpoint) do
local swappedModifier = string.sub(modifier, 1, string.len('increased')) == 'increased' and string.gsub(modifier, 'increased', 'decreased') or string.gsub(modifier, 'decreased', 'increased')
-- The modifier already exists, so we add the two modifiers together
if modifiers[modifier] ~= nil then
modifiers[modifier] = modifiers[modifier] + magnitude
-- The inverse modifier already exists, so we subtract the negative value of the new modifier
elseif modifiers[swappedModifier] ~= nil then
modifiers[swappedModifier] = modifiers[swappedModifier] - magnitude
-- The modifier does not exist, so create the modifier
else
modifiers[modifier] = magnitude
end
end
end
table.insert(ret, '\n|' .. Constants.getModifiersText(modifiers))
end
table.insert(ret, '\n|}')


-- Gets the Township level and population requirements for a tier
return table.concat(ret)
-- Returns {population=X, level=X}
function p._GetTierRequirements(tier)
return Township.populationForTier[tier]
end
end


-- Gets a building and prepares all the relevant stats for the building
-- Gets a building and prepares all the relevant stats for the building, presented as an infobox
function p.GetBuildingTable(frame)
function p.getBuildingInfoBox(frame)
local name = frame.args ~= nil and frame.args[1] or frame
local name = frame.args ~= nil and frame.args[1] or frame
local building = Shared.clone(p._GetBuildingByName(name))
local building = p._getBuildingByName(name)
local resources = p._ResourcesData()
if building == nil then
return Shared.printError('No building named "' .. name .. '" exists in the data module')
end
 
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._GetTierRequirements(building.tier)
local tier = p._getTierText(building.tier)
table.insert(ret, '\r\n|-\r\n| <b>Tier '..building.tier..'</b><ul><li>'..Icons._SkillReq('Township', tier.level, false)..'</li><li>'..Icons.Icon({'Population', type='township', notext=true})..'&nbsp;'..tier.population..'</li></ul>')
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>')
if building.upgradesFrom ~= nil then
local upgradesFrom = p._getBuildingDowngrade(building)
local upgradesFromBuilding = p._GetBuildingByID(building.upgradesFrom)
if upgradesFrom ~= nil then
table.insert(ret, '<br>'..Icons.Icon({upgradesFromBuilding.name, type='building'})..'<br>')
table.insert(ret, '<br/>' .. Icons.Icon({upgradesFrom.name, type='building'}))
end
end
-- Cost
-- Cost
local cost = {}
--table.insert(ret, '<br/>' .. p._getBuildingGroupedCostText(building))
for _, resource in ipairs(building.cost) do
local function getGroupedText(building, groupFunc)
local resource_data = GameData.getEntityByID(resources, resource.id)
local biomeGroups = groupFunc(building)
table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..resource.quantity)
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
end
table.insert(ret, table.concat(cost, ', '))
 
table.insert(ret, '\n' .. getGroupedText(building, p._getBuildingGroupedCosts))
 
-- Upgrades To
local upgradesTo = p._getBuildingUpgrade(building)
if upgradesTo ~= nil then
table.insert(ret, '\n|-\n| <b>Upgrades To:</b>')
table.insert(ret, '<br/>' .. Icons.Icon({upgradesTo.name, type='building'}))
table.insert(ret, '\n' .. getGroupedText(upgradesTo, p._getBuildingGroupedCosts))
end
 
-- Maximum built
local biomeCount = Shared.tableCount(building.biomes)
local maxText = Shared.formatnum(building.maxUpgrades)
if biomeCount > 1 then
maxText = maxText .. ' per biome, ' .. Shared.formatnum(biomeCount * building.maxUpgrades) .. ' total'
end
table.insert(ret, '\n|-\n| <b>Maximum Built:</b><br/>' .. maxText)
-- Upgrades To
-- Benefits
local function checkFunc(entity)
local benefits = p._getBuildingGroupedBenefitText(building)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == building.id
if benefits ~= nil and benefits ~= '' then
table.insert(ret, '\n|-\n| <b>Provides:</b><br/>' .. benefits)
end
 
-- Biomes
table.insert(ret, '\n|-\n| <b>Biomes:</b>')
for _, biomeid in ipairs(building.biomes) do
local biome = GameData.getEntityByID(Township.biomes, biomeid)
table.insert(ret, '<br/>' .. Icons.Icon({biome.name, type='biome', nolink=true}))
end
 
-- End
table.insert(ret, '\n|}')
return table.concat(ret)
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)
if building == nil then
return Shared.printError('No building named "' .. buildingname .. '" exists in the data module')
end
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
 
if #upgradesTo > 0 then
-- Let's find the base building
table.insert(ret, '\r\n|-\r\n| <b>Upgrades To:</b><br>'..Icons.Icon({upgradesTo[1].name, type='building'}))
local baseBuilding = building
-- Cost
while true do
local upgrade_cost = {}
local previousBuilding = p._getBuildingDowngrade(baseBuilding)
for _, resource in ipairs(upgradesTo[1].cost) do
if previousBuilding ~= nil then
local resource_data = GameData.getEntityByID(resources, resource.id)
baseBuilding = previousBuilding
table.insert(upgrade_cost, Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..resource.quantity)
else
break
end
end
table.insert(ret, '<br>'..table.concat(upgrade_cost, ', '))
end
end


-- Fixed benefits
-- Let's make a list of all the buildings
local benefits = {}
-- Return empty string if there is only 1 building in the upgrade chain (i.e. no upgrades/downgrades)
local stats = {
local buildingList = {}
population = 'Population',
local _curBuilding = baseBuilding
happiness = 'Happiness',
while true do
education = 'Education',
table.insert(buildingList, _curBuilding)
storage = 'Storage',
_curBuilding = p._getBuildingUpgrade(_curBuilding)
deadStorage = 'Dead Storage'
if _curBuilding == nil then
}
break
for key, stat in pairs(stats) do
if building.provides[key] ~= nil and building.provides[key] ~= 0 then
table.insert(benefits, Icons.Icon({stat, type='township', notext=true})..'&nbsp;'..building.provides[key])
end
end
end
end
if #benefits > 0 then
if #buildingList == 1 then
table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..table.concat(benefits, ', '))
return ''
end
end


-- Production
local ret = {}
local production = p._GetBuildingBaseProduction(building)
table.insert(ret, '\n== Upgrade Chart ==')
if production ~= nil then
table.insert(ret, '\n{| class="wikitable" style="text-align:center"')
table.insert(ret, '\r\n|-\r\n| <b>Base Production per '..Icons.Icon({'Workers', type='township', notext=true})..':</b><br>')
 
table.insert(ret, production)
-- Name
table.insert(ret, '\n|-\n!colspan="2"| Name')
for _, building in ipairs(buildingList) do
table.insert(ret, '\n!' .. Icons.getExpansionIcon(building.id) .. Icons.Icon({building.name, type='building'}))
end
end


-- Modifiers
-- Tier
if building.modifiers ~= nil and not Shared.tableIsEmpty(building.modifiers) then
table.insert(ret, '\n|-\n!colspan="2"| Requirements')
table.insert(ret, '\r\n|-\r\n| <b>Modifiers:</b>\r\n'..Constants.getModifiersText(building.modifiers, true))
for _, building in ipairs(buildingList) do
table.insert(ret, '\n|' .. p._getTierText(building.tier))
end
end
 
-- Biomes
-- Cost
table.insert(ret, '\r\n|-\r\n| <b>Biomes:</b><br><ul>')
local biomeCount = Shared.tableCount(baseBuilding.biomes)
for _, biomeid in ipairs(building.biomes) do
table.insert(ret, '\n|-\n!rowspan="' .. biomeCount .. '"| Cost')
local biomename = GameData.getEntityByID(Township.biomes, biomeid).name
local firstBiome = true
-- Optional hidden bonus/penalty for building
for _, biomeID in ipairs(baseBuilding.biomes) do
local modifier = nil
local biome = GameData.getEntityByID(Township.biomes, biomeID)
if #building.biomeModifiers > 0 then
table.insert(ret, (firstBiome and '' or '\n|-') .. '\n! ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
modifier = GameData.getEntityByProperty(building.biomeModifiers, 'biomeID', biomeid)
for _, building in ipairs(buildingList) do
local cost = p._getBuildingCostText(building, biomeID)
table.insert(ret, '\n| ' .. cost)
end
end
if modifier ~= nil then
firstBiome = false
local color = modifier.value < 0 and 'red' or 'green'
end
local modifier_value = Shared.numStrWithSign(modifier.value)
 
table.insert(ret, '<li style="color:'..color..'"><b>'..biomename..' ('..modifier_value..'%)</b></li>')
-- Benefits
else
local benefitText = {}
table.insert(ret, '<li>'..biomename..'</li>')
table.insert(benefitText, '\n|-\n!rowspan="' .. biomeCount .. '"| Benefits')
firstBiome = true
local hasText = false
for _, biomeID in ipairs(baseBuilding.biomes) do
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
local benefit = p._getBuildingBenefitText(building, biomeID, true) or ''
if not hasText and benefit ~= '' then
hasText = true
end
table.insert(benefitText, '\n| ' .. benefit)
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
 
-- End
-- End
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
 
return table.concat(ret)
return table.concat(ret)
end
end


-- Given a resource id, return the job id
-- Returns a row containing a task given a title and a task table
-- e.g. melvorF:Bar -> melvorF:Blacksmith
function p._getTaskRow(title, task, isDailyTask)
function p._GetJobFromResource(resource_id)
local ret = {}
local job = GameData.getEntityByProperty(Township.jobs, 'produces', resource_id)
 
return job.id
-- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
local hasDescription = false
if task.description ~= nil then
hasDescription = true
end
local titlespan = hasDescription == true and 'rowspan="2"|' or ''
 
-- Title
table.insert(ret, '\n|-')
table.insert(ret, '\n!' .. titlespan .. title)
-- Description
if hasDescription then
table.insert(ret, '\n|colspan="2"|' .. task.description)
table.insert(ret, '\n|-')
end
-- Requirements
table.insert(ret, '\n|')
-- Determines order of requirements output
local reqOrder = {
["items"] = 10,
["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
local function getMonsterText(monsterID)
local monster = Monsters.getMonsterByID(monsterID)
if monster == nil then
return Shared.printError('Unknown monster: ' .. (monsterID or 'nil'))
else
return Icons.Icon({Monsters.getMonsterName(monster), type='monster'})
end
end
 
for goalType, goalData in pairs(task.goals) do
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 = Shared.formatnum(goalObj.quantity) .. ' ' .. getItemText(goalObj.id)
elseif goalType == 'monsters' then
goalText = Shared.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 = Shared.formatnum(goalObj.quantity) .. ' ' .. getMonsterText(goalObj.monsterID) .. ' with ' .. table.concat(itemsText, ', ') .. ' equipped'
elseif goalType == 'skillXP' then
local skillName = GameData.getSkillData(goalObj.id).name
goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. Icons.Icon({skillName, type='skill'}) .. ' XP'
elseif goalType == 'buildings' then
local buildingName = p._GetBuildingByID(goalObj.id).name
goalText = Shared.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 ' .. Shared.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'}) .. ' ' .. Shared.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
 
table.sort(reqTextPart,
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
-- 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/>'))
-- Rewards
table.insert(ret, '\n|')
local rewards = {}
local rewardsVariableQty = {}
if task.rewards.gp > 0 and not isDailyTask then
table.insert(rewards, Icons.GP(task.rewards.gp))
end
if task.rewards.slayerCoins > 0 then
if isDailyTask then
table.insert(rewardsVariableQty, Icons.SC())
else
table.insert(rewards, Icons.SC(task.rewards.slayerCoins))
end
end
for _, item in ipairs(task.rewards.items) do
local itemname = GameData.getEntityByID('items', item.id).name
table.insert(rewards, Shared.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
end
for _, skill in ipairs(task.rewards.skillXP) do
if not (isDailyTask and skill.id == 'melvorD:Township') then
local skillname = GameData.getSkillData(skill.id).name
table.insert(rewards, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
end
end
for _, townshipResource in ipairs(task.rewards.townshipResources) do
local resourcename = p._getResourceByID(townshipResource.id).name
table.insert(rewards, Shared.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
return table.concat(ret)
end
end


-- Gets a string displaying the base production of a building
-- Returns all the tasks of a given category
function p._GetBuildingBaseProduction(building)
-- TODO: Support casual tasks
local production = Shared.clone(building.provides.resources)
function p.getTaskTable(frame)
local resources = p._ResourcesData()
local category = frame.args ~= nil and frame.args[1] or frame
local categoryData = GameData.getEntityByID(Township.taskCategories, category)
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'))
end
 
local taskcount = 0
local ret = {}
table.insert(ret, '{| class="wikitable lighttable stickyHeader" style="text-align:left"')
table.insert(ret, '\n|- class="headerRow-0"')
table.insert(ret, '\n!Task')
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.GP() .. ' & ' .. Icons.Icon({'Township', type='skill', notext=true}) .. ' XP)')
end
if isDailyTask then
table.insert(ret, '\n!Unlock Requirements')
end
if #production == 0 then
for _, task in ipairs(taskData) do
return nil
-- Filter out other categories
if task.category == category then
taskcount = taskcount + 1
local title = categoryName .. ' ' .. taskcount
table.insert(ret, p._getTaskRow(title, task, isDailyTask))
end
end
end
table.insert(ret, '\n|}')
local retResources = {}
return table.concat(ret)
for _, resource in ipairs(production) do
end
local retProduction = {}
 
local job = p._GetJobFromResource(resource.id)
-- Returns a table containing all the tasks that reference an item or monster
local workers = GameData.getEntityByID(building.provides.workers, job).quantity
-- e.g. p.getTaskReferenceTable({'Chicken Coop', 'dungeon'})
-- Sourced from township.js -> Township.computeTownResourceGain()
-- name = item or monster name
local production = resource.quantity*100*(Township.tickLength/10)
-- type = 'item' or 'monster' or 'dungeon'
local resource_data = GameData.getEntityByID(resources, resource.id)
function p.getTaskReferenceTable(frame)
table.insert(retProduction, '<span style="color:green">'..Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;+'..production..'</span>')
-- Returns a set containing all the desired IDs
if resource_data.requires ~= nil and #resource_data.requires > 0 then
local function GetReferenceIDs(referenceName, referenceType)
for _, required_resource in ipairs(resource_data.requires) do
local IDs = {}
local demand = production*required_resource.quantity*100
if referenceType == 'dungeon' then
local required_resource_data = GameData.getEntityByID(resources, resource.id)
-- We get the tasks associated with all monsters in the dungeon
table.insert(retProduction, '<span style="color:red">'..Icons.Icon({required_resource_data.name, type='resource', notext=true})..'&nbsp;-'..demand..'</span>')
local monsters = GameData.getEntityByName('dungeons', referenceName).monsterIDs
for _, monster in ipairs(monsters) do
IDs[monster] = true
end
end
end
end
retProduction = table.concat(retProduction, ', ')..'/t ('..Icons.Icon({'Workers', type='township', notext=true})..'&nbsp;'..workers..')'
if referenceType == 'item' then
table.insert(retResources, retProduction)
IDs[GameData.getEntityByName('items', referenceName).id] = true
end
if referenceType == 'monster' then
IDs[Monsters.getMonster(referenceName).id] = true
end
return IDs
end
end
return table.concat(retResources, '<br>')
-- For a task, returns where to search for the desired IDs, given the type
end
local function GetGetSearchTables(referenceType)
local function searchItems(task)
return {task.goals.items, task.rewards.items}
end
local function searchMonsters(task)
return {task.goals.monsters}
end
-- item -> searchItems; monster or dungeon -> searchMonsters
return referenceType == 'item' and searchItems or searchMonsters
end
 
local args = frame.args ~= nil and frame.args or frame
local referenceName = Shared.fixPagename(args[1])
local referenceType = args[2]
local referenceIDs = GetReferenceIDs(referenceName, referenceType)
-- GetSearchTables = function searchItems/Monsters(task)
local GetSearchTables = GetGetSearchTables(referenceType)


-- Testing
local function checkTask(task)
p.GameData = GameData
local function checkID(entry)
p.Icons = Icons
return referenceIDs[entry.id] ~= nil
p.Township = Township
end
for _, searchTable in ipairs(GetSearchTables(task)) do
-- 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 #GameData.getEntities(searchTable, checkID) ~= 0 then -- Make sure we have at least 1 match
return true
end
end
end
return false
end
-- Find all tasks that contain the desired ids
local tasks = GameData.getEntities(Township.tasks, checkTask)
if #tasks == 0 then
return ''
end


-- Build the table
local ret = {}
table.insert(ret, '==Tasks==')
table.insert(ret, '\n{| class="wikitable" style="text-align:left"')
table.insert(ret, '\n!Task')
table.insert(ret, '\n!Requirements')
table.insert(ret, '\n!Rewards')
for _, task in ipairs(tasks) do
local categoryname = GameData.getEntityByID(Township.taskCategories, task.category).name
local title = '[[Township/Tasks#'..categoryname..'|'..categoryname..']]'
table.insert(ret, p._getTaskRow(title, task, false))
end
table.insert(ret, '\n|}')
return table.concat(ret)
end


return p
return p
918

edits