|
|
(118 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 resource_data = {
| |
| ['melvorF:GP'] = {icon = {'Coins'}, skill = nil},
| |
| ['melvorF:Food'] = {icon = {'Raw Beef', type='item'}, skill = 'melvorD:Cooking'},
| |
| ['melvorF:Wood'] = {icon = {'Wood', type='resource'}, skill = 'melvorD:Woodcutting'},
| |
| ['melvorF:Stone'] = {icon = {'Stone', type='resource'}, skill = 'melvorD:Mining'},
| |
| ['melvorF:Ore'] = {icon = {'Iron', type='rock'}, skill = 'melvorD:Mining'},
| |
| ['melvorF:Coal'] = {icon = {'Coal', type='resource'}, skill = 'melvorD:Mining'},
| |
| ['melvorF:Bar'] = {icon = {'Iron Bar', type='item'}, skill = 'melvorD:Smithing'},
| |
| ['melvorF:Herbs'] = {icon = {'Garum Herb', type='item'}, skill = 'melvorD:Farming'},
| |
| ['melvorF:Rune_Essence'] = {icon = {'Rune Essence', type='item'}, skill = 'melvorD:Mining'},
| |
| ['melvorF:Leather'] = {icon = {'Leather', type='item'}, skill = nil},
| |
| ['melvorF:Potions'] = {icon = {'Potion', type='resource'}, skill = 'melvorD:Herblore'},
| |
| ['melvorF:Planks'] = {icon = {'Planks', type='resource'}, skill = 'melvorD:Woodcutting'},
| |
| ['melvorF:Clothing'] = {icon = {'Leather Body', type='item'}, skill = 'melvorD:Crafting'}
| |
| }
| |
|
| |
| local Township = GameData.getSkillData('melvorD:Township')
| |
|
| |
| local p = {}
| |
|
| |
| -- Returns the namespace name (eventually we should use an icon)
| |
| function p.PLACEHOLDER_NAMESPACE_ICON(namespace)
| |
| local namespaces = {
| |
| melvorD = 'Demo',
| |
| melvorF = 'Full',
| |
| melvorTotH = 'TotH'
| |
| }
| |
| return namespaces[namespace]
| |
| end
| |
|
| |
| -- 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[1].ids)
| |
| resources = Shared.clone(resources)
| |
|
| |
| return resources
| |
| end
| |
|
| |
| -- Returns a list of all the Township resources along with the Trader's trade ratios
| |
| function p._TraderData()
| |
| -- Get the list of resources
| |
| 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)
| |
| local valid_tiers = {'Leather', 'Hard Leather', 'Dragonhide', 'Elderwood', 'Revenant', 'Carrion'}
| |
| for _, tier in ipairs(valid_tiers) do
| |
| if item.tier == tier then
| |
| return true
| |
| end
| |
| end
| |
| return false
| |
| 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
| |
| 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
| |
|
| |
| -- Builds the table of trader items
| |
| function p.GetTraderTable(frame)
| |
| -- Get the resources data with associated trader data
| |
| local resources = p._TraderData()
| |
|
| |
| -- Build the text
| |
| local ret = {}
| |
| for _, resource in ipairs(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!DLC')
| |
| 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.Icon({item.name, type='item', noicon=true}))
| |
| -- DLC
| |
| local item_namespace, item_localid = GameData.getLocalID(item.id)
| |
| table.insert(ret_resource, '\r\n|style="text-align:center"|'..p.PLACEHOLDER_NAMESPACE_ICON(item_namespace))
| |
| -- 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(p._GetResourceIcon(resource.id))..' '..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
| |
|
| |
| -- TODO integrate into Icons.imgOverrides
| |
| -- Gets the icon of a resource
| |
| -- e.g. Icons.Icon(_GetResourceIcon('melvorF:Bar'))
| |
| function p._GetResourceIcon(id)
| |
| local resource_icon = Shared.clone(resource_data[id].icon)
| |
| resource_icon.notext = true
| |
| resource_icon.nolink = true
| |
| return resource_icon
| |
| end
| |
|
| |
| -- Gets the associated skill of a resource by id
| |
| function p._GetResourceSkill(id)
| |
| return resource_data[id].skill
| |
| end
| |
|
| |
| -- Gets a Township building by ID, e.g. melvorF:Hunters_Cabin
| |
| function p._GetBuildingByID(id)
| |
| return GameData.getEntityByID(Township.buildings, id)
| |
| end
| |
|
| |
| -- Gets a Township building by name, e.g. Hunters Cabin
| |
| function p._GetBuildingByName(name)
| |
| return GameData.getEntityByName(Township.buildings, name)
| |
| 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
| |
|
| |
| -- 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 = p._GetBuildingByName(name)
| |
| local resources = p._ResourcesData()
| |
| 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}))
| |
| -- Description - disabled because all buildings have the same description "A basic house"
| |
| -- table.insert(ret, '\r\n|-\r\n|style="text-align:center"|'..building.description)
| |
| -- 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._GetTierRequirements(building.tier)
| |
| table.insert(ret, '\r\n|-\r\n| <b>Tier '..building.tier..'</b><ul><li>'..Icons._SkillReq('Township', tier.level, false)..'</li><li>'..p.GetTownshipStatIcon('Population')..tier.population..'</li></ul>')
| |
| -- Cost
| |
| local cost = {}
| |
| for _, resource in ipairs(building.cost) do
| |
| table.insert(cost, Icons.Icon(p._GetResourceIcon(resource.id))..' '..resource.quantity)
| |
| end
| |
| table.insert(ret, '\r\n|-\r\n| <b>Base Cost:</b> '..table.concat(cost, ', '))
| |
| -- Fixed benefits
| |
| local benefits = {}
| |
| local stats = {
| |
| population = 'Population',
| |
| happiness = 'Happiness',
| |
| education = 'Education',
| |
| storage = 'Storage',
| |
| deadStorage = 'Dead Storage'
| |
| }
| |
| for key, name in pairs(stats) do
| |
| if building.provides[key] ~= nil and building.provides[key] ~= 0 then
| |
| table.insert(benefits, p.GetTownshipStatIcon(name)..' '..building.provides[key])
| |
| end
| |
| end
| |
| if #benefits > 0 then
| |
| table.insert(ret, '\r\n|-\r\n| <b>Provides:</b> '..table.concat(benefits, ', '))
| |
| end
| |
|
| |
| -- Production
| |
| local production = Shared.clone(building.provides.resources)
| |
| if #production > 0 then
| |
| table.insert(ret, '\r\n|-\r\n| <b>Produces:</b><br>')
| |
| for _, resource in ipairs(production) do
| |
| local retProduction = {}
| |
| local job = p._GetJobFromResource(resource.id)
| |
| local workers = GameData.getEntityByID(building.provides.workers, job).quantity
| |
| -- township.js -> Township.computeTownResourceGain()
| |
| local production = workers*resource.quantity*100*(Township.tickLength/10)
| |
| local resource_data = GameData.getEntityByID(resources, resource.id)
| |
| table.insert(retProduction, '<span style="color:green">'..Icons.Icon(p._GetResourceIcon(resource.id))..' +'..production..'</span>')
| |
| if resource_data.requires ~= nil and #resource_data.requires > 0 then
| |
| for _, required_resource in ipairs(resource_data.requires) do
| |
| local demand = workers*resource.quantity*100*(Township.tickLength/10)
| |
| table.insert(retProduction, '<span style="color:red">'..Icons.Icon(p._GetResourceIcon(required_resource.id))..' -'..demand..'</span>')
| |
| end
| |
| end
| |
| table.insert(ret, table.concat(retProduction, ', '))
| |
| table.insert(ret, '/tick/'..p.GetTownshipStatIcon('Workers'))
| |
| end
| |
| end
| |
|
| |
| -- Modifiers
| |
| if building.modifiers ~= nil and not Shared.tableIsEmpty(building.modifiers) then
| |
| table.insert(ret, "\r\n|-\r\n| <b>Modifiers:</b>\r\n"..Constants.getModifiersText(building.modifiers, true))
| |
| end
| |
|
| |
| -- End
| |
| table.insert(ret, '\r\n|}')
| |
| return table.concat(ret)
| |
| end
| |
|
| |
| -- Todo - integrate into Icons.Icon
| |
| -- Township Icons are licensed from Font Awesome Free 5.14.0 under CC BY 4.0 (https://github.com/FortAwesome/Font-Awesome/tree/5.14.0)
| |
| function p.GetTownshipStatIcon(stat)
| |
| local changing_color = {
| |
| Population = true,
| |
| Workers = true,
| |
| Storage = true,
| |
| Happiness = false,
| |
| Education = false,
| |
| Health = false,
| |
| Worship = false,
| |
| ["Dead Storage"] = false,
| |
| }
| |
| local class = changing_color[stat] and ' class="township-colortoggle"' or ''
| |
| return '<span style="display:inline-block"'..class..'>[[File:'..stat..'_(township).svg|25x25px|link='..stat..']]</span>'
| |
| 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
| |
|
| |
| -- Testing
| |
| p.GameData = GameData
| |
| p.Icons = Icons
| |
| p.Township = Township
| |
|
| |
|
| |
| return p
| |