Anonymous

Module:Township: Difference between revisions

From Melvor Idle
Fixed currencies not showing up in task list
(Refer to actual data module)
(Fixed currencies not showing up in task list)
(30 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 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 9: Line 13:
p.Township = Township
p.Township = 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
-- Gets a resource from id
function p._getResourceByID(id)
return GameData.getEntityByID(Township.resources, id)
end


-- Returns the recipe for the item of a desired skill.
-- Given a building, find the next building upgrade
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data so we instead use this quick function
function p._getBuildingUpgrade(building)
function p._FindItemRecipes(itemid, skill)
local function checkFunc(entity)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == building.id
-- No skill? No recipes
end
if skill == nil then
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
return {}
if #upgradesTo > 0 then
return upgradesTo[1]
end
return nil
end
 
-- Given a building, find the building's downgrade
function p._getBuildingDowngrade(building)
if building.upgradesFrom ~= nil then
return p._getBuildingByID(building.upgradesFrom)
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
if itemid == recipe[productID] then
table.insert(results, Shared.clone(recipe))
end
end
end
end
end
end
end
return results
end
end


-- Returns a list of all the Township resources
-- Given a building, groups biomes for which that building has a common cost
function p._ResourcesData()
function p._getBuildingGroupedCosts(building)
local biomeGroups = {}
-- Get a sorted list of all the resources
for i, biomeID in ipairs(building.biomes) do
local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder)
local currentBiomeCost = p._getBuildingCostText(building, biomeID)
resources = Shared.clone(resources)
local found = false
for j, biomeGroup in ipairs(biomeGroups) do
return resources
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
end


-- Returns a sorted list of all Township buildings
-- Given a building, returns a text string repesenting the building costs for all biomes
function p._SortedBuildings()
function p._getBuildingGroupedCostText(building)
return GameData.sortByOrderTable(Township.buildings, Township.buildingDisplayOrder)
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


