Module:Construction list
Jump to navigation
Jump to search
Module documentation
This documentation is transcluded from Module:Construction list/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Construction list/doc. [edit]
Module:Construction list's function main is invoked by Template:Construction list.
Module:Construction list requires .
| Function list |
|---|
| L 14 — members_icon L 22 — normalize_and_key L 41 — getLastElement L 48 — plink_list L 56 — format_plinked_list L 67 — parse_recipe L 127 — query_construction_recipes L 176 — build_row L 207 — build_header L 223 — p.main L 228 — p._main |
require("strict")
require('Module:Mw.html extension')
local getArgs = require("Module:Arguments").getArgs
local plink = require('Module:Plink')._plink
local plinkp = require('Module:Plink')._plinkp
local scp = require('Module:Skill clickpic')._main
local addcommas = require('Module:Addcommas')._add
local p = {}
local SKILL_NAME = "Construction"
local BATCH_SIZE = 5000
local function members_icon(is_members)
if is_members then
return "[[File:P2P icon.png|21px|link=Members]]"
end
return "[[File:F2P icon.png|21px|link=Free-to-play]]"
end
--- Normalize nail materials (if condense is true) and return a dedup key + new materials list.
local function normalize_and_key(item, condense_nails)
local materials = {}
local mat_names = {}
local nail_added = false
for _, mat in ipairs(item.materials) do
local mat_name = mat.name or mat.page or ""
if condense_nails and not nail_added and mat_name:match('[Nn]ails$') then
materials[#materials + 1] = { name = "Nails", page = "Nails", pic = "Steel nails", quantity = mat.quantity }
mat_names[#mat_names + 1] = "Nails"
nail_added = true
elseif not (condense_nails and mat_name:match('[Nn]ails$')) then
materials[#materials + 1] = mat
mat_names[#mat_names + 1] = mat.name or ""
end
end
local key = item.page_name .. "|" .. tostring(item.level or "") .. "|" .. table.concat(mat_names, ",")
return key, materials
end
local function getLastElement(val)
if type(val) == "table" then
return val[#val]
end
return val
end
local function plink_list(items)
local images = {}
for i, entry in ipairs(items) do
images[i] = plink(entry.name, { size = "30x30", pic = entry.pic })
end
return images
end
local function format_plinked_list(items)
if not items then return "" end
local images = plink_list(items)
for i, entry in ipairs(items) do
if entry.quantity and entry.quantity > 1 then
images[i] = addcommas(entry.quantity) .. " × " .. images[i]
end
end
return table.concat(images, "<br/>")
end
local function parse_recipe(row)
local production_json = row["recipe.production_json"]
if not production_json then return nil end
local ok, production = pcall(mw.text.jsonDecode, production_json)
if not ok or not production then return nil end
local con_level, con_exp
if production.skills then
for _, skill in ipairs(production.skills) do
if skill.name == SKILL_NAME then
con_level = skill.level
con_exp = skill.experience
break
end
end
end
local fac_str = table.concat(row["recipe.uses_facility"], ",")
local output_image
if production.outputs and production.outputs[1] then
output_image = production.outputs[1].image
end
local parsed_mats = {}
if production.materials then
for _, mat in ipairs(production.materials) do
parsed_mats[#parsed_mats + 1] = {
name = mat.name,
pic = mat.image and mat.image:gsub('%.png$', ''),
quantity = tonumber(mat.quantity),
}
end
end
local parsed_tools = {}
local raw_tools = row["recipe.uses_tool"]
if raw_tools then
for _, t in ipairs(raw_tools) do
parsed_tools[#parsed_tools + 1] = { name = t }
end
end
return {
page_name = row["recipe.page_name"],
page_name_sub = row["recipe.page_name_sub"],
image = getLastElement(row["infobox_scenery.image"])
or output_image
or ("File:"..row["recipe.page_name"]..".png"),
members = row["recipe.is_members_only"] or getLastElement(row["infobox_scenery.is_members_only"]),
level = con_level,
experience = con_exp,
materials = parsed_mats,
tools = parsed_tools,
facilities = fac_str,
name = production.name or row["recipe.page_name"],
}
end
local function query_construction_recipes(args)
local all_data = {}
local limit = tonumber(args.limit) or 5000
local category = args.category
local notcategory = args.notcategory
for offset = 0, limit, BATCH_SIZE do
local b = bucket("recipe")
.select(
"recipe.production_json",
"infobox_scenery.image",
"infobox_scenery.is_members_only",
"infobox_scenery.scenery_location",
"recipe.page_name",
"recipe.page_name_sub",
"recipe.uses_tool",
"recipe.uses_facility",
"recipe.is_members_only"
)
.join("infobox_scenery", "recipe.page_name", "infobox_scenery.page_name")
.where("recipe.uses_skill", SKILL_NAME)
.where(bucket.Not("Category:Flatpacks"))
.where("infobox_scenery.scenery_location", "[[Player-owned house]]")
.orderBy("recipe.page_name", "ASC")
.limit(BATCH_SIZE)
.offset(offset)
if category then
b.where("Category:" .. category)
end
if notcategory then
b.where(bucket.Not("Category:" .. notcategory))
end
local rows = b.run()
if not rows or #rows == 0 then break end
for _, row in ipairs(rows) do
local item = parse_recipe(row)
if item then
all_data[#all_data + 1] = item
end
end
end
return all_data
end
local function build_row(item)
local pic = item.image:gsub('^File:', ''):gsub('%.[gp][in][fg]$', '')
local image
if item.image:match('%.gif$') then
image = string.format(
'<span class="pic-link">[[File:%s.gif|link=%s|35x35px]]</span>',
pic, item.page_name, item.page_name
)
else
image = plinkp(item.page_name, { size = "35x35", pic = pic })
end
local tools = format_plinked_list(item.tools)
local tr = mw.html.create('tr')
tr:td(image)
:td(item.name_cell)
:td(members_icon(item.members))
:td(scp(SKILL_NAME, item.level))
:td(item.experience or "?")
:td(format_plinked_list(item.materials))
:IF(tools == "")
:na()
:ELSE()
:td(tools)
:END()
:td('[[' .. item.facilities .. ']]')
return tr
end
local function build_header()
local tbl = mw.html.create('table')
:addClass('wikitable sortable sticky-header align-center-1 align-center-3')
tbl:tr()
:th({ 'Name', attr = {'colspan', '2'} })
:th(members_icon('both'))
:th('[[File:Construction-icon.png|21px|link=Construction]] Level')
:th('XP')
:th('Materials')
:th('Tools')
:th('Facility')
return tbl
end
function p.main(frame)
local args = getArgs(frame)
return p._main(args)
end
function p._main(args)
local data = query_construction_recipes(args)
local condense_nails = args.condense_nails ~= "no"
local seen = {}
local page_count = {}
local items = {}
for _, item in ipairs(data) do
local key, materials = normalize_and_key(item, condense_nails)
item.materials = materials
if not seen[key] then
seen[key] = true
page_count[item.page_name] = (page_count[item.page_name] or 0) + 1
items[#items + 1] = item
end
end
table.sort(items, function(a, b)
local lvl_a = tonumber(a.level) or 0
local lvl_b = tonumber(b.level) or 0
if lvl_a ~= lvl_b then
return lvl_a < lvl_b
end
return (a.name or ""):lower() < (b.name or ""):lower()
end)
local tbl = build_header()
for _, item in ipairs(items) do
local src = item.page_name_sub or item.name
local title = mw.text.split(src, '#')[1]
local subtitle = mw.text.split(src, '#')[2]
if title == subtitle then subtitle = nil end
item.name_cell = string.format("[[%s|%s]]", item.page_name, title)
if subtitle and page_count[item.page_name] > 1 then
item.name_cell = item.name_cell .. ' <small>(' .. subtitle:gsub('_', ' ') .. ')</small>'
end
tbl:node(build_row(item))
end
return tbl
end
return p