|
|
| Line 1: |
Line 1: |
| -- ==================================================================== | | -- File: Module:NpcData |
| -- Dependencies and Initial Setup | | -- Provides functions to access data about non-player characters (NPCs) |
| -- ==================================================================== | | -- from Data:NpcData.json. It mirrors the functionality of Module:HeroData. |
| local lang = require "Module:Lang"
| |
| local commonutils = require "Module:Utilities"
| |
| local utils = require "Module:Abilities/utils"
| |
|
| |
|
| local p = {} | | local p = {} |
| local data = mw.loadJsonData("Data:AbilityCards.json")
| |
|
| |
|
| -- (ATTR_TYPE_ICON_MAP and helper functions remain the same) | | -- Load data and utility modules |
| local ATTR_TYPE_ICON_MAP = {
| | -- It's best practice to load data only once. |
| bullet_armor_up = {img='Bullet_Armor.png', link='Damage_Resistance', color = 'NoColor'},
| | local npcs_data = mw.loadJsonData("Data:NpcData.json") |
| bullet_armor_down = {img='Bullet_Armor.png', link='Damage_Resistance', color = 'NoColor'},
| | local util_module = require('Module:Utilities') |
| cast = {img='AttributeIconMaxChargesIncrease.png', link=''},
| | local lang_module = require('Module:Lang') |
| charges = {img='AttributeIconMaxChargesIncrease.png', link='', color = 'Purple'},
| |
| damage = {img='Damage_heart.png', link='', color = 'NoColor'},
| |
| bullet_damage = {img='Bullet_damage.png', link='Bullet_Damage', color = 'NoColor'},
| |
| fire_rate = {img='Fire Rate.png', link='Fire_Rate', color = 'Brown'},
| |
| healing = {img='Healing.png', link='Healing', color = 'Green'},
| |
| health = {img='Health.png', link='Health', color = 'Green'},
| |
| move_speed = {img='Move speed.png', link='Move Speed', color = 'Green'},
| |
| range = {img='CastRange.png', link='Ability_Range', color = 'Purple'},
| |
| tech_armor_up = {img='Spirit_Armor.png', link='Damage_Resistance', color = 'NoColor'},
| |
| tech_damage = {img='AttributeIconTechShieldHealth.png', link='Spirit_Damage', color = 'NoColor',size = '12px'},
| |
| distance = {img='AttributeIconTechRange.png', link='Ability_Range', color = 'Purple'},
| |
| duration = {img='AttributeIconTechDuration.png', link='Ability Duration', color = 'Purple'},
| |
| slow = {img='MoveSlow.png', link='', color = 'Purple'},
| |
| melee_damage = {img='Melee damage.png', link='Melee Damage'},
| |
| cooldown = {img='Cooldown Icon.png', link='Ability Cooldown', color = 'Purple'},
| |
| combat_barrier = {img='Barrier.png', link='Barrier', color = 'Green'},
| |
| time = {img='AttributeIconTechDuration.png', link='Ability Duration', color = 'Purple'},
| |
| }
| |
|
| |
|
| function get_hero_key(hero_name) -- Unchanged
| | --[[ |
| for i, hero in pairs(data) do
| | Returns the entire data table for a specific NPC. |
| if hero["Name"] == hero_name then return i end
| | This function is intended for use by other Lua modules. |
| | @param name {string} The key of the NPC in Data:NpcData.json (e.g., "npc_boss_tier2"). |
| | @return {table|nil} The NPC's data table, or nil if not found. |
| | ]] |
| | function p.get_npc_data(name) |
| | if not name or name == '' then |
| | return nil |
| end | | end |
| return nil | | return npcs_data[name] |
| end | | end |
|
| |
|
| function get_icon(attr_type) -- Unchanged
| | --[[ |
| local mappedAttr = ATTR_TYPE_ICON_MAP[attr_type]
| | Retrieves a specific stat value for a given NPC. |
| local img = 'GenericProperty.png'
| | Usage: {{#invoke:NpcData|get_npc_var|NPC_NAME|STAT_NAME|sig_figs_or_localize}} |
| local link = ''
| | - NPC_NAME: The key of the NPC (e.g., "npc_boss_tier2"). |
| local size = '' | | - STAT_NAME: The name of the stat (e.g., "MaxHealth"). |
| local color = 'Grey' | | - sig_figs_or_localize: (Optional) |
| if mappedAttr then | | - A number for rounding to significant figures. |
| img = mappedAttr.img
| | - "true" to localize the value (if it's a localization key). |
| link = mappedAttr.link
| | ]] |
| size = mappedAttr.size
| | p.get_npc_var = function(frame) |
| color = mappedAttr.color or color
| | -- Get arguments from the #invoke call and trim whitespace |
| end | | local npc_name = frame.args[1] and mw.text.trim(frame.args[1]) or nil |
| return {img=img, link=link, color=color, size=size}
| | local npc_stat_key = frame.args[2] and mw.text.trim(frame.args[2]) or nil |
| end
| | local sig_figs_or_localize = frame.args[3] and mw.text.trim(frame.args[3]) or nil |
|
| |
|
| function get_attr_ss(attr) -- Unchanged
| | -- Validate required arguments |
| if not attr then return nil end | | if not npc_name or npc_name == '' then |
| local scale = attr.Scale | | return "Error: NPC name not provided." |
| if not scale or scale.Type ~= 'spirit' or scale.Value == 0 then return nil end | | end |
| return commonutils.round_to_sig_fig(scale.Value, 3)
| | if not npc_stat_key or npc_stat_key == '' then |
| end | | return "Error: Stat key not provided." |
| | end |
|
| |
|
| -- ==================================================================== | | -- Retrieve the NPC data table |
| -- HTML Builder Functions (With Final Fixes)
| | local npc = npcs_data[npc_name] |
| -- ====================================================================
| | if not npc then |
| | return "Error: NPC '" .. npc_name .. "' not found." |
| | end |
|
| |
|
| local function buildMultiplierHtml(type, value, icon_size) -- Unchanged from last version
| | -- Get the specific stat value |
| local config = {
| | local var_value = npc[npc_stat_key] |
| spirit = { color = "#E3BDFA", bgColor = "#533669", icon = "Spirit scaling.png", link = "Spirit Power#Spirit Power Scaling", size = icon_size or "40px" },
| | if var_value == nil then |
| melee = { color = "#cdb89e", bgColor = "#80550f", icon = "Melee scaling.png", link = "Melee Damage#Abilities", size = icon_size or "40px" },
| | return 0 -- Mimics HeroData behavior for non-existent stats |
| boon = { color = "#bec1ac", bgColor = "#217a68", icon = "Boon scaling.png", link = "Level#Boons from Patron", size = icon_size or "25px" },
| | end |
| weapon_damage_increase = { color = "#cdb89e", bgColor = "#80550f", icon = "Weapon scaling.png", link = "Weapon Damage", size = icon_size or "40px" }
| |
| }
| |
| local c = config[type] | |
| if not c or not value then return '' end | |
|
| |
| local icon = string.format('[[File:%s|link=%s|%s]]', c.icon, c.link, c.size)
| |
|
| |
| return string.format(
| |
| '<span style="font-size: 0.8em; color: %s; white-space: nowrap;">%s<span style="background-color: %s; font-family: \'Retail Demo Regular\', \'PT Serif\', \'Palatino\'; font-weight:bold; width: auto; border-radius: 5px; padding: 2px 2px 0px 2px; margin-left: -2px;"><i>x</i>%s</span></span>',
| |
| c.color, icon, c.bgColor, value
| |
| ) | |
| end | |
|
| |
|
| --- FIXED: Replicates the original HeaderAttr template exactly. | | -- Handle optional rounding |
| local function buildHeaderAttrHtml(value, uom, ss, icon, style)
| | if sig_figs_or_localize and tonumber(sig_figs_or_localize) then |
| if not value or value == '' then return '' end | | var_value = util_module.round_to_sig_fig(var_value, sig_figs_or_localize) |
|
| | if var_value == nil then |
| local frame = mw.getCurrentFrame()
| | -- Propagate error from utility module |
| -- The original template used {{Icon/Grey}}, which we can replicate with a span and filter for simplicity,
| | return "Error: Rounding failed for value." |
| -- or just output the icon directly if the filter isn't critical. Let's keep it simple.
| | end |
| local mainBox = string.format('%s %s',
| |
| icon, | |
| frame:expandTemplate{ title = "ValueAndUom", args = {
| |
| "'''" .. value .. "'''",
| |
| uom, | |
| uom_style = "font-size: calc(1em - 2px); color: #B2B2B2"
| |
| }}
| |
| )
| |
| | |
| local spiritScale = ''
| |
| if ss and ss ~= '' then
| |
| spiritScale = string.format(
| |
| '<div style="position: relative;"><div style="position: absolute; top: -24px; right: -14px">%s</div></div>', | |
| buildMultiplierHtml('spirit', ss, '28px')
| |
| ) | |
| end | | end |
|
| |
| return string.format(
| |
| '<div style="background-color: #2C2C2C; padding: 10px 6px 10px 6px; margin-right: 20px; white-space: nowrap; %s">%s</div>%s',
| |
| style or '', mainBox, spiritScale
| |
| )
| |
| end
| |
|
| |
| --- FIXED: Removed "overflow: hidden;" to prevent clipping.
| |
| local function buildMainBoxHtml(prop, hero_key)
| |
| local frame = mw.getCurrentFrame()
| |
| local scale_type = prop.Scale and prop.Scale.Type
| |
| local scale_value = prop.Scale and commonutils.round_to_sig_fig(prop.Scale.Value, 3)
| |
|
| |
| local scale_styles = {
| |
| melee = { grad = "radial-gradient(circle, rgba(36,37,36,1) 0%%, rgba(66,55,47,1) 100%%)", border = "#5b412b", shadow = "#3d2c4d" },
| |
| spirit = { grad = "radial-gradient(circle, rgba(42,41,43,1) 0%%, rgba(59,49,69,1) 100%%)", border = "#583D6F", shadow = "#3d2c4d" },
| |
| power_increase = { grad = "radial-gradient(circle, rgba(42,41,43,1) 0%%, rgba(61,78,70,1) 100%%)", border = "#48675a", shadow = "#3d2c4d" }
| |
| }
| |
| local current_style = scale_styles[scale_type] or { grad = "#2A2A2A", border = "#2A2A2A", shadow = "#2A2A2A" }
| |
|
| |
| local multiplierHtml = buildMultiplierHtml(scale_type, scale_value)
| |
|
| |
|
| local icon_data = get_icon(prop.Type) | | -- Handle optional localization |
| local value = prop.Value
| | if sig_figs_or_localize == "true" then |
| if scale_type == 'melee' then | | return lang_module.get_string(var_value) |
| local hero_data = mw.loadJsonData("Data:HeroData.json") | |
| local hero = hero_data[hero_key]
| |
| if hero and hero.LightMeleeDamage and prop.Scale.Value then
| |
| value = prop.Value + hero.LightMeleeDamage * prop.Scale.Value
| |
| end
| |
| end | | end |
|
| |
| local iconHtml = frame:expandTemplate{ title = 'Icon/' .. (icon_data.color or 'Grey'), args = {
| |
| string.format('[[File:%s|%s|link=%s]]', icon_data.img, icon_data.size or '18px', icon_data.link),
| |
| frame:expandTemplate{ title = 'ValueAndUom', args = {
| |
| "'''" .. value .. "'''",
| |
| id = prop.Key,
| |
| frame:expandTemplate{ title = 'Lang', args = { key = prop.Key .. '_postfix' } },
| |
| uom_style = 'font-size: calc(1em - 2px); color: #B2B2B2'
| |
| }}
| |
| }}
| |
|
| |
|
| return string.format( | | return var_value |
| -- REMOVED "overflow: hidden;" FROM THE FIRST DIV'S STYLE
| |
| '<div style="display:flex; flex-direction:column; flex-grow: 1; flex-basis: 0; max-height: 100%%; padding: 10px 0px 10px 0px; margin: 3px;">' ..
| |
| ' %s' ..
| |
| ' <div style="position: relative;"><div style="position: absolute; top: -20px; right: -2px">%s</div></div>' ..
| |
| ' <div style="display:flex; flex-direction:column; flex-grow: 1; justify-content: space-between; background: %s; text-align: center; border: 3px solid %s; box-shadow: 0 0 10px %s; border-radius: 1px">' ..
| |
| ' <div style="font-weight: bold;">%s</div>' ..
| |
| ' <div style="font-size: 0.8rem; padding-bottom: 3px">%s</div>' ..
| |
| ' </div>' ..
| |
| '</div>',
| |
| prop.Title and string.format('<div style="padding-bottom: 2px; font-size: 0.8rem; color: #9C9C9C; font-variant: small-caps;"><b>%s</b></div>', prop.Title) or '',
| |
| multiplierHtml,
| |
| current_style.grad,
| |
| current_style.border,
| |
| current_style.shadow,
| |
| iconHtml,
| |
| frame:expandTemplate{ title = 'Lang', args = { key = prop.Key .. '_label' }}
| |
| )
| |
| end | | end |
|
| |
|
| local buildAltBoxHtml = function(...) end -- Forward declare
| | --[[ |
| local buildUpgradeBoxHtml = function(...) end -- Forward declare
| | Retrieves an element from a list (array) within an NPC's data. |
| | | Usage: {{#invoke:NpcData|get_list_elem|NPC_NAME|LIST_NAME|INDEX|localize}} |
| -- ==================================================================== | | - NPC_NAME: The key of the NPC. |
| -- Main Entry Point (With Final Fixes) | | - LIST_NAME: The key for the list property (e.g., "NearbyEnemyResistanceValues"). |
| -- ====================================================================
| | - INDEX: The 1-based index of the element to retrieve. |
| | | - localize: (Optional) "true" to localize the element. |
| function p.get_ability_card(frame_args)
| | ]] |
| local frame = mw.getCurrentFrame() | | p.get_list_elem = function(frame) |
| local args = frame_args.args
| | -- Get arguments and trim whitespace |
| | | local npc_name = frame.args[1] and mw.text.trim(frame.args[1]) or nil |
| local hero_name = args[1]
| | local var = frame.args[2] and mw.text.trim(frame.args[2]) or nil |
| local ability_num = args[2] | | local number_str = frame.args[3] and mw.text.trim(frame.args[3]) or nil |
| local add_link = args[3] == 'true'
| | local localize = frame.args[4] and mw.text.trim(frame.args[4]) or nil |
| local notes = args[4] | |
|
| |
| local hero_key = get_hero_key(hero_name)
| |
| if not hero_key then return 'Hero with name "' .. tostring(hero_name) .. '" not found' end
| |
|
| |
| local ability = utils.get_ability_card_data(hero_key, ability_num) | |
| if not ability then return 'Ability data not found for hero ' .. tostring(hero_key) .. ' and num ' .. tostring(ability_num) end
| |
| | | |
| local html = {} | | -- Validate arguments |
| local ability_name_localized = lang.get_string(ability.Key) | | if not npc_name or npc_name == '' then return "Error: NPC name not provided." end |
| local name_link_target = add_link and ability_name_localized or nil | | if not var or var == '' then return "Error: List variable name not provided." end |
| | if not number_str or number_str == '' then return "Error: Index not provided." end |
| | | |
| -- Header Section
| | local number_int = tonumber(number_str) |
| local header_attrs_left = {}
| | if not number_int then return "Error: Invalid index provided. Must be a number." end |
| local cast_time_data = ability.Cast and ability.Cast.AbilityChannelTime | |
| table.insert(header_attrs_left, buildHeaderAttrHtml(cast_time_data and cast_time_data.Value, 's', get_attr_ss(cast_time_data), '[[File:AttributeIconMaxChargesIncrease.png|Channel Time|20px|link=]]'))
| |
| table.insert(header_attrs_left, buildHeaderAttrHtml(ability.AbilityCastRange and ability.AbilityCastRange.Value, 'm', get_attr_ss(ability.AbilityCastRange), '[[File:CastRange.png|Cast Range|20px|link=]]')) | |
| table.insert(header_attrs_left, buildHeaderAttrHtml(ability.Radius and ability.Radius.Value, 'm', get_attr_ss(ability.Radius), '[[File:AttributeIconTechRange.png|Radius|20px|link=]]'))
| |
| table.insert(header_attrs_left, buildHeaderAttrHtml(ability.AbilityDuration and ability.AbilityDuration.Value, 's', get_attr_ss(ability.AbilityDuration), '[[File:AttributeIconTechDuration.png|Duration|20px|link=]]'))
| |
|
| |
|
| local header_attrs_right_top = {} | | -- Retrieve NPC data |
| table.insert(header_attrs_right_top, buildHeaderAttrHtml(ability.AbilityCharges and ability.AbilityCharges.Value, '', nil, '[[File:AttributeIconMaxChargesIncrease.png|Number of Charges|20px|link=]]', 'margin-right: 0;')) | | local npc = npcs_data[npc_name] |
| table.insert(header_attrs_right_top, buildHeaderAttrHtml(ability.AbilityCooldownBetweenCharge and ability.AbilityCooldownBetweenCharge.Value, 's', get_attr_ss(ability.AbilityCooldownBetweenCharge), '[[File:AttributeIconTechCooldownBetweenChargeUses.png|Charge Cooldown|16px|link=]]', 'margin-left: 1px; margin-right: 0;'))
| | if not npc then return "Error: NPC '" .. npc_name .. "' not found." end |
| | | |
| local header_attrs_right_bottom = buildHeaderAttrHtml(ability.AbilityCooldown and ability.AbilityCooldown.Value, 's', get_attr_ss(ability.AbilityCooldown), '[[File:AttributeIconTechCooldown.png|Cooldown|20px|link=]]') | | -- Retrieve list and element |
| | | local list = npc[var] |
| table.insert(html, string.format( | | if list == nil or type(list) ~= "table" then return "" end -- Return empty string if list doesn't exist or isn't a table |
| '<div style="width: 100%%;"><div style="display:flex; flex-direction:row; justify-content: space-between; background: #121212; font-size: calc(1em - 2px); border-radius: 13px 13px 0 0; padding: 0 8px 14px 8px;">' ..
| |
| ' <div style="display:flex; flex-direction:column;">' ..
| |
| ' <div style="font-size: 1.3rem;"><span style="filter: brightness(0) saturate(100%%) invert(98%%) sepia(19%%) saturate(1458%%) hue-rotate(301deg) brightness(102%%) contrast(109%%); padding: 0 5px 0 7px">[[File:%s.png|45px|link=]]</span><span style="font-family:\'Retail Demo\',\'Open Sans\'; font-weight:bold">%s</span></div>' ..
| |
| ' <div style="display:flex; flex-direction:row; margin-left: 10px; height: 40px;">%s</div>' ..
| |
| ' </div>' ..
| |
| ' <div style="display:flex; flex-direction:column; align-items: end; justify-content: flex-end;">' ..
| |
| ' <div style="display:flex; flex-direction:row; justify-content: flex-end; margin-bottom: 5px; margin-right: 20px;">%s</div>' ..
| |
| ' %s' ..
| |
| ' </div>' ..
| |
| '</div></div>',
| |
| lang.get_string(ability.Key, 'en'),
| |
| name_link_target and string.format('[[%s|%s]]', name_link_target, ability_name_localized) or ability_name_localized,
| |
| table.concat(header_attrs_left),
| |
| table.concat(header_attrs_right_top),
| |
| header_attrs_right_bottom
| |
| ))
| |
| | | |
| -- Info Sections | | local element = list[number_int] |
| table.insert(html, '<div style="margin: 5px 10px 0px 10px; padding: 3px 0 5px 0; width: calc(100%% - 18px); box-sizing: unset;">')
| | if element == nil then return "" end -- Return empty string if element at index doesn't exist |
| for i = 1, 3 do
| |
| local info_section = ability['Info' .. i]
| |
| if info_section then
| |
| local desc = (info_section.DescKey and lang.get_string(info_section.DescKey)) or ''
| |
| local main_boxes, alt_boxes = {}, {}
| |
|
| |
| if info_section.Main and info_section.Main.Props then
| |
| for _, prop in ipairs(info_section.Main.Props) do
| |
| if prop.Value and prop.Value ~= 0 then
| |
| table.insert(main_boxes, buildMainBoxHtml(prop, hero_key))
| |
| end
| |
| end
| |
| end
| |
|
| |
| if info_section.Alt then
| |
| for _, prop in ipairs(info_section.Alt) do
| |
| if prop.Value and prop.Value ~= 0 then
| |
| table.insert(alt_boxes, buildAltBoxHtml(prop))
| |
| end
| |
| end
| |
| end
| |
| | |
| if desc ~= '' or #main_boxes > 0 or #alt_boxes > 0 then
| |
| local alt_box_container = ''
| |
| if #alt_boxes > 0 then
| |
| -- FIXED: Added the missing wrapper div for alt boxes.
| |
| alt_box_container = string.format(
| |
| '<div style="display:flex; flex-direction:row; flex-wrap: wrap; justify-content: center; align-items: center; background-color: #2A2A2A; width: calc(97%% - 19px); min-width: 267px; padding: 0 4px 0 4px; column-gap: 15px">%s</div>',
| |
| table.concat(alt_boxes)
| |
| )
| |
| end
| |
| | |
| table.insert(html, string.format(
| |
| '<div style="display:flex; flex-direction:column; align-items: center; width: 100%%; padding-top: 8px;">' ..
| |
| ' <div style="padding: 0 10px 10px 10px">%s</div>' ..
| |
| ' <div style="display:flex; flex-direction:row; flex-wrap: wrap; justify-content: center; width: calc(97%% - 5px); min-width: 280px;">%s</div>' ..
| |
| ' %s' ..
| |
| '</div>',
| |
| frame:preprocess(desc),
| |
| table.concat(main_boxes),
| |
| alt_box_container
| |
| ))
| |
| end
| |
| end
| |
| end
| |
| table.insert(html, '</div>')
| |
| | | |
| -- Upgrades Section | | if localize == "true" then |
| local upgrade_boxes = {}
| | element = lang_module.get_string(element) |
| if ability.Upgrades then
| |
| for i, prop in ipairs(ability.Upgrades) do | |
| table.insert(upgrade_boxes, buildUpgradeBoxHtml(prop, i))
| |
| end
| |
| end | | end |
| table.insert(html, string.format(
| |
| '<div style="display:flex; flex-direction:row; flex-wrap: wrap; margin-bottom: 5px; justify-content: center; width: calc(97%% - 16px); min-width: 280px;">%s</div>',
| |
| table.concat(upgrade_boxes)
| |
| ))
| |
| | | |
| -- Notes Section | | return element |
| if notes and notes ~= '' then
| | end |
| table.insert(html, string.format(
| |
| '<div style="align-self: flex-start; margin: 10px 0 10px 20px; font-size: 0.8rem;">'..
| |
| '<div><span style="font-size:1.15rem;">%s</span></div>' ..
| |
| '<div class="mw-collapsible mw-collapsed" style="margin-top: 5px;">' ..
| |
| '<div>%s</div>' ..
| |
| '</div></div>',
| |
| frame:callParserFunction( '#invoke', 'Dictionary', 'translate', 'Notes' ),
| |
| frame:preprocess(notes)
| |
| ))
| |
| end
| |
|
| |
|
| local final_card = string.format(
| | --[[ |
| '{{anchor|%s}}<div style="font-size: 0.9rem; line-height: 1.4; font-family: \'Retail Demo\', \'Open Sans\', \'PT Serif\', serif; color: #FFEFD7; display:flex; flex-direction:column; background: linear-gradient(90deg, rgba(52,52,52,1) 0%%, rgba(66,66,66,1) 14%%, rgba(77,77,77,1) 100%%); align-items: center; width: 100%%; max-width: 500px; border-radius: 13px 13px 4px 4px;">%s</div>',
| | Retrieves an ability key from an NPC's "BoundAbilities" map. |
| ability_name_localized,
| | Usage: {{#invoke:NpcData|get_ability_key|NPC_NAME|SLOT_NAME}} |
| table.concat(html, '\n')
| | - NPC_NAME: The key of the NPC. |
| ) | | - SLOT_NAME: The name of the ability slot (e.g., "ESlot_Signature_1"). |
| | | ]] |
| -- FIXED: Preprocess the entire output to render the anchor and other wikitext.
| | p.get_ability_key = function(frame) |
| return frame:preprocess(final_card) | | -- Get arguments and trim whitespace |
| end
| | local npc_name = frame.args[1] and mw.text.trim(frame.args[1]) or nil |
| | local slot_name = frame.args[2] and mw.text.trim(frame.args[2]) or nil |
|
| |
|
| -- We re-paste the remaining builder functions here so the module is complete in one block. | | -- Validate arguments |
| buildAltBoxHtml = function(prop)
| | if not npc_name or npc_name == '' then return "Error: NPC name not provided." end |
| local frame = mw.getCurrentFrame()
| | if not slot_name or slot_name == '' then return "Error: Slot name not provided." end |
| local scaleHtml = ''
| |
| if prop.Scale and prop.Scale.Type and prop.Scale.Value then | |
| scaleHtml = ' ' .. buildMultiplierHtml(prop.Scale.Type, commonutils.round_to_sig_fig(prop.Scale.Value, 3), '35px')
| |
| end
| |
|
| |
| local icon_data = get_icon(prop.Type)
| |
| local iconHtml = frame:expandTemplate{ title = 'Icon/' .. (icon_data.color or 'Grey'), args = {
| |
| string.format('[[File:%s|%s|link=%s]]', icon_data.img, icon_data.size or '18px', icon_data.link),
| |
| frame:expandTemplate{ title = 'ValueAndUom', args = {
| |
| "'''" .. prop.Value .. "'''",
| |
| id = prop.Key,
| |
| frame:expandTemplate{ title = 'Lang', args = { key = prop.Key .. '_postfix' } },
| |
| uom_style = 'font-size: 10px; color: #9C9C9C'
| |
| }}
| |
| }}
| |
|
| |
| return string.format(
| |
| '<div style="display:flex; flex-direction:row; flex-grow: 1; flex-basis: 0; justify-content: left; background-color: #2A2A2A; font-size: 0.85em; margin: 3px; text-align: center; box-shadow: 0 0 10px #2A2A2A; border-radius: 1px; white-space: nowrap">' ..
| |
| ' <span style="white-space: nowrap">%s <span style="font-size: 0.75rem; white-space: nowrap">%s%s</span></span>' ..
| |
| '</div>',
| |
| iconHtml,
| |
| frame:expandTemplate{ title = 'Lang', args = { key = prop.Key .. '_label' }},
| |
| scaleHtml
| |
| )
| |
| end | |
|
| |
|
| UPGRADE_COST_MAP = {1, 2, 5}
| | -- Retrieve NPC data |
| buildUpgradeBoxHtml = function(prop, index)
| | local npc_data = npcs_data[npc_name] |
| local frame = mw.getCurrentFrame() | | if not npc_data then return "Error: NPC '"..npc_name.. "' not found." end |
| local description = lang.get_string(prop.DescKey) | |
|
| |
| if (description == nil or description == '') then | |
| local desc_parts = {}
| |
| local seen = {}
| |
| for k, v in pairs(prop) do
| |
| if type(v) ~= 'table' then
| |
| local formatted_value = utils.format_value_with_prepost(k, v, frame)
| |
| local attr_name = lang.get_string(k..'_label')
| |
| local key = formatted_value .. '|' .. attr_name
| |
| if not seen[key] then
| |
| table.insert(desc_parts, string.format('%s %s', formatted_value, attr_name))
| |
| seen[key] = true
| |
| end
| |
| end
| |
| end
| |
| description = table.concat(desc_parts, '<br>')
| |
| end
| |
|
| |
|
| local d_len = mw.ustring.len(description) | | -- Retrieve ability data |
| local fontsize = '1em' | | local bound_abilities_data = npc_data["BoundAbilities"] |
| if d_len > 60 and d_len < 71 then fontsize = '0.95rem' | | if not bound_abilities_data or type(bound_abilities_data) ~= "table" then |
| elseif d_len > 70 and d_len < 91 then fontsize = '0.875rem'
| | return "Error: NPC '" .. npc_name .. "' has no 'BoundAbilities' data." |
| elseif d_len > 90 then fontsize = '0.8rem'
| |
| end | | end |
|
| |
|
| local scaleHtml = '' | | -- Retrieve ability key from the slot |
| if prop.Scale and prop.Scale.Type and prop.Scale.Value then
| | local ability_key = bound_abilities_data[slot_name] |
| scaleHtml = buildMultiplierHtml(prop.Scale.Type, commonutils.round_to_sig_fig(prop.Scale.Value, 3))
| | if not ability_key then |
| | return "Error: Slot '" .. slot_name .. "' not found for NPC '" .. npc_name .. "'." |
| end | | end |
|
| | |
| return string.format( | | return ability_key |
| '<div style="display:flex; flex-direction:column; flex-grow: 1; flex-basis: 0; justify-content: flex-start; max-height: 140px; background-color: #555555; padding: 0 0 20px 0px; margin: 6px; text-align: center; box-shadow: 0 0 10px #2A2A2A; border-radius: 5px">' ..
| |
| ' <div style="padding: 2px 0 0 2px; font-family:Retail Demo bold; font-size: clamp(0.70em, 2vw, 1em); background-color: #121212; border-radius: 5px 5px 0 0; ">%s</div>' ..
| |
| ' <div style="padding: 10px 4px 4px 4px; font-size: clamp(0.70em, 2vw, %s);">%s</div>' ..
| |
| ' <div>%s</div>' ..
| |
| '</div>',
| |
| frame:expandTemplate{ title = 'Ap', args = { UPGRADE_COST_MAP[index], icon_size = '15px' } },
| |
| fontsize,
| |
| frame:preprocess(description),
| |
| scaleHtml
| |
| )
| |
| end | | end |
|
| |
|
| return p | | return p |