-- 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. We get a copy instead of directly using p.resources because we are going to modify the table
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
end
local includeMods = includeModifiers
local function matchFood(item)
if includeMods == nil then
return item.type == 'Food' and (not string.match(item.id, '_Perfect')) and item.category ~= 'Farming' and (not item.ignoreCompletion)
includeMods = false
end
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 = {
local providesData = nil
['melvorF:GP'] = {traderMatches = matchNone},
for i, provides in ipairs(building.provides) do
['melvorF:Food'] = {traderMatches = matchFood},
if provides.biomeID == biomeID then
['melvorF:Wood'] = {traderMatches = matchLogs},
providesData = provides
['melvorF:Stone'] = {traderMatches = matchOre},
break
['melvorF:Ore'] = {traderMatches = matchOre},
end
['melvorF:Coal'] = {traderMatches = matchCoal},
end
['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
if providesData ~= nil then
resource.itemConversions = Shared.clone(GameData.getEntities('items', traderMatchesList[resource.id].traderMatches))
local resultPart = {}
end
local stats = {
population = 'Population',
-- Calculate the trader's conversion ratios
happiness = 'Happiness',
-- See township.js TownshipResource.getBaseConvertToTownshipRatio and TownshipResource.getBaseConvertFromTownshipRatio for the conversion prices
education = 'Education',
for _, resource in ipairs(resources) do
storage = 'Storage',
if resource.id == 'melvorF:Food' then
worship = 'Worship',
for _, item in ipairs(resource.itemConversions) do
fortification = 'Fortification'
item.toTownship = math.max(math.floor(1000/(item.healsFor*10)), 2)
}
item.fromTownship = item.healsFor*5*6*5
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 .. '">' .. Num.numStrWithSign(quantity) .. '</span>'
end
end
elseif resource.id == 'melvorF:Planks' then
 
for _, item in ipairs(resource.itemConversions) do
-- Resources
item.toTownship = math.max(math.floor(3000/math.max(item.sellsFor, 1)), 2)
if providesData.resources ~= nil then
item.fromTownship = math.max(math.ceil(item.sellsFor/2)*6, 1);
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
end
elseif resource.id == 'melvorF:Rune_Essence' then
 
for _, item in ipairs(resource.itemConversions) do
-- Other stats
item.toTownship = 5
for key, stat in pairs(stats) do
item.fromTownship = (item.sellsFor+1)*10*6
local quantity = providesData[key]
if quantity ~= nil and quantity ~= 0 then
table.insert(resultPart, resourceText(stat, 'township', quantity))
end
end
end
elseif resource.id == 'melvorF:Leather' then
 
for _, item in ipairs(resource.itemConversions) do
-- Modifiers
item.toTownship = 20
if includeMods and building.modifiers ~= nil then
item.fromTownship = 20*6
table.insert(resultPart, Modifiers.getModifiersText(building.modifiers))
end
end
else
 
for _, item in ipairs(resource.itemConversions) do
if not Shared.tableIsEmpty(resultPart) then
        item.toTownship = math.max(math.floor(1000/math.max(item.sellsFor, 1)), 2)
return table.concat(resultPart, delim)
    item.fromTownship = math.max(item.sellsFor*6, 1)
end
end
end
end
end
end
return resources
end
end
p.resources = p._TraderData()


-- Builds the table of trader items
-- Given a building, groups biomes for which that building has a common benefit/provides
function p.GetTraderTable(frame)
function p._getBuildingGroupedBenefits(building, includeModifiers)
-- Get the resources data with associated trader data
if includeModifiers == nil then
includeModifiers = true
-- Build the text
end
local ret = {}
local biomeGroups = {}
for _, resource in ipairs(p.resources) do
for i, biomeID in ipairs(building.biomes) do
if #resource.itemConversions ~= 0 then -- Skips GP
local currentBiomeBenefit = p._getBuildingBenefitText(building, biomeID, includeModifiers)
local ret_resource = {}
local found = false
for j, biomeGroup in ipairs(biomeGroups) do
-- Header
if biomeGroup.benefit == currentBiomeBenefit then
table.insert(ret_resource, '\r\n==='..resource.name..'===')
-- Another biome exists with this cost
table.insert(ret_resource, '\r\n{| class="wikitable sortable stickyHeader"')
table.insert(biomeGroup.biomeIDs, biomeID)
table.insert(ret_resource, '\r\n|- class="headerRow-0"')
found = true
table.insert(ret_resource, '\r\n!Item')
break
table.insert(ret_resource, '\r\n!Name')
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
end
end
-- Each item
if not found then
for _, item in ipairs(resource.itemConversions) do
table.insert(biomeGroups, { biomeIDs = { biomeID }, cost = currentBiomeBenefit})
-- To indicate the skill level, we need to find the recipe of the item in the target skill
end
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data
end
local required_level = nil
return biomeGroups
local recipes = nil
end
 
-- Get the skill based on the item.id or else use the resource's default skill
-- Given a building, returns a text string repesenting the building benefits for all biomes
local skill_overrides = {
function p._getBuildingGroupedBenefitText(building, includeModifiers)
['melvorD:Raw_Magic_Fish'] = 'melvorD:Fishing',
if includeModifiers == nil then
['melvorF:Apple'] = 'melvorD:Farming',
includeModifiers = true
}
end
local skill = skill_overrides[item.id] or p._GetResourceSkill(resource.id)
local resultPart = {}
local skill_namespace, skill_localid = GameData.getLocalID(skill or '')
local biomeGroups = p._getBuildingGroupedBenefits(building, includeModifiers)
if Shared.tableCount(biomeGroups) == 1 then
-- Check for upgraded Crafting items and downgrade them so we can display the crafting level for the base item
-- If only one entry then simply output the cost
-- e.g. converts Black_Dhide_Body_U -> Black_Dhide_Body for the purposes of the lookup
table.insert(resultPart, biomeGroups[1].cost)
local lookup_id = item.id
else
if string.match(item.id, '_U$') then
-- Otherwise, split by biome group
lookup_id = string.sub(item.id, 1, #item.id - 2)
for i, biomeGroup in ipairs(biomeGroups) do
end
local biomeText = {}
for j, biomeID in ipairs(biomeGroup.biomeIDs) do
-- Find the recipe's level
local biome = GameData.getEntityByID(Township.biomes, biomeID)
local recipes = p._FindItemRecipes(lookup_id, skill)
table.insert(biomeText, Icons.Icon({biome.name, type='biome', notext=true, nolink=true, alt=biome.name}))
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
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)
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
table.insert(resultPart, table.concat(biomeText, ', ') .. ': ' .. biomeGroup.cost)
table.insert(ret_resource, '\r\n|}')
table.insert(ret, table.concat(ret_resource))
end
end
end
end
return table.concat(ret)
return table.concat(resultPart, '<br/>')
end
end


-- Gets the associated skill of a resource by id
-- Returns a sorted list of all Township buildings
local resource_skill = {
function p._sortedBuildings(keepUnsorted)
['melvorF:GP'] = {skill = nil},
local ku = true
['melvorF:Food'] = {skill = 'melvorD:Cooking'},
if keepUnsorted ~= nil then
['melvorF:Wood'] = {skill = 'melvorD:Woodcutting'},
ku = keepUnsorted
['melvorF:Stone'] = {skill = 'melvorD:Mining'},
end
['melvorF:Ore'] = {skill = 'melvorD:Mining'},
return GameData.sortByOrderTable(Township.buildings, Township.buildingDisplayOrder, ku)
['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
end


-- Gets a Township building by ID, e.g. melvorF:Hunters_Cabin
-- Gets the Township level or abyssalLevel, population and fortification requirements for a tier
function p._GetBuildingByID(id)
-- Returns {population=X, level=X} for non-abyssal tiers
-- Check for the special statue case
-- Returns {population=X, abyssalLevel=X, fortification=X} for abyssal tiers
if id == 'melvorF:Statues' then
function p._getTierRequirements(tier, abyssalTier)
local building = Shared.clone(GameData.getEntityByID(Township.buildings, id))
local tierData = Township.populationForTier[tier]
building.name = 'Statue of Worship'
if abyssalTier ~= nil then
return building
local abyssalTierData = Shared.clone(Township.abyssalTierRequirements[abyssalTier + 1])
abyssalTierData.population = tierData.population
return abyssalTierData
else
else
return GameData.getEntityByID(Township.buildings, id)
return tierData
end
end
end
end


-- Gets a Township building by name, e.g. Hunters Cabin
-- Returns a string containing the Township level and population requirements for a tier
function p._GetBuildingByName(name)
function p._getTierText(tier, abyssalTier)
-- Check for the special statue case
local realmID = (abyssalTier ~= nil and 'melvorItA:Abyssal' or 'melvorD:Melvor')
if name == 'Statues' then
local tierData = p._getTierRequirements(tier, abyssalTier)
name = 'Statue of Worship'
if tierData ~= nil then
local tierText = Icons._SkillReq('Township', tierData.abyssalLevel or tierData.level, false, realmID)
if tierData.population ~= nil and tierData.population > 0 then
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
return tierText
end
end
local STATUE_OF = 'Statue of '
end
if string.sub(name, 1, string.len(STATUE_OF)) == STATUE_OF then
 
local building = Shared.clone(GameData.getEntityByID(Township.buildings, 'melvorF:Statues'))
-- Generates a table of all seasons, their type/requirements, and modifiers
building.name = name
function p.getSeasonTable(frame)
return building
-- Manual data specifying the worship requirement for those rare seasons
else
local seasonReqs = {
return GameData.getEntityByName(Township.buildings, name)
["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'}),
["EternalDarkness"] = Icons.Icon({'Township%23Worship', 'Xon Worship', img='Statue of Xon', type='building'}),
}
 
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| ' .. Modifiers.getModifiersText(season.modifiers))
end
end
table.insert(resultPart, '\n|}')
return table.concat(resultPart)
end
end


-- Gets the Township level and population requirements for a tier
-- Generates a table listing all biomes and their associated requirements
-- Returns {population=X, level=X}
function p.getBiomeTable(frame)
function p._GetTierRequirements(tier)
local resultPart = {}
return Township.populationForTier[tier]
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
end
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="3"| 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' }))
table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Forification', img='Fortification', type='township', section='Fortification' }))
 
for i, biome in ipairs(Township.biomes) do
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| ' .. biome.name)
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 .. '"| ' .. Num.formatnum(reqs.population))
table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. fortification .. '"| ' .. Num.formatnum(fortification))
end
table.insert(resultPart, '\n|}')


-- Returns a string containing the Township level and population requirements for a tier
return table.concat(resultPart)
function p._GetTierText(tierlevel)
local tier = p._GetTierRequirements(tierlevel)
return Icons._SkillReq('Township', tier.level, false)..'<br>'..Icons.Icon({'Population', type='township', notext=true})..'&nbsp;'..tier.population
end
end


-- Gets a building and prepares all the relevant stats for the building
-- Generates a table showing which buildings can be built in which biomes
function p.GetBuildingTable(frame)
-- Skips upgraded buildings
local name = frame.args ~= nil and frame.args[1] or frame
function p.getBuildingBiomeTable(frame)
local building = Shared.clone(p._GetBuildingByName(name))
local tbl = mw.html.create('table')
local ret = {}
:addClass('wikitable sortable stickyHeader')
:css('text-align', 'center')


-- Header
local header = mw.html.create('tr'):addClass('headerRow-0')
table.insert(ret, '\r\n{| class="wikitable infobox"')
local level = mw.html.create('tr'):addClass('sorttop')
-- Name
local pop = mw.html.create('tr'):addClass('sorttop')
table.insert(ret, '\r\n|-\r\n!'..building.name)
local fort = mw.html.create('tr'):addClass('sorttop')
-- Icon
table.insert(ret, '\r\n|-\r\n|style="text-align:center"|'..Icons.Icon({building.name, type='building', size='250', notext=true}))
-- ID
table.insert(ret, '\r\n|-\r\n| <b>Building ID:</b> '..building.id)
-- Type
table.insert(ret, '\r\n|-\r\n| <b>Type:</b> '..building.type)
-- Tier
local tier = p._GetTierText(building.tier)
table.insert(ret, '\r\n|-\r\n| <b>Requirements:</b><br>'..tier)


-- Upgrades From
header:tag('th')
table.insert(ret, '\r\n|-\r\n| <b>Base Cost:</b>')
:css('z-index', '2')
local upgradesFrom = p._GetBuildingDowngrade(building)
:wikitext('Building')
if upgradesFrom ~= nil then
level:tag('th')
table.insert(ret, '<br>'..Icons.Icon({upgradesFrom.name, type='building'}))
: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
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
-- Cost
local cost = p._GetBuildingBaseCost(building)
table.insert(ret, '<br>'..cost)
-- Upgrades To
tbl:node(header)
local upgradesTo = p._GetBuildingIDUpgrade(building.id)
tbl:node(level)
if upgradesTo ~= nil then
tbl:node(pop)
table.insert(ret, '\r\n|-\r\n| <b>Upgrades To:</b>')
tbl:node(fort)
table.insert(ret, '<br>'..Icons.Icon({upgradesTo.name, type='building'}))
 
local upgrade_cost = p._GetBuildingBaseCost(upgradesTo)
for _, _building in ipairs(p._sortedBuildings(false)) do
table.insert(ret, '<br>'..upgrade_cost)
-- 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


-- Fixed benefits
return tostring(tbl)
local benefits = p._GetBuildingBenefits(building)
end
if benefits ~= nil then
 
table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..benefits)
-- Generates a table contaning each building plus their relevant information
function p.getBuildingTable(frame)
local resultPart = {}
 
-- Change structure of biomes data for ease of use later
local biomesByID = {}
for i, biome in ipairs(Township.biomes) do
biomesByID[biome.id] = biome
end
end


-- Production
-- Generate table header
local production = p._GetBuildingBaseProduction(building)
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
if production ~= nil then
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(ret, '\r\n|-\r\n| <b>Base Production per '..Icons.Icon({'Workers', type='township', notext=true})..':</b><br>')
table.insert(resultPart, '\n!colspan="2"|Building\n!Requirements\n!Max Built')
table.insert(ret, production)
table.insert(resultPart, '\n!Biomes\n!Cost\n!Provides')
 
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, building.abyssalTier) or ''))
table.insert(resultPart, '\n|style="text-align:right"' .. rowSpan .. '| ' .. building.maxUpgrades)
firstRow = false
else
table.insert(resultPart, '\n|-')
end
-- This section generates by biome rows
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
local providesText = p._getBuildingBenefitText(building, biomeID)
if building.modifiers ~= nil then
local modText = Modifiers.getModifiersText(building.modifiers)
if providesText == nil then
providesText = modText
else
providesText = providesText .. '<br/>' .. modText
end
end
table.insert(resultPart, '\n| ' .. (providesText or ''))
end
end
end
table.insert(resultPart, '\n|}')


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


