Home
Random
Log in
Settings
About the Deadlock Wiki
Search
Editing
Module
:
Infobox item
Give feedback
Warning:
You are not logged in. Once you make an edit, a temporary account will be created for you.
Learn more
.
Log in
or
create an account
to continue receiving notifications after this account expires, and to access other features.
Anti-spam check. Do
not
fill this in!
local p = {} local lang = require "Module:Lang" local util_module = require "Module:Utilities" local data = mw.loadJsonData("Data:ItemCards.json") local stat_links = mw.loadJsonData("Data:StatLinks.json") local ATTR_TYPE_ICON_MAP = require("Module:Icon/attr") -- Local references for hot-path globals local format = string.format local concat = table.concat local insert = table.insert local ipairs = ipairs local pairs = pairs local tonumber = tonumber local tostring = tostring local type = type -- Status Effect Icon Map local STATUS_EFFECT_ICON_MAP = { StatusEffectDisarmed = {img='Status_Disarm.png', link='Disarm', color='Red'}, StatusEffectEMP = {img='Status_Silence.png', link='Silence', color='Purple'}, StatusEffectInvisible= {img='Invisible.png', link='Invisible',color='Green'}, StatusEffectStun = {img='Status_Stun.png', link='Stun', color='NoColor'}, StatusEffectSlow = {img='Status_Movement_Slow.png', link='Slow', color='Purple'}, } -- Pre-computed constants local STATUS_FMT = "[[{{#invoke:Lang|get_string|Citadel_%s}}]]" .. " [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]" -- Property keys to completely ignore during processing local IGNORED_PROP_KEYS = { BuildUpPerShot = true, } -- Label key remapping table (replaces if/elseif chain) local LABEL_KEY_REMAP = { DotDuration = "DOTDuration", } -- Item name override mapping (old name -> new name) local ITEM_NAME_OVERRIDES = { ["debuff remover"] = "Dispel Magic", ["backstabber"] = "Stalker", } -- Suffix pairs for lang data existence checks local LANG_SUFFIX_PAIRS = { { "_label", "_Label" }, { "_prefix", "_Prefix" }, { "_postfix", "_Postfix" }, { "_postvalue_label", "_Postvalue_Label" }, } -------------------------------------------------------------------------------- -- Caches -------------------------------------------------------------------------------- local lang_cache = {} local info_sections_cache = setmetatable({}, { __mode = "k" }) -- weak keys local function cached_get_string(key) local v = lang_cache[key] if v ~= nil then return v or nil end -- false sentinel β nil local result = lang.get_string(key) lang_cache[key] = result or false return result end local conditional_label -- lazily initialised -------------------------------------------------------------------------------- -- Item name resolution -------------------------------------------------------------------------------- local function resolve_item_name(name) -- Check if there's an override for this name (case-insensitive) local lowercase_name = name:lower() local override = ITEM_NAME_OVERRIDES[lowercase_name] if override then return override end -- If no override, try to find the item in data and return its proper Name -- First try direct key lookup if data[name] and not data[name].IsDisabled then return data[name].Name or name end -- Then try case-insensitive search by Name field local lowercase_search = name:lower() for _, v in pairs(data) do if v.Name and v.Name:lower() == lowercase_search and not v.IsDisabled then return v.Name end end -- Fallback to original name return name end -------------------------------------------------------------------------------- -- Utility helpers -------------------------------------------------------------------------------- local function get_lang_pair(base_key, suffix1, suffix2) local v1 = cached_get_string(base_key .. suffix1) if v1 and v1 ~= "" then return v1 end local v2 = cached_get_string(base_key .. suffix2) if v2 and v2 ~= "" then return v2 end return "" end -------------------------------------------------------------------------------- -- Data access helpers -------------------------------------------------------------------------------- local function get_json_item(name) -- Apply name override before lookup local resolved_name = resolve_item_name(name) local direct = data[resolved_name] if direct and not direct.IsDisabled then return direct end local fallback for _, v in pairs(data) do if v.Name == resolved_name then if not v.IsDisabled then return v end fallback = fallback or v end end return fallback end local function get_info_sections(item) local cached = info_sections_cache[item] if cached then return cached end local sections = {} local i = 1 while item["Info" .. i] do sections[i] = item["Info" .. i] i = i + 1 end info_sections_cache[item] = sections return sections end local function find_property_obj(item, prop_key) for _, section in ipairs(get_info_sections(item)) do for _, obj in ipairs(section.Main or {}) do if obj.Key == prop_key then return obj end end for _, obj in ipairs(section.Alt or {}) do if obj.Key == prop_key then return obj end end end local other = item.Other return other and other[prop_key] or nil end local function is_property_in_lang_data(prop, loc_override) local has_override = loc_override and loc_override ~= prop for _, pair in ipairs(LANG_SUFFIX_PAIRS) do if cached_get_string(prop .. pair[1]) or cached_get_string(prop .. pair[2]) then return true end if has_override and ( cached_get_string(loc_override .. pair[1]) or cached_get_string(loc_override .. pair[2]) ) then return true end end return false end local function get_component_of_sorted(item_input) -- Apply name override before processing local resolved_input = resolve_item_name(item_input) local target_key if data[resolved_input] then target_key = resolved_input else for k, v in pairs(data) do if v.Name == resolved_input and not v.IsDisabled then target_key = k break end end end if not target_key then return {} end local parents = {} for key, item in pairs(data) do if not item.IsDisabled and type(item.Components) == "table" then for _, comp in ipairs(item.Components) do if comp == target_key then insert(parents, key) break end end end end table.sort(parents, function(a, b) return (tonumber(data[a] and data[a].Tier) or 0) < (tonumber(data[b] and data[b].Tier) or 0) end) return parents end -------------------------------------------------------------------------------- -- Shared formatting primitives -------------------------------------------------------------------------------- local function resolve_label_key(prop_key, loc_override) local base = (loc_override or prop_key):gsub("^#", "") return LABEL_KEY_REMAP[base] or base end local function resolve_prefix(prefix, numeric_val) if prefix == "{s:sign}" then return numeric_val >= 0 and "+" or "" end return prefix end local function resolve_suffix(postfix, css_type) if postfix == "HP/s" then return " " .. postfix end return postfix end local function append_label(val_str, prop_key, label, attr_link, link_override, is_main) if label == "" then return val_str end -- Resolve link priority: Override > JSON Stat Link > Attribute Map Link local link_target = link_override if not link_target or link_target == "" then link_target = stat_links[prop_key] end if not link_target or link_target == "" then link_target = attr_link end -- Construct label with link if valid local display_label = label if link_target and link_target ~= "" then if link_target == label then display_label = "[[" .. label .. "]]" else display_label = "[[" .. link_target .. "|" .. label .. "]]" end end -- Place label on a new line only for Main stats if is_main then return val_str .. "<br>" .. display_label else return val_str .. " " .. display_label end end local function append_conditional(val_str, allow_conditional, usage_flags) if not allow_conditional then return val_str end if usage_flags ~= "ConditionallyApplied" and usage_flags ~= "ConditionallyEnemyApplied" then return val_str end if not conditional_label then conditional_label = format( '<span style="color:#C0C0C0">%s</span>', lang.get_string("Citadel_Shop_ConditionalAttribute") ) end -- Place conditional text on a new line return val_str .. "<br>" .. conditional_label end local function extract_numeric(value) if value == nil or value == 0 or value == "" then return nil end local n = tonumber(tostring(value):match("^-?%d+%.?%d*")) return (n and n ~= 0) and n or nil end -------------------------------------------------------------------------------- -- Attribute Icon/Style detection helper -------------------------------------------------------------------------------- local function resolve_attr_style(css_type) if not css_type then return nil end local attr = ATTR_TYPE_ICON_MAP[css_type] if not attr then return nil end local color = attr.color or "Grey" -- Fallback color local img = attr.img or "GenericProperty.png" local link = attr.link local size = attr.size -- Optional -- Construct icon using the defined color template: {{Icon/Color|img|size|link=link}} local icon_str if size and size ~= "" then icon_str = format("{{Icon/%s|[[File:%s|%spx|link=%s]]}}", color, img, size, link or "") else icon_str = format("{{Icon/%s|[[File:%s|%spx|link=%s]]}}", color, img, 18, link or "") end return { icon = icon_str, link = link } end -------------------------------------------------------------------------------- -- Status Effect Icon helper -------------------------------------------------------------------------------- local function resolve_status_effect_icon(prop_key) local effect_config = STATUS_EFFECT_ICON_MAP[prop_key] if not effect_config then return nil end local color = effect_config.color or "NoColor" local img = effect_config.img or "GenericProperty.png" local link = effect_config.link -- Construct icon using the defined color template local icon_str = format( "{{Icon/%s|[[File:%s|18px|link=%s]]}}", color, img, link or "" ) return { icon = icon_str, link = link } end -------------------------------------------------------------------------------- -- Core formatting: non-enhanced -------------------------------------------------------------------------------- local function format_value(prop_key, value, css_type, loc_override, skip_label, allow_conditional, usage_flags, scale_info, disable_attr, is_main) local numeric_val = extract_numeric(value) if not numeric_val then return nil end local label_key = resolve_label_key(prop_key, loc_override) local label = skip_label and "" or get_lang_pair(label_key, "_label", "_Label") local prefix = get_lang_pair(label_key, "_prefix", "_Prefix") local postfix = get_lang_pair(label_key, "_postfix", "_Postfix") if prefix == "" and postfix == "" and label == "" then return nil end prefix = resolve_prefix(prefix, numeric_val) local suffix = resolve_suffix(postfix, css_type) -- Compose numeric + scaling first local val_str = tostring(numeric_val) if scale_info and scale_info.Value and scale_info.Value ~= 0 then local scale_val = util_module.round_to_sig_fig(scale_info.Value, 2) local tmpl = scale_info.Type == "power_increase" and "Boon" or "Ss" val_str = val_str .. " " .. mw.getCurrentFrame():expandTemplate{ title = tmpl, args = { scale_val } } end -- Then prepend prefix if prefix ~= "" then val_str = prefix .. val_str end -- Then append postfix if suffix ~= "" and not val_str:find(suffix, 1, true) then val_str = val_str .. suffix end -- Attribute styling (Icon via template) based on css_type local attr_style if not disable_attr then attr_style = resolve_attr_style(css_type) end if attr_style and attr_style.icon then val_str = attr_style.icon .. " " .. val_str end val_str = append_label(val_str, label_key, label, attr_style and attr_style.link or nil, nil, is_main) -- link_override not used here in base calls return append_conditional(val_str, allow_conditional, usage_flags) end -------------------------------------------------------------------------------- -- Core formatting: enhanced -------------------------------------------------------------------------------- local function format_value_enhanced(item_name, prop_key, value, css_type, loc_override, skip_label, allow_conditional, usage_flags, disable_attr, is_main) local numeric_val = extract_numeric(value) if not numeric_val then return nil end local label_key = resolve_label_key(prop_key, loc_override) -- Enhanced requires at least one lang token to exist local has_lang = false for _, pair in ipairs(LANG_SUFFIX_PAIRS) do if cached_get_string(label_key .. pair[1]) or cached_get_string(label_key .. pair[2]) then has_lang = true break end end if not has_lang then return nil end local label = skip_label and "" or get_lang_pair(label_key, "_label", "_Label") local prefix = get_lang_pair(label_key, "_prefix", "_Prefix") local postfix = get_lang_pair(label_key, "_postfix", "_Postfix") if prefix == "" and postfix == "" and label == "" then return nil end prefix = resolve_prefix(prefix, numeric_val) local suffix = resolve_suffix(postfix, css_type) -- Build Enhanced invoke local extras = {} if prefix ~= "" then insert(extras, "prefix=" .. prefix) end if suffix ~= "" and suffix ~= "%" then insert(extras, "suffix=" .. suffix) end insert(extras, "noicon=true") local val_str = format( "{{#invoke:Enhanced|render|%s|%s|%s}}", item_name, prop_key, concat(extras, "|") ) -- Append postfix if not already present if postfix ~= "" and postfix ~= "%" and not val_str:find(postfix, 1, true) then val_str = val_str .. (postfix == "HP/s" and (" " .. postfix) or postfix) end -- Attribute styling (Icon via template) based on css_type local attr_style if not disable_attr then attr_style = resolve_attr_style(css_type) end if attr_style and attr_style.icon then val_str = attr_style.icon .. " " .. val_str end val_str = append_label(val_str, label_key, label, attr_style and attr_style.link or nil, nil, is_main) return append_conditional(val_str, allow_conditional, usage_flags) end -------------------------------------------------------------------------------- -- Status Effect formatting helper -------------------------------------------------------------------------------- local function format_status_effect(prop_key) local lang_str = format("[[{{#invoke:Lang|get_string|Citadel_%s}}]]", prop_key) .. " [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]" -- Get icon for this status effect local icon_style = resolve_status_effect_icon(prop_key) if icon_style and icon_style.icon then return icon_style.icon .. " " .. lang_str end return lang_str end -------------------------------------------------------------------------------- -- Tooltip builder helpers -------------------------------------------------------------------------------- local function format_section_cooldown(section, item_name, enhanced) local result = {} local cd, cu = section.Cooldown, section.ChargeUp if cd and cd ~= 0 then result.cooldown = enhanced and format_value_enhanced(item_name, "AbilityCooldown", cd, nil, nil, true, false, nil) or format_value("AbilityCooldown", cd, nil, nil, true, false, nil, nil) end if cu and cu ~= 0 then result.chargeup = enhanced and format_value_enhanced(item_name, "AbilityChargeUpTime", cu, nil, nil, true, false, nil) or format_value("AbilityChargeUpTime", cu, nil, nil, true, false, nil, nil) end return result end local function process_stat_list(lines, param_fmt, counter, start_index, prop_list, seen, formatter) local stat_index = start_index for _, prop_obj in ipairs(prop_list or {}) do local key = prop_obj.Key if not seen[key] and not IGNORED_PROP_KEYS[key] and ( key:match("^StatusEffect") or is_property_in_lang_data(key, prop_obj.LocTokenOverride) ) then local val = formatter(prop_obj) if val then insert(lines, format(param_fmt, counter, stat_index, val)) stat_index = stat_index + 1 seen[key] = true end end end return stat_index end -------------------------------------------------------------------------------- -- Unified tooltip builder (handles both default and enhanced modes) -------------------------------------------------------------------------------- local function build_tooltip_impl(frame, enhanced) local item_name = frame.args[1] if not item_name then return "Item name missing" end -- Resolve the item name (apply override if exists) local resolved_name = resolve_item_name(item_name) local item = get_json_item(item_name) if not item then return "Item not found" end local ItemData = require("Module:ItemData") local lines = { "{{Infobox item" } if enhanced then insert(lines, "| enhanced = true") end -- Use resolved name for item_name parameter insert(lines, "| item_name = " .. resolved_name) if item.StreetBrawl == true then insert(lines, "| street_brawl = true") end -- Pass through nocat arg to the Infobox item template if provided local nocat = frame.args.nocat if nocat and nocat ~= "" then insert(lines, "| nocat = " .. nocat) end local sections = get_info_sections(item) ------------------------------------------------------------------ -- Innate stats (Main and Alt merged into item_stat sequentially) ------------------------------------------------------------------ local innate_index = 1 for _, section in ipairs(sections) do if section.Type == "Innate" then for _, obj in ipairs(section.Main or {}) do if not IGNORED_PROP_KEYS[obj.Key] and is_property_in_lang_data(obj.Key, obj.LocTokenOverride) then local val if enhanced then val = format_value_enhanced( resolved_name, obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, nil, false, obj.UsageFlags, true -- disable_attr ) else val = format_value( obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, false, obj.UsageFlags, obj.Scale, true -- disable_attr ) end if val then insert(lines, format("| item_stat%d = %s", innate_index, val)) innate_index = innate_index + 1 end end end for _, obj in ipairs(section.Alt or {}) do if not IGNORED_PROP_KEYS[obj.Key] and is_property_in_lang_data(obj.Key, obj.LocTokenOverride) then local val if enhanced then val = format_value_enhanced( resolved_name, obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, nil, false, obj.UsageFlags, true -- disable_attr ) else val = format_value( obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, false, obj.UsageFlags, obj.Scale, true -- disable_attr ) end if val then insert(lines, format("| item_stat%d = %s", innate_index, val)) innate_index = innate_index + 1 end end end end end ------------------------------------------------------------------ -- Build formatters once (closures capture resolved_name) ------------------------------------------------------------------ local main_formatter, alt_formatter if enhanced then main_formatter = function(obj) if obj.Key:match("^StatusEffect") then return format_status_effect(obj.Key) end return format_value_enhanced( resolved_name, obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, true, obj.UsageFlags, nil, true ) end alt_formatter = function(obj) if obj.Key:match("^StatusEffect") then return format_status_effect(obj.Key) end return format_value_enhanced( resolved_name, obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, nil, false, obj.UsageFlags, true -- disable_attr ) end else main_formatter = function(obj) if obj.Key:match("^StatusEffect") then return format_status_effect(obj.Key) end return format_value( obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, true, obj.UsageFlags, obj.Scale, nil, true ) end alt_formatter = function(obj) if obj.Key:match("^StatusEffect") then return format_status_effect(obj.Key) end return format_value( obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, false, obj.UsageFlags, obj.Scale, true -- disable_attr ) end end ------------------------------------------------------------------ -- Passive & Active sections β Main and Alt output separately ------------------------------------------------------------------ local passive_counter, active_counter = 1, 1 for _, section in ipairs(sections) do local stype = section.Type if stype ~= "Innate" then local is_active = (stype == "Active") local prefix = is_active and "active" or "passive" local counter = is_active and active_counter or passive_counter -- Description local loc = section.DescKey if loc then loc = loc:gsub("#", "") if loc ~= "" then insert(lines, format( "| %s%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}", prefix, counter, loc, resolved_name )) end end -- Main stats β prefix{N}_main{M} local main_fmt = "| " .. prefix .. "%d_main%d = %s" local main_seen = {} local main_index = 1 for _, prop_obj in ipairs(section.Main or {}) do local key = prop_obj.Key if not main_seen[key] and not IGNORED_PROP_KEYS[key] and ( key:match("^StatusEffect") or is_property_in_lang_data(key, prop_obj.LocTokenOverride) ) then local val = main_formatter(prop_obj) if val then insert(lines, format(main_fmt, counter, main_index, val)) main_index = main_index + 1 main_seen[key] = true end end end -- Alt stats β prefix{N}_alt{M} local alt_fmt = "| " .. prefix .. "%d_alt%d = %s" local alt_seen = {} local alt_index = 1 for _, prop_obj in ipairs(section.Alt or {}) do local key = prop_obj.Key -- Skip keys already emitted as main stats if not main_seen[key] and not alt_seen[key] and not IGNORED_PROP_KEYS[key] and ( key:match("^StatusEffect") or is_property_in_lang_data(key, prop_obj.LocTokenOverride) ) then local val = alt_formatter(prop_obj) if val then insert(lines, format(alt_fmt, counter, alt_index, val)) alt_index = alt_index + 1 alt_seen[key] = true end end end -- Cooldown / ChargeUp local cd = format_section_cooldown(section, resolved_name, enhanced) if cd.cooldown then insert(lines, format("| %s%d_cooldown = %s", prefix, counter, cd.cooldown)) end if cd.chargeup then insert(lines, format("| %s%d_chargeup = %s", prefix, counter, cd.chargeup)) end if is_active then active_counter = active_counter + 1 else passive_counter = passive_counter + 1 end end end ------------------------------------------------------------------ -- Components ------------------------------------------------------------------ for i = 1, 2 do local comp = ItemData.get_component_name { args = { resolved_name, i } } if comp and comp ~= "" then insert(lines, format("| component%d_name = %s", i, comp)) end end ------------------------------------------------------------------ -- Is Component Of (sorted by tier) ------------------------------------------------------------------ local parents = get_component_of_sorted(item_name) for i, key in ipairs(parents) do local d = data[key] local parent_name = d and d.Name or "" if parent_name ~= "" then insert(lines, format("| iscomponentof%d_name = %s", i, parent_name)) end end ------------------------------------------------------------------ -- Sounds (pass-through from frame args) ------------------------------------------------------------------ for key, value in pairs(frame.args) do local index = tostring(key):match("^sound(%d+)$") if index and value ~= "" then insert(lines, format("| sound%s = %s", index, value)) end end insert(lines, "}}") return concat(lines, "\n") end -------------------------------------------------------------------------------- -- Public interface -------------------------------------------------------------------------------- -- Render only Default item infobox function p.fill_tooltip(frame) local output = build_tooltip_impl(frame, false) local debug = tonumber(frame.args.debug) or 0 if debug == 1 or debug == 2 then return output end return mw.getCurrentFrame():preprocess(output) end -- Render only Enhanced item infobox function p.fill_tooltip_enhanced(frame) return mw.getCurrentFrame():preprocess(build_tooltip_impl(frame, true)) end -- Render both Default and Enhanced item infobox using tabber function p.render(frame) local output = concat({ "<tabber>", lang.get_string("citadel_settings_camera_preset_default") .. "=", build_tooltip_impl(frame, false), "|-|", lang.get_string("Citadel_ItemDraft_Enhanced") .. "=", build_tooltip_impl(frame, true), "</tabber>" }, "\n") return mw.getCurrentFrame():preprocess(output) end return p
Summary:
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see
Deadlock:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Preview page with this template
Page included on this page:
Module:Infobox item/doc
(
view source
)