Module:Infobox: Difference between revisions
Remove inline-block when float=none (via update-page on MediaWiki MCP Server) |
Add NPC infobox function (via update-page on MediaWiki MCP Server) |
||
| Line 209: | Line 209: | ||
local tables = 'Monsters' | local tables = 'Monsters' | ||
local fields = 'name,sprite,type,hp,xp,armor,defense,attack_speed,move_speed,respawn' | local fields = 'name,sprite,type,hp,xp,armor,defense,attack_speed,move_speed,respawn' | ||
local args = { | |||
where = 'name="' .. name .. '"', | |||
limit = 1 | |||
} | |||
local result = mw.ext.cargo.query(tables, fields, args) | |||
if result and result[1] then | |||
return result[1] | |||
end | |||
return nil | |||
end | |||
-- Query NPC data from Cargo | |||
local function getNPCData(name) | |||
local tables = 'NPCs' | |||
local fields = 'name,sprite,location,type,trainer' | |||
local args = { | local args = { | ||
where = 'name="' .. name .. '"', | where = 'name="' .. name .. '"', | ||
| Line 434: | Line 451: | ||
-- Category is handled by MonsterEntry template | -- Category is handled by MonsterEntry template | ||
return tostring(root) | |||
end | |||
-- NPC infobox (queries from Cargo) | |||
function p.npc(frame) | |||
local args = frame:getParent().args | |||
local npcName = args.name or args[1] or mw.title.getCurrentTitle().text | |||
-- Query Cargo for NPC data | |||
local data = getNPCData(npcName) | |||
if not data then | |||
return '<span class="error">NPC not found: ' .. (npcName or 'nil') .. '</span>' | |||
end | |||
local root = mw.html.create('table') | |||
root:addClass('wikitable') | |||
:addClass('infobox') | |||
:addClass('npc-infobox') | |||
:css('float', 'right') | |||
:css('clear', 'right') | |||
:css('margin', '0 0 1em 1em') | |||
:css('width', '250px') | |||
-- Header | |||
root:tag('tr') | |||
:tag('th') | |||
:attr('colspan', 2) | |||
:css('text-align', 'center') | |||
:wikitext(data.name or 'Unknown') | |||
-- Image | |||
local sprite = data.sprite or data.name | |||
local spriteContent = frame:expandTemplate{ | |||
title = 'Sprite', | |||
args = { sprite, '128' } | |||
} | |||
root:tag('tr') | |||
:tag('td') | |||
:attr('colspan', 2) | |||
:css('text-align', 'center') | |||
:wikitext(spriteContent) | |||
-- Helper to add a row | |||
local function addRow(label, value) | |||
if not value or value == '' then return end | |||
local row = root:tag('tr') | |||
row:tag('th') | |||
:css('text-align', 'right') | |||
:wikitext(label) | |||
row:tag('td') | |||
:css('text-align', 'left') | |||
:wikitext(value) | |||
end | |||
-- Type | |||
addRow('Type', data.type) | |||
-- Trainer | |||
if data.trainer and data.trainer ~= '' then | |||
addRow('Trainer', data.trainer .. ' Trainer') | |||
end | |||
-- Location | |||
addRow('Location', data.location) | |||
-- Category is handled by NPCEntry template | |||
return tostring(root) | return tostring(root) | ||
end | end | ||
return p | return p | ||
Revision as of 18:40, 30 January 2026
Documentation for this module may be created at Module:Infobox/doc
local p = {}
-- Rarity color mapping
local rarityColors = {
common = "silver",
uncommon = "mint",
rare = "sky",
epic = "pink",
legendary = "gold"
}
-- Rarity display names
local rarityNames = {
common = "Common item.",
uncommon = "Uncommon item.",
rare = "Rare item.",
epic = "Epic item.",
legendary = "Legendary item."
}
-- Type to plural category mapping
local typePlurals = {
["Arrows"] = "Arrows",
["Axe"] = "Axes",
["Book"] = "Books",
["Book Products"] = "Book Products",
["Bow"] = "Bows",
["Cloth Products"] = "Cloth Products",
["Conjured Arrows"] = "Conjured Arrows",
["Container"] = "Containers",
["Cooked Food"] = "Cooked Food",
["Cooking Items"] = "Cooking Items",
["Currency"] = "Currency",
["Dagger"] = "Daggers",
["Desert Products"] = "Desert Products",
["Drinks"] = "Drinks",
["Edible Food"] = "Edible Food",
["Flowers"] = "Flowers",
["Forge Products"] = "Forge Products",
["Gloves"] = "Gloves",
["Grave Products"] = "Grave Products",
["Heavy Boots"] = "Heavy Boots",
["Heavy Chest"] = "Heavy Chest Armor",
["Heavy Helmet"] = "Heavy Helmets",
["Heavy Legs"] = "Heavy Legs",
["Holy Products"] = "Holy Products",
["Knife"] = "Knives",
["Large Axe"] = "Large Axes",
["Large Shield"] = "Large Shields",
["Large Sword"] = "Large Swords",
["Light Armor"] = "Light Armor",
["Light Boots"] = "Light Boots",
["Light Chest"] = "Light Chest Armor",
["Light Helmet"] = "Light Helmets",
["Light Legs"] = "Light Legs",
["Light Mask"] = "Light Masks",
["Light Neck"] = "Light Neck Armor",
["Light Sources"] = "Light Sources",
["Monster Products"] = "Monster Products",
["Necklace"] = "Necklaces",
["North Products"] = "North Products",
["Orb"] = "Orbs",
["Plains Products"] = "Plains Products",
["Potions"] = "Potions",
["Raw Food"] = "Raw Food",
["Regular Sword"] = "Regular Swords",
["Ring"] = "Rings",
["Small Shield"] = "Small Shields",
["Special Food"] = "Special Food",
["Staff"] = "Staves",
["Swamp Products"] = "Swamp Products",
["Sword"] = "Swords",
["Tools"] = "Tools",
}
-- Pluralize a type name for category
local function pluralize(singular)
return typePlurals[singular] or (singular .. "s")
end
-- Stat definitions for monsters
local monsterStats = {
hp = { text = 'HP', icon = 'Health.png' },
mp = { text = 'MP', icon = 'Mana.png' },
armor = { text = 'Armor', icon = 'Armor.png' },
defense = { text = 'Defense', icon = 'Defense.png' },
move_speed = { text = 'Move Speed', icon = 'Move_Speed.png' },
attack_speed = { text = 'Attack Speed', icon = 'Attack_Speed.png' },
xp = { text = 'Experience', icon = 'Experience.png' },
respawn = { text = 'Respawn Time', icon = 'Respawn_Time.png' },
}
local function makeIcon(frame, icon, tooltip)
local img = frame:preprocess('{{#spritescale:' .. icon .. '|stat}}')
return '<span title="' .. mw.text.encode(tooltip) .. '">' .. img .. '</span>'
end
-- Format a stat line with positive/negative coloring
local function formatStat(label, value)
if not value or value == "" then
return nil
end
local numValue = tonumber(value)
if numValue then
local colorClass, prefix
if numValue >= 0 then
colorClass = "color-conifer"
prefix = "+"
else
colorClass = "color-coral"
prefix = ""
end
return string.format('<p>%s: <span class="%s">%s%s</span></p>', label, colorClass, prefix, value)
else
return string.format('<p>%s: %s</p>', label, value)
end
end
-- Format a plain text line
local function formatLine(text, colorClass)
if not text or text == "" then
return nil
end
if colorClass then
return string.format('<p class="%s">%s</p>', colorClass, text)
else
return string.format('<p>%s</p>', text)
end
end
-- Format description with bracketed text on separate lines and colored
local function formatDescription(desc)
if not desc or desc == "" then
return {}
end
local lines = {}
local remaining = desc
-- Pattern to find [bracketed text]
while remaining and remaining ~= "" do
local beforeBracket, bracketContent, afterBracket = remaining:match("^(.-)%[([^%]]+)%](.*)$")
if bracketContent then
-- Add any text before the bracket as jade-colored description
if beforeBracket and beforeBracket:match("%S") then
local trimmed = beforeBracket:match("^%s*(.-)%s*$")
if trimmed ~= "" then
table.insert(lines, string.format('<p class="color-jade">%s</p>', trimmed))
end
end
-- Add the bracketed text as columbia-colored (item-important)
table.insert(lines, string.format('<p class="color-columbia">[%s]</p>', bracketContent))
remaining = afterBracket
else
-- No more brackets, add remaining text as jade-colored
local trimmed = remaining:match("^%s*(.-)%s*$")
if trimmed ~= "" then
table.insert(lines, string.format('<p class="color-jade">%s</p>', trimmed))
end
remaining = nil
end
end
return lines
end
-- Split a comma-separated string into a table
local function splitTypes(typeStr)
if not typeStr or typeStr == "" then
return {}
end
local types = {}
for t in typeStr:gmatch("[^,]+") do
local trimmed = t:match("^%s*(.-)%s*$")
if trimmed and trimmed ~= "" then
table.insert(types, trimmed)
end
end
return types
end
-- Query item data from Cargo
local function getItemData(name, rarity)
local tables = 'Items'
local fields = 'name,sprite,slot,type,weight,rng,damage,health,mana,ability,magic,armor,defense,move_speed,attack_speed,size,container,health_regen,mana_regen,description,rarity'
local args = {
where = 'name="' .. name .. '"',
limit = 1
}
if rarity and rarity ~= '' then
args.where = args.where .. ' AND rarity="' .. rarity .. '"'
end
local result = mw.ext.cargo.query(tables, fields, args)
if result and result[1] then
return result[1]
end
return nil
end
-- Query monster data from Cargo
local function getMonsterData(name)
local tables = 'Monsters'
local fields = 'name,sprite,type,hp,xp,armor,defense,attack_speed,move_speed,respawn'
local args = {
where = 'name="' .. name .. '"',
limit = 1
}
local result = mw.ext.cargo.query(tables, fields, args)
if result and result[1] then
return result[1]
end
return nil
end
-- Query NPC data from Cargo
local function getNPCData(name)
local tables = 'NPCs'
local fields = 'name,sprite,location,type,trainer'
local args = {
where = 'name="' .. name .. '"',
limit = 1
}
local result = mw.ext.cargo.query(tables, fields, args)
if result and result[1] then
return result[1]
end
return nil
end
function p.item(frame)
local args = frame:getParent().args
local itemName = args.name or args[1] or mw.title.getCurrentTitle().text
local rarity = args.rarity
local float = args.float or "right"
local spriteSize = args.spriteSize or "64"
local previewMode = args.preview == "true" or args.preview == "1"
local data
-- If preview mode or direct data provided, use args instead of Cargo
if previewMode or args.damage or args.armor or args.type then
data = {
name = itemName,
sprite = args.sprite or itemName,
slot = args.slot,
type = args.type,
weight = args.weight,
rng = args.rng,
damage = args.damage,
health = args.health,
mana = args.mana,
ability = args.ability,
magic = args.magic,
armor = args.armor,
defense = args.defense,
move_speed = args.move_speed,
attack_speed = args.attack_speed,
size = args.size,
container = args.container,
health_regen = args.health_regen,
mana_regen = args.mana_regen,
description = args.description,
rarity = rarity or args.rarity or "common"
}
else
-- Query Cargo for item data
data = getItemData(itemName, rarity)
if not data then
return '<span class="error">Item not found: ' .. (itemName or 'nil') .. '</span>'
end
end
local itemRarity = string.lower(data.rarity or "common")
local rarityColor = rarityColors[itemRarity] or "silver"
local rarityText = rarityNames[itemRarity] or ""
local html = {}
-- Container with tooltip-panel styling
if float == "none" then
table.insert(html, '<div class="tooltip-panel font-apogea-long infobox">')
else
table.insert(html, string.format('<div class="tooltip-panel font-apogea-long infobox" style="float:%s; margin-%s:15px; margin-bottom:10px;">',
float,
float == "right" and "left" or "right"))
end
-- Sprite
local sprite = data.sprite or data.name
table.insert(html, string.format('{{Sprite|%s|%s|class=pageimage}}', sprite, spriteSize))
-- Item name with rarity color
table.insert(html, string.format('<p class="font-bitcell color-%s">%s</p>', rarityColor, data.name))
-- Description (with bracketed text handling)
local descLines = formatDescription(data.description)
for _, line in ipairs(descLines) do
table.insert(html, line)
end
-- Stats
if data.rng and data.rng ~= "" then
table.insert(html, formatStat("Range", data.rng))
end
if data.damage and data.damage ~= "" then
table.insert(html, formatStat("Damage", data.damage))
end
if data.attack_speed and data.attack_speed ~= "" then
table.insert(html, formatStat("Attackspeed", data.attack_speed))
end
if data.armor and data.armor ~= "" then
table.insert(html, formatStat("Armor", data.armor))
end
if data.defense and data.defense ~= "" then
table.insert(html, formatStat("Defense", data.defense))
end
if data.move_speed and data.move_speed ~= "" then
table.insert(html, formatStat("Movespeed", data.move_speed))
end
if data.health and data.health ~= "" then
table.insert(html, formatStat("HP", data.health))
end
if data.mana and data.mana ~= "" then
table.insert(html, formatStat("MP", data.mana))
end
if data.ability and data.ability ~= "" then
table.insert(html, formatStat("Ability", data.ability))
end
if data.magic and data.magic ~= "" then
table.insert(html, formatStat("Magic", data.magic))
end
if data.health_regen and data.health_regen ~= "" then
table.insert(html, formatStat("HP Regen", data.health_regen))
end
if data.mana_regen and data.mana_regen ~= "" then
table.insert(html, formatStat("MP Regen", data.mana_regen))
end
if data.container and data.container ~= "" then
table.insert(html, formatStat("Slots", data.container))
end
-- Size
if data.size and data.size ~= "" then
table.insert(html, string.format('<p>Size: %s/10</p>', data.size))
end
-- Weight
if data.weight and data.weight ~= "" then
table.insert(html, string.format('<p>It weighs %s oz.</p>', data.weight))
end
-- Rarity text (skip for common)
if itemRarity ~= "common" and rarityText ~= "" then
table.insert(html, formatLine(rarityText, "color-" .. rarityColor))
end
-- Type display (show all types)
local types = splitTypes(data.type)
if #types > 0 then
table.insert(html, formatLine("[" .. table.concat(types, ", ") .. "]", "color-silver"))
end
table.insert(html, '</div>')
-- Categories (skip in preview mode)
local categories = ''
if not previewMode then
for _, t in ipairs(types) do
categories = categories .. '[[Category:' .. pluralize(t) .. ']]'
end
end
return frame:preprocess(table.concat(html, '\n')) .. categories
end
-- Monster infobox (queries from Cargo)
function p.monster(frame)
local args = frame:getParent().args
local monsterName = args.name or args[1] or mw.title.getCurrentTitle().text
-- Query Cargo for monster data
local data = getMonsterData(monsterName)
if not data then
return '<span class="error">Monster not found: ' .. (monsterName or 'nil') .. '</span>'
end
local root = mw.html.create('table')
root:addClass('wikitable')
:addClass('infobox')
:addClass('monster-infobox')
:css('float', 'right')
:css('clear', 'right')
:css('margin', '0 0 1em 1em')
:css('width', '250px')
-- Header
root:tag('tr')
:tag('th')
:attr('colspan', 2)
:css('text-align', 'center')
:wikitext(data.name or 'Unknown')
-- Image
local sprite = data.sprite or data.name
local previewContent = frame:expandTemplate{
title = 'MonsterPreview',
args = { sprite, '128', mode = args.mode or 'auto', file = args.file or '' }
}
root:tag('tr')
:tag('td')
:attr('colspan', 2)
:css('text-align', 'center')
:wikitext(previewContent)
-- Stats
local function addStat(key, value)
if not value or value == '' then return end
local stat = monsterStats[key]
if not stat then return end
local row = root:tag('tr')
row:tag('th')
:css('text-align', 'right')
:css('width', '32px')
:wikitext(makeIcon(frame, stat.icon, stat.text))
row:tag('td')
:css('text-align', 'left')
:wikitext(value)
end
addStat('hp', data.hp)
addStat('xp', data.xp)
addStat('armor', data.armor)
addStat('defense', data.defense)
addStat('attack_speed', data.attack_speed)
addStat('move_speed', data.move_speed)
addStat('respawn', data.respawn)
-- Category is handled by MonsterEntry template
return tostring(root)
end
-- NPC infobox (queries from Cargo)
function p.npc(frame)
local args = frame:getParent().args
local npcName = args.name or args[1] or mw.title.getCurrentTitle().text
-- Query Cargo for NPC data
local data = getNPCData(npcName)
if not data then
return '<span class="error">NPC not found: ' .. (npcName or 'nil') .. '</span>'
end
local root = mw.html.create('table')
root:addClass('wikitable')
:addClass('infobox')
:addClass('npc-infobox')
:css('float', 'right')
:css('clear', 'right')
:css('margin', '0 0 1em 1em')
:css('width', '250px')
-- Header
root:tag('tr')
:tag('th')
:attr('colspan', 2)
:css('text-align', 'center')
:wikitext(data.name or 'Unknown')
-- Image
local sprite = data.sprite or data.name
local spriteContent = frame:expandTemplate{
title = 'Sprite',
args = { sprite, '128' }
}
root:tag('tr')
:tag('td')
:attr('colspan', 2)
:css('text-align', 'center')
:wikitext(spriteContent)
-- Helper to add a row
local function addRow(label, value)
if not value or value == '' then return end
local row = root:tag('tr')
row:tag('th')
:css('text-align', 'right')
:wikitext(label)
row:tag('td')
:css('text-align', 'left')
:wikitext(value)
end
-- Type
addRow('Type', data.type)
-- Trainer
if data.trainer and data.trainer ~= '' then
addRow('Trainer', data.trainer .. ' Trainer')
end
-- Location
addRow('Location', data.location)
-- Category is handled by NPCEntry template
return tostring(root)
end
return p