-- Given a resource id, return the job id
-- Builds the table of trader items
-- e.g. melvorF:Bar -> melvorF:Blacksmith
function p.getTraderTable(frame)
function p._GetJobFromResource(resource_id)
local resultPart = {}
local job = GameData.getEntityByProperty(Township.jobs, 'produces', resource_id)
 
return job.id
-- Build table header
end
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
table.insert(resultPart, '\n|- class="headerRow-0"')
table.insert(resultPart, '\n!colspan="2"| Item\n!Description\n!style="min-width:60px"| Cost\n!Requirements')


-- Gets a string displaying the base production of a building, or nil if no production
for i, tsResource in ipairs(Township.itemConversions.fromTownship) do
function p._GetBuildingBaseProduction(building)
local res = GameData.getEntityByID(Township.resources, tsResource.resourceID)
-- TODO Fix always using first biome
for j, tradeDef in ipairs(tsResource.items) do
local production = Shared.clone(building.provides[1].resources)
local item = Items.getItemByID(tradeDef.itemID)
local itemDesc = item.customDescription
if #production == 0 then
if itemDesc == nil then
return nil
if item.modifiers ~= nil then
end
itemDesc = Modifiers.getModifiersText(item.modifiers, false, true)
else
local retResources = {}
itemDesc = ''
for _, resource in ipairs(production) do
end
local retProduction = {}
local job = p._GetJobFromResource(resource.id)
-- TODO Fix always using first biome
local workers = GameData.getEntityByID(building.provides[1].workers, job).quantity
-- 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
local resQty = math.max(item.sellsFor, 2)
local costSort = i * 10000 + resQty
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
retProduction = table.concat(retProduction, ', ')..'/t ('..Icons.Icon({'Workers', type='township', notext=true})..'&nbsp;'..workers..')'
table.insert(retResources, retProduction)
end
end
return table.concat(retResources, '<br>')
table.insert(resultPart, '\n|}')
 
