Module:Township: Difference between revisions

7,093 bytes removed ,  26 April 2023
getBuildingInfoBox, getBuildingUpgradeTable, getTraderTable: Update for v1.1.2
(getWorshipTable: Add season modifiers)
(getBuildingInfoBox, getBuildingUpgradeTable, getTraderTable: Update for v1.1.2)
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 Shop = require('Module:Shop')
local Shop = require('Module:Shop')
local GameData = require('Module:GameData')
local GameData = require('Module:GameData')
Line 19: Line 20:
else
else
return GameData.getEntityByID(Township.buildings, id)
return GameData.getEntityByID(Township.buildings, id)
end
end
-- Gets a Township building by name, e.g. Hunters Cabin
function p._getBuildingByName(name)
-- Check for the special statue case
if name == 'Statues' then
name = 'Statue of Worship'
end
local STATUE_OF = 'Statue of '
if string.sub(name, 1, string.len(STATUE_OF)) == STATUE_OF then
local building = Shared.clone(GameData.getEntityByID(Township.buildings, 'melvorF:Statues'))
building.name = name
return building
else
return GameData.getEntityByName(Township.buildings, name)
end
end
end
end
Line 25: Line 42:
function p._getResourceByID(id)
function p._getResourceByID(id)
return GameData.getEntityByID(Township.resources, id)
return GameData.getEntityByID(Township.resources, id)
end
-- Given a building, find the next building upgrade
function p._getBuildingUpgrade(building)
local function checkFunc(entity)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == building.id
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
if #upgradesTo > 0 then
return upgradesTo[1]
end
return nil
end
end


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


-- Given a building and biome ID, returns a string displaying the building's benefits,
-- Given a building and biome ID, returns a string displaying the building's benefits,
-- or nil if no benefits
-- or nil if no benefits
function p._getBuildingBenefits(building, biomeID, includeModifiers, delimiter)
function p._getBuildingBenefitText(building, biomeID, includeModifiers, delimiter)
-- Basic validation of inputs
-- Basic validation of inputs
if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
Line 81: Line 152:
end
end
end
end
 
if providesData ~= nil then
if providesData ~= nil then
local resultPart = {}
local resultPart = {}
Line 109: Line 180:
-- Other stats
-- Other stats
for key, stat in pairs(stats) do
for key, stat in pairs(stats) do
-- TODO Fix always using first biome
local quantity = providesData[key]
local quantity = providesData[key]
if quantity ~= nil and quantity ~= 0 then
if quantity ~= nil and quantity ~= 0 then
Line 121: Line 191:
end
end
end
end
end
-- Given a building, groups biomes for which that building has a common benefit/provides
function p._getBuildingGroupedBenefits(building, includeModifiers)
if includeModifiers == nil then
includeModifiers = true
end
local biomeGroups = {}
for i, biomeID in ipairs(building.biomes) do
local currentBiomeBenefit = p._getBuildingBenefitText(building, biomeID, includeModifiers)
local found = false
for j, biomeGroup in ipairs(biomeGroups) do
if biomeGroup.benefit == currentBiomeBenefit then
-- Another biome exists with this cost
table.insert(biomeGroup.biomeIDs, biomeID)
found = true
break
end
end
if not found then
table.insert(biomeGroups, { biomeIDs = { biomeID }, cost = currentBiomeBenefit})
end
end
return biomeGroups
end
-- Given a building, returns a text string repesenting the building benefits for all biomes
function p._getBuildingGroupedBenefitText(building, includeModifiers)
if includeModifiers == nil then
includeModifiers = true
end
local resultPart = {}
local biomeGroups = p._getBuildingGroupedBenefits(building, includeModifiers)
if Shared.tableCount(biomeGroups) == 1 then
-- If only one entry then simply output the cost
table.insert(resultPart, biomeGroups[1].cost)
else
-- Otherwise, split by biome group
for i, biomeGroup in ipairs(biomeGroups) do
local biomeText = {}
for j, biomeID in ipairs(biomeGroup.biomeIDs) do
local biome = GameData.getEntityByID(Township.biomes, biomeID)
table.insert(biomeText, Icons.Icon({biome.name, type='biome', notext=true, nolink=true, alt=biome.name}))
end
table.insert(resultPart, table.concat(biomeText, ', ') .. ': ' .. biomeGroup.cost)
end
end
return table.concat(resultPart, '<br/>')
end
end


