Module:ItemData/nav: Difference between revisionsGive feedback
added support for language subpages with more that 2 letters |
Tag: Undo |
||
| (36 intermediate revisions by 9 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local items_data = mw.loadJsonData("Data:ItemData.json") | local items_data = mw.loadJsonData("Data:ItemData.json") | ||
| Line 5: | Line 7: | ||
local util_module = require('Module:Utilities') | local util_module = require('Module:Utilities') | ||
-- Exceptions if the link is different from the item name | |||
local linkOverrides = { | |||
["Bullet Lifesteal"] = "Bullet Lifesteal (item)", | |||
["Spirit Lifesteal"] = "Spirit Lifesteal (item)" | |||
} | |||
-- The card navboxes put every item on a single page, so their cards opt into | |||
-- the cheaper ItemBox rendering via lowGraphics. This swaps the paper texture | |||
-- from the normal-mode multiply (which blends two images and is re-rasterised | |||
-- every scrolled frame) to a transparent-paper overlay that is a single | |||
-- cacheable image, the one change that carried the navbox scroll cost. The wear | |||
-- and icon masks stay at full detail, since a single-image mask caches fine. | |||
-- See [[Template:ItemBox]]. Only the ItemBox branch of write_wrapped_item_list | |||
-- uses this; the ItemIcon lists are unaffected. | |||
local LOW_GRAPHICS = "|lowGraphics=true" | |||
-- Cached items grouped by slot to prevent redundant full-table evaluations | |||
local cached_slots = nil | |||
local function get_items_by_slot(slot) | |||
if not cached_slots then | |||
cached_slots = { table = {}, Weapon = {}, Armor = {}, Tech = {} } | |||
for item_key, item_data in pairs(items_data) do | |||
local this_slot = item_data["Slot"] | |||
if this_slot and cached_slots[this_slot] then | |||
table.insert(cached_slots[this_slot], { key = item_key, data = item_data }) | |||
end | |||
end | |||
end | end | ||
return cached_slots[slot] or {} | |||
return | |||
end | end | ||
--With debug_mode on, it outputs unprocessed | -- With debug_mode on, it outputs unprocessed wikitext | ||
local function process_debug_mode(wikitext, debug_mode) | local function process_debug_mode(wikitext, debug_mode) | ||
if debug_mode == 'true' then | |||
return wikitext | |||
elseif debug_mode == 'false' or debug_mode == nil then | |||
return mw.getCurrentFrame():preprocess(wikitext) | |||
else | |||
return "debug_mode must be 'true' or 'false'" | |||
end | |||
end | end | ||
--Writes list of items of a certain slot within the min and max soul bounds | -- Writes list of items of a certain slot within the min and max soul bounds | ||
local function write_wrapped_item_list(slot, min_souls, max_souls, template, sep, filter_mode) | |||
local function write_wrapped_item_list(slot, min_souls, max_souls, template, sep) | |||
if slot ~= 'Weapon' and slot ~= 'Armor' and slot ~= 'Tech' then | if slot ~= 'Weapon' and slot ~= 'Armor' and slot ~= 'Tech' then | ||
return 'slot must be Weapon, Armor (Vitality), or Tech (Spirit)' | return 'slot must be Weapon, Armor (Vitality), or Tech (Spirit)' | ||
| Line 54: | Line 59: | ||
if min_souls == nil or max_souls == nil then return 'Min/Max souls must be numerical' end | if min_souls == nil or max_souls == nil then return 'Min/Max souls must be numerical' end | ||
-- | filter_mode = filter_mode or "all" | ||
-- Lifted out of the loop: Language code doesn't change per item | |||
local lang_code = lang_module.get_lang_code() | |||
local items = {} | local items = {} | ||
for | local slot_items = get_items_by_slot(slot) -- Drastically reduces search pool size | ||
for _, item_entry in ipairs(slot_items) do | |||
local item_key = item_entry.key | |||
local item_data = item_entry.data | |||
local this_cost = tonumber(item_data["Cost"]) | local this_cost = tonumber(item_data["Cost"]) | ||
local | local is_street_brawl = item_data["StreetBrawl"] == true | ||
if item_data["Name"] ~= nil and item_data["IsDisabled"] == false and this_cost | local should_include = true | ||
if | |||
local item_name_english = lang_module.get_string(item_key, "en") | -- Filter logic for Street Brawl items | ||
local | if filter_mode == "street_brawl_only" and not is_street_brawl then | ||
local item_name_local = lang_module.get_string(item_key, lang_code) | should_include = false | ||
elseif filter_mode == "exclude_street_brawl" and is_street_brawl then | |||
should_include = false | |||
end | |||
if should_include and item_data["Name"] ~= nil and item_data["IsDisabled"] == false and this_cost ~= nil then | |||
if this_cost >= min_souls and this_cost < max_souls then | |||
local item_name_english = lang_module.get_string(item_key, "en") | |||
local item_link = linkOverrides[item_name_english] or item_name_english | |||
local item_name_local = lang_module.get_string(item_key, lang_code) | |||
local item_template | local item_template | ||
if template == "ItemIcon" then | if template == "ItemIcon" then | ||
if lang_code == "en" then | if lang_code == "en" then | ||
item_template = "{{ItemIcon|" .. item_name_english .. "|size=26px}}" | |||
item_template = "{{ItemIcon|" .. item_name_english .. "}}" | |||
else | else | ||
item_template = "{{ItemIcon|" .. item_name_english .. "|lang=" .. lang_code .. "|l1=" .. item_name_local .. "|size=26px}}" | |||
item_template = "{{ItemIcon|" .. item_name_english .. "|lang=" .. lang_code .. "|" .. item_name_local .. "}}" | end | ||
local search_data = item_name_english | |||
if lang_code ~= "en" and item_name_local then | |||
search_data = search_data .. " " .. item_name_local | |||
end | end | ||
item_template = '<span class="item-nav-search-wrapper"><span class="item-nav-search-item" data-item-name="' .. search_data .. '">' .. item_template .. '</span>' | |||
elseif template == "ItemBox" then | elseif template == "ItemBox" then | ||
if lang_code == "en" then | if lang_code == "en" then | ||
if is_street_brawl then | |||
item_template = "{{ItemBox| | item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. item_link .. "|overrideTier=5" .. LOW_GRAPHICS .. "}}" | ||
else | |||
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. item_link .. LOW_GRAPHICS .. "}}" | |||
end | |||
else | else | ||
local link_local = item_link .. "/" .. lang_code | |||
local | if is_street_brawl then | ||
item_template = "{{ItemBox| | item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. link_local .. "|item_price={{#invoke:Lang|get_string|Citadel_ItemDraft_Legendary}}" .. LOW_GRAPHICS .. "}}" | ||
else | |||
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. link_local .. LOW_GRAPHICS .. "}}" | |||
end | |||
end | end | ||
else | else | ||
| Line 94: | Line 126: | ||
-- Order list alphabetically | -- Order list alphabetically | ||
table.sort(items | table.sort(items) | ||
-- | -- Append closing tags and bullets | ||
if template == "ItemIcon" then | |||
local total_items = #items | |||
for i = 1, total_items do | |||
if i == total_items then | |||
items[i] = items[i] .. '</span>' | |||
else | |||
items[i] = items[i] .. '<span class="item-nav-search-sep"> • </span></span>' | |||
end | |||
end | |||
end | |||
return | return table.concat(items, sep) | ||
end | end | ||
-- for [[Template:Item Navbox]] | -- for [[Template:Item Navbox]] | ||
function p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode | function p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode, filter_mode) | ||
if type(slot) == "table" and slot.args then | if type(slot) == "table" and slot.args then | ||
local frame = slot | local frame = slot | ||
| Line 112: | Line 151: | ||
max_souls = frame.args[3] | max_souls = frame.args[3] | ||
debug_mode = frame.args["debug_mode"] | debug_mode = frame.args["debug_mode"] | ||
filter_mode = frame.args["filter"] | |||
end | end | ||
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemIcon", '', filter_mode) | |||
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemIcon", | |||
return process_debug_mode(item_list, debug_mode) | return process_debug_mode(item_list, debug_mode) | ||
end | end | ||
-- for [[Template:Infobox ShopItems]] | -- for [[Template:Infobox ShopItems]] | ||
function p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode | function p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode, filter_mode) | ||
if type(slot) == "table" and slot.args then | if type(slot) == "table" and slot.args then | ||
local frame = slot | local frame = slot | ||
| Line 130: | Line 166: | ||
max_souls = frame.args[3] | max_souls = frame.args[3] | ||
debug_mode = frame.args["debug_mode"] | debug_mode = frame.args["debug_mode"] | ||
filter_mode = frame.args["filter"] | |||
end | end | ||
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemBox", ' ', filter_mode) | |||
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemBox", | |||
return process_debug_mode(item_list, debug_mode) | return process_debug_mode(item_list, debug_mode) | ||
end | end | ||
-- for [[Template:Item Navbox]] subgroup rows | |||
function p.write_item_slot_subgroup(frame) | function p.write_item_slot_subgroup(frame) | ||
local slot = frame.args[1] | |||
local type_func = frame.args[2] | |||
local debug_mode = frame.args['debug_mode'] | |||
local street_brawl_label = frame.args['street_brawl_label'] or '{{Legendary|{{#invoke:Lang|get_string|Citadel_ItemDraft_Legendary}}}}' | |||
if slot == nil then return "'slot' parameter is required" end | |||
local template_args = { | |||
["groupstyle"] = "background-color:" .. util_module.get_slot_color(slot) .. ";width:10%;min-width:70px;border-radius: 8px 0 0 8px", | |||
["grouppadding"] = "5px", | |||
["listpadding"] = "0 0.25rem", | |||
} | |||
local soul_style = "font-size: 12px; text-shadow: 1px 1px rgba(0, 0, 0, 0.3);" | |||
local prices = generic_module.get_item_price_per_tier() | |||
local group_index = 1 | |||
for i, souls in ipairs(prices) do | |||
local min_souls = souls | |||
if min_souls ~= 0 then | |||
local max_souls = prices[i+1] or (min_souls * 10) | |||
local list | |||
if type_func == 'get_item_nav_cards' then | |||
list = p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode, "exclude_street_brawl") | |||
elseif type_func == 'get_item_nav_bulletpoints' then | |||
list = p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode, "exclude_street_brawl") | |||
else | |||
return "'type' should be get_item_nav_cards or get_item_nav_bulletpoints" | |||
end | |||
if list and list ~= "" then | |||
template_args["group" .. group_index] = frame:expandTemplate{title="Souls", args={[1] = min_souls, ["Shadow"] = soul_style}} | |||
template_args["list" .. group_index] = list | |||
group_index = group_index + 1 | |||
end | |||
end | |||
end | |||
local sb_list | |||
if type_func == 'get_item_nav_cards' then | |||
sb_list = p.get_item_nav_cards(slot, 0, 999999, debug_mode, "street_brawl_only") | |||
elseif type_func == 'get_item_nav_bulletpoints' then | |||
sb_list = p.get_item_nav_bulletpoints(slot, 0, 999999, debug_mode, "street_brawl_only") | |||
end | |||
if sb_list and sb_list ~= "" then | |||
local sb_style = "color:#98ffde; font-family:'Retail Demo', sans-serif; text-wrap: nowrap; text-shadow: 0px 0px 2px #2c312e, 1.3px 1.3px rgba(0, 0, 0, 0.2);padding:5px;" | |||
local sb_link_text = "[[Street Brawl{{if_lang}}|<span style=\"" .. sb_style .. "\"><b>" .. street_brawl_label .. "</b></span>]]" | |||
template_args["group" .. group_index] = frame:preprocess(sb_link_text) | |||
template_args["list" .. group_index] = sb_list | |||
end | |||
return frame:expandTemplate{title="Navbox subgroup", args=template_args} | |||
end | |||
-- for [[Template:Active Items]] | |||
-- Displayed when a stat does not apply to an item | |||
local NO_VALUE = "—" | |||
-- One expanded inline attribute as produced by [[Module:Lang]]: | |||
-- wrapper span > icon span > spacer span > wikilink | |||
local INLINE_ATTRIBUTE_PATTERN = | |||
'<span class="no%-blue%-link" style="text%-wrap:nowrap; font%-weight:bold; color:[^;"]*;">' .. | |||
'<span style="[^"]*">%[%[File:[^%]]*%]%]</span>' .. | |||
'<span style="color: inherit;"> </span> ' .. | |||
'%[%[([^|%]]+)|([^%]]*)%]%]</span>' | |||
-- Range is taken from the first of these the item has a usable value for | |||
local RANGE_PROPERTIES = { "AbilityCastRange", "Radius", "EndRadius" } | |||
-- Rewrites the description markup [[Module:Lang]] produces | |||
local function restyle_description(text) | |||
text = text:gsub(INLINE_ATTRIBUTE_PATTERN, '[[%1|%2]]') | |||
-- Attributes with an icon but no link keep their span; follow the site text color | |||
text = text:gsub('color:#ffefd7;', 'color:var(--color-base);') | |||
-- Diminished notes ("Cannot be used while Stunned...") | |||
return (text:gsub('color:#C0C0C0', 'color:var(--color-subtle)')) | |||
end | |||
-- Numeric value of a stat, used for sorting | |||
local function stat_sort_value(value) | |||
if type(value) == "table" then value = value["Value"] end | |||
if value == nil then return -1 end | |||
return tonumber(tostring(value):match("[-%d%.]+")) or -1 | |||
end | |||
-- First range property the item has a usable value for | |||
local function get_range_property(item_data) | |||
for _, property in ipairs(RANGE_PROPERTIES) do | |||
local value = item_data[property] | |||
if value ~= nil and stat_sort_value(value) > 0 then | |||
return property, value | |||
end | |||
end | |||
return nil, nil | |||
end | |||
-- Builds a stat cell, falling back to an em dash when the stat does not apply | |||
local function stat_cell(raw_value, item_name, property, postfix) | |||
if raw_value == nil or property == nil then | |||
return '| data-sort-value="-1" style="text-align:center;font-size:16px" | ' .. NO_VALUE | |||
end | |||
return string.format( | |||
'| data-sort-value="%s" style="text-align:center;font-size:16px" | {{#invoke:ItemData|get_prop|%s|%s}}%s', | |||
stat_sort_value(raw_value), item_name, property, postfix or "") | |||
end | |||
function p.generate_active_items_table(frame) | |||
local active_items = {} | |||
for item_key, item_data in pairs(items_data) do | |||
local cost = tonumber(item_data["Cost"]) | |||
if item_data["Name"] and | |||
item_data["IsDisabled"] == false and | |||
cost ~= nil and cost > 0 and | |||
item_data["Activation"] and | |||
item_data["Activation"] ~= "Passive" then | |||
local range_property, range_value = get_range_property(item_data) | |||
table.insert(active_items, { | |||
name = item_data["Name"], | |||
key = item_key, | |||
cost = cost, | |||
slot = item_data["Slot"] or "Weapon", | |||
has_active_desc = item_data["ActiveDescription"] ~= nil, | |||
cooldown = item_data["AbilityCooldown"], | |||
duration = item_data["AbilityDuration"], | |||
range_property = range_property, | |||
range_value = range_value | |||
}) | |||
end | |||
end | |||
table.sort(active_items, function(a, b) return a.name < b.name end) | |||
-- Optimized string compilation using a structural buffer table | |||
local rows = {} | |||
table.insert(rows, [=[{| class="wikitable mw-collapsible sortable" style="width:100%" | |||
|+{{#invoke:Dictionary|translate|Active Items}} | |||
! colspan="2" | {{#invoke:Lang|get_string|Citadel_HeroBuilds_CategoryNameLabel}} | |||
! {{#invoke:Lang|get_string|Citadel_UserFeedback_TypeLabel}} | |||
! {{Souls|{{#invoke:Dictionary|translate|Cost}}}} | |||
! {{#invoke:Lang|get_string|Citadel_Mod_Tooltip_Active}} | |||
! {{#invoke:Lang|get_string|AbilityCooldown_label}} | |||
! {{#invoke:Lang|get_string|AbilityDuration_label}} | |||
! {{#invoke:Lang|get_string|AbilityCastRange_label}}]=]) | |||
for _, item in ipairs(active_items) do | |||
local bg_color = util_module.get_slot_color(item.slot) | |||
local item_type | |||
if item.slot == "Armor" then | |||
item_type = "Armor" | |||
elseif item.slot == "Tech" then | |||
item_type = "Tech" | |||
else | |||
item_type = "Weapon" | |||
end | end | ||
local row = string.format([=[ | |||
|- | |||
! style="background-color:%s" | [[File:%s.png|64x64px]] | |||
! [[%s{{If_lang}}|{{#invoke:Lang|get_string|%s}}]] | |||
| style="text-align:center;font-weight:bold" | {{ItemType|%s|{{#invoke:Lang|get_string|CitadelCategory%s}}}} | |||
| data-sort-value="%d" style="text-align:center;font-size:15px" | {{Souls|%d}} | |||
| {{#invoke:Utilities|process_variables|%s_active_desc|%s}}{{#invoke:Utilities|process_variables|%s_desc|%s}}{{#invoke:Utilities|process_variables|%s_active|%s}} | |||
%s | |||
%s | |||
%s]=], | |||
bg_color, | |||
item.name, | |||
item.name, item.key, | |||
item_type, item_type, | |||
item.cost, item.cost, | |||
item.key, item.name, item.key, item.name, item.key, item.name, | |||
stat_cell(item.cooldown, item.name, "AbilityCooldown", "s"), | |||
stat_cell(item.duration, item.name, "AbilityDuration", "s"), | |||
stat_cell(item.range_value, item.name, item.range_property)) | |||
table.insert(rows, row) | |||
end | |||
table.insert(rows, "\n|}") | |||
return restyle_description(frame:preprocess(table.concat(rows, "\n"))) | |||
end | end | ||
return p | return p | ||
Latest revision as of 19:54, 10 September 2026
Overview
[edit source]Functions for creating navigation boxes/lists for items, grouped by a slot/category and souls. Supports filtering for Street Brawl items.
Functions
[edit source]get_item_nav_bulletpoints
[edit source]Gets a list of items that are each sent to the Template:ItemIcon template, separated by bullet points.
Filters down to a slot/specific category, and within a range of souls. Can optionally filter for Street Brawl items.
Parameters
[edit source]- slot - Slot/category that the items should be, should be Weapon, Armor, or Tech
- min_souls - Minimum souls that the items should have
- max_souls - Maximum souls that the items should have (Note: the command does not include items equal to this value, so in order to include them it should be written as
cost + 1) - debug_mode - (OPTIONAL) - if set to 'true', the wikitext is unprocessed, allowing for it to be read more clearly. Also used for showcasing the documentation examples more clearly.
- filter - (OPTIONAL) - Filters items based on Street Brawl status. Options:
exclude_street_brawl(show only regular items),street_brawl_only(show only Street Brawl items), or unset (show all)
Examples
[edit source]With debug_mode on (for illustration purposes)
{{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000|debug_mode=true}}
{{ItemIcon|Battle Vest|size=26px}} • {{ItemIcon|Bullet Lifesteal|size=26px}} • {{ItemIcon|Debuff Reducer|size=26px}} • {{ItemIcon|Enchanter's Emblem|size=26px}} • {{ItemIcon|Enduring Speed|size=26px}} • {{ItemIcon|Guardian Ward|size=26px}} • {{ItemIcon|Healbane|size=26px}} • {{ItemIcon|Healing Booster|size=26px}} • {{ItemIcon|Reactive Barrier|size=26px}} • {{ItemIcon|Restorative Locket|size=26px}} • {{ItemIcon|Return Fire|size=26px}} • {{ItemIcon|Spirit Lifesteal|size=26px}} • {{ItemIcon|Spirit Shielding|size=26px}} • {{ItemIcon|Trophy Collector|size=26px}} • {{ItemIcon|Weapon Shielding|size=26px}}
With debug_mode off
{{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000}}
Battle Vest • Bullet Lifesteal • Debuff Reducer • Enchanter's Emblem • Enduring Speed • Guardian Ward • Healbane • Healing Booster • Reactive Barrier • Restorative Locket • Return Fire • Spirit Lifesteal • Spirit Shielding • Trophy Collector • Weapon Shielding
To include the 3000 items
{{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3001}}
Battle Vest • Bullet Lifesteal • Debuff Reducer • Enchanter's Emblem • Enduring Speed • Guardian Ward • Healbane • Healing Booster • Reactive Barrier • Restorative Locket • Return Fire • Spirit Lifesteal • Spirit Shielding • Trophy Collector • Weapon Shielding
get_item_nav_cards
[edit source]Gets a list of items that are each sent to the Template:ItemBox template, separated by space.
Filters down to a specific slot/category, and within a range of souls. Can optionally filter for Street Brawl items.
Parameters
[edit source]Same as get_item_nav_bulletpoints
Examples
[edit source]With debug_mode on (for illustration purposes)
{{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000|debug_mode=true}}
{{ItemBox|itemName=Battle Vest|overrideLinkClick=Battle Vest|lowGraphics=true}} {{ItemBox|itemName=Bullet Lifesteal|overrideLinkClick=Bullet Lifesteal (item)|lowGraphics=true}} {{ItemBox|itemName=Debuff Reducer|overrideLinkClick=Debuff Reducer|lowGraphics=true}} {{ItemBox|itemName=Enchanter's Emblem|overrideLinkClick=Enchanter's Emblem|lowGraphics=true}} {{ItemBox|itemName=Enduring Speed|overrideLinkClick=Enduring Speed|lowGraphics=true}} {{ItemBox|itemName=Guardian Ward|overrideLinkClick=Guardian Ward|lowGraphics=true}} {{ItemBox|itemName=Healbane|overrideLinkClick=Healbane|lowGraphics=true}} {{ItemBox|itemName=Healing Booster|overrideLinkClick=Healing Booster|lowGraphics=true}} {{ItemBox|itemName=Reactive Barrier|overrideLinkClick=Reactive Barrier|lowGraphics=true}} {{ItemBox|itemName=Restorative Locket|overrideLinkClick=Restorative Locket|lowGraphics=true}} {{ItemBox|itemName=Return Fire|overrideLinkClick=Return Fire|lowGraphics=true}} {{ItemBox|itemName=Spirit Lifesteal|overrideLinkClick=Spirit Lifesteal (item)|lowGraphics=true}} {{ItemBox|itemName=Spirit Shielding|overrideLinkClick=Spirit Shielding|lowGraphics=true}} {{ItemBox|itemName=Trophy Collector|overrideLinkClick=Trophy Collector|lowGraphics=true}} {{ItemBox|itemName=Weapon Shielding|overrideLinkClick=Weapon Shielding|lowGraphics=true}}
With debug_mode off
{{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000}}
write_item_slot_subgroup
[edit source]Writes a sub group for the navbox on Template:Item Navbox and Template:Infobox ShopItems. The sub group contains all items in each price range from "ItemPricePerTier" in Data:GenericData.json. Street Brawl items are automatically separated into their own row at the end.
Parameters
[edit source]- slot - Slot/category to create the subgroup for, ie Weapon, Armor, or Tech
- type - The subfunction to call that determines the list formatting style. Options are 'get_item_nav_bulletpoints' or 'get_item_nav_cards'
- debug_mode - (OPTIONAL) - if set to 'true', the wikitext is unprocessed, allowing for it to be read more clearly. Also used for showcasing the documentation examples more clearly.
- street_brawl_label - (OPTIONAL) - Custom label text for the Street Brawl row. Defaults to "Street Brawl"
Examples
[edit source]With debug_mode on (for illustration purposes)
{{#invoke:ItemData/nav|write_item_slot_subgroup|Weapon|get_item_nav_bulletpoints|debug_mode=true}}
{{ItemIcon|Close Quarters|size=26px}} • {{ItemIcon|Extended Magazine|size=26px}} • {{ItemIcon|Headshot Booster|size=26px}} • {{ItemIcon|High-Velocity Rounds|size=26px}} • {{ItemIcon|Monster Rounds|size=26px}} • {{ItemIcon|Rapid Rounds|size=26px}} • {{ItemIcon|Restorative Shot|size=26px}} | |
{{ItemIcon|Active Reload|size=26px}} • {{ItemIcon|Fleetfoot|size=26px}} • {{ItemIcon|Intensifying Magazine|size=26px}} • {{ItemIcon|Kinetic Dash|size=26px}} • {{ItemIcon|Long Range|size=26px}} • {{ItemIcon|Melee Charge|size=26px}} • {{ItemIcon|Mystic Shot|size=26px}} • {{ItemIcon|Opening Rounds|size=26px}} • {{ItemIcon|Recharging Rush|size=26px}} • {{ItemIcon|Slowing Bullets|size=26px}} • {{ItemIcon|Spirit Shredder Bullets|size=26px}} • {{ItemIcon|Split Shot|size=26px}} • {{ItemIcon|Stalker|size=26px}} • {{ItemIcon|Swift Striker|size=26px}} • {{ItemIcon|Titanic Magazine|size=26px}} • {{ItemIcon|Weakening Headshot|size=26px}} | |
{{ItemIcon|Alchemical Fire|size=26px}} • {{ItemIcon|Ballistic Enchantment|size=26px}} • {{ItemIcon|Berserker|size=26px}} • {{ItemIcon|Blood Tribute|size=26px}} • {{ItemIcon|Burst Fire|size=26px}} • {{ItemIcon|Cultist Sacrifice|size=26px}} • {{ItemIcon|Escalating Resilience|size=26px}} • {{ItemIcon|Express Shot|size=26px}} • {{ItemIcon|Headhunter|size=26px}} • {{ItemIcon|Heroic Aura|size=26px}} • {{ItemIcon|Hollow Point|size=26px}} • {{ItemIcon|Hunter's Aura|size=26px}} • {{ItemIcon|Point Blank|size=26px}} • {{ItemIcon|Shadow Weave|size=26px}} • {{ItemIcon|Sharpshooter|size=26px}} • {{ItemIcon|Spirit Rend|size=26px}} • {{ItemIcon|Tesla Bullets|size=26px}} • {{ItemIcon|Toxic Bullets|size=26px}} • {{ItemIcon|Weighted Shots|size=26px}} | |
{{ItemIcon|Armor Piercing Rounds|size=26px}} • {{ItemIcon|Capacitor|size=26px}} • {{ItemIcon|Crippling Headshot|size=26px}} • {{ItemIcon|Crushing Fists|size=26px}} • {{ItemIcon|Frenzy|size=26px}} • {{ItemIcon|Glass Cannon|size=26px}} • {{ItemIcon|Lucky Shot|size=26px}} • {{ItemIcon|Ricochet|size=26px}} • {{ItemIcon|Silencer|size=26px}} • {{ItemIcon|Spellslinger|size=26px}} • {{ItemIcon|Spiritual Overflow|size=26px}} | |
{{ItemIcon|Haunting Shot|size=26px}} • {{ItemIcon|Infinite Rounds|size=26px}} • {{ItemIcon|Runed Gauntlets|size=26px}} |
With debug_mode off
{{#invoke:ItemData/nav|write_item_slot_subgroup|Weapon|get_item_nav_bulletpoints}}
Recall that it creates subgroup parameters for the mentioned templates.
generate_active_items_table
[edit source]Creates table of localized released active items. Used in Template:Active Items
Example
[edit source]{{#invoke:ItemData/nav|generate_active_items_table}}
| Name | Type | Active | Cooldown | Duration | Cast Range | ||
|---|---|---|---|---|---|---|---|
| Alchemical Fire | Throw a flask that explodes on contact, creating an area that does increasing spirit damage per second and reduces enemy Bullet Resist. 50% less effective vs non-heroes. |
30s | 5s | 10m | |||
| Arctic Blast | Release an expanding ice blast that deals spirit damage, Freezing and then Slowing targets it hits. Slowed targets have their stamina regen frozen |
24s | — | 16m | |||
| Blood Tribute | Toggle: Continually sacrifice Health to improve fire rate, Debuff Resistance and Move Speed. | — | — | — | |||
| Capacitor | Launch a projectile that deals |
40s | — | — | |||
| Celestial Blessing | Applies an extremely powerful cleanse that replenishes your allies globally. | 30s | — | 999m | |||
| Cold Front | Release an expanding ice blast that deals spirit damage and Slows targets it hits. | 25s | 4s | 10m | |||
| Colossus | Grow larger in size, gaining bullet resist, spirit resist, and melee damage. Nearby enemies suffer from slow and have reduced dash speed. |
37s | 7s | 14m | |||
| Cultist Sacrifice | Target an enemy NPC and consume it for 180% Bonus Souls and grants a powerful long lasting buff. | 270s | 160s | 7m | |||
| Cursed Relic | Curses an enemy - interrupting, Silencing, Disarming, and preventing item usage. Removes all non-ultimate buffs. Your own Damage Output is reduced for the duration. |
55s | 3.25s | 20m | |||
| Decay | Inflict damage over time to a target, dealing damage based on their current health. Decay's damage is non-lethal and does not apply item procs. |
30s | 10s | 20m x0.1 | |||
| Disarming Hex | Disarms enemy target and reduces their Bullet Resist. | 16s | 4.25s | 32m | |||
| Dispel Magic | Purge all non-ultimate negative effects currently applied to you. If any effects were removed, heal yourself and gain a move speed bonus. Cannot be used while Stunned or Slept. |
45s | — | — | |||
| Divine Barrier | Remove all non-stun debuffs from the target and provide them with a Barrier and Move Speed. Can be self-cast. Cooldown is reduced by half when cast on someone else. |
45s | — | 40m | |||
| Echo Shard | Reset the cooldown of the imbued non-ultimate ability. This item's cooldown is increased by the cooldown of the imbued ability. |
30s | — | — | |||
| Ethereal Shift | You enter a void state and become untargetable and invincible for a short duration, during which you float slowly and cannot perform actions. Afterwards you gain Spirit Power, Move Speed, and Spirit Resist. Can be canceled early. Activation cancels any active ability. |
37s | 4s | — | |||
| Fleetfoot | Gain bonus Move Speed and Slow Resistance. | 16s | 5s | — | |||
| Focus Lens | Target an enemy to Silence them. A portion of all damage dealt during the silence gets applied to the target when the silence wears off. | 45s | 4.5s | 20m | |||
| Fury Trance | Grants Fire Rate, Spirit Resistance and Move Speed, and removes the Move Speed penalty while shooting, but Silences you and disables stamina usage and regeneration. | 18s | 6.5s | — | |||
| Golden Goose Egg | Hatch the egg, gaining souls and permanent buffs. The value of the egg grows the longer you hold onto it. Gain a permanent buff per 80 Souls accrued when hatched.Gain souls over time, as long as you are alive. |
— | — | — | |||
| Grit | Gain a Barrier for a short duration. | 60s | — | — | |||
| Guardian Ward | Provide the target with a Barrier and temporary Move Speed. Can be self-cast. Cooldown is reduced by half when cast on someone else. |
60s | — | 40m | |||
| Healing Nova | Heal yourself and nearby allies. | 60s | — | — | |||
| Healing Rite | Grant Regen and Sprint Speed to the target. Gets dispelled if you take damage from enemy players or objectives. Can be self-cast. | 70s | — | 30m | |||
| Heroic Aura | Provides move speed and fire rate to you and nearby allies. Minions get double value.Provides Bullet Resist to nearby friendly units. | 22s | 7s | 35m | |||
| Infuser | Gain Spirit Lifesteal and Spirit Power. | 30s | 7s | — | |||
| Knockdown | Apply a Stun after 2s. Stun duration is increased against airborne targets. Increases the target's gravity for the duration of the stun. |
35s | — | 45m | |||
| Magic Carpet | Summon a Magic Carpet that will fly you away. While flying you are immune to slows and doing any action will dismiss the carpet. Cannot use abilities while the carpet is being summoned. |
32s | 12s | — | |||
| Majestic Leap | Launch yourself high into the air and grant yourself a Barrier. While in the air, you can use the active again to drop down faster. Cannot be used for 5s if attacked by enemy Hero. |
45s | — | — | |||
| Metal Skin | Become immune to bullets. | 24s | 5s | — | |||
| Mystical Piano | After a short delay, enemies in the target area will be stunned and have their stamina depleted. After the stun they will be temporarily dazed. | 23s | 1.7s | 12m | |||
| Nullification Burst | Removes any positive buffs and prevents stamina usage and healing effects on enemies. | 18s | 7s | 20m | |||
| Phantom Strike | Teleport to an enemy target and pull them to the ground. Dealing damage, Move speed reduction and Disarm. | 35s | — | 25m | |||
| Prism Blast | You enter a void state and become untargetable and invincible for a short duration, during which lasers blast out and rotate around you. | 40s | 6s | — | |||
| Refresher | Reset the cooldown of all your abilities and restore all your charges. | 300s | — | — | |||
| Rescue Beam | Heals a target allied hero and yourself for a percentage of Max Health. Once while healing, you can Pull the target towards you. Can be self-cast. | 60s | — | 35m | |||
| Restorative Locket | When an enemy uses an ability within 35m range from you, store one Restoration Stack. Consume all stacks to heal yourself and replenish up to 3 stamina based on how many stacks you have. |
20s | — | 35m | |||
| Return Fire | Automatically fire a bullet towards any attacker who damages you with their abilities or weapon. | 23s | 6.5s | — | |||
| Rusted Barrel | Target an enemy to reduce their Fire Rate and Bullet Resistance. | 16s | 5s | 32m | |||
| Scourge | Apply Spirit Resist, Debuff Resist and an aura on a friendly target that deals Can be self cast. |
35s | 10s | 35m | |||
| Shadow Weave | Become Stealthed. Whenever you take damage while Stealthed you get briefly revealed. | 45s | 13s | — | |||
| Shrink Ray | Reduces Model Size and grants Move Speed to the target. Allows usage of tunnels in this mode. Can be self-cast. | 30s | — | 40m | |||
| Silence Wave | Launch an expanding projectile which Silences enemies for a short duration and deals impact damage. Silence does not interrupt channeling abilities. |
42s | 3s | 40m | |||
| Slowing Hex | Slows movement of enemy target. Also Silences their movement-based items and abilities. Increases the target's gravity. Does not affect target's stamina usage. |
27s | 3.5s | 25m | |||
| Spirit Sap | Target an enemy to reduce their Spirit Resist and Spirit Power. | 18s | 12s | 40m | |||
| Split Shot | Make your weapon fire multishot. Hitting more than one Hero per attack will grant a stacking weapon damage bonus. Targets can only be hit once per multishot. |
27s | — | — | |||
| Unstable Concoction | Consume a concoction that grants you Unstoppable and increased speed, health, spirit and weapon damage. After a short duration you die and explode, stunning nearby enemies and dealing damage based on your maximum health. Dying this way reduces your respawn time by 50%. | 25s | 4s | 22m | |||
| Unstoppable | Temporarily suppress negative status effects and become immune to Stun, Silence, Sleep, Root, and Disarm. Cannot be used while Stunned or Slept. |
60s | 5.5s | — | |||
| Vampiric Burst | Grants Lifesteal, Fire Rate, and Ammo. This added Ammo is not limited by your max magazine size. | 30s | 5s | — | |||
| Vortex Web | Throw a vacuum grenade, pulling all enemies into a small area and applying Slowing Hex. Alt Cast to Target Unit Directly. |
42s | 4s | 30m | |||
| Warp Stone | Teleport straight ahead, gaining Bullet Resist. | 16s | — | 11m | |||
local p = {}
local items_data = mw.loadJsonData("Data:ItemData.json")
local lang_module = require('Module:Lang')
local generic_module = require('Module:GenericData')
local util_module = require('Module:Utilities')
-- Exceptions if the link is different from the item name
local linkOverrides = {
["Bullet Lifesteal"] = "Bullet Lifesteal (item)",
["Spirit Lifesteal"] = "Spirit Lifesteal (item)"
}
-- The card navboxes put every item on a single page, so their cards opt into
-- the cheaper ItemBox rendering via lowGraphics. This swaps the paper texture
-- from the normal-mode multiply (which blends two images and is re-rasterised
-- every scrolled frame) to a transparent-paper overlay that is a single
-- cacheable image, the one change that carried the navbox scroll cost. The wear
-- and icon masks stay at full detail, since a single-image mask caches fine.
-- See [[Template:ItemBox]]. Only the ItemBox branch of write_wrapped_item_list
-- uses this; the ItemIcon lists are unaffected.
local LOW_GRAPHICS = "|lowGraphics=true"
-- Cached items grouped by slot to prevent redundant full-table evaluations
local cached_slots = nil
local function get_items_by_slot(slot)
if not cached_slots then
cached_slots = { table = {}, Weapon = {}, Armor = {}, Tech = {} }
for item_key, item_data in pairs(items_data) do
local this_slot = item_data["Slot"]
if this_slot and cached_slots[this_slot] then
table.insert(cached_slots[this_slot], { key = item_key, data = item_data })
end
end
end
return cached_slots[slot] or {}
end
-- With debug_mode on, it outputs unprocessed wikitext
local function process_debug_mode(wikitext, debug_mode)
if debug_mode == 'true' then
return wikitext
elseif debug_mode == 'false' or debug_mode == nil then
return mw.getCurrentFrame():preprocess(wikitext)
else
return "debug_mode must be 'true' or 'false'"
end
end
-- Writes list of items of a certain slot within the min and max soul bounds
local function write_wrapped_item_list(slot, min_souls, max_souls, template, sep, filter_mode)
if slot ~= 'Weapon' and slot ~= 'Armor' and slot ~= 'Tech' then
return 'slot must be Weapon, Armor (Vitality), or Tech (Spirit)'
end
local min_souls = tonumber(min_souls)
local max_souls = tonumber(max_souls)
if min_souls == nil or max_souls == nil then return 'Min/Max souls must be numerical' end
filter_mode = filter_mode or "all"
-- Lifted out of the loop: Language code doesn't change per item
local lang_code = lang_module.get_lang_code()
local items = {}
local slot_items = get_items_by_slot(slot) -- Drastically reduces search pool size
for _, item_entry in ipairs(slot_items) do
local item_key = item_entry.key
local item_data = item_entry.data
local this_cost = tonumber(item_data["Cost"])
local is_street_brawl = item_data["StreetBrawl"] == true
local should_include = true
-- Filter logic for Street Brawl items
if filter_mode == "street_brawl_only" and not is_street_brawl then
should_include = false
elseif filter_mode == "exclude_street_brawl" and is_street_brawl then
should_include = false
end
if should_include and item_data["Name"] ~= nil and item_data["IsDisabled"] == false and this_cost ~= nil then
if this_cost >= min_souls and this_cost < max_souls then
local item_name_english = lang_module.get_string(item_key, "en")
local item_link = linkOverrides[item_name_english] or item_name_english
local item_name_local = lang_module.get_string(item_key, lang_code)
local item_template
if template == "ItemIcon" then
if lang_code == "en" then
item_template = "{{ItemIcon|" .. item_name_english .. "|size=26px}}"
else
item_template = "{{ItemIcon|" .. item_name_english .. "|lang=" .. lang_code .. "|l1=" .. item_name_local .. "|size=26px}}"
end
local search_data = item_name_english
if lang_code ~= "en" and item_name_local then
search_data = search_data .. " " .. item_name_local
end
item_template = '<span class="item-nav-search-wrapper"><span class="item-nav-search-item" data-item-name="' .. search_data .. '">' .. item_template .. '</span>'
elseif template == "ItemBox" then
if lang_code == "en" then
if is_street_brawl then
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. item_link .. "|overrideTier=5" .. LOW_GRAPHICS .. "}}"
else
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. item_link .. LOW_GRAPHICS .. "}}"
end
else
local link_local = item_link .. "/" .. lang_code
if is_street_brawl then
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. link_local .. "|item_price={{#invoke:Lang|get_string|Citadel_ItemDraft_Legendary}}" .. LOW_GRAPHICS .. "}}"
else
item_template = "{{ItemBox|itemName=" .. item_name_english .. "|overrideLinkClick=" .. link_local .. LOW_GRAPHICS .. "}}"
end
end
else
return "Invalid template type"
end
table.insert(items, item_template)
end
end
end
-- Order list alphabetically
table.sort(items)
-- Append closing tags and bullets
if template == "ItemIcon" then
local total_items = #items
for i = 1, total_items do
if i == total_items then
items[i] = items[i] .. '</span>'
else
items[i] = items[i] .. '<span class="item-nav-search-sep"> • </span></span>'
end
end
end
return table.concat(items, sep)
end
-- for [[Template:Item Navbox]]
function p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode, filter_mode)
if type(slot) == "table" and slot.args then
local frame = slot
slot = frame.args[1]
min_souls = frame.args[2]
max_souls = frame.args[3]
debug_mode = frame.args["debug_mode"]
filter_mode = frame.args["filter"]
end
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemIcon", '', filter_mode)
return process_debug_mode(item_list, debug_mode)
end
-- for [[Template:Infobox ShopItems]]
function p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode, filter_mode)
if type(slot) == "table" and slot.args then
local frame = slot
slot = frame.args[1]
min_souls = frame.args[2]
max_souls = frame.args[3]
debug_mode = frame.args["debug_mode"]
filter_mode = frame.args["filter"]
end
local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemBox", ' ', filter_mode)
return process_debug_mode(item_list, debug_mode)
end
-- for [[Template:Item Navbox]] subgroup rows
function p.write_item_slot_subgroup(frame)
local slot = frame.args[1]
local type_func = frame.args[2]
local debug_mode = frame.args['debug_mode']
local street_brawl_label = frame.args['street_brawl_label'] or '{{Legendary|{{#invoke:Lang|get_string|Citadel_ItemDraft_Legendary}}}}'
if slot == nil then return "'slot' parameter is required" end
local template_args = {
["groupstyle"] = "background-color:" .. util_module.get_slot_color(slot) .. ";width:10%;min-width:70px;border-radius: 8px 0 0 8px",
["grouppadding"] = "5px",
["listpadding"] = "0 0.25rem",
}
local soul_style = "font-size: 12px; text-shadow: 1px 1px rgba(0, 0, 0, 0.3);"
local prices = generic_module.get_item_price_per_tier()
local group_index = 1
for i, souls in ipairs(prices) do
local min_souls = souls
if min_souls ~= 0 then
local max_souls = prices[i+1] or (min_souls * 10)
local list
if type_func == 'get_item_nav_cards' then
list = p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode, "exclude_street_brawl")
elseif type_func == 'get_item_nav_bulletpoints' then
list = p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode, "exclude_street_brawl")
else
return "'type' should be get_item_nav_cards or get_item_nav_bulletpoints"
end
if list and list ~= "" then
template_args["group" .. group_index] = frame:expandTemplate{title="Souls", args={[1] = min_souls, ["Shadow"] = soul_style}}
template_args["list" .. group_index] = list
group_index = group_index + 1
end
end
end
local sb_list
if type_func == 'get_item_nav_cards' then
sb_list = p.get_item_nav_cards(slot, 0, 999999, debug_mode, "street_brawl_only")
elseif type_func == 'get_item_nav_bulletpoints' then
sb_list = p.get_item_nav_bulletpoints(slot, 0, 999999, debug_mode, "street_brawl_only")
end
if sb_list and sb_list ~= "" then
local sb_style = "color:#98ffde; font-family:'Retail Demo', sans-serif; text-wrap: nowrap; text-shadow: 0px 0px 2px #2c312e, 1.3px 1.3px rgba(0, 0, 0, 0.2);padding:5px;"
local sb_link_text = "[[Street Brawl{{if_lang}}|<span style=\"" .. sb_style .. "\"><b>" .. street_brawl_label .. "</b></span>]]"
template_args["group" .. group_index] = frame:preprocess(sb_link_text)
template_args["list" .. group_index] = sb_list
end
return frame:expandTemplate{title="Navbox subgroup", args=template_args}
end
-- for [[Template:Active Items]]
-- Displayed when a stat does not apply to an item
local NO_VALUE = "—"
-- One expanded inline attribute as produced by [[Module:Lang]]:
-- wrapper span > icon span > spacer span > wikilink
local INLINE_ATTRIBUTE_PATTERN =
'<span class="no%-blue%-link" style="text%-wrap:nowrap; font%-weight:bold; color:[^;"]*;">' ..
'<span style="[^"]*">%[%[File:[^%]]*%]%]</span>' ..
'<span style="color: inherit;"> </span> ' ..
'%[%[([^|%]]+)|([^%]]*)%]%]</span>'
-- Range is taken from the first of these the item has a usable value for
local RANGE_PROPERTIES = { "AbilityCastRange", "Radius", "EndRadius" }
-- Rewrites the description markup [[Module:Lang]] produces
local function restyle_description(text)
text = text:gsub(INLINE_ATTRIBUTE_PATTERN, '[[%1|%2]]')
-- Attributes with an icon but no link keep their span; follow the site text color
text = text:gsub('color:#ffefd7;', 'color:var(--color-base);')
-- Diminished notes ("Cannot be used while Stunned...")
return (text:gsub('color:#C0C0C0', 'color:var(--color-subtle)'))
end
-- Numeric value of a stat, used for sorting
local function stat_sort_value(value)
if type(value) == "table" then value = value["Value"] end
if value == nil then return -1 end
return tonumber(tostring(value):match("[-%d%.]+")) or -1
end
-- First range property the item has a usable value for
local function get_range_property(item_data)
for _, property in ipairs(RANGE_PROPERTIES) do
local value = item_data[property]
if value ~= nil and stat_sort_value(value) > 0 then
return property, value
end
end
return nil, nil
end
-- Builds a stat cell, falling back to an em dash when the stat does not apply
local function stat_cell(raw_value, item_name, property, postfix)
if raw_value == nil or property == nil then
return '| data-sort-value="-1" style="text-align:center;font-size:16px" | ' .. NO_VALUE
end
return string.format(
'| data-sort-value="%s" style="text-align:center;font-size:16px" | {{#invoke:ItemData|get_prop|%s|%s}}%s',
stat_sort_value(raw_value), item_name, property, postfix or "")
end
function p.generate_active_items_table(frame)
local active_items = {}
for item_key, item_data in pairs(items_data) do
local cost = tonumber(item_data["Cost"])
if item_data["Name"] and
item_data["IsDisabled"] == false and
cost ~= nil and cost > 0 and
item_data["Activation"] and
item_data["Activation"] ~= "Passive" then
local range_property, range_value = get_range_property(item_data)
table.insert(active_items, {
name = item_data["Name"],
key = item_key,
cost = cost,
slot = item_data["Slot"] or "Weapon",
has_active_desc = item_data["ActiveDescription"] ~= nil,
cooldown = item_data["AbilityCooldown"],
duration = item_data["AbilityDuration"],
range_property = range_property,
range_value = range_value
})
end
end
table.sort(active_items, function(a, b) return a.name < b.name end)
-- Optimized string compilation using a structural buffer table
local rows = {}
table.insert(rows, [=[{| class="wikitable mw-collapsible sortable" style="width:100%"
|+{{#invoke:Dictionary|translate|Active Items}}
! colspan="2" | {{#invoke:Lang|get_string|Citadel_HeroBuilds_CategoryNameLabel}}
! {{#invoke:Lang|get_string|Citadel_UserFeedback_TypeLabel}}
! {{Souls|{{#invoke:Dictionary|translate|Cost}}}}
! {{#invoke:Lang|get_string|Citadel_Mod_Tooltip_Active}}
! {{#invoke:Lang|get_string|AbilityCooldown_label}}
! {{#invoke:Lang|get_string|AbilityDuration_label}}
! {{#invoke:Lang|get_string|AbilityCastRange_label}}]=])
for _, item in ipairs(active_items) do
local bg_color = util_module.get_slot_color(item.slot)
local item_type
if item.slot == "Armor" then
item_type = "Armor"
elseif item.slot == "Tech" then
item_type = "Tech"
else
item_type = "Weapon"
end
local row = string.format([=[
|-
! style="background-color:%s" | [[File:%s.png|64x64px]]
! [[%s{{If_lang}}|{{#invoke:Lang|get_string|%s}}]]
| style="text-align:center;font-weight:bold" | {{ItemType|%s|{{#invoke:Lang|get_string|CitadelCategory%s}}}}
| data-sort-value="%d" style="text-align:center;font-size:15px" | {{Souls|%d}}
| {{#invoke:Utilities|process_variables|%s_active_desc|%s}}{{#invoke:Utilities|process_variables|%s_desc|%s}}{{#invoke:Utilities|process_variables|%s_active|%s}}
%s
%s
%s]=],
bg_color,
item.name,
item.name, item.key,
item_type, item_type,
item.cost, item.cost,
item.key, item.name, item.key, item.name, item.key, item.name,
stat_cell(item.cooldown, item.name, "AbilityCooldown", "s"),
stat_cell(item.duration, item.name, "AbilityDuration", "s"),
stat_cell(item.range_value, item.name, item.range_property))
table.insert(rows, row)
end
table.insert(rows, "\n|}")
return restyle_description(frame:preprocess(table.concat(rows, "\n")))
end
return p