return table.concat(resultPart)
end
end


-- Gets a string displaying the building's benefits, or nil if no benefits
-- Generates a table showing all the worship options
function p._GetBuildingBenefits(building)
function p.getWorshipTable()
local benefits = {}
local function getCheckpointCell(checkpoint)
local stats = {
return '\n|-\n!' .. checkpoint .. '%<br/>' .. Num.formatnum(checkpoint * Township.maxWorship / 100) .. '/' .. Num.formatnum(Township.maxWorship)
population = 'Population',
end
happiness = 'Happiness',
 
education = 'Education',
local worships = GameData.getEntities(Township.worships, function(w) return not w.isHidden end)
storage = 'Storage',
local ret = {}
deadStorage = 'Dead Storage',
 
worship = 'Worship'
table.insert(ret, '{| class="wikitable stickyHeader"')
}
table.insert(ret, '\n!' .. Icons.Icon({'Worship', type='township', nolink=true}))
for key, stat in pairs(stats) do
-- Names
-- TODO Fix always using first biome
for _, worship in ipairs(worships) do
if building.provides[1][key] ~= nil and building.provides[1][key] ~= 0 then
table.insert(ret, '\n!' .. Icons.Icon({worship.name, type='monster', size=50}) .. Icons.Icon({'Statue of ' .. worship.name, type='building', size=50, notext=true}))
local quantity = building.provides[1][key]
end
if quantity < 0 then
 