Line 291: Line 409:
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
table.insert(resultPart, '\n| ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
table.insert(resultPart, '\n| ' .. p._getBuildingCostText(building, biomeID))
local providesText = p._getBuildingBenefits(building, biomeID)
local providesText = p._getBuildingBenefitText(building, biomeID)
if building.modifiers ~= nil then
if building.modifiers ~= nil then
local modText = Constants.getModifiersText(building.modifiers)
local modText = Constants.getModifiersText(building.modifiers)
Line 310: Line 428:
-- Builds the table of trader items
-- Builds the table of trader items
function p.getTraderTable(frame)
function p.getTraderTable(frame)
-- Get the resources data with associated trader data
local resultPart = {}


-- Build the text
-- Build table header
local ret = {}
table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
for _, resource in ipairs(p.resources) do
table.insert(resultPart, '\n|- class="headerRow-0"')
if #resource.itemConversions ~= 0 then -- Skips GP
table.insert(resultPart, '\n!colspan="2"| Item\n!Description\n!style="min-width:60px"| Cost\n!Requirements')
local ret_resource = {}


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


Line 479: Line 542:
end
end


-- TODO Check if functions below this line are still in use
-- Gets a building and prepares all the relevant stats for the building, presented as an infobox
 
function p.getBuildingInfoBox(frame)
-- Returns the recipe for the item of a desired skill.
local name = frame.args ~= nil and frame.args[1] or frame
-- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data so we instead use this quick function
local building = p._getBuildingByName(name)
function p._FindItemRecipes(itemid, skill)
if building == nil then
return Shared.printError('No building named "' .. name .. '" exists in the data module')
-- No skill? No recipes
if skill == nil then
return {}
end
-- the key name for each skill in the json file
local skill_recipe_keys = {
['melvorD:Woodcutting'] = {recipes='trees', productID='productId'}, -- lowercase "d"
['melvorD:Fishing'] = {recipes='fish', productID='productId'}, -- lowercase "d"
['melvorD:Cooking'] = {recipes='recipes', productID='productID'},
['melvorD:Mining'] = {recipes='rockData', productID='productId'}, -- lowercase "d"
['melvorD:Smithing'] = {recipes='recipes', productID='productID'},
['melvorD:Farming'] = {recipes='recipes', productID='productId'}, -- lowercase "d"
['melvorD:Summoning'] = {recipes='recipes', productID='productID'},
['melvorD:Fletching'] = {recipes='recipes', productID='productID'},
['melvorD:Crafting'] = {recipes='recipes', productID='productID'},
['melvorD:Runecrafting'] = {recipes='recipes', productID='productID'},
['melvorD:Herblore'] = {recipes='recipes', productID='potionIDs'} -- Special case potions I-IV
--[[ Excluded skills:
Attack, Strength, Defence, Magic, Ranged, Prayer, Slayer
Thieving, Agility, Astrology, Firemaking, Township (not items)]]
}
 
local results = {}
local SkillData = GameData.getSkillData(skill)
local recipes = skill_recipe_keys[skill].recipes
local productID = skill_recipe_keys[skill].productID
if SkillData[recipes] ~= nil then
for _, recipe in ipairs(SkillData[recipes]) do
-- Special case for Herblore
if skill == 'melvorD:Herblore' then
-- Iterate over the 4 potion tiers
for _, potionid in ipairs(recipe[productID]) do
if itemid == potionid then
table.insert(results, Shared.clone(recipe))
end
end
-- Base case
else
if itemid == recipe[productID] then
table.insert(results, Shared.clone(recipe))
end
end
end
end
end
return results
end
-- Returns a list of all the Township resources
function p._ResourcesData()
-- Get a sorted list of all the resources
local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder)
resources = Shared.clone(resources)
return resources
end


