|
|
(2 intermediate revisions by the same user not shown) |
Line 1: |
Line 1: |
| local Shared = require('Module:Shared')
| |
| 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 Constants = require('Module:Constants')
| |
| local Num = require('Module:Number')
| |
|
| |
|
| local p = {}
| |
|
| |
| local Township = GameData.getSkillData('melvorD: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
| |
|
| |
| -- 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
| |
|
| |
| -- Given a building, find the building's downgrade
| |
| function p._getBuildingDowngrade(building)
| |
| if building.upgradesFrom ~= nil then
| |
| return p._getBuildingByID(building.upgradesFrom)
| |
| end
| |
| return nil
| |
| end
| |
|
| |
| -- 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
| |
| -- cannot be built within that biome.
| |
| function p._getBuildingCostText(building, biomeID, delimiter)
| |
| -- Basic validation of inputs
| |
| if type(building) == 'table' and building.cost ~= nil and biomeID ~= nil then
| |
| local delim = delimiter
| |
| if delim == nil then
| |
| delim = ', '
| |
| end
| |
| for i, costDef in ipairs(building.cost) do
| |
| if costDef.biomeID == biomeID then
| |
| local resultPart = {}
| |
| 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
| |
| return table.concat(resultPart, delim)
| |
| 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
| |
|
| |
| -- Given a building and biome ID, returns a string displaying the building's benefits,
| |
| -- or nil if no benefits
| |
| function p._getBuildingBenefitText(building, biomeID, includeModifiers, delimiter)
| |
| -- Basic validation of inputs
| |
| if type(building) == 'table' and building.provides ~= nil and biomeID ~= nil then
| |
| local delim = delimiter
| |
| if delim == nil then
| |
| delim = ', '
| |
| end
| |
| local includeMods = includeModifiers
| |
| if includeMods == nil then
| |
| includeMods = false
| |
| end
| |
|
| |
| local providesData = nil
| |
| for i, provides in ipairs(building.provides) do
| |
| if provides.biomeID == biomeID then
| |
| providesData = provides
| |
| break
| |
| end
| |
| end
| |
|
| |
| if providesData ~= nil then
| |
| local resultPart = {}
| |
| local stats = {
| |
| population = 'Population',
| |
| happiness = 'Happiness',
| |
| education = 'Education',
| |
| storage = 'Storage',
| |
| worship = 'Worship'
| |
| }
| |
| local resourceText = function(resName, resType, quantity)
| |
| local elemClass = (quantity < 0 and 'text-negative') or 'text-positive'
| |
| local resIcon = Icons.Icon({resName, type=resType, notext=true})
| |
| return resIcon .. ' <span class="' .. elemClass .. '">' .. Shared.numStrWithSign(quantity) .. '</span>'
| |
| end
| |
|
| |
| -- Resources
| |
| if providesData.resources ~= nil then
| |
| for i, resource in ipairs(providesData.resources) do
| |
| local resData = p._getResourceByID(resource.id)
| |
| if resData ~= nil and resource.quantity ~= 0 then
| |
| table.insert(resultPart, resourceText(resData.name, 'resource', resource.quantity))
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- Other stats
| |
| for key, stat in pairs(stats) do
| |
| local quantity = providesData[key]
| |
| if quantity ~= nil and quantity ~= 0 then
| |
| table.insert(resultPart, resourceText(stat, 'township', quantity))
| |
| end
| |
| end
| |
|
| |
| -- Modifiers
| |
| if includeMods and building.modifiers ~= nil then
| |
| table.insert(resultPart, Constants.getModifiersText(building.modifiers))
| |
| end
| |
|
| |
| if not Shared.tableIsEmpty(resultPart) then
| |
| return table.concat(resultPart, delim)
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| -- 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
| |
|
| |
| -- Returns a sorted list of all Township buildings
| |
| function p._sortedBuildings(keepUnsorted)
| |
| local ku = true
| |
| if keepUnsorted ~= nil then
| |
| ku = keepUnsorted
| |
| end
| |
| return GameData.sortByOrderTable(Township.buildings, Township.buildingDisplayOrder, ku)
| |
| end
| |
|
| |
| -- Gets the Township level and population requirements for a tier
| |
| -- Returns {population=X, level=X}
| |
| function p._getTierRequirements(tier)
| |
| return Township.populationForTier[tier]
| |
| end
| |
|
| |
| -- Returns a string containing the Township level and population requirements for a tier
| |
| function p._getTierText(tier)
| |
| local tierData = p._getTierRequirements(tier)
| |
| if tierData ~= nil then
| |
| local tierText = Icons._SkillReq('Township', tierData.level, false)
| |
| if tierData.population > 0 then
| |
| tierText = tierText .. '<br/>' .. Icons.Icon({'Population', type='township', notext=true}) .. ' ' .. Shared.formatnum(tierData.population)
| |
| end
| |
| return tierText
| |
| end
| |
| end
| |
|
| |
| -- Generates a table of all seasons, their type/requirements, and modifiers
| |
| function p.getSeasonTable(frame)
| |
| -- Manual data specifying the worship requirement for those rare seasons
| |
| local seasonReqs = {
| |
| ["Nightfall"] = Icons.Icon({'Township%23Worship', 'Bane Worship', img='Statue of Bane', type='building'}),
| |
| ["SolarEclipse"] = Icons.Icon({'Township%23Worship', 'The Herald Worship', img='Statue of The Herald', type='building'}),
| |
| ["Lemon"] = Icons.Icon({'Ancient_Relics', 'Ancient Relics', img='Ancient Relics'})
| |
| }
| |
|
| |
| local seasons = Shared.shallowClone(Township.seasons)
| |
| table.sort(seasons, function(a, b) return a.order < b.order end)
| |
|
| |
| local resultPart = {}
| |
| table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
| |
| table.insert(resultPart, '\n|- class="headerRow-0"')
| |
| table.insert(resultPart, '\n!colspan="2" | Season\n!Type\n!Modifiers')
| |
|
| |
| for i, season in ipairs(seasons) do
| |
| local ns, localSeasonID = Shared.getLocalID(season.id)
| |
| local reqs = seasonReqs[localSeasonID]
| |
| table.insert(resultPart, '\n|-')
| |
| table.insert(resultPart, '\n|class="table-img"| ' .. Icons.Icon({season.name, type='township', size=50, nolink=true, notext=true}))
| |
| table.insert(resultPart, '\n| ' .. Icons.Icon({season.name, type='township', nolink=true, noicon=true}))
| |
| table.insert(resultPart, '\n| ' .. (season.order <= 3 and 'Regular' or 'Rare'))
| |
| if reqs ~= nil then
| |
| table.insert(resultPart, '<br/>Requires ' .. reqs)
| |
| end
| |
| table.insert(resultPart, '\n| ' .. Constants.getModifiersText(season.modifiers))
| |
| end
| |
| table.insert(resultPart, '\n|}')
| |
|
| |
| return table.concat(resultPart)
| |
| end
| |
|
| |
| -- Generates a table listing all biomes and their associated requirements
| |
| function p.getBiomeTable(frame)
| |
| local resultPart = {}
| |
| table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
| |
| table.insert(resultPart, '\n|- class="headerRow-0"')
| |
| table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="2"| Requirements')
| |
| table.insert(resultPart, '\n|- class="headerRow-1"')
| |
| table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Level', type='skill', nolink=true}))
| |
| table.insert(resultPart, '\n! ' .. Icons.Icon({'Township', 'Population', img='Population', type='township', section='Population' }))
| |
|
| |
| for i, biome in ipairs(Township.biomes) do
| |
| local reqs = p._getTierRequirements(biome.tier)
| |
| table.insert(resultPart, '\n|-\n|class="table-img"| ' .. Icons.Icon({biome.name, type='biome', size=50, nolink=true, notext=true}))
| |
| table.insert(resultPart, '\n| ' .. biome.name)
| |
| table.insert(resultPart, '\n|style="text-align:right"| ' .. reqs.level)
| |
| table.insert(resultPart, '\n|style="text-align:right" data-sort-value="' .. reqs.population .. '"| ' .. Shared.formatnum(reqs.population))
| |
| end
| |
| table.insert(resultPart, '\n|}')
| |
|
| |
| return table.concat(resultPart)
| |
| end
| |
|
| |
| -- Generates a table showing which buildings can be built in which biomes
| |
| -- Skips upgraded buildings
| |
| function p.getBuildingBiomeTable(frame)
| |
| local tbl = mw.html.create('table')
| |
| :addClass('wikitable sortable stickyHeader')
| |
| :css('text-align', 'center')
| |
|
| |
| local header = mw.html.create('tr'):addClass('headerRow-0')
| |
| local level = mw.html.create('tr'):addClass('sorttop')
| |
| local pop = mw.html.create('tr'):addClass('sorttop')
| |
|
| |
| header:tag('th')
| |
| :wikitext('Building')
| |
| level:tag('th')
| |
| :wikitext(Icons.Icon({'Township', 'Level', type='skill', nolink=true}))
| |
| pop:tag('th')
| |
| :wikitext(Icons.Icon({'Township', 'Population', img='Population', type='township', section='Population' }))
| |
|
| |
| for _, biome in ipairs(Township.biomes) do
| |
| local reqs = p._getTierRequirements(biome.tier)
| |
| header:tag('th')
| |
| :wikitext(Icons.Icon({biome.name, type='biome', notext=true, nolink=true}).. '<br/>' .. biome.name)
| |
| level:tag('td')
| |
| :wikitext(Num.formatnum(reqs.level))
| |
| pop:tag('td')
| |
| :wikitext(Num.formatnum(reqs.population))
| |
| end
| |
|
| |
| tbl:node(header)
| |
| tbl:node(level)
| |
| tbl:node(pop)
| |
|
| |
| for _, _building in ipairs(p._sortedBuildings(false)) do
| |
| -- Fix melvorF:Statues
| |
| local building = p._getBuildingByID(_building.id)
| |
| -- Skip upgraded buildings
| |
| if p._getBuildingDowngrade(building) == nil then
| |
| -- Populate the biome habitability data
| |
| local buildingBiomes = {}
| |
| -- Set all valid biomes to true
| |
| for _, biomeid in ipairs(building.biomes) do
| |
| buildingBiomes[biomeid] = true
| |
| end
| |
|
| |
| local trow = tbl:tag('tr')
| |
| trow:tag('th')
| |
| :css('text-align', 'left')
| |
| :attr('data-sort-value', building.name)
| |
| :wikitext(Icons.Icon({building.name, type='building'}))
| |
|
| |
| for _, biome in ipairs(Township.biomes) do
| |
| if buildingBiomes[biome.id] then
| |
| trow:tag('td')
| |
| :addClass('table-positive')
| |
| :wikitext('✓')
| |
| else
| |
| trow:tag('td')
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| return tostring(tbl)
| |
| end
| |
|
| |
| -- 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
| |
|
| |
| -- Generate table header
| |
| table.insert(resultPart, '{| class="wikitable sortable stickyHeader"')
| |
| table.insert(resultPart, '\n|- class="headerRow-0"')
| |
| table.insert(resultPart, '\n!colspan="2"|Building\n!Requirements\n!Max Built')
| |
| 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) 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 = Constants.getModifiersText(building.modifiers)
| |
| if providesText == nil then
| |
| providesText = modText
| |
| else
| |
| providesText = providesText .. '<br/>' .. modText
| |
| end
| |
| end
| |
| table.insert(resultPart, '\n| ' .. (providesText or ''))
| |
| end
| |
| end
| |
| table.insert(resultPart, '\n|}')
| |
|
| |
| return table.concat(resultPart)
| |
| end
| |
|
| |
| -- Builds the table of trader items
| |
| function p.getTraderTable(frame)
| |
| local resultPart = {}
| |
|
| |
| -- Build table header
| |
| 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')
| |
|
| |
| for i, tsResource in ipairs(Township.itemConversions.fromTownship) do
| |
| local res = GameData.getEntityByID(Township.resources, tsResource.resourceID)
| |
| for j, tradeDef in ipairs(tsResource.items) do
| |
| local item = Items.getItemByID(tradeDef.itemID)
| |
| local itemDesc = item.customDescription
| |
| if itemDesc == nil then
| |
| if item.modifiers ~= nil then
| |
| itemDesc = Constants.getModifiersText(item.modifiers, false, true)
| |
| else
| |
| itemDesc = ''
| |
| 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
| |
| table.insert(resultPart, '\n|}')
| |
|
| |
| return table.concat(resultPart)
| |
| end
| |
|
| |
| -- Generates a table showing all the worship options
| |
| function p.getWorshipTable()
| |
| local function getCheckpointCell(checkpoint)
| |
| return '\n|-\n!' .. checkpoint .. '%<br/>' .. Shared.formatnum(checkpoint * Township.maxWorship / 100) .. '/' .. Shared.formatnum(Township.maxWorship)
| |
| end
| |
|
| |
| local worships = GameData.getEntities(Township.worships, function(w) return not w.isHidden end)
| |
| local ret = {}
| |
|
| |
| table.insert(ret, '{| class="wikitable stickyHeader"')
| |
| table.insert(ret, '\n!' .. Icons.Icon({'Worship', type='township', nolink=true}))
| |
| -- Names
| |
| for _, worship in ipairs(worships) do
| |
| table.insert(ret, '\n!' .. Icons.Icon({worship.name, type='monster', size=50}) .. Icons.Icon({'Statue of ' .. worship.name, type='building', size=50, notext=true}))
| |
| end
| |
|
| |
| -- Requirements
| |
| table.insert(ret, '\n|-\n!Requirements')
| |
| for _, worship in ipairs(worships) do
| |
| local cellStyle = (Shared.tableIsEmpty(worship.unlockRequirements) and 'class="table-na"') or 'style="text-align:center"'
| |
| table.insert(ret, '\n|' .. cellStyle ..'| ' .. Shop.getRequirementString(worship.unlockRequirements))
| |
| end
| |
|
| |
| -- Season multipliers
| |
| table.insert(ret, '\n|-\n!Bonus Seasons')
| |
| for _, worship in ipairs(worships) do
| |
| local bonusPart = {}
| |
| local cellStyle = 'style="text-align:center"'
| |
| if Shared.tableIsEmpty(worship.seasonMultiplier) then
| |
| bonusPart, cellStyle = {'None'}, 'class="table-na"'
| |
| end
| |
| for i, seasonMult in ipairs(worship.seasonMultiplier) do
| |
| local season = GameData.getEntityByID(Township.seasons, seasonMult.seasonID)
| |
| if season ~= nil then
| |
| table.insert(bonusPart, Icons.Icon({season.name, type='township', nolink=true}) .. ' (' .. seasonMult.multiplier .. 'x)')
| |
| end
| |
| end
| |
| table.insert(ret, '\n|' .. cellStyle .. '| ' .. table.concat(bonusPart, '<br/>'))
| |
| end
| |
|
| |
| -- Base modifiers
| |
| table.insert(ret, getCheckpointCell(0))
| |
| for _, worship in ipairs(worships) do
| |
| table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.modifiers))
| |
| end
| |
|
| |
| -- Checkpoint modifiers
| |
| for i, checkpoint in ipairs(Township.worshipCheckpoints) do
| |
| table.insert(ret, getCheckpointCell(checkpoint))
| |
| for _, worship in ipairs(worships) do
| |
| table.insert(ret, '\n| ' .. Constants.getModifiersText(worship.checkpoints[i]))
| |
| end
| |
| end
| |
|
| |
| -- Total sum
| |
| 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|' .. Constants.getModifiersText(modifiers))
| |
| end
| |
| table.insert(ret, '\n|}')
| |
|
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Gets a building and prepares all the relevant stats for the building, presented as an infobox
| |
| function p.getBuildingInfoBox(frame)
| |
| local name = frame.args ~= nil and frame.args[1] or frame
| |
| local building = p._getBuildingByName(name)
| |
| if building == nil then
| |
| return Shared.printError('No building named "' .. name .. '" exists in the data module')
| |
| end
| |
|
| |
| local ret = {}
| |
| -- Header
| |
| table.insert(ret, '{| class="wikitable infobox"')
| |
| -- Name
| |
| table.insert(ret, '\n|-\n! ' .. Icons.getExpansionIcon(building.id) .. building.name)
| |
| -- Icon
| |
| table.insert(ret, '\n|-\n|style="text-align:center"| ' .. Icons.Icon({building.name, type='building', size='250', notext=true}))
| |
| -- ID
| |
| table.insert(ret, '\n|-\n| <b>Building ID:</b> ' .. building.id)
| |
| -- Tier
| |
| local tier = p._getTierText(building.tier)
| |
| 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
| |
| -- Cost
| |
| --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))
| |
|
| |
| -- Upgrades To
| |
| local upgradesTo = p._getBuildingUpgrade(building)
| |
| if upgradesTo ~= nil then
| |
| table.insert(ret, '\n|-\n| <b>Upgrades To:</b>')
| |
| table.insert(ret, '<br/>' .. Icons.Icon({upgradesTo.name, type='building'}))
| |
| table.insert(ret, '\n' .. getGroupedText(upgradesTo, p._getBuildingGroupedCosts))
| |
| end
| |
|
| |
| -- Maximum built
| |
| local biomeCount = Shared.tableCount(building.biomes)
| |
| local maxText = Shared.formatnum(building.maxUpgrades)
| |
| if biomeCount > 1 then
| |
| maxText = maxText .. ' per biome, ' .. Shared.formatnum(biomeCount * building.maxUpgrades) .. ' total'
| |
| end
| |
| table.insert(ret, '\n|-\n| <b>Maximum Built:</b><br/>' .. maxText)
| |
|
| |
| -- Benefits
| |
| local benefits = p._getBuildingGroupedBenefitText(building)
| |
| if benefits ~= nil and benefits ~= '' then
| |
| table.insert(ret, '\n|-\n| <b>Provides:</b><br/>' .. benefits)
| |
| end
| |
|
| |
| -- Biomes
| |
| table.insert(ret, '\n|-\n| <b>Biomes:</b>')
| |
| for _, biomeid in ipairs(building.biomes) do
| |
| local biome = GameData.getEntityByID(Township.biomes, biomeid)
| |
| table.insert(ret, '<br/>' .. Icons.Icon({biome.name, type='biome', nolink=true}))
| |
| end
| |
|
| |
| -- End
| |
| table.insert(ret, '\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Returns an upgrade table of a building
| |
| function p.getBuildingUpgradeTable(frame)
| |
| local buildingname = frame.args ~= nil and frame.args[1] or frame
| |
| local building = p._getBuildingByName(buildingname)
| |
| if building == nil then
| |
| return Shared.printError('No building named "' .. buildingname .. '" exists in the data module')
| |
| end
| |
|
| |
| -- Let's find the base building
| |
| local baseBuilding = building
| |
| while true do
| |
| local previousBuilding = p._getBuildingDowngrade(baseBuilding)
| |
| if previousBuilding ~= nil then
| |
| baseBuilding = previousBuilding
| |
| else
| |
| break
| |
| end
| |
| end
| |
|
| |
| -- 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)
| |
| local buildingList = {}
| |
| local _curBuilding = baseBuilding
| |
| while true do
| |
| table.insert(buildingList, _curBuilding)
| |
| _curBuilding = p._getBuildingUpgrade(_curBuilding)
| |
| if _curBuilding == nil then
| |
| break
| |
| end
| |
| end
| |
| if #buildingList == 1 then
| |
| return ''
| |
| end
| |
|
| |
| local ret = {}
| |
| table.insert(ret, '\n== Upgrade Chart ==')
| |
| table.insert(ret, '\n{| class="wikitable" style="text-align:center"')
| |
|
| |
| -- Name
| |
| table.insert(ret, '\n|-\n!colspan="2"| Name')
| |
| for _, building in ipairs(buildingList) do
| |
| table.insert(ret, '\n!' .. Icons.getExpansionIcon(building.id) .. Icons.Icon({building.name, type='building'}))
| |
| end
| |
|
| |
| -- Tier
| |
| table.insert(ret, '\n|-\n!colspan="2"| Requirements')
| |
| for _, building in ipairs(buildingList) do
| |
| table.insert(ret, '\n|' .. p._getTierText(building.tier))
| |
| end
| |
|
| |
| -- Cost
| |
| local biomeCount = Shared.tableCount(baseBuilding.biomes)
| |
| table.insert(ret, '\n|-\n!rowspan="' .. biomeCount .. '"| Cost')
| |
| local firstBiome = true
| |
| 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
| |
|
| |
| -- Benefits
| |
| local benefitText = {}
| |
| table.insert(benefitText, '\n|-\n!rowspan="' .. biomeCount .. '"| Benefits')
| |
| firstBiome = true
| |
| local hasText = false
| |
| for _, biomeID in ipairs(baseBuilding.biomes) do
| |
| local biome = GameData.getEntityByID(Township.biomes, biomeID)
| |
| table.insert(benefitText, (firstBiome and '' or '\n|-') .. '\n! ' .. Icons.Icon({biome.name, type='biome', nolink=true}))
| |
| for _, building in ipairs(buildingList) do
| |
| local benefit = p._getBuildingBenefitText(building, biomeID, true) or ''
| |
| if not hasText and benefit ~= '' then
| |
| hasText = true
| |
| end
| |
| table.insert(benefitText, '\n| ' .. benefit)
| |
| end
| |
| 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
| |
| 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, isDailyTask)
| |
| local ret = {}
| |
|
| |
| -- If has description, we will need to rowspan the title by 2, and insert a description with colspan 2
| |
| local hasDescription = false
| |
| if task.description ~= nil then
| |
| hasDescription = true
| |
| end
| |
| local titlespan = hasDescription == true and 'rowspan="2"|' or ''
| |
|
| |
| -- Title
| |
| table.insert(ret, '\n|-')
| |
| table.insert(ret, '\n!' .. titlespan .. title)
| |
| -- Description
| |
| if hasDescription then
| |
| table.insert(ret, '\n|colspan="2"|' .. task.description)
| |
| table.insert(ret, '\n|-')
| |
| end
| |
| -- Requirements
| |
| table.insert(ret, '\n|')
| |
| -- Determines order of requirements output
| |
| local reqOrder = {
| |
| ["items"] = 10,
| |
| ["monsters"] = 20,
| |
| ["monsterWithItems"] = 30,
| |
| ["skillXP"] = 40,
| |
| ["buildings"] = 50,
| |
| ["numPOIs"] = 60,
| |
| ["numRefinements"] = 70
| |
| }
| |
| local reqTextPart = {}
| |
|
| |
| local function getItemText(itemID)
| |
| local item = Items.getItemByID(itemID)
| |
| if item == nil then
| |
| return Shared.printError('Unknown item: ' .. (itemID or 'nil'))
| |
| else
| |
| return Icons.Icon({item.name, type='item'})
| |
| end
| |
| end
| |
| local function getMonsterText(monsterID)
| |
| local monster = Monsters.getMonsterByID(monsterID)
| |
| if monster == nil then
| |
| return Shared.printError('Unknown monster: ' .. (monsterID or 'nil'))
| |
| else
| |
| return Icons.Icon({Monsters.getMonsterName(monster), type='monster'})
| |
| end
| |
| end
| |
|
| |
| for goalType, goalData in pairs(task.goals) do
| |
| local typeOrder = reqOrder[goalType] or 0
| |
| local goalText = nil
| |
| if type(goalData) == 'table' then
| |
| -- Goal data is a table
| |
| for goalIdx, goalObj in ipairs(goalData) do
| |
| if goalType == 'items' then
| |
| goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. getItemText(goalObj.id)
| |
| elseif goalType == 'monsters' then
| |
| goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. getMonsterText(goalObj.id)
| |
| elseif goalType == 'monsterWithItems' then
| |
| local itemsText = {}
| |
| for i, itemID in ipairs(goalObj.itemIDs) do
| |
| table.insert(itemsText, getItemText(itemID))
| |
| end
| |
| goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. getMonsterText(goalObj.monsterID) .. ' with ' .. table.concat(itemsText, ', ') .. ' equipped'
| |
| elseif goalType == 'skillXP' then
| |
| local skillName = GameData.getSkillData(goalObj.id).name
| |
| goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. Icons.Icon({skillName, type='skill'}) .. ' XP'
| |
| elseif goalType == 'buildings' then
| |
| local buildingName = p._GetBuildingByID(goalObj.id).name
| |
| goalText = Shared.formatnum(goalObj.quantity) .. ' ' .. Icons.Icon({buildingName, type='building'})
| |
| elseif goalType == 'numPOIs' then
| |
| local mapName = GameData.getEntityByID(GameData.skillData.Cartography.worldMaps, goalObj.worldMapID).name
| |
| goalText = 'Discover ' .. Shared.formatnum(goalObj.quantity) .. ' Points of Interest in ' .. Icons.Icon({'Cartography', type='skill'}) .. ' world map of ' .. mapName
| |
| else
| |
| goalText = Shared.printError('Unknown goal type: ' .. (goalType or 'nil'))
| |
| end
| |
| table.insert(reqTextPart, {
| |
| ["goalOrder"] = typeOrder,
| |
| ["subOrder"] = goalIdx,
| |
| ["text"] = goalText
| |
| })
| |
| end
| |
| else
| |
| -- Goal data is another value of some type
| |
| if goalType == 'numRefinements' then
| |
| goalText = 'Refine dig site maps in ' .. Icons.Icon({'Cartography', type='skill'}) .. ' ' .. Shared.formatnum(goalData) .. ' times'
| |
| else
| |
| goalText = Shared.printError('Unknown goal type: ' .. (goalType or 'nil'))
| |
| end
| |
| table.insert(reqTextPart, {
| |
| ["goalOrder"] = typeOrder,
| |
| ["subOrder"] = 0,
| |
| ["text"] = goalText
| |
| })
| |
| end
| |
| end
| |
|
| |
| table.sort(reqTextPart,
| |
| function(a, b)
| |
| if a.goalOrder == b.goalOrder then
| |
| return a.subOrder < b.subOrder
| |
| else
| |
| return a.goalOrder < b.goalOrder
| |
| end
| |
| end
| |
| )
| |
|
| |
| local requirements = {}
| |
| for i, req in ipairs(reqTextPart) do
| |
| table.insert(requirements, req.text)
| |
| end
| |
| -- We don't check tasks.requirements (so far it's only used to enumerate the Tutorial tasks so you only see 1 at a time)
| |
| table.insert(ret, table.concat(requirements, '<br/>'))
| |
| -- Rewards
| |
| table.insert(ret, '\n|')
| |
| local rewards = {}
| |
| local rewardsVariableQty = {}
| |
| if task.rewards.gp > 0 and not isDailyTask then
| |
| table.insert(rewards, Icons.GP(task.rewards.gp))
| |
| end
| |
| if task.rewards.slayerCoins > 0 then
| |
| if isDailyTask then
| |
| table.insert(rewardsVariableQty, Icons.SC())
| |
| else
| |
| table.insert(rewards, Icons.SC(task.rewards.slayerCoins))
| |
| end
| |
| end
| |
| for _, item in ipairs(task.rewards.items) do
| |
| local itemname = GameData.getEntityByID('items', item.id).name
| |
| table.insert(rewards, Shared.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
| |
| end
| |
| for _, skill in ipairs(task.rewards.skillXP) do
| |
| if not (isDailyTask and skill.id == 'melvorD:Township') then
| |
| local skillname = GameData.getSkillData(skill.id).name
| |
| table.insert(rewards, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
| |
| end
| |
| end
| |
| for _, townshipResource in ipairs(task.rewards.townshipResources) do
| |
| local resourcename = p._getResourceByID(townshipResource.id).name
| |
| table.insert(rewards, Shared.formatnum(townshipResource.quantity)..' '..Icons.Icon({resourcename, type='resource'}))
| |
| end
| |
| if not Shared.tableIsEmpty(rewardsVariableQty) then
| |
| table.insert(ret, '[[Township#Casual Tasks|Variable]] ' .. table.concat(rewardsVariableQty, ', ') .. '<br/>')
| |
| end
| |
| table.insert(ret, table.concat(rewards, '<br/>'))
| |
|
| |
| -- Unlock requirements, daily task specific
| |
| if isDailyTask then
| |
| table.insert(ret, '\n|' .. Shop.getRequirementString(task.requirements))
| |
| end
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Returns all the tasks of a given category
| |
| -- TODO: Support casual tasks
| |
| function p.getTaskTable(frame)
| |
| local category = frame.args ~= nil and frame.args[1] or frame
| |
| local categoryData = GameData.getEntityByID(Township.taskCategories, category)
| |
| local taskData, categoryName, isDailyTask = nil, nil, false
| |
| if category == 'Daily' then
| |
| isDailyTask = true
| |
| taskData = Township.casualTasks
| |
| categoryName = 'Casual'
| |
| elseif categoryData ~= nil then
| |
| taskData = Township.tasks
| |
| categoryName = categoryData.name
| |
| else
| |
| return Shared.printError('Invalid task category specified: ' .. (tostring(category) or 'nil'))
| |
| end
| |
|
| |
| local taskcount = 0
| |
| local ret = {}
| |
| table.insert(ret, '{| class="wikitable lighttable stickyHeader" style="text-align:left"')
| |
| table.insert(ret, '\n|- class="headerRow-0"')
| |
| table.insert(ret, '\n!Task')
| |
| table.insert(ret, '\n!Requirements')
| |
| table.insert(ret, '\n!Rewards')
| |
| if isDailyTask then
| |
| table.insert(ret, '<br/>(In addition to [[Township#Casual Tasks|Variable]] ' .. Icons.GP() .. ' & ' .. Icons.Icon({'Township', type='skill', notext=true}) .. ' XP)')
| |
| end
| |
| if isDailyTask then
| |
| table.insert(ret, '\n!Unlock Requirements')
| |
| end
| |
|
| |
| for _, task in ipairs(taskData) do
| |
| -- Filter out other categories
| |
| if task.category == category then
| |
| taskcount = taskcount + 1
| |
| local title = categoryName .. ' ' .. taskcount
| |
| table.insert(ret, p._getTaskRow(title, task, isDailyTask))
| |
| end
| |
| end
| |
| table.insert(ret, '\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Returns a table containing all the tasks that reference an item or monster
| |
| -- e.g. p.getTaskReferenceTable({'Chicken Coop', 'dungeon'})
| |
| -- name = item or monster name
| |
| -- type = 'item' or 'monster' or 'dungeon'
| |
| function p.getTaskReferenceTable(frame)
| |
| -- Returns a set containing all the desired IDs
| |
| local function GetReferenceIDs(referenceName, referenceType)
| |
| local IDs = {}
| |
| if referenceType == 'dungeon' then
| |
| -- We get the tasks associated with all monsters in the dungeon
| |
| local monsters = GameData.getEntityByName('dungeons', referenceName).monsterIDs
| |
| for _, monster in ipairs(monsters) do
| |
| IDs[monster] = true
| |
| end
| |
| end
| |
| if referenceType == 'item' then
| |
| IDs[GameData.getEntityByName('items', referenceName).id] = true
| |
| end
| |
| if referenceType == 'monster' then
| |
| IDs[Monsters.getMonster(referenceName).id] = true
| |
| end
| |
| return IDs
| |
| end
| |
| -- For a task, returns where to search for the desired IDs, given the type
| |
| local function GetGetSearchTables(referenceType)
| |
| local function searchItems(task)
| |
| return {task.goals.items, task.rewards.items}
| |
| end
| |
| local function searchMonsters(task)
| |
| return {task.goals.monsters}
| |
| end
| |
| -- item -> searchItems; monster or dungeon -> searchMonsters
| |
| return referenceType == 'item' and searchItems or searchMonsters
| |
| end
| |
|
| |
| local args = frame.args ~= nil and frame.args or frame
| |
| local referenceName = Shared.fixPagename(args[1])
| |
| local referenceType = args[2]
| |
| local referenceIDs = GetReferenceIDs(referenceName, referenceType)
| |
| -- GetSearchTables = function searchItems/Monsters(task)
| |
| local GetSearchTables = GetGetSearchTables(referenceType)
| |
|
| |
| local function checkTask(task)
| |
| local function checkID(entry)
| |
| return referenceIDs[entry.id] ~= nil
| |
| end
| |
| for _, searchTable in ipairs(GetSearchTables(task)) do
| |
| -- Check to see if the table contains any of the IDs in referenceIDs
| |
| if searchTable[1] ~= nil then -- Make sure table is not empty
| |
| if #GameData.getEntities(searchTable, checkID) ~= 0 then -- Make sure we have at least 1 match
| |
| return true
| |
| end
| |
| end
| |
| end
| |
| return false
| |
| end
| |
| -- Find all tasks that contain the desired ids
| |
| local tasks = GameData.getEntities(Township.tasks, checkTask)
| |
| if #tasks == 0 then
| |
| return ''
| |
| end
| |
|
| |
| -- Build the table
| |
| local ret = {}
| |
| table.insert(ret, '==Tasks==')
| |
| table.insert(ret, '\n{| class="wikitable" style="text-align:left"')
| |
| table.insert(ret, '\n!Task')
| |
| table.insert(ret, '\n!Requirements')
| |
| table.insert(ret, '\n!Rewards')
| |
| for _, task in ipairs(tasks) do
| |
| local categoryname = GameData.getEntityByID(Township.taskCategories, task.category).name
| |
| local title = '[[Township/Tasks#'..categoryname..'|'..categoryname..']]'
| |
| table.insert(ret, p._getTaskRow(title, task, false))
| |
| end
| |
| table.insert(ret, '\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| return p
| |