quantity = '<span style="color:red">'..quantity..'</span>'
-- Requirements
else
table.insert(ret, '\n|-\n!Requirements')
quantity = Shared.numStrWithSign(quantity)
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(benefits, Icons.Icon({stat, type='township', notext=true})..'&nbsp;'..quantity)
end
end
table.insert(ret, '\n|' .. cellStyle .. '| ' .. table.concat(bonusPart, '<br/>'))
end
end
if #benefits > 0 then
 
return table.concat(benefits, ', ')
-- Base modifiers
table.insert(ret, getCheckpointCell(0))
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Modifiers.getModifiersText(worship.modifiers))
end
end
return nil
end


-- Given a building id, find the next building upgrade
-- Checkpoint modifiers
function p._GetBuildingIDUpgrade(buildingid)
for i, checkpoint in ipairs(Township.worshipCheckpoints) do
local function checkFunc(entity)
table.insert(ret, getCheckpointCell(checkpoint))
return entity.upgradesFrom ~= nil and entity.upgradesFrom == buildingid
for _, worship in ipairs(worships) do
table.insert(ret, '\n| ' .. Modifiers.getModifiersText(worship.checkpoints[i]))
end
end
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
 
if #upgradesTo > 0 then
-- Total sum
return upgradesTo[1]
-- TODO Needs fixing, no function currently for aggregating modifiers
--[==[
table.insert(ret, '\n|-\n!Total')
for _, worship in ipairs(worships) do
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|' .. Modifiers.getModifiersText(modifiers))
end
end
return nil
--]==]
table.insert(ret, '\n|}')
 