-- Returns a list of all the Township resources along with the Trader's trade ratios
function p._TraderData()
-- Get the list of resources. We get a copy instead of directly using p.resources because we are going to modify the table
local resources = p._ResourcesData()
-- Get the list of tradeable items
-- See township.js -> TownshipResource.buildResourceItemConversions for the calculation of valid items
local function matchNone(item)
return false
end
local function matchFood(item)
return item.type == 'Food' and (not string.match(item.id, '_Perfect')) and item.category ~= 'Farming' and (not item.ignoreCompletion)
end
local function matchLogs(item)
return item.type == 'Logs'
end
local function matchOre(item)
return item.type == 'Ore' and item.id ~= 'melvorTotH:Meteorite_Ore'
end
local function matchCoal(item)
return item.id == 'melvorD:Coal_Ore'
end
local function matchBar(item)
return item.type == 'Bar' and item.id ~= 'melvorTotH:Meteorite_Bar'
end
local function matchHerb(item)
return item.type == 'Herb'
end
local function matchEssence(item)
return item.id == 'melvorD:Rune_Essence' or item.id == 'melvorTotH:Pure_Essence'
end
local function matchLeather(item)
return item.id == 'melvorD:Leather'
end
local function matchPotion(item)
return item.type == 'Potion' and string.match(item.id, '_IV')
end
local function matchClothing(item)
return item.id == 'melvorD:Green_Dragonhide' or item.id == 'melvorD:Blue_Dragonhide' or item.id == 'melvorD:Red_Dragonhide' or item.id == 'melvorD:Black_Dragonhide' or item.id == 'melvorF:Elder_Dragonhide'
end
local traderMatchesList = {
['melvorF:GP'] = {traderMatches = matchNone},
['melvorF:Food'] = {traderMatches = matchFood},
['melvorF:Wood'] = {traderMatches = matchLogs},
['melvorF:Stone'] = {traderMatches = matchOre},
['melvorF:Ore'] = {traderMatches = matchOre},
['melvorF:Coal'] = {traderMatches = matchCoal},
['melvorF:Bar'] = {traderMatches = matchBar},
['melvorF:Herbs'] = {traderMatches = matchHerb},
['melvorF:Rune_Essence'] = {traderMatches = matchEssence},
['melvorF:Leather'] = {traderMatches = matchLeather},
['melvorF:Potions'] = {traderMatches = matchPotion},
['melvorF:Planks'] = {traderMatches = matchLogs},
['melvorF:Clothing'] = {traderMatches = matchClothing}
}
for _, resource in ipairs(resources) do
resource.itemConversions = Shared.clone(GameData.getEntities('items', traderMatchesList[resource.id].traderMatches))
end
-- Calculate the trader's conversion ratios
-- See township.js TownshipResource.getBaseConvertToTownshipRatio and TownshipResource.getBaseConvertFromTownshipRatio for the conversion prices
for _, resource in ipairs(resources) do
if resource.id == 'melvorF:Food' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = math.max(math.floor(1000/(item.healsFor*10)), 2)
item.fromTownship = item.healsFor*5*6*5
end
elseif resource.id == 'melvorF:Planks' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = math.max(math.floor(3000/math.max(item.sellsFor, 1)), 2)
item.fromTownship = math.max(math.ceil(item.sellsFor/2)*6, 1);
end
elseif resource.id == 'melvorF:Rune_Essence' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = 5
item.fromTownship = (item.sellsFor+1)*10*6
end
elseif resource.id == 'melvorF:Leather' then
for _, item in ipairs(resource.itemConversions) do
item.toTownship = 20
item.fromTownship = 20*6
end
else
for _, item in ipairs(resource.itemConversions) do
        item.toTownship = math.max(math.floor(1000/math.max(item.sellsFor, 1)), 2)
    item.fromTownship = math.max(item.sellsFor*6, 1)
