Module:AbilityTable/ComplexRenderers: Difference between revisionsGive feedback
Jump to navigation
Jump to search
add base damage column to melee list |
add extra columns rendererer to heavymelee list |
||
| Line 321: | Line 321: | ||
if #notes > 0 then cells["Notes"] = table.concat(notes, ". ") end | if #notes > 0 then cells["Notes"] = table.concat(notes, ". ") end | ||
return cells | |||
end | |||
-- Renders base damage and heavy melee scaling. | |||
-- Notes when heavy melee is only available via upgrade. | |||
function p.RenderHeavyMelee(ability) | |||
-- Find base heavy_melee property | |||
local base_damage, base_scale | |||
for k, v in pairs(ability) do | |||
if k ~= "Upgrades" and type(v) == "table" then | |||
local scale = v["Scale"] | |||
if type(scale) == "table" and scale["Type"] == "heavy_melee" | |||
and type(scale["Value"]) == "number" then | |||
base_damage = v["Value"] or 0 | |||
base_scale = scale["Value"] | |||
break | |||
end | |||
end | |||
end | |||
local cells = {} | |||
-- Active at base | |||
if base_scale and base_scale > 0 then | |||
cells["Base Damage"] = tostring(base_damage) | |||
cells["Heavy Melee Scaling"] = "×" .. base_scale | |||
return cells | |||
end | |||
-- Not at base — find in upgrades | |||
local upgrades = ability["Upgrades"] | |||
if type(upgrades) ~= "table" then return cells end | |||
for i, tier in ipairs(upgrades) do | |||
if type(tier) == "table" then | |||
for k, v in pairs(tier) do | |||
if type(v) == "table" then | |||
local scale = v["Scale"] | |||
if type(scale) == "table" and scale["Type"] == "heavy_melee" | |||
and type(scale["Value"]) == "number" and scale["Value"] > 0 then | |||
cells["Base Damage"] = tostring(v["Value"] or 0) | |||
cells["Heavy Melee Scaling"] = "×" .. scale["Value"] | |||
cells["Notes"] = "Only becomes 'eavy Melee on '''T" .. i .. "''' upgrade" | |||
return cells | |||
end | |||
end | |||
end | |||
end | |||
end | |||
return cells | |||
end | |||
-- Renders base damage and heavy melee scaling. | |||
-- Notes when heavy melee is only available via upgrade. | |||
function p.RenderHeavyMelee(ability) | |||
-- Find base heavy_melee property | |||
local base_damage, base_scale | |||
for k, v in pairs(ability) do | |||
if k ~= "Upgrades" and type(v) == "table" then | |||
local scale = v["Scale"] | |||
if type(scale) == "table" and scale["Type"] == "heavy_melee" | |||
and type(scale["Value"]) == "number" then | |||
base_damage = v["Value"] or 0 | |||
base_scale = scale["Value"] | |||
break | |||
end | |||
end | |||
end | |||
local cells = {} | |||
-- Active at base | |||
if base_scale and base_scale > 0 then | |||
cells["Base Damage"] = tostring(base_damage) | |||
cells["Heavy Melee Scaling"] = "×" .. base_scale | |||
return cells | |||
end | |||
-- Not at base — find in upgrades | |||
local upgrades = ability["Upgrades"] | |||
if type(upgrades) ~= "table" then return cells end | |||
for i, tier in ipairs(upgrades) do | |||
if type(tier) == "table" then | |||
for k, v in pairs(tier) do | |||
if type(v) == "table" then | |||
local scale = v["Scale"] | |||
if type(scale) == "table" and scale["Type"] == "heavy_melee" | |||
and type(scale["Value"]) == "number" and scale["Value"] > 0 then | |||
cells["Base Damage"] = tostring(v["Value"] or 0) | |||
cells["Heavy Melee Scaling"] = "×" .. scale["Value"] | |||
cells["Notes"] = "Only becomes '''Heavy Melee''' on T" .. i .. " upgrade" | |||
return cells | |||
end | |||
end | |||
end | |||
end | |||
end | |||
return cells | return cells | ||
end | end | ||
Revision as of 15:02, 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 base damage and melee scaling.
-- Special case for upgrades that switch from Light to Heavy Melee scaling.
function p.RenderMelee(ability)
local melee_types = {}
for _, t in ipairs(Lists.lists["melee"]) do melee_types[t] = true end
-- Find the base property with active melee/heavy_melee scaling (Scale.Value > 0)
local base_prop, base_damage, base_scale, base_type
for k, v in pairs(ability) do
if k ~= "Upgrades" and type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and melee_types[scale["Type"]]
and type(scale["Value"]) == "number" and scale["Value"] > 0 then
base_prop = k
base_damage = v["Value"] or 0
base_scale = scale["Value"]
base_type = scale["Type"]
break
end
end
end
local cells = {}
cells["Base Damage"] = tostring(base_damage or 0)
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 = {}
local running_damage = base_damage or 0
local running_scale = base_scale or 0
local running_type = base_type
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
local damage_delta = 0
local new_scale = nil
local new_type = nil
for k, v in pairs(tier) do
if type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and melee_types[scale["Type"]] then
damage_delta = damage_delta + (v["Value"] or 0)
if scale["Value"] > 0 then
new_scale = scale["Value"]
new_type = scale["Type"]
end
end
end
end
local flat_delta = 0
if base_prop and type(tier[base_prop]) == "number" then
flat_delta = tier[base_prop]
end
local tier_parts = {}
if new_type and new_type ~= running_type then
running_damage = running_damage + damage_delta
running_scale = new_scale
running_type = new_type
local type_label = new_type == "heavy_melee" and "'''Heavy Melee'''" or "Light Melee"
local parts = {}
if running_damage > 0 then
table.insert(parts, running_damage .. " base damage")
end
table.insert(parts, "×" .. new_scale .. " " .. type_label .. " scaling")
table.insert(tier_parts, "on T" .. i .. " becomes " .. table.concat(parts, " with "))
elseif new_scale then
running_damage = running_damage + damage_delta
running_scale = running_scale + new_scale
local parts = {}
if damage_delta ~= 0 then
table.insert(parts, "+" .. damage_delta .. " base damage")
end
table.insert(parts, "+×" .. new_scale .. " scaling (total ×" .. running_scale .. ")")
table.insert(tier_parts, "on T" .. i .. " gets " .. table.concat(parts, ", "))
end
if flat_delta ~= 0 and not new_scale then
running_damage = running_damage + flat_delta
table.insert(tier_parts, "on T" .. i .. " gets +" .. flat_delta .. " base damage (total " .. running_damage .. ")")
end
if #tier_parts > 0 then
table.insert(notes, table.concat(tier_parts, ", "))
end
end
end
if #notes > 0 then cells["Notes"] = table.concat(notes, ". ") end
return cells
end
-- Renders base damage and heavy melee scaling.
-- Notes when heavy melee is only available via upgrade.
function p.RenderHeavyMelee(ability)
-- Find base heavy_melee property
local base_damage, base_scale
for k, v in pairs(ability) do
if k ~= "Upgrades" and type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and scale["Type"] == "heavy_melee"
and type(scale["Value"]) == "number" then
base_damage = v["Value"] or 0
base_scale = scale["Value"]
break
end
end
end
local cells = {}
-- Active at base
if base_scale and base_scale > 0 then
cells["Base Damage"] = tostring(base_damage)
cells["Heavy Melee Scaling"] = "×" .. base_scale
return cells
end
-- Not at base — find in upgrades
local upgrades = ability["Upgrades"]
if type(upgrades) ~= "table" then return cells end
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
for k, v in pairs(tier) do
if type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and scale["Type"] == "heavy_melee"
and type(scale["Value"]) == "number" and scale["Value"] > 0 then
cells["Base Damage"] = tostring(v["Value"] or 0)
cells["Heavy Melee Scaling"] = "×" .. scale["Value"]
cells["Notes"] = "Only becomes 'eavy Melee on '''T" .. i .. "''' upgrade"
return cells
end
end
end
end
end
return cells
end
-- Renders base damage and heavy melee scaling.
-- Notes when heavy melee is only available via upgrade.
function p.RenderHeavyMelee(ability)
-- Find base heavy_melee property
local base_damage, base_scale
for k, v in pairs(ability) do
if k ~= "Upgrades" and type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and scale["Type"] == "heavy_melee"
and type(scale["Value"]) == "number" then
base_damage = v["Value"] or 0
base_scale = scale["Value"]
break
end
end
end
local cells = {}
-- Active at base
if base_scale and base_scale > 0 then
cells["Base Damage"] = tostring(base_damage)
cells["Heavy Melee Scaling"] = "×" .. base_scale
return cells
end
-- Not at base — find in upgrades
local upgrades = ability["Upgrades"]
if type(upgrades) ~= "table" then return cells end
for i, tier in ipairs(upgrades) do
if type(tier) == "table" then
for k, v in pairs(tier) do
if type(v) == "table" then
local scale = v["Scale"]
if type(scale) == "table" and scale["Type"] == "heavy_melee"
and type(scale["Value"]) == "number" and scale["Value"] > 0 then
cells["Base Damage"] = tostring(v["Value"] or 0)
cells["Heavy Melee Scaling"] = "×" .. scale["Value"]
cells["Notes"] = "Only becomes '''Heavy Melee''' on T" .. i .. " upgrade"
return cells
end
end
end
end
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