return table.concat(ret)
end
end


-- Given a building, find the building's downgrade
-- Gets a building and prepares all the relevant stats for the building, presented as an infobox
function p._GetBuildingDowngrade(building)
function p.getBuildingInfoBox(frame)
if building.upgradesFrom ~= nil then
local name = frame.args ~= nil and frame.args[1] or frame
return p._GetBuildingByID(building.upgradesFrom)
local building = p._getBuildingByName(name)
if building == nil then
return Shared.printError('No building named "' .. name .. '" exists in the data module')
end
end
return nil
end


-- Given a building, find the base resource cost
local ret = {}
function p._GetBuildingBaseCost(building, _join)
-- Header
local join = _join ~= nil and _join or ', '
table.insert(ret, '{| class="wikitable infobox"')
local cost = {}
-- Name
-- TODO Cost can vary by biome, properly handle this rather than
table.insert(ret, '\n|-\n! ' .. Icons.getExpansionIcon(building.id) .. building.name)
-- always taking costs for the first biome
-- Icon
for _, resource in ipairs(building.cost[1].cost) do
table.insert(ret, '\n|-\n|style="text-align:center"| ' .. Icons.Icon({building.name, type='building', size='250', notext=true}))
local resource_data = p._GetResourceByID(resource.id)
-- ID
table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..resource.quantity)
table.insert(ret, '\n|-\n| <b>Building ID:</b> ' .. building.id)
-- Tier
local tier = p._getTierText(building.tier, building.abyssalTier)
table.insert(ret, '\n|-\n| <b>Requirements:</b><br/>' .. tier)
 
-- Upgrades From
table.insert(ret, '\n|-\n| <b>Base Cost:</b>')
local upgradesFrom = p._getBuildingDowngrade(building)
if upgradesFrom ~= nil then
table.insert(ret, '<br/>' .. Icons.Icon({upgradesFrom.name, type='building'}))
end
end
return table.concat(cost, join)
-- Cost
end
--table.insert(ret, '<br/>' .. p._getBuildingGroupedCostText(building))
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))


-- Gets a resource from id
-- Upgrades To
function p._GetResourceByID(id)
local upgradesTo = p._getBuildingUpgrade(building)
return GameData.getEntityByID(p.resources, id)
if upgradesTo ~= nil then
end
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


