Module:AutomaticInfoboxItem
Jump to navigation
Jump to search
Used in {{Automatic infobox item}}.
local p = {}
local cargo = mw.ext.cargo
local LinkBatch = require( 'Module:LinkBatch' )
-- Get HTML of pretty-printed list of tags.
-- @param {string} tagsString Comma-separated tags. Example: "ranged,tool,mininggun,mininglaser".
-- @param {string} categoryPrefix String between "Category:" and ":<tag here>" in links to categories. Example: "ColonyTag".
-- @param {bool} nocat If true, categories are not added to the page.
-- @return {table} { html = "resulting html", tags = { tag1 = true, tag2 = true, ... } }
local function tagCloud( tagsString, categoryPrefix, nocat )
if not tagsString or tagsString == '' then
return { html = '', tags = {} }
end
local tagsLine = ''
local foundTags = {}
for _, tag in ipairs( mw.text.split( tagsString, ',' ) ) do
local category = 'Category:' .. categoryPrefix .. ':' .. tag
-- TODO: move inline CSS into TemplateStyles or something
tagsLine = tagsLine .. ' <span class="infobox-tag" style="border: 1px solid #7f7f7f; padding: 3px; display: inline-block; margin: 2px 1px;">[[:' ..
category .. '|' .. tag .. ']]</span>'
if not nocat then
tagsLine = tagsLine .. '[[' .. category .. ']]'
end
foundTags[tag] = true
end
return { html = tagsLine, tags = foundTags }
end
-- Perform a SQL query to "item" table in the Cargo database (see Special:CargoTables/item).
-- @param {string} itemId
-- @return {table} Database row.
local function queryItem( itemId )
local tables = 'item'
local fields = 'name,description,category,tier,rarity,price,stackSize,twoHanded,wikiPage,id,tags,colonyTags'
local queryOpt = {
where = 'id="' .. itemId .. '"',
limit = 1
}
return ( cargo.query( tables, fields, queryOpt ) or {} )[1]
end
-- Perform a SQL query to "item_metadata" table in the Cargo database (see Special:CargoTables/item_metadata).
-- @param {string} itemId
-- @return {table} Array with arbitrary information: { key1 = value1, ... }, where both keys and values are strings.
local function queryItemMetadata( itemId )
local queryOpt = {
where = 'id="' .. itemId .. '"'
}
local rows = cargo.query( 'item_metadata', 'prop,value', queryOpt )
local metadata = {}
for _, row in ipairs( rows or {} ) do
metadata[row.prop] = row.value
end
return metadata
end
-- Maps damage type (e.g. "cosmic") to the name of image in the wiki.
local damageTypeIcons = {
physical = 'Physical (Attack).png',
fire = 'Fire (Attack).png',
ice = 'Frost (Attack).png',
poison = 'Poison (Attack).png',
electric = 'Electric (Attack).png',
radioactive = 'Radioactive (Attack).png',
shadow = 'Shadow (Attack).png',
cosmic = 'Cosmic (Attack).png'
}
-- Based on item metadata, return wikitext that describes primaryAbility or altAbility of item.
-- @param {table} metadata Result of queryItemMetadata()
-- @param {bool} isPrimary True for primary ability, false for alt ability.
-- @return {table|nil} Either array of arguments to {{Infobox/field}} (if ability can be described) or nil.
local function describeAbility( metadata, isPrimary )
local keyPrefix = 'alt.'
if isPrimary then
keyPrefix = ''
end
local damagePerHit = metadata[keyPrefix .. 'damagePerHit']
local hitsPerSecond = metadata[keyPrefix .. 'hitsPerSecond']
local comboSteps = metadata[keyPrefix .. 'comboSteps']
local damageType = metadata[keyPrefix .. 'damageType']
local abilityName = metadata[keyPrefix .. 'ability']
local ret = ''
if damagePerHit then
ret = ret .. 'Damage per hit: ' .. damagePerHit .. "<br>"
end
if hitsPerSecond then
ret = ret .. 'Rate of fire: ' .. hitsPerSecond .. "<br>"
end
if comboSteps then
ret = ret .. comboSteps .. '-hit combo<br>'
end
if abilityName then
ret = ret .. "'''Special''': " .. abilityName
end
if ret == '' then
return
end
local fieldName = 'Alt'
if isPrimary then
fieldName = 'Primary'
end
if damageType then
if damageTypeIcons[damageType] then
local tooltip = mw.getContentLanguage():ucfirst( damageType )
fieldName = fieldName .. ' [[File:' .. damageTypeIcons[damageType] .. '|32px|' .. tooltip .. '|link=]]\n'
else
-- Unknown type, doesn't have an icon (yet?).
ret = damageType .. "\n" .. ret
end
end
return { fieldName, ret }
end
-- Based on item metadata, return wikitext that describes rotting status of this food.
-- @param {table} metadata Result of queryItemMetadata()
-- @return {string|nil}
function p.DescribeRotting( metadata )
local rottingInfo, rotInfoColor
if metadata.noRotting then
rotInfoColor = 'green' -- Beneficial
rottingInfo = 'This food doesn\'t rot.'
else
local rotMinutes = tonumber( metadata.rotMinutes or 200 )
rottingInfo = mw.getContentLanguage():formatDuration( 60 * rotMinutes )
if rotMinutes > 200 then
rotInfoColor = 'green' -- Better than default
elseif rotMinutes < 200 then
rotInfoColor = '#bb0000' -- Worse than default
end
end
local style = ''
if rotInfoColor then
style = ' style="font-weight: bold; color: ' .. rotInfoColor .. ';"'
end
return '<span' .. style .. '>' .. rottingInfo .. '</span>'
end
-- Print the automatic infobox of item. (based on [[Special:CargoTables/item]])
-- Usage: {{#invoke: AutomaticInfoboxItem|Main|carbonpickaxe}}
-- First parameter: item ID, e.g. "aentimber".
-- Optional parameter: nocat=1 - if present, this infobox won't add any categories to the current article. (can used in examples, help pages, etc.)
-- Optional parameter: image=Something.png - if present, will be used as infobox image (replaces the default image, which is inventory icon of the item).
-- Optional parameter: image_size=150px - if present, sets the width of image. This is only used for image from image= parameter, not for inventory icons, etc.
function p.Main( frame )
local args = frame.args
if not args[1] then
-- If called from a template like {{Automatic infobox item}} without parameters, use parameters of the parent template instead.
args = frame:getParent().args
end
local id = args[1] or 'fu_carbon'
local nocat = args['nocat'] or false
local image = args['image']
-- Perform a SQL query to the Cargo database (see Special:CargoTables/item).
local row = queryItem( id )
if not row then
-- Item not found in the database.
if nocat then
return ''
end
return '[[Category:Item pages with broken automatic infobox]]'
end
-- Also load item metadata (properties like "foodValue" don't have their own column,
-- because they only make sense for a small subset of items)
local metadata = queryItemMetadata( id )
local ret = ''
if not nocat then
-- Add categories.
ret = ret .. frame:expandTemplate{ title = 'ItemPageCategory', args = { row.category } }
-- Sanity check: if the item got renamed in-game, then an article with old name can still have the infobox.
-- Add such article into the tracking category:
if row.wikiPage ~= mw.title.getCurrentTitle().text then
ret = ret .. '[[Category:Item pages where title is different from expected]]'
end
ret = ret .. '\n'
end
-- Format "row" (information about item: row.name, row.description, etc.) as wikitext.
ret = ret .. '{| class="infobox"\n'
-- Check if there are images that can be added automatically.
-- Most obtainable items have "File:Item_icon_<id>.png" (their 16x16 inventory icon).
local foundImages = {}
local normalizedId = string.gsub( row.id, ':', '.' )
-- Possible image 1: Inventory icon.
local iconTitle = mw.title.new( 'Item icon ' .. normalizedId .. '.png', 6 )
if iconTitle.fileExists then
table.insert( foundImages, iconTitle.text )
end
-- Possible image 2: image explicitly specified by a human editor (image=Something.png parameter).
if image then
table.insert( foundImages, image )
end
-- Possible image 3: depiction of placed blocks (3x3 grid). These are uploaded manually (not by the bot).
if row.category == "block" then
local blockImageTitle = mw.title.new( 'Block image ' .. normalizedId .. '.png', 6 )
if blockImageTitle.fileExists then
table.insert( foundImages, blockImageTitle.text )
end
end
-- Possible image 4: image of placeable object, e.g. furniture. These are auto-uploaded by the bot.
local placedImageTitle = mw.title.new( 'Item image ' .. normalizedId .. '.png', 6 )
if placedImageTitle.fileExists then
table.insert( foundImages, placedImageTitle.text )
end
if #foundImages > 0 then
if #foundImages == 1 then
-- If we only have one image, then it uses {{infobox/image}} (entire row of the infobox).
local imageParams = { foundImages[1] }
if args['image_size'] then
imageParams['width'] = args['image_size']
end
ret = ret .. frame:expandTemplate{ title = 'infobox/image', args = imageParams }
else
-- If we have 2+ images (e.g. inventory icon and placeable image),
-- then we display them side-to-side with inventory icon (in the same row of the infobox).
-- Note: if we have 3 or more images, then only the first two are shown.
ret = ret .. '|-\n'
ret = ret .. '| style="vertical-align: middle; text-align: right" | [[File:' .. foundImages[1] .. ']]\n'
ret = ret .. '| style="vertical-align: middle; text-align: left" | [[File:' .. foundImages[2]
if args['image_size'] then
-- Allow human editor to override the width of second image.
-- First image is very likely an inventory icon and therefore doesn't need this.
ret = ret .. '|' .. args['image_size']
end
ret = ret .. ']]\n'
-- For Extension:OpenGraphMeta:
-- because we didn't use {{infobox/image}}, we must call {{#setmainimage:}} explicitly.
-- We use block image here, because it's larger than an icon.
frame:callParserFunction( '#setmainimage', { foundImages[2] } )
end
end
-- Remove "+N Fuel" from description, because we show "Ship fuel" as a separate row (see below).
local description = string.gsub( row.description or '', '%s+%+%d+ Fuel', '' )
ret = ret .. frame:expandTemplate{ title = 'infobox/title', args = { row.name } }
ret = ret .. frame:expandTemplate{ title = 'infobox/line', args = { description } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Category', row.category } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Tier', row.tier } }
-- Item-specific properties like "Food value" must be shown higher than generic properties like "Price".
local edibleByHuman = row.category == "food" or row.category == "preparedFood" or row.category == "drink"
local animalDiet = metadata.whichAnimalsEat
if edibleByHuman or animalDiet then
-- Note: some foods don't have foodValue key.
-- Default is 0 for player (buff foods that don't satisfy hunger) and 20 for farm beasts.
local foodValue = metadata.foodValue
if edibleByHuman and foodValue then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Rpb food icon.svg|16px|left|link=|alt=]] Food value',
foodValue
} }
end
if animalDiet then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Nutrition (583) - The Noun Project.svg|24px|left|link=|alt=]] Farm beast food value',
( foodValue or 20 ) .. '<br><small>(' .. animalDiet .. ')</small>'
} }
end
-- Display rotting time, except for animal-only foods like Oil or Moondust (they don't rot).
if edibleByHuman then
local rottingInfo = p.DescribeRotting( metadata )
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[List of foods by rotting time|Rotting]]', rottingInfo
} }
end
end
if metadata.shipFuel then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Linearicons_rocket.svg|32px|left|link=|alt=]] [[Acceptable Ship Fuel|Ship fuel]]',
metadata.shipFuel
} }
end
if metadata.mechFuel then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Node_icon_fu_engineering.mechsbasic.png|32px|left|link=|alt=]] [[Acceptable Mech Fuel|Mech fuel]]',
metadata.mechFuel
} }
end
if metadata.slotCount then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Farm-Fresh box open.png|32px|left|link=|alt=]] Slot count',
metadata.slotCount
} }
end
if metadata.blockHealth then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Noun project 528.svg|16px|left|link=|alt=]] [[List of blocks by durability|Block hitpoints]]',
metadata.blockHealth
} }
end
if metadata.lightLevel and metadata.lightColor then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[List of lights|Light]]',
metadata.lightLevel .. ' ' .. frame:expandTemplate{ title = 'LightColor', args = { metadata.lightColor } }
} }
end
if metadata.tileEffects then
local effects = mw.text.split( metadata.tileEffects, ',' )
for _, effectCode in ipairs( effects ) do
LinkBatch.AddEffect( effectCode )
end
local effectLinks = {}
for _, effectCode in ipairs( effects ) do
table.insert( effectLinks, LinkBatch.GetEffectLink( effectCode, {
nolink = true,
icon = 'ifExists',
allowUnknown = true
} ) .. '[[Category:TileEffect:' .. effectCode .. ']]' )
end
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:VisualEditor - Icon - Alert.svg|16px|left|link=|alt=]] Effects',
table.concat( effectLinks, '\n' )
} }
end
local primaryAbility = describeAbility( metadata, true )
if primaryAbility then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = primaryAbility }
end
local altAbility = describeAbility( metadata, false )
if altAbility then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = altAbility }
end
if metadata.shieldHealth and metadata.perfectBlockTime then
local shieldInfo = 'Shield health: ' .. metadata.shieldHealth ..
'\nPerfect block time: ' .. metadata.perfectBlockTime
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = {
'[[File:Open Iconic shield.svg|16px|left|link=|alt=]] Shield',
shieldInfo
} }
end
if metadata.powerMultiplier and metadata.protection and metadata.maxEnergy and metadata.maxHealth then
-- Armor
local bonus = '[[File:Damage icon.png|24px|link=|Damage]] ' .. metadata.powerMultiplier .. '%' ..
'\n\n[[File:Defence icon.png|24px|link=|Defense]] ' .. metadata.protection ..
'\n\n[[File:Energy icon.png|24px|link=|Energy]] ' .. metadata.maxEnergy ..
'\n\n[[File:Health icon.png|24px|link=|Health]] ' .. metadata.maxHealth
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Bonus', bonus } }
-- What other armor pieces are necessary for this set? (usually 3 items: head/chest/legs)
local setQueryFields = 'headPage__full=head,chestPage__full=chest,legsPage__full=legs'
local setQueryOpt = {
where = 'head HOLDS "' .. id .. '" OR chest HOLDS "' .. id .. '" OR legs HOLDS "' .. id .. '"',
limit = 1
}
local setRow = ( cargo.query( 'armorset', setQueryFields, setQueryOpt ) or {} )[1]
if setRow then
local armorset = ''
if setRow.head and setRow.head ~= '' then
for _, linkTarget in ipairs( mw.text.split( setRow.head, ',' ) ) do
armorset = armorset .. '[[File:Museum icon Military.png|head|16px|link=]] [[' .. linkTarget .. ']]<br>'
end
end
if setRow.chest and setRow.chest ~= '' then
for _, linkTarget in ipairs( mw.text.split( setRow.chest, ',' ) ) do
armorset = armorset .. '[[File:Rpb clothing icon.svg|chest|16px|link=]] [[' .. linkTarget .. ']]<br>'
end
end
if setRow.legs and setRow.legs ~= '' then
for _, linkTarget in ipairs( mw.text.split( setRow.legs, ',' ) ) do
armorset = armorset .. '[[File:Android Emoji 1f45f.svg|legs|16px|link=]] [[' .. linkTarget .. ']]<br>'
end
end
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'In set with', armorset } }
end
end
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Rarity', row.rarity } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Price', row.price } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Stack size', row.stackSize } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field/bool', args = { 'Two-handed?', row.twoHanded } }
local itemTagCloud = tagCloud( row.tags, 'ItemTag', nocat )
if itemTagCloud.tags.upgradeableWeapon or itemTagCloud.tags.upgradeableTool then
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Upgradeable?', 'Yes' } }
end
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Tags', itemTagCloud.html } }
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'Colony tags', tagCloud( row.colonyTags, 'ColonyTag', nocat ).html } }
-- Item ID is last, because very few people need it
ret = ret .. frame:expandTemplate{ title = 'infobox/field', args = { 'ID', row.id } }
ret = ret .. '\n|}\n' .. frame:expandTemplate{ title = 'Item unlocked by', args = { row.id } }
-- For codexes only: add a top-level ==Section== with text, prepended by {{Spoiler}} template
if row.category == 'codex' then
local codexRow = ( cargo.query( 'codex_text', 'text', { where = 'id="' .. id .. '"' } ) or {} )[1]
if codexRow then
ret = ret .. frame:expandTemplate{ title = 'Spoiler', args = { nocat = 1 } } ..
'\n== Contents ==\n' ..
frame:expandTemplate{ title = 'Codex', args = { text = codexRow.text } }
end
end
-- Include pages of edible items into diet-specific categories.
if edibleByHuman then
local foodTypeInfo = description:match( '>Type: (.*)</span>' )
if foodTypeInfo then
foodTypeInfo = foodTypeInfo:gsub( '+', ',' ):gsub( '\\.', '' ):gsub( '?', '' )
for _, dietName in ipairs( mw.text.split( foodTypeInfo, ',' ) ) do
dietName = mw.text.trim( dietName )
if dietName == 'Raw Meat' then
ret = ret .. '[[Category:Raw meat foods]]'
end
if dietName == 'Meaty Plant' then
ret = ret .. '[[Category:Meaty plant foods]]'
end
if dietName == 'Cooked Meat' then
ret = ret .. '[[Category:Cooked meat foods]]'
end
if dietName == 'Seafood' or dietName == 'Cooked Seafood' then
ret = ret .. '[[Category:Fish foods]]'
end
if dietName == 'Plant' or dietName == 'Vegetable' or dietName == 'Vegetables'
or dietName == 'Fruit' or dietName == 'Fruity'
or dietName == 'Herbivore' or dietName == 'Herb' or dietName == 'Grain'
then
ret = ret .. '[[Category:Plant foods]]'
end
if dietName == 'Robot' or dietName == 'Robot Food' then
ret = ret .. '[[Category:Robotic foods]]'
end
if dietName:match( 'Sugar' ) then
ret = ret .. '[[Category:Sugar foods]]'
end
end
end
end
return ret
end
return p