Module:Infobox item: Difference between revisionsGive feedback
m Monster Domosed moved page Module:Infobox Item to Module:Infobox item: Misspelled title |
append_label: prefer per-stat StatLinks target over generic attribute-type link (fixes e.g. Debuff Resist → Ability Duration) (with help from vergir-bot LLM) |
||
| (14 intermediate revisions by 4 users not shown) | |||
| Line 4: | Line 4: | ||
local data = mw.loadJsonData("Data:ItemCards.json") | local data = mw.loadJsonData("Data:ItemCards.json") | ||
local stat_links = mw.loadJsonData("Data:StatLinks.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 references for hot-path globals | ||
| Line 14: | Line 15: | ||
local tostring = tostring | local tostring = tostring | ||
local type = type | 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 | -- Pre-computed constants | ||
| Line 19: | Line 29: | ||
.. " [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]" | .. " [[{{#invoke:Lang|get_string|Citadel_StatusEffect}}]]" | ||
-- | -- Property keys to completely ignore during processing | ||
local | local IGNORED_PROP_KEYS = { | ||
BuildUpPerShot = true, | |||
} | |||
-- Label key remapping table (replaces if/elseif chain) | -- Label key remapping table (replaces if/elseif chain) | ||
local LABEL_KEY_REMAP = { | local LABEL_KEY_REMAP = { | ||
DotDuration = "DOTDuration", | DotDuration = "DOTDuration", | ||
} | |||
-- Item name override mapping (old name -> new name) | |||
local ITEM_NAME_OVERRIDES = { | |||
["debuff remover"] = "Dispel Magic", | |||
["backstabber"] = "Stalker", | |||
} | } | ||
| Line 54: | Line 69: | ||
local conditional_label -- lazily initialised | 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 | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 72: | Line 117: | ||
local function get_json_item(name) | local function get_json_item(name) | ||
local direct = data[ | -- 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 | if direct and not direct.IsDisabled then return direct end | ||
local fallback | local fallback | ||
for _, v in pairs(data) do | for _, v in pairs(data) do | ||
if v.Name == | if v.Name == resolved_name then | ||
if not v.IsDisabled then return v end | if not v.IsDisabled then return v end | ||
fallback = fallback or v | fallback = fallback or v | ||
| Line 129: | Line 177: | ||
local function get_component_of_sorted(item_input) | local function get_component_of_sorted(item_input) | ||
-- Apply name override before processing | |||
local resolved_input = resolve_item_name(item_input) | |||
local target_key | local target_key | ||
if data[ | if data[resolved_input] then | ||
target_key = | target_key = resolved_input | ||
else | else | ||
for k, v in pairs(data) do | for k, v in pairs(data) do | ||
if v.Name == | if v.Name == resolved_input and not v.IsDisabled then | ||
target_key = k | target_key = k | ||
break | break | ||
| Line 182: | Line 233: | ||
end | end | ||
local function append_label(val_str, prop_key, label, | local function append_label(val_str, prop_key, label, attr_link, link_override, is_main) | ||
if label == "" then return val_str end | if label == "" then return val_str end | ||
local | -- Resolve link priority: Override > JSON Stat Link > Attribute Map Link | ||
if | local link_target = link_override | ||
if | 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 .. "]]" | display_label = "[[" .. label .. "]]" | ||
else | else | ||
display_label = "[[" .. | display_label = "[[" .. link_target .. "|" .. label .. "]]" | ||
end | end | ||
end | |||
-- Place label on a new line only for Main stats | |||
if is_main then | |||
return val_str .. "<br>" .. display_label | |||
else | else | ||
return val_str .. " " .. display_label | |||
end | end | ||
end | end | ||
| Line 210: | Line 271: | ||
if not conditional_label then | if not conditional_label then | ||
conditional_label = format( | conditional_label = format( | ||
' <span style="color:#C0C0C0"> | '<span style="color:#C0C0C0">%s</span>', | ||
lang.get_string("Citadel_Shop_ConditionalAttribute") | lang.get_string("Citadel_Shop_ConditionalAttribute") | ||
) | ) | ||
end | end | ||
return val_str .. conditional_label | -- Place conditional text on a new line | ||
return val_str .. "<br>" .. conditional_label | |||
end | end | ||
| Line 224: | Line 286: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Attribute Icon/Style detection helper | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
local function | local function resolve_attr_style(css_type) | ||
return prop_key == " | 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 | end | ||
| Line 236: | Line 342: | ||
local function format_value(prop_key, value, css_type, loc_override, | local function format_value(prop_key, value, css_type, loc_override, | ||
skip_label, allow_conditional, usage_flags, scale_info) | skip_label, allow_conditional, usage_flags, scale_info, | ||
disable_attr, is_main) | |||
local numeric_val = extract_numeric(value) | local numeric_val = extract_numeric(value) | ||
if not numeric_val then return nil end | if not numeric_val then return nil end | ||
| Line 267: | Line 374: | ||
end | end | ||
-- | -- Attribute styling (Icon via template) based on css_type | ||
local | local attr_style | ||
if | if not disable_attr then | ||
val_str = | attr_style = resolve_attr_style(css_type) | ||
end | |||
if attr_style and attr_style.icon then | |||
val_str = attr_style.icon .. " " .. val_str | |||
end | end | ||
val_str = append_label(val_str, label_key, label, | 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) | return append_conditional(val_str, allow_conditional, usage_flags) | ||
end | end | ||
| Line 285: | Line 397: | ||
local function format_value_enhanced(item_name, prop_key, value, css_type, | local function format_value_enhanced(item_name, prop_key, value, css_type, | ||
loc_override, skip_label, | loc_override, skip_label, | ||
allow_conditional, usage_flags) | allow_conditional, usage_flags, | ||
disable_attr, is_main) | |||
local numeric_val = extract_numeric(value) | local numeric_val = extract_numeric(value) | ||
if not numeric_val then return nil end | if not numeric_val then return nil end | ||
| Line 327: | Line 440: | ||
end | end | ||
-- | -- Attribute styling (Icon via template) based on css_type | ||
local | local attr_style | ||
if | if not disable_attr then | ||
val_str = | attr_style = resolve_attr_style(css_type) | ||
end | |||
if attr_style and attr_style.icon then | |||
val_str = attr_style.icon .. " " .. val_str | |||
end | end | ||
val_str = append_label(val_str, label_key, label, | 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) | return append_conditional(val_str, allow_conditional, usage_flags) | ||
end | 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 | -- Tooltip builder helpers | ||
| Line 358: | Line 494: | ||
end | end | ||
local function | local function process_stat_list(lines, param_fmt, counter, start_index, | ||
prop_list, seen, formatter) | |||
local stat_index = start_index | local stat_index = start_index | ||
for _, prop_obj in ipairs(prop_list or {}) do | for _, prop_obj in ipairs(prop_list or {}) do | ||
local key = prop_obj.Key | local key = prop_obj.Key | ||
if not seen[key] and ( | if not seen[key] and not IGNORED_PROP_KEYS[key] and ( | ||
key:match("^StatusEffect") | key:match("^StatusEffect") | ||
or is_property_in_lang_data(key, prop_obj.LocTokenOverride) | or is_property_in_lang_data(key, prop_obj.LocTokenOverride) | ||
| Line 369: | Line 505: | ||
local val = formatter(prop_obj) | local val = formatter(prop_obj) | ||
if val then | if val then | ||
insert(lines, format( | insert(lines, format(param_fmt, counter, stat_index, val)) | ||
stat_index = stat_index + 1 | stat_index = stat_index + 1 | ||
seen[key] = true | seen[key] = true | ||
| Line 377: | Line 513: | ||
return stat_index | return stat_index | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Unified tooltip builder (handles both default and enhanced modes) | -- Unified tooltip builder (handles both default and enhanced modes) | ||
| Line 383: | Line 520: | ||
local item_name = frame.args[1] | local item_name = frame.args[1] | ||
if not item_name then return "Item name missing" end | 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) | local item = get_json_item(item_name) | ||
| Line 393: | Line 533: | ||
insert(lines, "| enhanced = true") | insert(lines, "| enhanced = true") | ||
end | end | ||
insert(lines, "| item_name = " .. | -- Use resolved name for item_name parameter | ||
insert(lines, "| item_name = " .. resolved_name) | |||
if item.StreetBrawl == true then | if item.StreetBrawl == true then | ||
insert(lines, "| street_brawl = true") | 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 | end | ||
| Line 402: | Line 549: | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- Innate stats | -- Innate stats (Main and Alt merged into item_stat sequentially) | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local innate_index = 1 | local innate_index = 1 | ||
| Line 408: | Line 555: | ||
if section.Type == "Innate" then | if section.Type == "Innate" then | ||
for _, obj in ipairs(section.Main or {}) do | 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 | |||
if is_property_in_lang_data(obj.Key, obj.LocTokenOverride) then | |||
local val | local val | ||
if enhanced then | if enhanced then | ||
val = format_value_enhanced( | val = format_value_enhanced( | ||
resolved_name, obj.Key, obj.Value, obj.Type, | |||
obj.LocTokenOverride, nil, false, obj.UsageFlags, | |||
true -- disable_attr | |||
) | |||
else | else | ||
val = format_value(obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, false, false, obj.UsageFlags, obj.Scale) | val = format_value( | ||
obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, | |||
false, false, obj.UsageFlags, obj.Scale, | |||
true -- disable_attr | |||
) | |||
end | end | ||
if val then | if val then | ||
| Line 423: | Line 577: | ||
end | end | ||
for _, obj in ipairs(section.Alt or {}) do | 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 | |||
if is_property_in_lang_data(obj.Key, obj.LocTokenOverride) then | |||
local val | local val | ||
if enhanced then | if enhanced then | ||
val = format_value_enhanced( | val = format_value_enhanced( | ||
resolved_name, obj.Key, obj.Value, obj.Type, | |||
obj.LocTokenOverride, nil, false, obj.UsageFlags | obj.LocTokenOverride, nil, false, obj.UsageFlags, | ||
true -- disable_attr | |||
) | ) | ||
else | else | ||
val = format_value( | val = format_value( | ||
obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, | obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, | ||
false, false, obj.UsageFlags, obj.Scale | false, false, obj.UsageFlags, obj.Scale, | ||
true -- disable_attr | |||
) | ) | ||
end | end | ||
| Line 447: | Line 602: | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- Build formatters once (closures capture | -- Build formatters once (closures capture resolved_name) | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local main_formatter, alt_formatter | local main_formatter, alt_formatter | ||
| Line 454: | Line 609: | ||
main_formatter = function(obj) | main_formatter = function(obj) | ||
if obj.Key:match("^StatusEffect") then | if obj.Key:match("^StatusEffect") then | ||
return | return format_status_effect(obj.Key) | ||
end | end | ||
return format_value_enhanced( | return format_value_enhanced( | ||
resolved_name, obj.Key, obj.Value, obj.Type, | |||
obj.LocTokenOverride, false, true, obj.UsageFlags | obj.LocTokenOverride, false, true, obj.UsageFlags, nil, true | ||
) | ) | ||
end | end | ||
alt_formatter = function(obj) | alt_formatter = function(obj) | ||
if obj.Key:match("^StatusEffect") then | |||
return format_status_effect(obj.Key) | |||
end | |||
return format_value_enhanced( | return format_value_enhanced( | ||
resolved_name, obj.Key, obj.Value, obj.Type, | |||
obj.LocTokenOverride, nil, false, obj.UsageFlags | obj.LocTokenOverride, nil, false, obj.UsageFlags, | ||
true -- disable_attr | |||
) | ) | ||
end | end | ||
else | 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) | alt_formatter = function(obj) | ||
if obj.Key:match("^StatusEffect") then | |||
return format_status_effect(obj.Key) | |||
end | |||
return format_value( | return format_value( | ||
obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, | obj.Key, obj.Value, obj.Type, obj.LocTokenOverride, | ||
false, false, obj.UsageFlags, obj.Scale | false, false, obj.UsageFlags, obj.Scale, | ||
true -- disable_attr | |||
) | ) | ||
end | end | ||
| Line 490: | Line 649: | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
-- Passive & Active | -- Passive & Active sections — Main and Alt output separately | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
local passive_counter, active_counter = 1, 1 | local passive_counter, active_counter = 1, 1 | ||
| Line 500: | Line 659: | ||
local prefix = is_active and "active" or "passive" | local prefix = is_active and "active" or "passive" | ||
local counter = is_active and active_counter or passive_counter | local counter = is_active and active_counter or passive_counter | ||
-- Description | -- Description | ||
| Line 509: | Line 667: | ||
insert(lines, format( | insert(lines, format( | ||
"| %s%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}", | "| %s%d_description = {{#invoke:Lang|get_string|%s|item_name=%s}}", | ||
prefix, counter, loc, | prefix, counter, loc, resolved_name | ||
)) | )) | ||
end | end | ||
end | end | ||
-- | -- Main stats → prefix{N}_main{M} | ||
local | local main_fmt = "| " .. prefix .. "%d_main%d = %s" | ||
local | local main_seen = {} | ||
lines, | 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 | -- Cooldown / ChargeUp | ||
local cd = format_section_cooldown(section, | local cd = format_section_cooldown(section, resolved_name, enhanced) | ||
if cd.cooldown then | if cd.cooldown then | ||
insert(lines, format("| %s%d_cooldown = %s", prefix, counter, cd.cooldown)) | insert(lines, format("| %s%d_cooldown = %s", prefix, counter, cd.cooldown)) | ||
| Line 546: | Line 732: | ||
------------------------------------------------------------------ | ------------------------------------------------------------------ | ||
for i = 1, 2 do | for i = 1, 2 do | ||
local comp = ItemData.get_component_name { args = { | local comp = ItemData.get_component_name { args = { resolved_name, i } } | ||
if comp and comp ~= "" then | if comp and comp ~= "" then | ||
insert(lines, format("| component%d_name = %s", i, comp)) | insert(lines, format("| component%d_name = %s", i, comp)) | ||
| Line 577: | Line 763: | ||
return concat(lines, "\n") | return concat(lines, "\n") | ||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Public interface | -- Public interface | ||