|
|
(4 intermediate revisions by 2 users not shown) |
Line 1: |
Line 1: |
| local Shared = require('Module:Shared')
| |
| local Icons = require('Module:Icons')
| |
| local GameData = require('Module:GameData')
| |
| local Constants = require('Module:Constants')
| |
|
| |
|
| local p = {}
| |
|
| |
| local Township = GameData.getSkillData('melvorD:Township')
| |
| p.Township = Township
| |
|
| |
|
| |
| -- Returns the recipe for the item of a desired skill.
| |
| -- Unfortunately Module:Items/SourceTables.getItemSources does not provide parseable data so we instead use this quick function
| |
| function p._FindItemRecipes(itemid, skill)
| |
|
| |
| -- No skill? No recipes
| |
| if skill == nil then
| |
| return {}
| |
| end
| |
|
| |
| -- the key name for each skill in the json file
| |
| local skill_recipe_keys = {
| |
| ['melvorD:Woodcutting'] = {recipes='trees', productID='productId'}, -- lowercase "d"
| |
| ['melvorD:Fishing'] = {recipes='fish', productID='productId'}, -- lowercase "d"
| |
| ['melvorD:Cooking'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Mining'] = {recipes='rockData', productID='productId'}, -- lowercase "d"
| |
| ['melvorD:Smithing'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Farming'] = {recipes='recipes', productID='productId'}, -- lowercase "d"
| |
| ['melvorD:Summoning'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Fletching'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Crafting'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Runecrafting'] = {recipes='recipes', productID='productID'},
| |
| ['melvorD:Herblore'] = {recipes='recipes', productID='potionIDs'} -- Special case potions I-IV
| |
| --[[ Excluded skills:
| |
| Attack, Strength, Defence, Magic, Ranged, Prayer, Slayer
| |
| Thieving, Agility, Astrology, Firemaking, Township (not items)]]
| |
| }
| |
|
| |
| local results = {}
| |
|
| |
| local SkillData = GameData.getSkillData(skill)
| |
| local recipes = skill_recipe_keys[skill].recipes
| |
| local productID = skill_recipe_keys[skill].productID
| |
|
| |
| if SkillData[recipes] ~= nil then
| |
| for _, recipe in ipairs(SkillData[recipes]) do
| |
| -- Special case for Herblore
| |
| if skill == 'melvorD:Herblore' then
| |
| -- Iterate over the 4 potion tiers
| |
| for _, potionid in ipairs(recipe[productID]) do
| |
| if itemid == potionid then
| |
| table.insert(results, Shared.clone(recipe))
| |
| end
| |
| end
| |
| -- Base case
| |
| else
| |
| if itemid == recipe[productID] then
| |
| table.insert(results, Shared.clone(recipe))
| |
| end
| |
| end
| |
| end
| |
| end
| |
|
| |
| return results
| |
| end
| |
|
| |
| -- Returns a list of all the Township resources
| |
| function p._ResourcesData()
| |
|
| |
| -- Get a sorted list of all the resources
| |
| local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder)
| |
| resources = Shared.clone(resources)
| |
|
| |
| return resources
| |
| end
| |
|
| |
| -- Returns a sorted list of all Township buildings
| |
| function p._SortedBuildings()
| |
| return GameData.sortByOrderTable(Township.buildings, Township.buildingDisplayOrder)
| |
| 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()
| |
|
| |
| -- Builds the table of trader items
| |
| function p.GetTraderTable(frame)
| |
| -- Get the resources data with associated trader data
| |
|
| |
| -- Build the text
| |
| local ret = {}
| |
| for _, resource in ipairs(p.resources) do
| |
| if #resource.itemConversions ~= 0 then -- Skips GP
| |
| local ret_resource = {}
| |
|
| |
| -- Header
| |
| table.insert(ret_resource, '\r\n==='..resource.name..'===')
| |
| table.insert(ret_resource, '\r\n{| class="wikitable sortable stickyHeader"')
| |
| table.insert(ret_resource, '\r\n|- class="headerRow-0"')
| |
| table.insert(ret_resource, '\r\n!Item')
| |
| 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
| |
|
| |
| -- 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
| |
| 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
| |
|
| |
| table.insert(ret_resource, '\r\n|}')
| |
|
| |
| table.insert(ret, table.concat(ret_resource))
| |
| end
| |
| end
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- 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 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 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(tierlevel)
| |
| local tier = p._GetTierRequirements(tierlevel)
| |
| return Icons._SkillReq('Township', tier.level, false)..'<br>'..Icons.Icon({'Population', type='township', notext=true})..' '..tier.population
| |
| end
| |
|
| |
|
| |
| -- Gets a building and prepares all the relevant stats for the building
| |
| function p.GetBuildingTable(frame)
| |
| local name = frame.args ~= nil and frame.args[1] or frame
| |
| local building = Shared.clone(p._GetBuildingByName(name))
| |
| local ret = {}
| |
|
| |
| -- Header
| |
| table.insert(ret, '\r\n{| class="wikitable infobox"')
| |
| -- Name
| |
| table.insert(ret, '\r\n|-\r\n!'..building.name)
| |
| -- 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
| |
| table.insert(ret, '\r\n|-\r\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
| |
| local cost = p._GetBuildingBaseCost(building)
| |
| table.insert(ret, '<br>'..cost)
| |
|
| |
| -- Upgrades To
| |
| local upgradesTo = p._GetBuildingIDUpgrade(building.id)
| |
| if upgradesTo ~= nil then
| |
| table.insert(ret, '\r\n|-\r\n| <b>Upgrades To:</b>')
| |
| table.insert(ret, '<br>'..Icons.Icon({upgradesTo.name, type='building'}))
| |
| local upgrade_cost = p._GetBuildingBaseCost(upgradesTo)
| |
| table.insert(ret, '<br>'..upgrade_cost)
| |
| end
| |
|
| |
| -- Fixed benefits
| |
| local benefits = p._GetBuildingBenefits(building)
| |
| if benefits ~= nil then
| |
| table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..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
| |
|
| |
| -- 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
| |
| -- Optional hidden bonus/penalty for building
| |
| local modifier = nil
| |
| if #building.biomeModifiers > 0 then
| |
| modifier = GameData.getEntityByProperty(building.biomeModifiers, 'biomeID', biomeid)
| |
| end
| |
| if modifier ~= nil then
| |
| local color = modifier.value < 0 and 'red' or 'green'
| |
| local modifier_value = Shared.numStrWithSign(modifier.value)
| |
| table.insert(ret, '<br>'..Icons.Icon({biomename, type='biome', notext=true, nolink=true})..' <span style="color:'..color..'"><b>'..biomename..' ('..modifier_value..'%)</b></span>')
| |
| else
| |
| table.insert(ret, '<br>'..Icons.Icon({biomename, type='biome', notext=true, nolink=true})..' <span>'..biomename..'</span>')
| |
| end
| |
| end
| |
|
| |
| -- End
| |
| table.insert(ret, '\r\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Given a resource id, return the job id
| |
| -- e.g. melvorF:Bar -> melvorF:Blacksmith
| |
| function p._GetJobFromResource(resource_id)
| |
| local job = GameData.getEntityByProperty(Township.jobs, 'produces', resource_id)
| |
| return job.id
| |
| end
| |
|
| |
| -- Gets a string displaying the base production of a building, or nil if no production
| |
| function p._GetBuildingBaseProduction(building)
| |
| local production = Shared.clone(building.provides.resources)
| |
|
| |
| if #production == 0 then
| |
| return nil
| |
| end
| |
|
| |
| local retResources = {}
| |
| for _, resource in ipairs(production) do
| |
| local retProduction = {}
| |
| local job = p._GetJobFromResource(resource.id)
| |
| local workers = GameData.getEntityByID(building.provides.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})..' '..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})..' -'..demand..'</span>')
| |
| end
| |
| end
| |
| retProduction = table.concat(retProduction, ', ')..'/t ('..Icons.Icon({'Workers', type='township', notext=true})..' '..workers..')'
| |
| table.insert(retResources, retProduction)
| |
| end
| |
| return table.concat(retResources, '<br>')
| |
| end
| |
|
| |
| -- Gets a string displaying the building's benefits, or nil if no benefits
| |
| function p._GetBuildingBenefits(building)
| |
| local benefits = {}
| |
| local stats = {
| |
| population = 'Population',
| |
| happiness = 'Happiness',
| |
| education = 'Education',
| |
| storage = 'Storage',
| |
| deadStorage = 'Dead Storage',
| |
| worship = 'Worship'
| |
| }
| |
| for key, stat in pairs(stats) do
| |
| if building.provides[key] ~= nil and building.provides[key] ~= 0 then
| |
| local quantity = building.provides[key]
| |
| if quantity < 0 then
| |
| quantity = '<span style="color:red">'..quantity..'</span>'
| |
| else
| |
| quantity = Shared.numStrWithSign(quantity)
| |
| end
| |
| table.insert(benefits, Icons.Icon({stat, type='township', notext=true})..' '..quantity)
| |
| end
| |
| end
| |
| if #benefits > 0 then
| |
| return table.concat(benefits, ', ')
| |
| end
| |
| return nil
| |
| 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 building's downgrade
| |
| function p._GetBuildingDowngrade(building)
| |
| if building.upgradesFrom ~= nil then
| |
| return p._GetBuildingByID(building.upgradesFrom)
| |
| 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 = {}
| |
| for _, resource in ipairs(building.cost) do
| |
| local resource_data = p._GetResourceByID(resource.id)
| |
| table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..' '..resource.quantity)
| |
| end
| |
| return table.concat(cost, join)
| |
| end
| |
|
| |
| -- Gets a resource from id
| |
| function p._GetResourceByID(id)
| |
| return GameData.getEntityByID(p.resources, id)
| |
| end
| |
|
| |
| -- Gets text for only the biomes that have a modifier for a building
| |
| function p._GetBiomeModifiers(building)
| |
| 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
| |
| if #biomeRet == 0 then
| |
| return nil
| |
| end
| |
| return table.concat(biomeRet, '<br>')
| |
| 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
| |
| 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._GetBuildingIDUpgrade(_curBuilding.id)
| |
| if _curBuilding == nil then
| |
| break
| |
| end
| |
| end
| |
| if #buildingList == 1 then
| |
| return ''
| |
| end
| |
|
| |
| local ret = {}
| |
| table.insert(ret, '\r\n== Upgrade Chart ==')
| |
| table.insert(ret, '\r\n{| class="wikitable"')
| |
|
| |
| -- Name
| |
| table.insert(ret, '\r\n|- style="text-align:center" \r\n! Name')
| |
| for _, building in ipairs(buildingList) do
| |
| table.insert(ret, '\r\n!'..Icons.Icon({building.name, type='building'}))
| |
| end
| |
|
| |
| -- Tier
| |
| table.insert(ret, '\r\n|-\r\n! Requirements')
| |
| for _, building in ipairs(buildingList) do
| |
| local tier = p._GetTierText(building.tier)
| |
| table.insert(ret, '\r\n|'..tier)
| |
| end
| |
|
| |
| -- Cost
| |
| table.insert(ret, '\r\n|-\r\n! Cost')
| |
| for _, building in ipairs(buildingList) do
| |
| local cost = p._GetBuildingBaseCost(building)
| |
| table.insert(ret, '\r\n|'..cost)
| |
| end
| |
|
| |
| -- Optional params
| |
|
| |
| -- Generate a row
| |
| -- textFunc: returns nil if no data for a building, or else returns a string
| |
| local function BuildOptionalRow(header, textFunc)
| |
| local texts = {}
| |
| local hasTexts = false
| |
| for _, building in ipairs(buildingList) do
| |
| local text = textFunc(building)
| |
| hasTexts = hasTexts == true or text ~= nil
| |
| texts = texts ~= nil and texts or ''
| |
| table.insert(texts, text)
| |
| end
| |
| if hasTexts == true then
| |
| texts = table.concat(texts, '\r\n|')
| |
| table.insert(ret, header..texts)
| |
| 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
| |
| table.insert(ret, '\r\n|}')
| |
|
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| local FREE_LAND = Township.sectionSize
| |
| -- 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()
| |
| 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)
| |
| end
| |
|
| |
| -- Generates a table showing which buildings can be built in which biomes
| |
| -- Skips upgraded buildings
| |
| function p.GetBuildingBiomeTable()
| |
| -- Setup the table
| |
| local ret = {}
| |
| table.insert(ret, '\r\n{| class="wikitable sortable" style="text-align:center"')
| |
| table.insert(ret, '\r\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 biomeModifiersMaster = {}
| |
| for _, biome in ipairs(Township.biomes) do
| |
| table.insert(ret, '\r\n!'..Icons.Icon({biome.name, type='biome', notext=true, nolink=true})..'<br>'..biome.name)
| |
| biomeModifiersMaster[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 biomeModifiers = Shared.clone(biomeModifiersMaster)
| |
| -- Set all valid biomes to 0
| |
| for _, biomeid in ipairs(building.biomes) do
| |
| biomeModifiers[biomeid] = 0
| |
| end
| |
| -- Then add the biome modifier values
| |
| for _, biomeModifier in ipairs(building.biomeModifiers) do
| |
| biomeModifiers[biomeModifier.biomeID] = biomeModifier.value
| |
| end
| |
|
| |
| -- Let's build the row
| |
| table.insert(ret, '\r\n|-')
| |
| table.insert(ret, '\r\n!data-sort-value="'..building.name..'" style="text-align:left"|'..Icons.Icon({building.name, type='building'}))
| |
| for _, biome in ipairs(Township.biomes) do
| |
| local modifier = biomeModifiers[biome.id]
| |
| if modifier then
| |
| if modifier == 0 then
| |
| -- Buildable but no bonuses
| |
| table.insert(ret, '\r\n|class="table-na"|+0%')
| |
| else
| |
| -- Bonus or penalty
| |
| local class = modifier < 0 and 'table-negative' or 'table-positive'
| |
| local modifier_value = Shared.numStrWithSign(modifier)
| |
| table.insert(ret, '\r\n|class="'..class..'"|<b>'..modifier_value..'%</b>')
| |
| end
| |
| else
| |
| -- Invalid biome
| |
| table.insert(ret, '\r\n|style="border:0px"|')
| |
| end
| |
| end
| |
| end
| |
| end
| |
| table.insert(ret, '\r\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
|
| |
| -- Generates a table showing all the maps and the number of biomes
| |
| -- Skips upgraded buildings
| |
| function p.GetMapTable()
| |
| -- Setup the table
| |
| local ret = {}
| |
| table.insert(ret, '\r\n{| class="wikitable sortable" style="text-align:center"')
| |
| table.insert(ret, '\r\n!Map')
| |
|
| |
| -- Make two table that will keep track of the max/min amount of land for each biome
| |
| -- At the same time, make the output table header
| |
| local biomeMax = {}
| |
| local biomeMin = {}
| |
| for _, biome in ipairs(Township.biomes) do
| |
| table.insert(ret, '\r\n!'..Icons.Icon({biome.name, type='biome', notext=true, nolink=true})..'<br>'..biome.name)
| |
| biomeMax[biome.id] = -1
| |
| biomeMin[biome.id] = Township.maxTownSize + 1
| |
| end
| |
|
| |
| -- Find the min and max amount for each biome
| |
| for _, map in ipairs(Township.maps) do
| |
| for _, biome in ipairs(map.biomes) do
| |
| biomeMax[biome.biomeID] = math.max(biomeMax[biome.biomeID], biome.count)
| |
| biomeMin[biome.biomeID] = math.min(biomeMin[biome.biomeID], biome.count)
| |
| end
| |
| end
| |
|
| |
| -- Draw all the map rows
| |
| for _, map in ipairs(Township.maps) do
| |
| table.insert(ret, '\r\n|-')
| |
| table.insert(ret, '\r\n!style="text-align:left"|'..map.name)
| |
|
| |
| for _, biome in ipairs(map.biomes) do
| |
| -- Color the cell if min or max value
| |
| local max = biomeMax[biome.biomeID]
| |
| local min = biomeMin[biome.biomeID]
| |
| local count = biome.count
| |
| local class = count == max and 'table-positive' or count == min and 'table-negative' or ''
| |
| -- Insert cell
| |
| table.insert(ret, '\r\n|class="'..class..'"|'..count)
| |
| end
| |
| end
| |
| table.insert(ret, '\r\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
| |
| local hasDescription = false
| |
| if task.description ~= nil then
| |
| hasDescription = true
| |
| end
| |
| local titlespan = hasDescription == true and 'rowspan="2"|' or ''
| |
|
| |
| -- Title
| |
| table.insert(ret, '\r\n|-')
| |
| table.insert(ret, '\r\n!'..titlespan..title)
| |
| -- Description
| |
| if hasDescription then
| |
| table.insert(ret, '\r\n|colspan="2"|'..task.description)
| |
| table.insert(ret, '\r\n|-')
| |
| end
| |
| -- Requirements
| |
| table.insert(ret, '\r\n|')
| |
| local requirements = {}
| |
| for _, item in ipairs(task.goals.items) do
| |
| local itemname = GameData.getEntityByID('items', item.id).name
| |
| table.insert(requirements, Shared.formatnum(item.quantity)..' '..Icons.Icon({itemname, type='item'}))
| |
| end
| |
| for _, monster in ipairs(task.goals.monsters) do
| |
| local monstername = GameData.getEntityByID('monsters', monster.id).name
| |
| table.insert(requirements, Shared.formatnum(monster.quantity)..' '..Icons.Icon({monstername, type='monster'}))
| |
| end
| |
| for _, skill in ipairs(task.goals.skillXP) do
| |
| local skillname = GameData.getSkillData(skill.id).name
| |
| table.insert(requirements, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
| |
| end
| |
| for _, building in ipairs(task.goals.buildings) do
| |
| local buildingname = p._GetBuildingByID(building.id).name
| |
| table.insert(requirements, Shared.formatnum(building.quantity)..' '..Icons.Icon({buildingname, type='building'}))
| |
| 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, '\r\n|')
| |
| local rewards = {}
| |
| if task.rewards.gp ~= 0 then
| |
| table.insert(rewards, Icons.GP(task.rewards.gp))
| |
| end
| |
| if task.rewards.slayerCoins ~= 0 then
| |
| table.insert(rewards, Icons.SC(task.rewards.slayerCoins))
| |
| 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
| |
| local skillname = GameData.getSkillData(skill.id).name
| |
| table.insert(rewards, Shared.formatnum(skill.quantity)..' '..Icons.Icon({skillname, type='skill'})..' XP')
| |
| 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
| |
| table.insert(ret, table.concat(rewards, '<br>'))
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Returns all the tasks of a given category
| |
| function p.GetTaskTable(frame)
| |
| local category = frame.args ~= nil and frame.args[1] or frame
| |
| local categoryname = GameData.getEntityByID(Township.taskCategories, category).name
| |
| local taskcount = 0
| |
|
| |
| local ret = {}
| |
| table.insert(ret, '\r\n{| class="wikitable lighttable" style="text-align:left"')
| |
| table.insert(ret, '\r\n!Task')
| |
| table.insert(ret, '\r\n!Requirements')
| |
| table.insert(ret, '\r\n!Rewards')
| |
|
| |
| for _, task in ipairs(Township.tasks) do
| |
| -- Filter out other categories
| |
| if task.category == category then
| |
| taskcount = taskcount + 1
| |
| local title = categoryname..' '..taskcount
| |
| table.insert(ret, p._GetTaskRow(title, task))
| |
| end
| |
| end
| |
| table.insert(ret, '\r\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[GameData.getEntityByName('monsters', 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, '\r\n{| class="wikitable" style="text-align:left"')
| |
| table.insert(ret, '\r\n!Task')
| |
| table.insert(ret, '\r\n!Requirements')
| |
| table.insert(ret, '\r\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))
| |
| end
| |
| table.insert(ret, '\r\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| 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({'Statue of '..worship.name, type='building', notext=true})..Icons.Icon({worship.name, type='monster'}))
| |
| end
| |
| end
| |
|
| |
| -- Requirements
| |
| -- Hard-coded because there's only 1 requirement
| |
| table.insert(ret, '\r\n|-\r\n!Requirements')
| |
| local requirements = {
| |
| ['melvorF:Bane'] = Icons.Icon({'Impending Darkness Event', type='dungeon'})..' completed'
| |
| }
| |
| 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|'..requirement)
| |
| end
| |
| end
| |
|
| |
| -- Base modifiers
| |
| table.insert(ret, GetCheckpointCell(0))
| |
| for _, worship in ipairs(Township.worships) do
| |
| mw.logObject(worship.isHidden == false)
| |
| 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)
| |
| end
| |
|
| |
| return p
| |