-- Gets text for only the biomes that have a modifier for a building
-- Maximum built
function p._GetBiomeModifiers(building)
local biomeCount = Shared.tableCount(building.biomes)
-- TODO Concept of biome modifiers doesn't exist anymore
local maxText = Num.formatnum(building.maxUpgrades)
return ''
if biomeCount > 1 then
--[==[
maxText = maxText .. ' per biome, ' .. Num.formatnum(biomeCount * building.maxUpgrades) .. ' total'
local biomeRet = {}
for _, biome in ipairs(building.biomeModifiers) do
local biomename = GameData.getEntityByID(Township.biomes, biome.biomeID).name
local color = biome.value < 0 and 'red' or 'green'
local biome_value = Shared.numStrWithSign(biome.value)
table.insert(biomeRet, Icons.Icon({biomename, type='biome', notext=true, nolink=true})..' <span style="color:'..color..'">'..biomename..' ('..biome_value..'%)</span>')
end
end
if #biomeRet == 0 then
table.insert(ret, '\n|-\n| <b>Maximum Built:</b><br/>' .. maxText)
return nil
-- Benefits
local benefits = p._getBuildingGroupedBenefitText(building)
if benefits ~= nil and benefits ~= '' then
table.insert(ret, '\n|-\n| <b>Provides:</b><br/>' .. benefits)
end
end
return table.concat(biomeRet, '<br>')
 
--]==]
-- 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
end


-- Returns an upgrade table of a building
-- Returns an upgrade table of a building
function p.GetBuildingUpgradeTable(frame)
function p.getBuildingUpgradeTable(frame)
local buildingname = frame.args ~= nil and frame.args[1] or frame
local buildingname = frame.args ~= nil and frame.args[1] or frame
local building = p._GetBuildingByName(buildingname)
local building = p._getBuildingByName(buildingname)
if building == nil then
return Shared.printError('No building named "' .. buildingname .. '" exists in the data module')
end
 
-- Let's find the base building
-- Let's find the base building
local baseBuilding = building
local baseBuilding = building
while true do
while true do
local previousBuilding = p._GetBuildingDowngrade(baseBuilding)
local previousBuilding = p._getBuildingDowngrade(baseBuilding)
if previousBuilding ~= nil then
if previousBuilding ~= nil then
baseBuilding = previousBuilding
baseBuilding = previousBuilding
Line 537: 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 544: 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 552: 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)
BuildOptionalRow('\r\n|-\r\n! Base Production per '..Icons.Icon({'Workers', type='township', notext=true})..'\r\n|', p._GetBuildingBaseProduction)
BuildOptionalRow('\r\n|-\r\n! Biome Production Modifiers\r\n|', p._GetBiomeModifiers)


-- End
-- End
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
return table.concat(ret)
end
 
-- TODO Fix
local FREE_LAND = Township.sectionSize or 0
-- Gets the cost of the current price of land
-- Taken from township.js -> Township.getNextSectionCost
function p.GetLandCost(frame)
local nthland = tonumber(frame.args ~= nil and frame.args[1] or frame)
return p._GetLandCost(nthland)
end


function p._GetLandCost(nthland)
-- First FREE_LAND plots of land are free
if nthland <= FREE_LAND then
return 0
end
return math.floor(15^(0.0100661358978*(nthland/32) + (nthland/32)^0.42))
end
-- Gets the cost to buy land until you have X amount of available land
-- Currently the max is 2048 land
function p.GetCumulativeLandCost(frame)
local totalLand = tonumber(frame.args ~= nil and frame.args[1] or frame)
return p._GetCumulativeLandCost(totalLand)
end
function p._GetCumulativeLandCost(totalLand)
local cost = 0
while totalLand > FREE_LAND do
cost = cost + p._GetLandCost(totalLand)
totalLand = totalLand - 1
end
return cost
end
-- Returns a table showing the land cost of a town
function p.GetLandCostTable()
-- TODO Fix
return ''
--[[=[
local ret = {}
table.insert(ret, '\r\n{| class="wikitable"')
table.insert(ret, '\r\n|- style="text-align:center" \r\n! Total Land \r\n! Single Land Cost \r\n! Total Cost')
for i=FREE_LAND,Township.maxTownSize,FREE_LAND do
table.insert(ret, '\r\n|-\r\n|'..i..'\r\n|'..Icons.GP(p._GetLandCost(i))..'\r\n|'..Icons.GP(p._GetCumulativeLandCost(i)))
end
table.insert(ret, '\r\n|}')
return table.concat(ret)
return table.concat(ret)
--]=]]
end
end


