16,254
edits
(getBiomeTable: Initial implementation; Re-arrange some functions & consistency of function naming with other modules) |
(Various amends to update for v1.1.2, still WIP) |
||
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 Shop = require('Module:Shop') | |||
local GameData = require('Module:GameData') | local GameData = require('Module:GameData') | ||
local Constants = require('Module:Constants') | local Constants = require('Module:Constants') | ||
Line 19: | Line 20: | ||
return GameData.getEntityByID(Township.buildings, id) | return GameData.getEntityByID(Township.buildings, id) | ||
end | end | ||
end | |||
-- Gets a resource from id | |||
function p._getResourceByID(id) | |||
return GameData.getEntityByID(Township.resources, id) | |||
end | end | ||
Line 27: | Line 33: | ||
end | end | ||
return nil | 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 and biome ID, returns a string displaying the building's benefits, | |||
-- or nil if no benefits | |||
function p._getBuildingBenefits(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 | |||
-- TODO Fix always using first biome | |||
local quantity = providesData[key] | |||
if quantity ~= nil and quantity ~= 0 then | |||
table.insert(resultPart, resourceText(stat, 'township', quantity)) | |||
end | |||
end | |||
if not Shared.tableIsEmpty(resultPart) then | |||
return table.concat(resultPart, delim) | |||
end | |||
end | |||
end | |||
end | end | ||
Line 42: | Line 136: | ||
function p._getTierRequirements(tier) | function p._getTierRequirements(tier) | ||
return Township.populationForTier[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 | end | ||
Line 56: | Line 162: | ||
local resultPart = {} | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable | table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | ||
table.insert(resultPart, '\n|- class="headerRow-0"') | table.insert(resultPart, '\n|- class="headerRow-0"') | ||
table.insert(resultPart, '\n!colspan="2" | Season\n!Type\n!Modifiers') | table.insert(resultPart, '\n!colspan="2" | Season\n!Type\n!Modifiers') | ||
Line 80: | Line 186: | ||
function p.getBiomeTable(frame) | function p.getBiomeTable(frame) | ||
local resultPart = {} | local resultPart = {} | ||
table.insert(resultPart, '{| class="wikitable sortable | table.insert(resultPart, '{| class="wikitable sortable stickyHeader"') | ||
table.insert(resultPart, '\n|- class="headerRow-0"') | table.insert(resultPart, '\n|- class="headerRow-0"') | ||
table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="2"| Requirements') | table.insert(resultPart, '\n!rowspan="2" colspan="2"| Biome\n!colspan="2"| Requirements') | ||
Line 104: | Line 210: | ||
-- Setup the table | -- Setup the table | ||
local ret = {} | local ret = {} | ||
table.insert(ret, '{| class="wikitable sortable | table.insert(ret, '{| class="wikitable sortable stickyHeader" style="text-align:center"') | ||
table.insert(ret, '\n|- class="headerRow-0"') | table.insert(ret, '\n|- class="headerRow-0"') | ||
table.insert(ret, '\n! Building') | table.insert(ret, '\n! Building') | ||
Line 143: | Line 249: | ||
return table.concat(ret) | return table.concat(ret) | ||
end | 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!Type\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' .. rowSpanOnly .. '| ' .. building.type) | |||
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._getBuildingBenefits(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) | |||
-- 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 | |||
-- 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 | |||
table.insert(ret, '\n|style="text-align:center"| ' .. Shop.getRequirementString(worship.unlockRequirements)) | |||
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 | |||
-- TODO Check if functions below this line are still in use | |||
-- Returns the recipe for the item of a desired skill. | -- Returns the recipe for the item of a desired skill. | ||
Line 303: | Line 622: | ||
end | end | ||
p.resources = p._TraderData() | p.resources = p._TraderData() | ||
-- Gets the associated skill of a resource by id | -- Gets the associated skill of a resource by id | ||
Line 430: | Line 658: | ||
end | end | ||
end | end | ||
-- Gets a building and prepares all the relevant stats for the building | -- Gets a building and prepares all the relevant stats for the building | ||
-- TODO Rename, getBuildingInfoBox or something of the sort | |||
function p.GetBuildingTable(frame) | function p.GetBuildingTable(frame) | ||
local name = frame.args ~= nil and frame.args[1] or frame | local name = frame.args ~= nil and frame.args[1] or frame | ||
Line 455: | Line 677: | ||
table.insert(ret, '\r\n|-\r\n| <b>Type:</b> '..building.type) | table.insert(ret, '\r\n|-\r\n| <b>Type:</b> '..building.type) | ||
-- Tier | -- Tier | ||
local tier = p. | local tier = p._getTierText(building.tier) | ||
table.insert(ret, '\r\n|-\r\n| <b>Requirements:</b><br>'..tier) | table.insert(ret, '\r\n|-\r\n| <b>Requirements:</b><br>'..tier) | ||
Line 478: | Line 700: | ||
-- Fixed benefits | -- Fixed benefits | ||
local benefits = p. | local benefits = p._getBuildingBenefits(building) | ||
if benefits ~= nil then | if benefits ~= nil then | ||
table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..benefits) | table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..benefits) | ||
Line 505: | Line 727: | ||
table.insert(ret, '\r\n|}') | table.insert(ret, '\r\n|}') | ||
return table.concat(ret) | return table.concat(ret) | ||
end | end | ||
Line 529: | Line 744: | ||
local production = resource.quantity*100*(Township.tickLength/10) | local production = resource.quantity*100*(Township.tickLength/10) | ||
local color = production < 0 and 'red' or 'green' | local color = production < 0 and 'red' or 'green' | ||
local resource_data = p. | 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>') | 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 | if resource_data.requires ~= nil and #resource_data.requires > 0 then | ||
for _, required_resource in ipairs(resource_data.requires) do | for _, required_resource in ipairs(resource_data.requires) do | ||
local demand = production*required_resource.quantity*100 | local demand = production*required_resource.quantity*100 | ||
local required_resource_data = p. | 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>') | table.insert(retProduction, '<span style="color:red">'..Icons.Icon({required_resource_data.name, type='resource', notext=true})..' -'..demand..'</span>') | ||
end | end | ||
Line 540: | Line 755: | ||
end | end | ||
return table.concat(retResources, '<br>') | return table.concat(retResources, '<br>') | ||
end | end | ||
Line 590: | Line 776: | ||
-- always taking costs for the first biome | -- always taking costs for the first biome | ||
for _, resource in ipairs(building.cost[1].cost) do | for _, resource in ipairs(building.cost[1].cost) do | ||
local resource_data = p. | local resource_data = p._getResourceByID(resource.id) | ||
table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..' '..resource.quantity) | table.insert(cost, Icons.Icon({resource_data.name, type='resource', notext=true})..' '..resource.quantity) | ||
end | end | ||
return table.concat(cost, join) | return table.concat(cost, join) | ||
end | end | ||
Line 664: | Line 826: | ||
table.insert(ret, '\r\n|-\r\n! Requirements') | table.insert(ret, '\r\n|-\r\n! Requirements') | ||
for _, building in ipairs(buildingList) do | for _, building in ipairs(buildingList) do | ||
local tier = p. | local tier = p._getTierText(building.tier) | ||
table.insert(ret, '\r\n|'..tier) | table.insert(ret, '\r\n|'..tier) | ||
end | end | ||
Line 693: | Line 855: | ||
end | end | ||
end | end | ||
BuildOptionalRow('\r\n|-\r\n! Benefits\r\n|', p. | BuildOptionalRow('\r\n|-\r\n! Benefits\r\n|', p._getBuildingBenefits) | ||
-- End | -- End | ||
Line 700: | Line 861: | ||
return table.concat(ret) | return table.concat(ret) | ||
end | end | ||
Line 810: | Line 921: | ||
end | end | ||
for _, townshipResource in ipairs(task.rewards.townshipResources) do | for _, townshipResource in ipairs(task.rewards.townshipResources) do | ||
local resourcename = p. | local resourcename = p._getResourceByID(townshipResource.id).name | ||
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 | ||
Line 923: | Line 1,034: | ||
end | end | ||
-- | -- TODO Temporary functions, these exist to enable renaming of functions | ||
-- above without causing templates to break until they are migrated to these | |||
-- newly-named functions | |||
function p.GetWorshipTable() | function p.GetWorshipTable() | ||
return p.getWorshipTable() | |||
return | |||
end | end | ||
return p | return p |