Module:AbilityTable/ComplexRenderers: Difference between revisionsGive feedback
Jump to navigation
Jump to search
update renderer comments, remove unused GameData require, extract find_base_melee_scale to module level (with help from vergir-bot LLM) |
strip trailing periods from auto-generated notes, period is now handled by main module ensure_period (with help from vergir-bot LLM) |
||
| Line 119: | Line 119: | ||
-- additively through upgrades. Shows value in a named column | -- additively through upgrades. Shows value in a named column | ||
-- and generates upgrade notes with running totals. | -- and generates upgrade notes with running totals. | ||
-- Note: auto-generated notes omit trailing periods; the main | |||
-- module's ensure_period handles that. | |||
-- ============================================================ | -- ============================================================ | ||
| Line 152: | Line 154: | ||
table.insert(notes, "T" .. i .. " upgrade increases " | table.insert(notes, "T" .. i .. " upgrade increases " | ||
.. col_name:lower() .. " by " .. delta .. suffix | .. col_name:lower() .. " by " .. delta .. suffix | ||
.. " (total " .. total .. suffix .. ") | .. " (total " .. total .. suffix .. ")") | ||
running = total | running = total | ||
end | end | ||
| Line 205: | Line 207: | ||
local charge_str = n .. " charge" .. (n ~= 1 and "s" or "") | local charge_str = n .. " charge" .. (n ~= 1 and "s" or "") | ||
if has_base then | if has_base then | ||
table.insert(note_parts, "+" .. charge_str .. " on T" .. upgrade_info.tier .. " upgrade | table.insert(note_parts, "+" .. charge_str .. " on T" .. upgrade_info.tier .. " upgrade") | ||
else | else | ||
local cd_str = upgrade_cooldown and (upgrade_cooldown .. "s") or "unknown" | local cd_str = upgrade_cooldown and (upgrade_cooldown .. "s") or "unknown" | ||
table.insert(note_parts, "Becomes charged on T" .. upgrade_info.tier | table.insert(note_parts, "Becomes charged on T" .. upgrade_info.tier | ||
.. " upgrade with " .. charge_str | .. " upgrade with " .. charge_str | ||
.. " and " .. cd_str .. " time between charges | .. " and " .. cd_str .. " time between charges") | ||
end | end | ||
end | end | ||
if ability["AbilityChargesConditionally"] ~= nil then | if ability["AbilityChargesConditionally"] ~= nil then | ||
table.insert(note_parts, "Has a conditional charge | table.insert(note_parts, "Has a conditional charge") | ||
end | end | ||
if #note_parts > 0 then | if #note_parts > 0 then | ||
| Line 243: | Line 245: | ||
if stype == "heavy_melee" and delta > 0 then | if stype == "heavy_melee" and delta > 0 then | ||
table.insert(notes, "T" .. i .. " upgrade changes scaling to ×" | table.insert(notes, "T" .. i .. " upgrade changes scaling to ×" | ||
.. delta .. " of Heavy Melee damage | .. delta .. " of Heavy Melee damage") | ||
elseif stype == "melee" and delta ~= 0 and base_scale then | elseif stype == "melee" and delta ~= 0 and base_scale then | ||
local final = math.floor((base_scale + delta) * 1000 + 0.5) / 1000 | local final = math.floor((base_scale + delta) * 1000 + 0.5) / 1000 | ||
table.insert(notes, "T" .. i .. " upgrade adds ×" | table.insert(notes, "T" .. i .. " upgrade adds ×" | ||
.. delta .. " scaling (total ×" .. final .. ") | .. delta .. " scaling (total ×" .. final .. ")") | ||
end | end | ||
end | end | ||
| Line 344: | Line 346: | ||
local channel_slow = unwrap_value(ability["ChannelSlowPercent"]) | local channel_slow = unwrap_value(ability["ChannelSlowPercent"]) | ||
if channel_slow and channel_slow ~= 0 then | if channel_slow and channel_slow ~= 0 then | ||
local note = "Also slows the caster for " .. math.abs(channel_slow) .. "% | local note = "Also slows the caster for " .. math.abs(channel_slow) .. "%" | ||
cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note | cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note | ||
end | end | ||
| Line 400: | Line 402: | ||
if caster_dash_slow then | if caster_dash_slow then | ||
local note = "Gives " .. caster_dash_slow .. "% dash slow for the caster | local note = "Gives " .. caster_dash_slow .. "% dash slow for the caster" | ||
cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note | cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note | ||
end | end | ||
Revision as of 13:09, 29 May 2026
Documentation for this module may be created at Module:AbilityTable/ComplexRenderers/doc
-- Complex cell renderers for AbilityTable.
local Lists = require("Module:AbilityTable/Lists")
-- ============================================================
-- Shared helpers
-- ============================================================
-- Unwraps a value that may be a raw number or a { Value = N } table.
local function unwrap_value(v)
if type(v) == "number" then return v end
if type(v) == "table" then return v["Value"] end
return nil
end
-- Searches Upgrades[] for a field and returns { tier = N, value = V } at its
-- first appearance, or nil if the field is absent from all upgrade tiers.
local function find_in_upgrades(ability, field)
local upgrades = ability["Upgrades"]
if type(upgrades) ~= "table" then return nil end
for i, tier in ipairs(upgrades) do
if type(tier) == "table" and tier[field] ~= nil then
return { tier = i, value = unwrap_value(tier[field]) }
end
end
return nil
end
-- Finds the first non-zero value for any of the given fields, checking base
-- then upgrades in order. Returns field, tier (nil = base), absolute value.
local function find_first_occurrence(ability, fields)
for _, field in ipairs(fields) do
local v = unwrap_value(ability[field])
if v and v ~= 0 then return field, nil, math.abs(v) end
end
local upgrades = ability["Upgrades"]
if type(upgrades) == "table" then
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
for _, field in ipairs(fields) do
local v = unwrap_value(tier[field])
if v and v ~= 0 then return field, i, math.abs(v) end
end
end
end
end
return nil, nil, nil
end
-- Returns true if prop exists with a non-zero value anywhere in the record
-- excluding the Upgrades key.
local function is_base_property(record, prop)
for k, v in pairs(record) do
if k == "Upgrades" then
-- skip
elseif k == prop then
if type(v) == "table" then v = v["Value"] end
if v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" then
return true
end
elseif type(v) == "table" then
if is_base_property(v, prop) then return true end
elseif type(v) == "string" then
if v == prop then return true end
end
end
return false
end
-- Finds the base-level melee scale value by scanning for Scale.Type == "melee".
local function find_base_melee_scale(ability)
for _, v in pairs(ability) do
if type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table"
and scale["Type"] == "melee"
and type(scale["Value"]) == "number"
and scale["Value"] > 0
then
return scale["Value"]
end
end
end
return nil
end
-- ============================================================
-- Debuff short names (used by RenderDebuff)
-- ============================================================
local debuff_short_name = {
["StunDuration"] = "stun",
["SleepDuration"] = "sleep",
["SilenceDuration"] = "silence",
["SilenceDebuff"] = "silence",
["PetrifyDuration"] = "petrify",
["HexDuration"] = "hex",
["ImmobilizeDuration"] = "immobilize",
["LiftDuration"] = "lift",
["HoldInPlaceDuration"] = "immobilize",
["RestrictionDuration"] = "immobilize",
["SlowPercent"] = "slow",
["EnemySlowPct"] = "slow",
["MoveSlowPercent"] = "slow",
["GroundDashReductionPercent"] = "dash slow",
["SlowDuration"] = "slow",
["MaxSlowDuration"] = "slow",
["BulletDamageAmpDuration"] = "bullet amp",
["BulletResistReduction"] = "bullet resist reduction",
["TimeSlowDuration"] = "time slow",
["LateCheckoutStun"] = "stun",
["BurnDuration"] = "burn",
["BleedDuration"] = "bleed",
["VenomDuration"] = "venom",
["TossDuration"] = "knockback",
}
-- ============================================================
-- Generic renderer for a single numeric stat that can increase
-- additively through upgrades. Shows value in a named column
-- and generates upgrade notes with running totals.
-- Note: auto-generated notes omit trailing periods; the main
-- module's ensure_period handles that.
-- ============================================================
local function render_upgradable_stat(ability, col_name, fields, suffix)
suffix = suffix or ""
local cells = {}
local notes = {}
local first_field, first_tier, first_val = find_first_occurrence(ability, fields)
if not first_field then return cells end
local cell_str = tostring(first_val) .. suffix
if first_tier then cell_str = cell_str .. " (T" .. first_tier .. ")" end
cells[col_name] = cell_str
local upgrades = ability["Upgrades"]
if type(upgrades) == "table" then
local running = first_val
local seen_first = (first_tier == nil)
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
if first_tier == i then
seen_first = true
elseif seen_first then
local delta = nil
for _, field in ipairs(fields) do
local v = unwrap_value(tier[field])
if v and v ~= 0 then delta = math.abs(v); break end
end
if delta then
local total = running + delta
table.insert(notes, "T" .. i .. " upgrade increases "
.. col_name:lower() .. " by " .. delta .. suffix
.. " (total " .. total .. suffix .. ")")
running = total
end
end
end
end
end
if #notes > 0 then cells["Notes"] = table.concat(notes, " ") end
return cells
end
-- ============================================================
-- Exported renderers
-- ============================================================
local p = {}
-- Renders charge count and time between charges.
-- Special case for abilities that gain charges only through upgrades.
function p.RenderCharges(ability)
local base_charges = unwrap_value(ability["AbilityCharges"])
local base_cooldown = ability["AbilityCooldownBetweenCharge"]
local has_base = base_charges ~= nil and base_charges > 0
local upgrade_info = find_in_upgrades(ability, "AbilityCharges")
local cooldown, upgrade_cooldown
if has_base then
if type(base_cooldown) == "number" and base_cooldown > 0 then
cooldown = base_cooldown
else
local cd = find_in_upgrades(ability, "AbilityCooldownBetweenCharge")
if cd and cd.value and cd.value > 0 then cooldown = cd.value end
end
else
local cd = find_in_upgrades(ability, "AbilityCooldownBetweenCharge")
if cd and cd.value and cd.value > 0 then upgrade_cooldown = cd.value end
end
local cells = {}
if has_base then
cells["Charges"] = tostring(base_charges)
if cooldown then
cells["Time Between Charges"] = tostring(cooldown) .. "s"
end
end
local note_parts = {}
if upgrade_info then
local n = upgrade_info.value or "?"
local charge_str = n .. " charge" .. (n ~= 1 and "s" or "")
if has_base then
table.insert(note_parts, "+" .. charge_str .. " on T" .. upgrade_info.tier .. " upgrade")
else
local cd_str = upgrade_cooldown and (upgrade_cooldown .. "s") or "unknown"
table.insert(note_parts, "Becomes charged on T" .. upgrade_info.tier
.. " upgrade with " .. charge_str
.. " and " .. cd_str .. " time between charges")
end
end
if ability["AbilityChargesConditionally"] ~= nil then
table.insert(note_parts, "Has a conditional charge")
end
if #note_parts > 0 then
cells["Notes"] = table.concat(note_parts, " ")
end
return cells
end
-- Renders light melee scaling.
-- Special case for upgrades that change to heavy melee scaling.
function p.RenderMelee(ability)
local cells = {}
local base_scale = find_base_melee_scale(ability)
if base_scale then cells["Light Melee Scaling"] = "×" .. base_scale end
local upgrades = ability["Upgrades"]
if type(upgrades) ~= "table" then return cells end
local notes = {}
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
for _, v in pairs(tier) do
if type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and type(scale["Value"]) == "number" then
local stype = scale["Type"]
local delta = scale["Value"]
if stype == "heavy_melee" and delta > 0 then
table.insert(notes, "T" .. i .. " upgrade changes scaling to ×"
.. delta .. " of Heavy Melee damage")
elseif stype == "melee" and delta ~= 0 and base_scale then
local final = math.floor((base_scale + delta) * 1000 + 0.5) / 1000
table.insert(notes, "T" .. i .. " upgrade adds ×"
.. delta .. " scaling (total ×" .. final .. ")")
end
end
end
end
end
end
if #notes > 0 then cells["Notes"] = table.concat(notes, " ") end
return cells
end
-- Renders heal reduction percentage.
-- Special case for DisableHealing, shown as 100%.
function p.RenderHealReduce(ability)
-- DisableHealing is always 100%, handle it separately
local v = unwrap_value(ability["DisableHealing"])
if v and v ~= 0 then
return { ["Heal Reduction"] = "100%" }
end
local upgrade = find_in_upgrades(ability, "DisableHealing")
if upgrade then
return { ["Heal Reduction"] = "100% (T" .. upgrade.tier .. ")" }
end
-- Everything else goes through the generic renderer
local filtered = {}
for _, prop in ipairs(Lists.lists["healreduce"]) do
if prop ~= "DisableHealing" then table.insert(filtered, prop) end
end
return render_upgradable_stat(ability, "Heal Reduction", filtered, "%")
end
-- Shows a note if an ability acquires a debuff via upgrades.
-- Shared by debuffresist and dispelmagic.
function p.RenderDebuff(ability, stat)
local stat_props = Lists.lists[stat]
local cells = {}
-- If any prop exists in base, this is a base-level debuff — no note needed.
for _, prop in ipairs(stat_props) do
if is_base_property(ability, prop) then
return cells
end
end
local upgrades = ability["Upgrades"]
if type(upgrades) ~= "table" then return cells end
local tier_debuffs = {}
local seen_names = {}
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
local names_this_tier = {}
local seen_this_tier = {}
for _, prop in ipairs(stat_props) do
local v = tier[prop]
if v ~= nil then
if type(v) == "table" then v = v["Value"] end
if v ~= nil and v ~= 0 and v ~= "" and v ~= "0" and v ~= "0m" then
local short = debuff_short_name[prop] or "debuff"
if not seen_this_tier[short] then
seen_this_tier[short] = true
if not seen_names[short] then
seen_names[short] = true
table.insert(names_this_tier, short)
end
end
end
end
end
if #names_this_tier > 0 then
tier_debuffs[i] = names_this_tier
end
end
end
local tier_indices = {}
for i in pairs(tier_debuffs) do table.insert(tier_indices, i) end
table.sort(tier_indices)
local parts = {}
for _, i in ipairs(tier_indices) do
table.insert(parts, "T" .. i .. " " .. table.concat(tier_debuffs[i], " + "))
end
if #parts > 0 then cells["Notes"] = table.concat(parts, ", ") end
return cells
end
-- Renders move slow percentage.
-- Appends a note if the ability also self-slows the caster.
function p.RenderMoveSlow(ability)
local cells = render_upgradable_stat(ability, "Move Slow", Lists.lists["moveslow"], "%")
local channel_slow = unwrap_value(ability["ChannelSlowPercent"])
if channel_slow and channel_slow ~= 0 then
local note = "Also slows the caster for " .. math.abs(channel_slow) .. "%"
cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note
end
return cells
end
-- Renders dash slow percentage.
-- Special case for GroundDashReductionPercent coexisting with another dashslow
-- prop, which means it's a caster self-slow and is noted separately.
function p.RenderDashSlow(ability)
local stat_props = Lists.lists["dashslow"]
local props = {}
for _, pr in ipairs(stat_props) do table.insert(props, pr) end
local caster_dash_slow = nil
local base_ground = unwrap_value(ability["GroundDashReductionPercent"])
if base_ground and base_ground ~= 0 then
local has_enemy_prop = false
for _, prop in ipairs(stat_props) do
if prop ~= "GroundDashReductionPercent" then
local v = unwrap_value(ability[prop])
if v and v ~= 0 then has_enemy_prop = true; break end
end
end
if not has_enemy_prop then
local upgrades = ability["Upgrades"]
if type(upgrades) == "table" then
for _, tier in ipairs(upgrades) do
if type(tier) == "table" then
for _, prop in ipairs(stat_props) do
if prop ~= "GroundDashReductionPercent" then
local v = unwrap_value(tier[prop])
if v and v ~= 0 then has_enemy_prop = true; break end
end
end
end
if has_enemy_prop then break end
end
end
end
if has_enemy_prop then
caster_dash_slow = math.abs(base_ground)
local filtered = {}
for _, prop in ipairs(props) do
if prop ~= "GroundDashReductionPercent" then
table.insert(filtered, prop)
end
end
props = filtered
end
end
local cells = render_upgradable_stat(ability, "Dash Slow", props, "%")
if caster_dash_slow then
local note = "Gives " .. caster_dash_slow .. "% dash slow for the caster"
cells["Notes"] = cells["Notes"] and (cells["Notes"] .. "; " .. note) or note
end
return cells
end
return p