-- Generates a table showing which buildings can be built in which biomes
-- Returns a row containing a task given a title and a task table
-- Skips upgraded buildings
function p._getTaskRow(title, task, isDailyTask)
function p.GetBuildingBiomeTable()
-- Setup the table
local ret = {}
local ret = {}
table.insert(ret, '{| class="wikitable sortable" style="text-align:center"')
table.insert(ret, '\n!Building')
-- Make a biomeModifiers table that will keep track of the bonus of each building
-- At the same time, make the output table header
local biomesMaster = {}
for _, biome in ipairs(Township.biomes) do
table.insert(ret, '\n!'..Icons.Icon({biome.name, type='biome', notext=true, nolink=true})..'<br>'..biome.name)
biomesMaster[biome.id] = false
end
for _, _building in ipairs(p._SortedBuildings()) do
-- Fix melvorF:Statues
local building = p._GetBuildingByID(_building.id)
-- Skip upgraded buildings
local downgrade = p._GetBuildingDowngrade(building)
if downgrade == nil then
-- Let's populate the biome habitability data
local buildingBiomes = Shared.clone(biomesMaster)
-- Set all valid biomes to 0
for _, biomeid in ipairs(building.biomes) do
buildingBiomes[biomeid] = true
end
-- Let's build the row
table.insert(ret, '\n|-')
table.insert(ret, '\n!data-sort-value="'..building.name..'" style="text-align:left"|'..Icons.Icon({building.name, type='building'}))
for _, biome in ipairs(Township.biomes) do
if buildingBiomes[biome.id] then
-- Buildable
table.insert(ret, '\n|class="table-positive"|✓')
else
-- Invalid biome
table.insert(ret, '\n|style="border:0px"|')
end
end
end
end
table.insert(ret, '\n|}')
return table.concat(ret)
end


-- Returns a row containing a task given a title and a task table
function p._GetTaskRow(title, task)
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 712: 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 805: 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 814: 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 829: 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 841: 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 856: 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)
end
 
-- Generates a table showing all the worship options
function p.GetWorshipTable()
local function GetCheckpointCell(checkpoint)
return '\r\n|-\r\n!'..checkpoint..'%<br>'..(checkpoint*Township.maxWorship/100)..'/'..Township.maxWorship
end
local ret = {}
table.insert(ret, '\r\n{| class="wikitable" style="text-align:left"')
table.insert(ret, '\r\n!'..Icons.Icon({'Worship', type='township', nolink=true}))
-- Names
for _, worship in ipairs(Township.worships) do
if worship.isHidden == false then
table.insert(ret, '\r\n!'..Icons.Icon({worship.name, type='monster', size=50})..Icons.Icon({'Statue of '..worship.name, type='building', size=50, notext=true}))
end
end
-- Requirements
-- Hard-coded because there's only 1 requirement
table.insert(ret, '\r\n|-\r\n!Requirements')
local requirements = {
['melvorF:Bane'] = 'Completion of<br>'..Icons.Icon({'Impending Darkness Event', type='dungeon'})
}
for _, worship in ipairs(Township.worships) do
if worship.isHidden == false then
local requirement = requirements[worship.id] ~= nil and requirements[worship.id] or 'None'
table.insert(ret, '\r\n|style="text-align:center|'..requirement)
end
end
-- Base modifiers
table.insert(ret, GetCheckpointCell(0))
for _, worship in ipairs(Township.worships) do
if worship.isHidden == false then
table.insert(ret, '\r\n|'..Constants.getModifiersText(worship.modifiers))
end
end
-- Checkpoint modifiers
for i, checkpoint in ipairs(Township.worshipCheckpoints) do
table.insert(ret, GetCheckpointCell(checkpoint))
for _, worship in ipairs(Township.worships) do
if worship.isHidden == false then
table.insert(ret, '\r\n|'..Constants.getModifiersText(worship.checkpoints[i]))
end
end
end
 
-- Total sum
table.insert(ret, '\r\n|-\r\n!Total')
for _, worship in ipairs(Township.worships) do
if worship.isHidden == false then
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, '\r\n|'..Constants.getModifiersText(modifiers))
end
end
table.insert(ret, '\r\n|}')
return table.concat(ret)
return table.concat(ret)
end
end


return p
return p