Documentation for this module may be created at Module:Obtaining/doc
local Args = require("Module:Args")
local FrameUtils = require("Module:FrameUtils")
local StringUtils = require("Module:StringUtils")
local TableUtils = require("Module:TableUtils")
local MathUtils = require("Module:MathUtils")
local ObjectInfo = require("Module:ObjectInfo")
local ObjectIdFromName = require("Module:ObjectIdFromName")
local SceneInfo = require("Module:SceneInfo")
local DungeonInfo = require("Module:DungeonInfo")
local data = mw.loadData("Module:Obtaining/data")
local SourceOrder = {
"terrain",
"oreBoulder",
"farming",
"validHouse",
"breeding",
"naturalSpawn",
"crafting",
"recipe",
"fishing",
"trading",
"merchant",
"vendor",
"cattleProduce",
"loot",
"drop",
"lootScene",
"dropScene",
"polishing",
"backgroundPerks",
"salvage",
"miscellaneous",
"plunder"
}
local SourceCate = {
crafting = "Craftable",
recipe = "Craftable",
fishing = "Fished",
trading = "Traded",
merchant = "Vendor",
vendor = "Vendor",
cattleProduce = "Cattle produce",
loot = "Loot",
lootScene = "Loot",
drop = "Drop",
dropScene = "Drop",
farming = "Farmed",
polishing = "Polished",
salvage = "Salvage",
plunder = "Plunder",
backgroundPerks = "Background perk",
naturalSpawn = "Natural spawn",
terrain = "Terrain generation",
validHouse = "Valid house",
oreBoulder = "Ore boulder extraction",
breeding = "Bred"
}
local AltSourceCate = {
plunder = "Structure contents"
}
local SourceDisplayName = {
crafting = "Crafting",
recipe = "Crafting",
fishing = "Fishing",
trading = "Trading",
merchant = "Merchant",
vendor = "Vendor",
cattleProduce = "Cattle produce",
loot = "Loot",
drop = "Drops",
lootScene = "Structure-exclusive loot",
dropScene = "Structure-exclusive drops",
farming = "Farming",
polishing = "Polishing",
salvage = "Salvaging",
plunder = "Plundering",
backgroundPerks = "Background perks",
naturalSpawn = "Natural spawn",
terrain = "Terrain generation",
validHouse = "Valid house",
miscellaneous = "Miscellaneous",
oreBoulder = "Ore boulder extraction",
breeding = "Breeding"
}
local AltSourceDisplayName = {
plunder = "Structure contents"
}
local BiomeNames = {
Slime = "Undergrounds",
Larva = "Clay Caves",
Stone = "Forgotten Ruins",
Nature = "Azeos' Wilderness",
Mold = "Mold Dungeon",
Sea = "Sunken Sea",
Desert = "Desert of Beginnings",
Lava = "Molten Quarry",
Crystal = "Shimmering Frontier",
Passage = "Passage"
}
local LiquidNames = {
Dirt = "Normal water",
LarvaHive = "Acid water",
Mold = "Mold water",
Sea = "Sea water",
Lava = "Lava",
Crystal = "Shimmering water",
Passage = "Grimy water"
}
local NoteSeparator = " • "
local MerchantAvailability = {
None = '<span class="note"><small>Always available.</small></span>',
LarvaBossStatueActivated = "After the [[Ghorm Statue]] has been activated",
HiveBossStatueActivated = "After the [[Malugaz Statue]] has been activated",
CoreActivated = "After [[The Core]] has been activated",
CoreBossDefeated = "After the [[Core Commander]] has been defeated"
}
local CustomEntityNames = {
AnyWall = "Any mined block"
}
local Notes = {
SeasonEaster = "During [[Easter]]",
SeasonHalloween = "During [[Halloween]]",
SeasonChristmas = "During [[Christmas]]",
RarePlant = "3–15%, when planted with the [[Expert gardener]] talent active",
Polishing = "10–50%, when crafted with the [[Jewelry crafter]] talent active",
ArcheologistWallLoot = "0.2–1%, when mined with the [[Archeologist]] talent active",
EventTerminal = "[[Challenge arena]] reward",
CrystalMerchantSpawnItem = "20% if the [[Brave Merchant]] has moved in",
PlunderLockedCopperChestSlime = 'Mining [[Dirt Block]] walls, [[Sand Block]] walls or [[Turf Block]] walls in the [[Undergrounds]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedCopperChestLarva = 'Mining [[Clay Block]] walls or [[Sand Block]] walls in the [[Clay Caves]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedIronChest = 'Mining [[Stone Block]] walls or [[Sand Block]] walls in the [[Forgotten Ruins]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedScarletChest = 'Mining [[Grass Block]] walls or [[Stone Block]] walls in [[Azeos\' Wilderness]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedOctarineChest = 'Mining [[Beach Block]] walls in the [[Sunken Sea]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedGalaxiteChest = 'Mining [[Desert Block]] walls in the [[Desert of Beginnings]] <span class="note"><small>(0.27%)</small></span>',
PlunderLockedSolariteChest = 'Mining [[Crystal Block]] walls in [[Azeos\' Wilderness]], [[Sunken Sea]], [[Desert of Beginnings]] or [[Shimmering Frontier]] <span class="note"><small>(0.27%)</small></span>',
PlunderChallengeArenaReward = "[[Challenge arena]] reward container",
OnlyInNature = "Only nearby [[Azeos' Wilderness]]",
OnlyInSea = "Only nearby [[Sunken Sea]]",
OnlyInDesert = "Only nearby [[Desert of Beginnings]]",
Greggy = 'Creating a character named "Greggy" <span class="note"><small>(10)</small></span>'
}
local BiomeTerrainWallTypes = {
Slime = {
"WallDirtBlock",
"WallTurfBlock",
"WallSandBlock"
},
Larva = {
"WallClayBlock",
"WallSandBlock"
},
Stone = {
"WallStoneBlock",
"WallSandBlock"
},
Nature = {
"WallGrassBlock",
"WallStoneBlock"
},
Sea = {
"WallLimestoneBlock"
},
Desert = {
"WallDesertBlock"
},
Crystal = {
"WallCrystalBlock"
},
Passage = {
"WallPassageBlock"
}
}
local function createTable()
return mw.html.create("table"):addClass("fandom-table sortable"):cssText("font-size: 90%;")
end
local function formatAmountRange(amount)
if type(amount) == "table" then
return amount[1] .. "–" .. amount[2]
else
return tostring(amount)
end
end
local function isAmountAboveOne(amount)
if type(amount) == "table" then
return amount[1] > 1 or amount[2] > 1
else
return amount > 1
end
end
local function item(name, notes, format)
assert(name ~= nil)
local result = FrameUtils.template("Item", { name })
if notes then
notes = TableUtils.map(notes, function(note)
return Notes[note] or note
end)
if TableUtils.length(notes) > 0 then
local separator = format == "inline" and " " or "<br/>"
result = result .. separator .. FrameUtils.note("(" .. table.concat(notes, NoteSeparator) .. ")")
end
end
return result
end
local function entityItem(entity, notes, format)
-- Item name
local name
if entity.custom then
name = CustomEntityNames[entity.custom] or entity.custom
else
name = ObjectInfo.getStat(entity.id, entity.variation, "name")
end
-- Notes
local allNotes = {}
for _, note in ipairs(notes or {}) do
TableUtils.push(allNotes, note)
end
return item(name, allNotes, format)
end
local function process(entries, functions)
local result = TableUtils.map(entries, functions.map)
if functions.sorter then
table.sort(result, functions.sorter)
end
return result
end
----------------------------------------------------------
----------------- Source data formatters -----------------
----------------------------------------------------------
local sourceFormatters = {}
-- Farming
sourceFormatters.farming = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Seed")
:tag("th"):wikitext("Growth time")
:done()
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return {
seed = item(name, entry.notes),
growthTime = string.format("%s minutes", MathUtils.round(entry.growthTime / 60))
}
end,
sorter = function(a, b)
return a.seed < b.seed
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.seed)
:tag("td"):wikitext(entry.growthTime)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return item(name, entry.notes, "inline")
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Merchant
sourceFormatters.merchant = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Merchant")
:tag("th"):wikitext("Stock")
:tag("th"):wikitext("Cost")
:tag("th"):wikitext("Availability")
:done()
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return {
merchant = item(name),
stock = entry.stock,
cost = ObjectInfo.getStat(id, variation, "buy"),
availability = MerchantAvailability[entry.requirement]
}
end,
sorter = function(a, b)
return a.merchant < b.merchant
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.merchant)
:tag("td"):attr("align", "center"):wikitext(entry.stock)
:tag("td"):attr("align", "center"):wikitext(entry.cost)
:tag("td"):wikitext(entry.availability)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return item(name, { ObjectInfo.getStat(id, variation, "buy") }, "inline")
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Vendor --
sourceFormatters.vendor = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Vendor")
:tag("th"):wikitext("Cost")
:done()
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return {
vendor = item(name),
cost = ObjectInfo.getStat(id, variation, "buy")
}
end,
sorter = function(a, b)
return a.vendor < b.vendor
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.vendor)
:tag("td"):attr("align", "center"):wikitext(entry.cost)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return item(name, { ObjectInfo.getStat(id, variation, "buy") }, "inline")
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Crafting
sourceFormatters.crafting = {
section = function(id, variation)
return FrameUtils.template("Recipes", {
result = ObjectInfo.getStat(id, variation, "name"),
noresultunlessneeded = true
})
end,
inline = function(id, variation)
if not Recipes then
Recipes = require("Module:Recipes")
end
return Recipes.extract(id)
end
}
-- Recipe
sourceFormatters.recipe = sourceFormatters.crafting
-- Drop
sourceFormatters.drop = {
section = function(id, variation, entries)
local output = createTable()
local isSceneLoot = entries[1].dungeons or entries[1].scenes
local showQuantity = TableUtils.any(entries, function(entry)
return isAmountAboveOne(entry.amount)
end)
local showChanceForOne = TableUtils.any(entries, function(entry)
return MathUtils.round(entry.chance * 100) ~= MathUtils.round(entry.chanceAtLeastOne * 100)
end)
local header = output:tag("tr")
if isSceneLoot then
header:tag("th"):wikitext("Structure"):done()
end
header:tag("th"):wikitext("Source"):done()
if showQuantity then
header:tag("th"):wikitext("Quantity"):done()
end
if showChanceForOne then
header:tag("th"):cssText("min-width: 100px;"):wikitext("Chance<br/><small>for one</small>"):done()
header:tag("th"):cssText("min-width: 100px;"):wikitext("Chance<br/><small>per roll</small>"):done()
else
header:tag("th"):wikitext("Chance"):done()
end
header:done()
entries = process(entries, {
map = function(entry)
local structures = {}
if isSceneLoot then
local scenes = TableUtils.map(entry.scenes or {}, function(scene)
return string.format("[[%s|%s]]", SceneInfo.getLink(scene), SceneInfo.getDisplayName(scene, true))
end)
local dungeons = TableUtils.map(entry.dungeons or {}, function(dungeon)
return string.format("[[%s|%s]]", DungeonInfo.getLink(dungeon), DungeonInfo.getDisplayName(dungeon, true))
end)
table.sort(scenes, function(a, b) return a < b end)
table.sort(dungeons, function(a, b) return a < b end)
for _, dungeon in ipairs(dungeons) do
TableUtils.push(structures, dungeon)
end
for _, scene in ipairs(scenes) do
TableUtils.push(structures, scene)
end
end
local pools = {}
if entry.guaranteedChance then
TableUtils.push(pools, {
amount = formatAmountRange(entry.guaranteedAmount),
rolls = formatAmountRange(entry.guaranteedRolls or 1),
chance = MathUtils.round(entry.guaranteedChance * 100) .. "%",
chanceAtLeastOne = MathUtils.round(entry.guaranteedChanceAtLeastOne * 100) .. "%",
})
end
TableUtils.push(pools, {
amount = formatAmountRange(entry.amount),
rolls = formatAmountRange(entry.rolls or 1),
chance = MathUtils.round(entry.chance * 100) .. "%",
chanceAtLeastOne = MathUtils.round(entry.chanceAtLeastOne * 100) .. "%",
})
return {
entity = entityItem(entry.entity, entry.notes),
pools = pools,
structures = #structures > 0 and table.concat(structures, "<hr/>") or nil
}
end,
sorter = function(a, b)
return a.entity < b.entity
end
})
for _, entry in ipairs(entries) do
local amounts = TableUtils.map(entry.pools, function(x)
return x.amount
end)
local chances = TableUtils.map(entry.pools, function(x)
return x.chance .. " " .. FrameUtils.note("(" .. x.rolls .. ")")
end)
local chanceAtLeastOnes = TableUtils.map(entry.pools, function(x)
return x.chanceAtLeastOne
end)
local row = output:tag("tr")
-- Structure
if isSceneLoot then
row:tag("td"):wikitext(entry.structures)
end
-- Source
row:tag("td"):wikitext(entry.entity)
-- Quantity
if showQuantity then
row:tag("td"):attr("align", "center"):wikitext(table.concat(amounts, "<hr/>"))
end
-- Chances
if showChanceForOne then
row:tag("td"):attr("align", "center"):wikitext(table.concat(chanceAtLeastOnes, "<hr/>"))
row:tag("td"):attr("align", "center"):wikitext(table.concat(chances, "<hr/>"))
else
row:tag("td"):attr("align", "center"):wikitext(table.concat(chanceAtLeastOnes, "<hr/>"))
end
row:done()
end
return output
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local notes = {
-- Chance
MathUtils.round(entry.chanceAtLeastOne * 100) .. "%"
}
for _, note in ipairs(entry.notes) do
TableUtils.push(notes, note)
end
for _, dungeon in ipairs(entry.dungeons or {}) do
TableUtils.push(notes, string.format("Found in the [[%s|%s]] dungeon", DungeonInfo.getLink(dungeon), DungeonInfo.getDisplayName(dungeon)))
end
for _, scene in ipairs(entry.scenes or {}) do
TableUtils.push(notes, string.format("Found in the [[%s|%s]] scene", SceneInfo.getLink(scene), SceneInfo.getDisplayName(scene)))
end
return entityItem(entry.entity, notes, "inline")
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
sourceFormatters.dropScene = sourceFormatters.drop
-- Loot
sourceFormatters.loot = {
section = function(id, variation, entries)
local output = createTable()
local isSceneLoot = entries[1].dungeons or entries[1].scenes
local showQuantity = TableUtils.any(entries, function(entry)
return isAmountAboveOne(entry.amount)
end)
local showChanceForOne = TableUtils.any(entries, function(entry)
return MathUtils.round(entry.chance * 100) ~= MathUtils.round(entry.chanceAtLeastOne * 100)
end)
local header = output:tag("tr")
if isSceneLoot then
header:tag("th"):wikitext("Structure"):done()
end
header:tag("th"):wikitext("Container"):done()
if showQuantity then
header:tag("th"):wikitext("Quantity"):done()
end
if showChanceForOne then
header:tag("th"):cssText("min-width: 100px;"):wikitext("Chance<br/><small>for one</small>"):done()
header:tag("th"):cssText("min-width: 100px;"):wikitext("Chance<br/><small>per roll</small>"):done()
else
header:tag("th"):wikitext("Chance"):done()
end
header:done()
entries = process(entries, {
map = function(entry)
local structures = {}
if isSceneLoot then
local scenes = TableUtils.map(entry.scenes or {}, function(scene)
return string.format("[[%s|%s]]", SceneInfo.getLink(scene), SceneInfo.getDisplayName(scene, true))
end)
local dungeons = TableUtils.map(entry.dungeons or {}, function(dungeon)
return string.format("[[%s|%s]]", DungeonInfo.getLink(dungeon), DungeonInfo.getDisplayName(dungeon, true))
end)
table.sort(scenes, function(a, b) return a < b end)
table.sort(dungeons, function(a, b) return a < b end)
for _, dungeon in ipairs(dungeons) do
TableUtils.push(structures, dungeon)
end
for _, scene in ipairs(scenes) do
TableUtils.push(structures, scene)
end
end
local pools = {}
if entry.guaranteedChance then
TableUtils.push(pools, {
amount = formatAmountRange(entry.guaranteedAmount),
rolls = formatAmountRange(entry.guaranteedRolls or 1),
chance = MathUtils.round(entry.guaranteedChance * 100) .. "%",
chanceAtLeastOne = MathUtils.round(entry.guaranteedChanceAtLeastOne * 100) .. "%",
})
end
TableUtils.push(pools, {
amount = formatAmountRange(entry.amount),
rolls = formatAmountRange(entry.rolls or 1),
chance = MathUtils.round(entry.chance * 100) .. "%",
chanceAtLeastOne = MathUtils.round(entry.chanceAtLeastOne * 100) .. "%",
})
return {
object = entityItem(entry.entity, entry.notes),
pools = pools,
structures = #structures > 0 and table.concat(structures, "<hr/>") or nil
}
end,
sorter = function(a, b)
return a.object < b.object
end
})
for _, entry in ipairs(entries) do
local amounts = TableUtils.map(entry.pools, function(x)
return x.amount
end)
local chances = TableUtils.map(entry.pools, function(x)
return x.chance .. " " .. FrameUtils.note("(" .. x.rolls .. ")")
end)
local chanceAtLeastOnes = TableUtils.map(entry.pools, function(x)
return x.chanceAtLeastOne
end)
local row = output:tag("tr")
-- Structure
if isSceneLoot then
row:tag("td"):wikitext(entry.structures)
end
-- Container
row:tag("td"):wikitext(entry.object)
-- Quantity
if showQuantity then
row:tag("td"):attr("align", "center"):wikitext(table.concat(amounts, "<hr/>"))
end
-- Chances
if showChanceForOne then
row:tag("td"):attr("align", "center"):wikitext(table.concat(chanceAtLeastOnes, "<hr/>"))
row:tag("td"):attr("align", "center"):wikitext(table.concat(chances, "<hr/>"))
else
row:tag("td"):attr("align", "center"):wikitext(table.concat(chanceAtLeastOnes, "<hr/>"))
end
row:done()
end
return output
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local notes = {
-- Chance
MathUtils.round(entry.chanceAtLeastOne * 100) .. "%"
}
for _, note in ipairs(entry.notes or {}) do
TableUtils.push(notes, note)
end
for _, dungeon in ipairs(entry.dungeons or {}) do
TableUtils.push(notes, string.format("Found in the [[%s|%s]] dungeon", DungeonInfo.getLink(dungeon), DungeonInfo.getDisplayName(dungeon)))
end
for _, scene in ipairs(entry.scenes or {}) do
TableUtils.push(notes, string.format("Found in the [[%s|%s]] scene", SceneInfo.getLink(scene), SceneInfo.getDisplayName(scene)))
end
return entityItem(entry.entity, notes, "inline")
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
sourceFormatters.lootScene = sourceFormatters.loot
-- Fishing
sourceFormatters.fishing = {
section = function(id, variation, entries)
local output = createTable()
output:tag("tr")
:tag("th"):wikitext("Source")
:tag("th"):wikitext("Catch type")
:tag("th"):wikitext("Chance")
:done()
entries = process(entries, {
map = function(entry)
local lines = {}
local normalWaterItem = item(LiquidNames.Dirt)
for _, liquid in ipairs(entry.liquids) do
TableUtils.push(lines, item(LiquidNames[liquid] or liquid))
end
for _, biome in ipairs(entry.biomes) do
TableUtils.push(lines, string.format("%s + %s", item(BiomeNames[biome] or biome), normalWaterItem))
end
return {
source = table.concat(lines, "<hr/>"),
type = entry.type,
chance = MathUtils.round(entry.chance * 100) .. "%"
}
end
})
for _, entry in ipairs(entries) do
output:tag("tr")
:tag("td"):wikitext(entry.source)
:tag("td"):attr("align", "center"):wikitext(entry.type)
:tag("td"):attr("align", "center"):wikitext(entry.chance)
:done()
end
return output
end
}
-- Polishing
sourceFormatters.polishing = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Item crafted")
:done()
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return item(name, { Notes.Polishing })
end,
sorter = function(a, b)
return a < b
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return string.format("Crafting %s", item(name, { Notes.Polishing }, "inline"))
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Miscellaneous
sourceFormatters.miscellaneous = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Source")
:done()
entries = process(entries, {
map = function(entry)
return Notes[entry.source]
end,
sorter = function(a, b)
return a < b
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
return Notes[entry.source]
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Background perks
sourceFormatters.backgroundPerks = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Background")
:tag("th"):wikitext("Quantity")
:done()
entries = process(entries, {
map = function(entry)
return {
background = item(entry.background),
amount = entry.amount
}
end,
sorter = function(a, b)
return a.background < b.background
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.background)
:tag("td"):attr("align", "center"):wikitext(entry.amount)
:done()
end
return table
end,
inline = function(id, variation, entries)
entries = process(entries, {
map = function(entry)
return string.format("%s perks", item(entry.background))
end,
sorter = function(a, b)
return a < b
end
})
return table.concat(entries, NoteSeparator)
end
}
-- Salvage
sourceFormatters.salvage = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Item")
:tag("th"):wikitext("Quantity")
:done()
entries = process(entries, {
map = function(entry)
local name = ObjectInfo.getStat(entry.id, entry.variation, "name")
return {
item = item(name),
amount = formatAmountRange(entry.amount)
}
end,
sorter = function(a, b)
return a.item < b.item
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.item)
:tag("td"):attr("align", "center"):wikitext(entry.amount)
:done()
end
return table
end
-- No inline support
}
-- Trading
sourceFormatters.trading = {
section = function(id, variation)
return FrameUtils.template("Recipes", {
result = ObjectInfo.getStat(id, variation, "name"),
noresultunlessneeded = true
})
end,
inline = function(id, variation)
if not Recipes then
Recipes = require("Module:Recipes")
end
return Recipes.extract(id)
end
}
-- Cattle produce
sourceFormatters.cattleProduce = {
section = function(id, variation)
return FrameUtils.template("Recipes", {
result = ObjectInfo.getStat(id, variation, "name"),
noresultunlessneeded = true
})
end,
inline = function(id, variation)
if not Recipes then
Recipes = require("Module:Recipes")
end
return Recipes.extract(id)
end
}
-- Natural spawn
sourceFormatters.naturalSpawn = {
section = function(id, variation, entries)
local displayInitial = entries.initial
local displayRespawn = entries.respawn
local displayObject = entries.object
local result = FrameUtils.template("Flex start")
if displayRespawn then
result = result .. FrameUtils.template("Spawns", {
ObjectInfo.getStat(id, variation, "name"),
type = "respawn"
})
end
if displayInitial then
result = result .. FrameUtils.template("Spawns", {
ObjectInfo.getStat(id, variation, "name"),
type = "initial"
})
end
if displayObject then
result = result .. FrameUtils.template("Spawns", {
ObjectInfo.getStat(id, variation, "name"),
type = "object"
})
end
return result .. FrameUtils.template("Flex end")
end
}
-- Terrain generation
sourceFormatters.terrain = {
section = function(id, variation, entries)
local output = createTable()
local tileType = ObjectInfo.getInfo(id, variation).tileType
local isContainedResource = tileType == "ore" or tileType == "ancientCrystal"
local header = output:tag("tr")
header:tag("th"):wikitext("Biome"):done()
if isContainedResource then
header:tag("th"):wikitext("Embedded within"):done()
end
header:done()
for _, entry in ipairs(entries) do
local row = output:tag("tr")
row:tag("td"):wikitext(item(BiomeNames[entry.biome] or entry.biome)):done()
if isContainedResource then
local embeddedWithin = TableUtils.map(BiomeTerrainWallTypes[entry.biome] or {}, function(x)
return item(ObjectInfo.getStat(x, 0, "name"))
end)
row:tag("td"):wikitext(table.concat(embeddedWithin, "<br/>")):done()
end
row:done()
end
return output
end
}
-- Scene/dungeon plunder
sourceFormatters.plunder = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):cssText("min-width: 220px;"):wikitext("Structure")
:tag("th"):wikitext("Amount")
:done()
local hasDungeon = false
local hasScene = false
entries = process(entries, {
map = function(entry)
if not hasDungeon and entry.dungeon then
hasDungeon = true
end
if not hasScene and entry.scene then
hasScene = true
end
return {
scene = entry.scene and string.format("[[%s|%s]]", SceneInfo.getLink(entry.scene), SceneInfo.getDisplayName(entry.scene, true)),
dungeon = entry.dungeon and string.format("[[%s|%s]]", DungeonInfo.getLink(entry.dungeon), DungeonInfo.getDisplayName(entry.dungeon, true)),
amount = entry.amount or "Varies"
}
end,
sorter = function(a, b)
if a.dungeon and not b.dungeon then
return 1
end
if a.dungeon and b.dungeon then
return a.dungeon < b.dungeon
end
if a.scene and b.scene then
return a.amount == b.amount and (a.scene < b.scene) or a.amount > b.amount
end
end
})
local addedDungeonHeader = false
local addedSceneHeader = false
for _, entry in ipairs(entries) do
if hasDungeon and hasScene then
if entry.dungeon and not addedDungeonHeader then
addedDungeonHeader = true
table:tag("tr")
:tag("th"):attr("colspan", "2"):wikitext("Dungeons")
:done()
end
if entry.scene and not addedSceneHeader then
addedSceneHeader = true
table:tag("tr")
:tag("th"):attr("colspan", "2"):wikitext("Scenes")
:done()
end
end
table:tag("tr")
:tag("td"):wikitext(entry.dungeon or entry.scene)
:tag("td"):attr("align", "center"):wikitext(entry.amount)
:done()
end
return table
end
-- No inline support
}
-- Valid house (merchants)
sourceFormatters.validHouse = {
section = function(id, variation, entries)
local output = createTable()
output:tag("tr")
:tag("th"):wikitext("Spawning item"):done()
:done()
for _, entry in ipairs(entries) do
output:tag("tr")
:tag("td"):wikitext(item(ObjectInfo.getStat(entry.item, 0, "name"))):done()
:done()
end
return output
end
}
-- Ore boulder extraction
sourceFormatters.oreBoulder = {
section = function(id, variation, entries)
local table = createTable()
table:tag("tr")
:tag("th"):wikitext("Ore boulder")
:tag("th"):wikitext("Total ore")
:done()
entries = process(entries, {
map = function(entry)
return {
boulder = item(ObjectInfo.getStat(entry.id, entry.variation, "name")),
total = entry.total
}
end,
sorter = function(a, b)
return a.boulder < b.boulder
end
})
for _, entry in ipairs(entries) do
table:tag("tr")
:tag("td"):wikitext(entry.boulder)
:tag("td"):attr("align", "center"):wikitext(entry.total)
:done()
end
return table
end
}
-- Cattle breeding
sourceFormatters.breeding = {
section = function(id, variation, entries)
local output = createTable()
output:tag("tr")
:tag("th"):wikitext("Source"):done()
:done()
for _, entry in ipairs(entries) do
local parent = item(ObjectInfo.getStat(entry.id, 0, "name"))
output:tag("tr")
:tag("td"):wikitext(string.format("%s + %s", parent, parent)):done()
:done()
end
return output
end
}
local outputFormatters = {}
outputFormatters.section = function(objects, shouldAddCategories)
local createSection = function(object)
local result = ""
local info = ObjectInfo.getInfo(object.id, object.variation)
local isCreature = info.type == "Creature" or info.type == "Critter"
local isNonObtainable = info.type == "NonObtainable" or info.indestructible or info.destructible or isCreature
for _, source in ipairs(SourceOrder) do
local sourceFormatter = sourceFormatters[source]
if sourceFormatter and sourceFormatter.section then
local entries = object.data[source]
if entries then
local sourceDisplayName = (isNonObtainable and AltSourceDisplayName[source]) or SourceDisplayName[source]
local sourceCate = (isNonObtainable and AltSourceCate[source]) or SourceCate[source]
result = result .. "<h3>" .. sourceDisplayName .. "</h3>"
result = result .. tostring(sourceFormatter.section(object.id, object.variation, entries))
if shouldAddCategories and sourceCate then
if isCreature then
sourceCate = sourceCate .. " creatures"
else
sourceCate = sourceCate .. " items"
end
result = result .. string.format("[[Category:%s]]", sourceCate)
end
end
end
end
return result
end
if #objects > 1 then
local sections = {}
local namesubs = Args.getArg("namesub") and StringUtils.explode("/", Args.getArg("namesub")) or {}
for _, object in ipairs(objects) do
local name = ObjectInfo.getStat(object.id, object.variation, "name")
for _, namesub in ipairs(namesubs) do
name = string.gsub(name, " " .. namesub, "")
name = string.gsub(name, namesub .. " ", "")
end
sections[#sections + 1] = string.format('%s=<div class="mobileonly"><h3>%s</h3></div>%s', name, name, createSection(object))
end
return FrameUtils.parserFunction("#tag", { "tabber", table.concat(sections, "|-|") })
else
return createSection(objects[1])
end
end
outputFormatters.inline = function(objects)
-- Currently doesn't support multiple objects
local object = objects[1]
local result = mw.html.create("div"):addClass("obtaining-inline")
local sections = {}
for _, source in ipairs(SourceOrder) do
local sourceFormatter = sourceFormatters[source]
if sourceFormatter and sourceFormatter.inline then
local entries = object.data[source]
if entries then
local tag = mw.html.create("div")
:tag("div"):wikitext(sourceFormatter.inline(object.id, object.variation, entries))
sections[#sections + 1] = tostring(tag)
end
end
end
result:wikitext(table.concat(sections, "<hr/>"))
return tostring(result)
end
local p = {}
function p.getSources(id, variation, type)
if data[id] and data[id][variation] then
if type then
return data[id][variation][type] or {}
else
return data[id][variation]
end
end
return {}
end
function p._main()
local title = mw.title.getCurrentTitle()
local format = Args.getArg("mode") or "section"
local objectNames = StringUtils.explode("/", Args.getArg(1) or title.baseText)
local shouldAddCategories = title.namespace == 0 and not Args.getFlag("nocat")
local objects = TableUtils.map(objectNames, function(name)
local id, variation = ObjectIdFromName.get(name, name)
if not id then return end
local sources = p.getSources(id, variation)
if TableUtils.length(sources) == 0 then return end
return {
id = id,
variation = variation,
data = sources
}
end)
assert(#objects > 0, "No sources to display (unknown objects or they have no sources)")
local outputFormatter = outputFormatters[format]
assert(outputFormatter, "Unknown format " .. tostring(format))
return outputFormatter(objects, shouldAddCategories)
end
return p