Module:Township
Documentation for this module may be created at Module:Township/doc
local Shared = require('Module:Shared')
local Icons = require('Module:Icons')
local GameData = require('Module:GameData')
-- Data Module
local DataDemo = require('Module:GauTest/DataDemo')
local DataFull = require('Module:GauTest/DataFull')
local DataTotH = require('Module:GauTest/DataTotH')
local Namespaces = {
melvorD = DataDemo,
melvorF = DataFull,
melvorTotH = DataTotH
}
local Data = {}
-- For a table that is indexed but uses a key, use this function to find the correct element
function Data.tableMatch(tabl, property, value)
for _, elem in ipairs(tabl) do
if elem[property] == value then
return elem
end
end
return nil
end
-- For a table that is indexed but uses a key, use this function to find the correct element, when there are several duplicates elements
function Data.tableMatches(tabl, property, value)
local matches = {}
for _, elem in ipairs(tabl) do
if elem[property] == value then
table.insert(matches, elem)
end
end
return matches
end
-- Separates the namespace and id of a string
-- e.g. 'melvorD:Coal_Ore' will return {namespace='melvorD', id='Coal_Ore'}
-- e.g. 'Coal_Ore' will return {namespace=nil, id='Coal_Ore'}
function Data.splitID(text)
local split = Shared.splitString(text, ':')
local target_namespace = nil
local target_id = nil
if #split == 2 then
return {namespace=split[1], id=split[2]}
elseif #split == 1 then
return {id=split[1]}
else
return nil
end
end
-- Returns the namespace name (eventually we should use an icon?)
function Data.PLACEHOLDER_NAMESPACE_ICON(namespace)
local namespaces = {
melvorD = 'Demo',
melvorF = 'Full',
melvorTotH = 'TotH'
}
return namespaces[namespace]
end
Data.Item = {}
-- Get all the items with a property equal to value
function Data.Item.Match(property, value)
local items = {}
for namespace, data in pairs(Namespaces) do
for k, item in pairs(data.data.items) do
if item[property] == value then
local itemcopy = Shared.clone(item)
itemcopy._namespace = namespace
table.insert(items, itemcopy)
end
end
end
return items
end
-- Get item by id
function Data.Item.ByID(id)
local target = Data.splitID(id)
for namespace, data in pairs(Namespaces) do
if target.namespace == nil or namespace == target.namespace then
for k, item in pairs(data.data.items) do
if item.id == target.id then
local itemcopy = Shared.clone(item)
itemcopy._namespace = namespace
return itemcopy
end
end
end
end
return nil
end
-- Returns the recipe for the item of a desired skill.
function Data.Item.FindRecipes(itemid, skill)
-- the key name for each skill in the json file
local skill_recipe_keys = {
Woodcutting = {recipes='trees', productID='productId'}, -- lowercase "d"
Fishing = {recipes='fish', productID='productId'}, -- lowercase "d"
Cooking = {recipes='recipes', productID='productID'},
Mining = {recipes='rockData', productID='productId'}, -- lowercase "d"
Smithing = {recipes='recipes', productID='productID'},
Farming = {recipes='recipes', productID='productId'}, -- lowercase "d"
Summoning = {recipes='recipes', productID='productID'},
Fletching = {recipes='recipes', productID='productID'},
Crafting = {recipes='recipes', productID='productID'},
Runecrafting = {recipes='recipes', productID='productID'},
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 item = Data.splitID(itemid)
local results = {}
if skill == nil then
return results
end
-- Find the recipe at data.data.skillData -> SKILL.data.KEY
for namespace, data in pairs(Namespaces) do
-- We match multiple entries because there's a bug - melvorF -> skillData Farming has two duplicate entries in the array with differing info
local Skill_matches = Data.tableMatches(data.data.skillData, 'skillID', 'melvorD:'..skill)
for _, Skill in ipairs(Skill_matches) do
local key = skill_recipe_keys[skill]
if Skill ~= nil and Skill.data ~= nil and Skill.data[key.recipes] ~= nil then
for _, recipe in ipairs(Skill.data[key.recipes]) do
-- Check if id matches
if skill == 'Herblore' then
-- Iterate over the 4 potion tiers
for _, potion in ipairs(recipe[key.productID]) do
-- Same as below
local recipe_id = Data.splitID(potion)
if item.id == recipe_id.id then
local recipecopy = Shared.clone(recipe)
recipecopy._namespace = namespace
table.insert(results, recipecopy)
end
end
else
-- Same as above
local recipe_id = Data.splitID(recipe[key.productID])
if item.id == recipe_id.id then
local recipecopy = Shared.clone(recipe)
recipecopy._namespace = namespace
table.insert(results, recipecopy)
end
end
end
end
end
end
return results
end
Data.Township = {}
-- Returns a list of all the Township resources
function Data.Township.Resources()
-- Get a sorted list of all the resources
local Township = GameData.getSkillData('melvorD:Township')
local resources = GameData.sortByOrderTable(Township.resources, Township.resourceDisplayOrder[1].ids)
resources = Shared.clone(resources)
-- Append the icon reference to each resource, as well as the associated skill for the recipes
-- From https://melvoridle.com/assets/data/melvorFull.json -> data.skillData -> Township.data.resources.media
local resource_data = {
['melvorF:GP'] = {_icon = {'Coins'}, _skill = nil},
['melvorF:Food'] = {_icon = {'Raw Beef', type='item'}, _skill = 'Cooking'},
['melvorF:Wood'] = {_icon = {'Wood', type='resource'}, _skill = 'Woodcutting'},
['melvorF:Stone'] = {_icon = {'Stone', type='resource'}, _skill = 'Mining'},
['melvorF:Ore'] = {_icon = {'Iron Ore', type='rock'}, _skill = 'Mining'},
['melvorF:Coal'] = {_icon = {'Coal', type='resource'}, _skill = 'Mining'},
['melvorF:Bar'] = {_icon = {'Iron Bar', type='item'}, _skill = 'Mining'},
['melvorF:Herbs'] = {_icon = {'Garum Herb', type='item'}, _skill = 'Farming'},
['melvorF:Rune_Essence'] = {_icon = {'Rune Essence', type='item'}, _skill = 'Mining'},
['melvorF:Leather'] = {_icon = {'Leather', type='item'}, _skill = nil},
['melvorF:Potions'] = {_icon = {'Potion', type='resource'}, _skill = 'Herblore'},
['melvorF:Planks'] = {_icon = {'Planks', type='resource'}, _skill = 'Woodcutting'},
['melvorF:Clothing'] = {_icon = {'Leather Body', type='item'}, _skill = 'Crafting'}
}
for _, resource in ipairs(resources) do
resource._skill = resource_data[resource.id]._skill
resource._icon = resource_data[resource.id]._icon
resource._icon.notext = true
resource._icon.nolink = true
end
return resources
end
-- Returns a list of all the Township resources along with the Trader's trade ratios
function Data.Township.Trader()
-- Get the list of resources
local resources = Data.Township.Resources()
-- 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 resource_data = {
['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', resource_data[resource.id].traderMatches))
end
-- Calculate the 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 Data.Township.getTraderTable(frame)
local resources = Data.Township.Trader()
local ret = {}
for _, resource in ipairs(resources) do
if #resource.itemConversions ~= 0 then -- Skips GP
local ret_resource = {}
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 =='Food' then
table.insert(ret_resource, '\r\n!Heals')
table.insert(ret_resource, '\r\n!Heals/Resource')
end
for _, item in ipairs(resource.itemConversions) do
-- Find the recipe to get the required level
local required_level = nil
local recipes = nil
local skill = resource._skill
local lookup_id = item.id
-- A few special skill overrides
if item.id == 'Raw_Magic_Fish' then
skill = 'Fishing'
elseif item.id == 'Apple' then
skill = 'Farming'
elseif string.match(item.id, '_U$') then
-- Upgraded Crafting item. Display the level for the base item
-- Converts Black_Dhide_Body_U -> Black_Dhide_Body for the purposes of the lookup
lookup_id = string.sub(item.id, 1, #item.id - 2)
end
local recipes = Data.Item.FindRecipes(lookup_id, skill)
if #recipes == 1 then
required_level = recipes[1].level
end
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
table.insert(ret_resource, '\r\n|style="text-align:center"|'..'XXX')
-- 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, 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._icon)..' '..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 =='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
local p = {}
p.getTraderTable = Data.Township.getTraderTable
return p