Module:ItemData/nav

Revision as of 20:25, 22 April 2025 by Monster Domosed (talk | contribs) (added generate_active_items_table)

Overview

Functions for creating navigation boxes/lists for items, grouped by a slot/category and souls. Supports filtering for Street Brawl items.

Functions

get_item_nav_bulletpoints

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

  • 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

With debug_mode on (for illustration purposes) {{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000|debug_mode=true}}

{{ItemIcon|Battle Vest}} • {{ItemIcon|Bullet Lifesteal}} • {{ItemIcon|Debuff Reducer}} • {{ItemIcon|Enchanter's Emblem}} • {{ItemIcon|Enduring Speed}} • {{ItemIcon|Guardian Ward}} • {{ItemIcon|Healbane}} • {{ItemIcon|Healing Booster}} • {{ItemIcon|Reactive Barrier}} • {{ItemIcon|Restorative Locket}} • {{ItemIcon|Return Fire}} • {{ItemIcon|Spirit Lifesteal}} • {{ItemIcon|Spirit Shielding}} • {{ItemIcon|Trophy Collector}} • {{ItemIcon|Weapon Shielding}}


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

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

Same as get_item_nav_bulletpoints

Examples

With debug_mode on (for illustration purposes) {{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000|debug_mode=true}}

{{ItemBox|item_name=Battle Vest}} {{ItemBox|item_name=Bullet Lifesteal}} {{ItemBox|item_name=Debuff Reducer}} {{ItemBox|item_name=Enchanter's Emblem}} {{ItemBox|item_name=Enduring Speed}} {{ItemBox|item_name=Guardian Ward}} {{ItemBox|item_name=Healbane}} {{ItemBox|item_name=Healing Booster}} {{ItemBox|item_name=Reactive Barrier}} {{ItemBox|item_name=Restorative Locket}} {{ItemBox|item_name=Return Fire}} {{ItemBox|item_name=Spirit Lifesteal}} {{ItemBox|item_name=Spirit Shielding}} {{ItemBox|item_name=Trophy Collector}} {{ItemBox|item_name=Weapon Shielding}}


With debug_mode off {{#invoke:ItemData/nav|get_item_nav_bulletpoints|Armor|1250|3000}}

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

<div class="itembox itembox-tier-Item Not Found. itembox-icon-v3 itembox-wear-v3 itembox-texture-v2 " style="font-size:10px;">

[[File:{{{1}}}.png|160px|link=]]
Imbue

{{{1}}}

Active

write_item_slot_subgroup

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

  • 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

With debug_mode on (for illustration purposes) {{#invoke:ItemData/nav|write_item_slot_subgroup|Weapon|get_item_nav_bulletpoints|debug_mode=true}}


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

Creates table of localized released active items. Used in Template:Active Items

Example

{{#invoke:ItemData/nav|generate_active_items_table}}
Active Items
Name Type SoulsCost Active Cooldown
Alchemical Fire Weapon Souls3,200 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
Arctic Blast Spirit Souls6,400 Release an expanding ice blast that deals spirit damage, Freezing and then Slowing targets it hits.

Slowed targets have their stamina regen frozen
24s
Blood Tribute Weapon Souls3,200 Toggle: Continually sacrifice Health to improve fire rate, Debuff Resistance and Move Speed. s
Capacitor Weapon Souls6,400 Launch a projectile that deals damage, applies a strong slow that recovers over time, prevents Stamina usage and Silences their movement-based items and abilities. 40s
Celestial Blessing Vitality Souls9,999 Applies an extremely powerful cleanse that replenishes your allies globally. 30s
Cold Front Spirit Souls1,600 Release an expanding ice blast that deals spirit damage and Slows targets it hits. 25s
Colossus Vitality Souls6,400 Grow larger in size, gaining bullet resist, spirit resist, and melee damage.

Nearby enemies suffer from slow and have reduced dash speed.
37s
Cultist Sacrifice Weapon Souls3,200 Target an enemy NPC and consume it for 180% Bonus Souls and grants a powerful long lasting buff. 270s
Cursed Relic Spirit Souls6,400 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
Decay Spirit Souls3,200 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
Disarming Hex Spirit Souls3,200 Disarms enemy target and reduces their Bullet Resist. 16s
Dispel Magic Vitality Souls3,200 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 Vitality Souls6,400 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
Echo Shard Spirit Souls6,400 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 Spirit Souls6,400 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
Fleetfoot Weapon Souls1,600 Gain bonus Move Speed and Slow Resistance. 16s
Focus Lens Spirit Souls6,400 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
Fury Trance Vitality Souls3,200 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
Golden Goose Egg Spirit Souls800 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.
s
Grit Vitality Souls800 Gain a Barrier for a short duration. 60s
Guardian Ward Vitality Souls1,600 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
Healing Nova Vitality Souls3,200 Heal yourself and nearby allies. 60s
Healing Rite Vitality Souls800 Grant Regen and Sprint Speed to the target. Gets dispelled if you take damage from enemy players or objectives. Can be self-cast. 70s
Heroic Aura Weapon Souls3,200 Provides move speed and fire rate to you and nearby allies. Minions get double value.Provides Bullet Resist to nearby friendly units. 22s
Infuser Vitality Souls6,400 Gain Spirit Lifesteal and Spirit Power. 30s
Knockdown Spirit Souls3,200 Apply a Stun after 2s. Stun duration is increased against airborne targets.

Increases the target's gravity for the duration of the stun.
35s
Magic Carpet Spirit Souls6,400 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
Majestic Leap Vitality Souls3,200 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 Vitality Souls3,200 Become immune to bullets. 24s
Mystical Piano Spirit Souls9,999 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
Nullification Burst Vitality Souls9,999 Removes any positive buffs and prevents stamina usage and healing effects on enemies. 18s
Phantom Strike Vitality Souls6,400 Teleport to an enemy target and pull them to the ground. Dealing damage, Move speed reduction and Disarm. 35s
File:Place Poster.png Place Poster Weapon SoulsCannot format non-number value. 0.5s
Prism Blast Spirit Souls9,999 You enter a void state and become untargetable and invincible for a short duration, during which lasers blast out and rotate around you. 40s
Refresher Spirit Souls6,400 Reset the cooldown of all your abilities and restore all your charges. 300s
Rescue Beam Vitality Souls3,200 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
Restorative Locket Vitality Souls1,600 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
Return Fire Vitality Souls1,600 Automatically fire a bullet towards any attacker who damages you with their abilities or weapon. 23s
Rusted Barrel Spirit Souls800 Target an enemy to reduce their Fire Rate and Bullet Resistance. 16s
Scourge Spirit Souls6,400 Apply Spirit Resist, Debuff Resist and an aura on a friendly target that deals damage to enemies proportional to their max health. Existing debuffs on the target are reduced.
Can be self cast.
35s
Shadow Weave Weapon Souls3,200 Become Stealthed. Whenever you take damage while Stealthed you get briefly revealed. 45s
Shrink Ray Spirit Souls9,999 Reduces Model Size and grants Move Speed to the target. Allows usage of tunnels in this mode. Can be self-cast. 30s
Silence Wave Spirit Souls3,200 Launch an expanding projectile which Silences enemies for a short duration and deals impact damage.

Silence does not interrupt channeling abilities.
42s
Slowing Hex Spirit Souls1,600 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
Snowball Weapon SoulsCannot format non-number value. Throw snowballs at your friends and enemies. Distance and other properties improve as you make progress in the 2025 Holiday Challenge.

Throwing a Snowball at an ally won't inflict damage and will reset its cooldown.
18s
Spirit Sap Spirit Souls1,600 Target an enemy to reduce their Spirit Resist and Spirit Power. 18s
Split Shot Weapon Souls1,600 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 Spirit Souls9,999 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
Unstoppable Vitality Souls6,400 Temporarily suppress negative status effects and become immune to Stun, Silence, Sleep, Root, and Disarm.
Cannot be used while Stunned or Slept.
60s
Vampiric Burst Vitality Souls6,400 Grants Lifesteal, Fire Rate, and Ammo. This added Ammo is not limited by your max magazine size. 30s
Vortex Web Spirit Souls6,400 Throw a vacuum grenade, pulling all enemies into a small area and applying Slowing Hex.

Alt Cast to Target Unit Directly.
42s
Warp Stone Vitality Souls3,200 Teleport straight ahead, gaining Bullet Resist. 16s

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')

--With debug_mode on, it outputs unprocessed wikitext
--With debug_mode off/unspecified, it processes the 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
-- Each item is wrapped and separated. For example of wrapping/separator, see
-- get_item_nav_bulletpoints. 'sep' should not be combined with 'right_wrap', 
-- as the trailing separator should also be removed from the string
local function write_wrapped_item_list(slot, min_souls, max_souls, template, sep)
    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
    
    -- Retrieve all items that fit the bounds
    local items = {}
    for item_key, item_data in pairs(items_data) do
        -- future proofing; Disabled will be renamed to IsDisabled soon
        local this_cost = tonumber(item_data["Cost"])
        local this_slot = item_data["Slot"]
        if item_data["Name"] ~= nil and item_data["IsDisabled"] == false and this_cost ~= nil and this_slot ~= nil then
            if slot == this_slot and this_cost >= min_souls and this_cost < max_souls then
                local item_name_english = lang_module.get_string(item_key, "en") -- Get the English name
                local lang_code = lang_module.get_lang_code() -- Get the language code from the subpage
                local item_name_local = lang_module.get_string(item_key, lang_code) -- Get the localized name
                
                -- Construct the template based on the type
                local item_template
                if template == "ItemIcon" then
                    if lang_code == "en" then
                        -- For English pages, do not include lang or localized name
                        item_template = "{{ItemIcon|" .. item_name_english .. "}}"
                    else
                        -- For non-English pages, include lang and localized name
                        item_template = "{{ItemIcon|" .. item_name_english .. "|lang=" .. lang_code .. "|" .. item_name_local .. "}}"
                    end
                elseif template == "ItemBox" then
                    if lang_code == "en" then
                        -- For English pages, do not include item_loc or link
                        item_template = "{{ItemBox|item_name=" .. item_name_english .. "}}"
                    else
                        -- For non-English pages, include item_loc and link
                        local link = item_name_english .. "/" .. lang_code -- Add subpage for non-English languages
                        item_template = "{{ItemBox|item_name=" .. item_name_english .. "|item_loc=" .. item_name_local .. "|link=" .. link .. "}}"
                    end
                else
                    return "Invalid template type"
                end
                table.insert(items, item_template)
            end
        end
    end
    
    -- Order list alphabetically
    table.sort(items) -- O(nlogn)
    
    -- Add each item to output
    -- Each item is already wrapped in the template, so no need for additional wrapping
    local ret = table.concat(items, sep)
    
    return ret
end

-- for [[Template:Item Navbox]]
function p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode)
    -- Handle the case where it's called via #invoke (i.e., from wikitext)
    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"]
    end
	
    local sep = ' &bull; '
	
    local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemIcon", sep)
	
    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)
    -- Handle the case where it's called via #invoke (i.e., from wikitext)
    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"]
    end
	
    local sep = ' '
	
    local item_list = write_wrapped_item_list(slot, min_souls, max_souls, "ItemBox", sep)
	
    return process_debug_mode(item_list, debug_mode)
end

function p.write_item_slot_subgroup(frame)
	local slot = frame.args[1]
	local type = frame.args[2]
	local debug_mode = frame.args['debug_mode']
	if slot == nil then return "'slot' parameter is required" end
	
	-- Define base args
	local template_title = "Navbox subgroup"
	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()
	for i, souls in ipairs(prices) do
		--Determine lower bound for soul
		min_souls = souls
		
		--Skip 0 to i1 as no items cost less than 500
		if min_souls ~= 0 then 
			
			-- Determine upper bound
			max_souls = prices[i+1]
			if max_souls == nil then 
				max_souls = min_souls * 10 --essentially have no upper bound
			end
			
			--Create the subgroup
			template_args["group" .. (i-1)] = frame:expandTemplate{title="Souls", args={[1] = min_souls, ["Shadow"] = soul_style}}
			local list
			if type=='get_item_nav_cards' then
				list = p.get_item_nav_cards(slot, min_souls, max_souls, debug_mode)
			elseif type=='get_item_nav_bulletpoints' then
				list = p.get_item_nav_bulletpoints(slot, min_souls, max_souls, debug_mode)
			else
				return "'type' should be get_item_nav_cards or get_item_nav_bulletpoints"
			end
			template_args["list" .. (i-1)] =  list
		end
	end
	
	return frame:expandTemplate{title=template_title, args=template_args}
end

-- for [[Template:Active Items]]
function p.generate_active_items_table(frame)
    local active_items = {}
    
    -- 1. Collect active items
    for item_key, item_data in pairs(items_data) do
        if item_data["Name"] and 
           item_data["IsDisabled"] == false and 
           item_data["Activation"] and 
           item_data["Activation"] ~= "Passive" then
            
            table.insert(active_items, {
                name = item_data["Name"],
                key = item_key,
                slot = item_data["Slot"] or "Weapon",
                has_active_desc = item_data["ActiveDescription"] ~= nil
            })
        end
    end
    
    -- 2. Sort by name
    table.sort(active_items, function(a, b) return a.name < b.name end)
    
    -- 3. Generate wiki markup
    local wikitext = [=[
{| class="wikitable 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}}
]=]
    
    -- 4. Add rows for each item
    for _, item in ipairs(active_items) do
        -- Determine background color and item type
        local bg_color, item_type
        if item.slot == "Armor" then
            bg_color = "#86C921"
            item_type = "Armor"
        elseif item.slot == "Tech" then
            bg_color = "#DE9CFF"
            item_type = "Tech"
        else
            bg_color = "#FCAC4D"
            item_type = "Weapon"
        end
        
        wikitext = wikitext .. "\n" .. 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}}}}
| style="text-align:center;font-size:15px" | {{Souls|{{#invoke:ItemData|get_cost|%s}}}}
| {{#invoke:Utilities|process_variables|%s_active_desc|%s}}{{#invoke:Utilities|process_variables|%s_desc|%s}}{{#invoke:Utilities|process_variables|%s_active|%s}}
| style="text-align:center;font-size:16px" | {{#invoke:ItemData|get_prop|%s|AbilityCooldown}}s
]=],
            bg_color, 
            item.name, 
            item.name, item.key,
            item_type, item_type,
            item.name,
            item.key, item.name, item.key, item.name, item.key, item.name,
            item.name)
    end
    
    wikitext = wikitext .. "\n|}"
    
    return frame:preprocess(wikitext)
end

return p