end
end
end
return resources
end
p.resources = p._TraderData()
-- Gets the associated skill of a resource by id
local resource_skill = {
['melvorF:GP'] = {skill = nil},
['melvorF:Food'] = {skill = 'melvorD:Cooking'},
['melvorF:Wood'] = {skill = 'melvorD:Woodcutting'},
['melvorF:Stone'] = {skill = 'melvorD:Mining'},
['melvorF:Ore'] = {skill = 'melvorD:Mining'},
['melvorF:Coal'] = {skill = 'melvorD:Mining'},
['melvorF:Bar'] = {skill = 'melvorD:Smithing'},
['melvorF:Herbs'] = {skill = 'melvorD:Farming'},
['melvorF:Rune_Essence'] = {skill = 'melvorD:Mining'},
['melvorF:Leather'] = {skill = nil},
['melvorF:Potions'] = {skill = 'melvorD:Herblore'},
['melvorF:Planks'] = {skill = 'melvorD:Woodcutting'},
['melvorF:Clothing'] = {skill = nil}
}
function p._GetResourceSkill(id)
return resource_skill[id].skill
end
-- Gets a Township building by name, e.g. Hunters Cabin
function p._GetBuildingByName(name)
-- Check for the special statue case
if name == 'Statues' then
name = 'Statue of Worship'
end
local STATUE_OF = 'Statue of '
if string.sub(name, 1, string.len(STATUE_OF)) == STATUE_OF then
local building = Shared.clone(GameData.getEntityByID(Township.buildings, 'melvorF:Statues'))
building.name = name
return building
else
return GameData.getEntityByName(Township.buildings, name)
end
end
-- Gets a building and prepares all the relevant stats for the building
-- TODO Rename, getBuildingInfoBox or something of the sort
function p.GetBuildingTable(frame)
local name = frame.args ~= nil and frame.args[1] or frame
local building = Shared.clone(p._GetBuildingByName(name))
local ret = {}
local ret = {}
-- Header
-- Header
table.insert(ret, '\r\n{| class="wikitable infobox"')
table.insert(ret, '{| class="wikitable infobox"')
-- Name
-- Name
table.insert(ret, '\r\n|-\r\n!'..building.name)
table.insert(ret, '\n|-\n! ' .. 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
-- Type
table.insert(ret, '\r\n|-\r\n| <b>Type:</b> '..building.type)
table.insert(ret, '\n|-\n| <b>Type:</b> ' .. building.type)
-- Tier
-- Tier
local tier = p._getTierText(building.tier)
local tier = p._getTierText(building.tier)
table.insert(ret, '\r\n|-\r\n| <b>Requirements:</b><br>'..tier)
table.insert(ret, '\n|-\n| <b>Requirements:</b><br/>' .. tier)


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


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


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


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


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


-- Given a building id, find the next building upgrade
function p._GetBuildingIDUpgrade(buildingid)
local function checkFunc(entity)
return entity.upgradesFrom ~= nil and entity.upgradesFrom == buildingid
end
local upgradesTo = GameData.getEntities(Township.buildings, checkFunc)
if #upgradesTo > 0 then
return upgradesTo[1]
end
return nil
end
-- Given a building, find the base resource cost
function p._GetBuildingBaseCost(building, _join)
local join = _join ~= nil and _join or ', '
local cost = {}
-- TODO Cost can vary by biome, properly handle this rather than
-- always taking costs for the first biome
for _, resource in ipairs(building.cost[1].cost) do
local resource_data = p._getResourceByID(resource.id)
table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..'&nbsp;'..resource.quantity)
end
return table.concat(cost, join)
end
-- Returns an upgrade table of a building
function p.GetBuildingUpgradeTable(frame)
local buildingname = frame.args ~= nil and frame.args[1] or frame
local building = p._GetBuildingByName(buildingname)
-- Let's find the base building
-- Let's find the base building
local baseBuilding = building
local baseBuilding = building
Line 815: Line 642:
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 822: Line 649:
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 830: Line 657:
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.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))
table.insert(ret, '\r\n|'..tier)
end
end


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


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


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


-- Returns a row containing a task given a title and a task table
-- Returns a row containing a task given a title and a task table
function p._GetTaskRow(title, task)
function p._getTaskRow(title, task)
local ret = {}
local ret = {}
 
-- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
-- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
local hasDescription = false
local hasDescription = false
Line 891: Line 726:
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 = {}
local requirements = {}
for _, item in ipairs(task.goals.items) do
for _, item in ipairs(task.goals.items) do
Line 920: Line 755:
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
if task.rewards.gp ~= 0 then
Line 942: Line 777:
table.insert(rewards, Shared.formatnum(townshipResource.quantity)..' '..Icons.Icon({resourcename, type='resource'}))
table.insert(rewards, Shared.formatnum(townshipResource.quantity)..' '..Icons.Icon({resourcename, type='resource'}))
end
end
table.insert(ret, table.concat(rewards, '<br>'))
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.getEntityByID(Township.taskCategories, category)
Line 955: Line 791:
local categoryname = categoryData.name
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" 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(Township.tasks) do
for _, task in ipairs(Township.tasks) do
Line 967: Line 803:
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))
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 1,008: Line 844:
return referenceType == 'item' and searchItems or searchMonsters
return referenceType == 'item' and searchItems or searchMonsters
end
end
 
local args = frame.args ~= nil and frame.args or frame
local args = frame.args ~= nil and frame.args or frame
local referenceName = Shared.fixPagename(args[1])
local referenceName = Shared.fixPagename(args[1])
Line 1,035: Line 871:
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
local categoryname = GameData.getEntityByID(Township.taskCategories, task.category).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))
end
end
table.insert(ret, '\r\n|}')
table.insert(ret, '\n|}')
return table.concat(ret)
return table.concat(ret)
end
end
Line 1,057: Line 893:
function p.GetWorshipTable()
function p.GetWorshipTable()
return p.getWorshipTable()
return p.getWorshipTable()
end
function p.GetBuildingTable(frame)
return p.getBuildingInfoBox()
end
function p.GetBuildingUpgradeTable(frame)
return p.getBuildingUpgradeTable(frame)
end
function p.GetTaskTable(frame)
return p.getTaskTable(frame)
end
function p.GetTaskReferenceTable(frame)
return p.getTaskReferenceTable(frame)
end
end


return p
return p