Module:StreetBrawlAbilities: Difference between revisions

From The Deadlock Wiki
Jump to navigation Jump to search
Add change() to describe a Street Brawl property change as reduced/increased by a percentage (with help from vergir-bot LLM)
Vergir (talk | contribs)
Round change output to nearest 5% step, prefixing ~ when rounding exceeds 1.5% (with help from vergir-bot LLM)
Line 84: Line 84:


-- {{#invoke:StreetBrawlAbilities|change|ABILITY|PROP}} describes how far a property moved
-- {{#invoke:StreetBrawlAbilities|change|ABILITY|PROP}} describes how far a property moved
-- in Street Brawl, as "reduced by 45%" or "increased by 44%".
-- in Street Brawl, as "reduced by 45%" or "increased by ~40%".
function p.change(frame)
function p.change(frame)
local ability = mw.text.trim(frame.args[1] or "")
local ability = mw.text.trim(frame.args[1] or "")
Line 104: Line 104:
local direction = sb > base and "increased" or "reduced"
local direction = sb > base and "increased" or "reduced"
local percent = math.abs(sb - base) / math.abs(base) * 100
local percent = math.abs(sb - base) / math.abs(base) * 100
return direction .. " by " .. string.format("%.0f", percent) .. "%"
-- Rounded to the nearest 5% so the prose reads as a round figure; when that rounding
-- moves the number by more than 1.5 points, "~" marks it as approximate.
local rounded = math.floor(percent / 5 + 0.5) * 5
local approx = math.abs(percent - rounded) > 1.5 and "~" or ""
return direction .. " by " .. approx .. string.format("%.0f", rounded) .. "%"
end
end



Revision as of 10:24, 29 July 2026

Documentation for this module may be created at Module:StreetBrawlAbilities/doc

-- Lists the Street Brawl ability overrides from [[Data:StreetBrawlData.json]] with the
-- {{DataProp}} call that cites each one and the base value it replaces.

local p = {}

local SB_PAGE    = "Data:StreetBrawlData.json"
local CARDS_PAGE = "Data:AbilityCards.json"
local HERO_PAGE  = "Data:HeroData.json"
local UNMATCHED  = "Unmatched abilities"

-- Maps each ability key to the hero who owns it.
local function build_owner_map()
	local cards  = mw.loadJsonData(CARDS_PAGE)
	local heroes = mw.loadJsonData(HERO_PAGE)
	local map = {}
	for hero_key, card in pairs(cards) do
		local hero = heroes[hero_key]
		-- Unreleased hero stubs reuse released heroes' ability keys (hero_gunslinger
		-- carries Paradox's Pulse Grenade), so only live hero records may claim one.
		if type(card) == "table" and hero and hero.Name and hero.IsDisabled ~= true then
			for slot = 1, 8 do
				-- The card's slot keys decode as numbers, so index with one.
				local ability = card[slot] or card[tostring(slot)]
				if type(ability) == "table" and ability.Key then
					map[ability.Key] = { hero = hero.Name, slot = slot, name = ability.Name }
				end
			end
		end
	end
	return map
end

-- Orders one node's keys: plain props before Upgrades, Value before Scale, tiers numerically.
local function sorted_keys(node, top)
	local keys = {}
	for k in pairs(node) do
		-- Name labels the record; it is not one of the changed properties.
		if not (top and k == "Name") then keys[#keys + 1] = k end
	end
	table.sort(keys, function(a, b)
		if top then
			if a == "Upgrades" then return false end
			if b == "Upgrades" then return true end
		end
		if a == "Value" then return true end
		if b == "Value" then return false end
		local na, nb = tonumber(a), tonumber(b)
		if na and nb then return na < nb end
		-- Upgrade tiers arrive as numbers, so never compare a number with a string.
		return tostring(a) < tostring(b)
	end)
	return keys
end

-- Collects the changed properties as dot-notation paths, the form {{DataProp}} accepts.
local function flatten(node, prefix, out, top)
	for _, key in ipairs(sorted_keys(node, top)) do
		local value = node[key]
		local path = prefix and (prefix .. "." .. key) or tostring(key)
		if type(value) == "table" then
			flatten(value, path, out, false)
		else
			out[#out + 1] = path
		end
	end
end

-- Reads a property both ways, returning its Street Brawl value and its normal one.
local function both_values(frame, ability, prop)
	local sb   = frame:expandTemplate{ title = "DataProp", args = { ability, prop, "streetbrawl" } }
	local base = frame:expandTemplate{ title = "DataProp", args = { ability, prop } }
	return tonumber(sb), tonumber(base)
end

-- Builds one property line: the wikitext to copy, its Street Brawl value, and the default.
local function render_line(frame, ability, path)
	local call = "{{DataProp|" .. ability .. "|" .. path .. "|streetbrawl}}"
	-- Expanded rather than read from the JSON, so this shows what a copied call really gives.
	local sb   = frame:expandTemplate{ title = "DataProp", args = { ability, path, "streetbrawl" } }
	local base = frame:expandTemplate{ title = "DataProp", args = { ability, path } }
	return "* <code>" .. mw.text.nowiki(call) .. "</code> &rarr; '''" .. sb
		.. "''' ''(default " .. base .. ")''"
end

-- {{#invoke:StreetBrawlAbilities|change|ABILITY|PROP}} describes how far a property moved
-- in Street Brawl, as "reduced by 45%" or "increased by ~40%".
function p.change(frame)
	local ability = mw.text.trim(frame.args[1] or "")
	local prop    = mw.text.trim(frame.args[2] or "")
	local sb, base = both_values(frame, ability, prop)

	-- DataProp answers a failed lookup with a red notice rather than a number.
	if not sb or not base or base == 0 then
		return '<span style="color:red;">No change for ' .. ability .. '/' .. prop .. '</span>'
	end
	-- Properties Street Brawl leaves alone fall back to the base value, so two equal
	-- numbers mean this property was never in the overlay.
	if sb == base then
		return '<span style="color:red;">Street Brawl does not change ' .. ability .. '/' .. prop .. '</span>'
	end

	-- Purely a comparison of the two numbers; whether that helps or hurts the hero is
	-- for the surrounding prose to say.
	local direction = sb > base and "increased" or "reduced"
	local percent = math.abs(sb - base) / math.abs(base) * 100
	-- Rounded to the nearest 5% so the prose reads as a round figure; when that rounding
	-- moves the number by more than 1.5 points, "~" marks it as approximate.
	local rounded = math.floor(percent / 5 + 0.5) * 5
	local approx = math.abs(percent - rounded) > 1.5 and "~" or ""
	return direction .. " by " .. approx .. string.format("%.0f", rounded) .. "%"
end

-- {{#invoke:StreetBrawlAbilities|report}} renders every change, grouped by hero.
function p.report(frame)
	local changes = mw.loadJsonData(SB_PAGE)["ability-changes"]
	local owners  = build_owner_map()

	local by_hero, hero_order = {}, {}
	for key, record in pairs(changes) do
		local owner = owners[key]
		local hero  = owner and owner.hero or UNMATCHED
		if not by_hero[hero] then
			by_hero[hero] = {}
			hero_order[#hero_order + 1] = hero
		end
		local paths = {}
		flatten(record, nil, paths, true)
		table.insert(by_hero[hero], {
			slot  = owner and owner.slot or 99,
			name  = record.Name or (owner and owner.name) or key,
			paths = paths,
		})
	end

	-- Anything the data adds that no live hero claims is listed last rather than dropped.
	table.sort(hero_order, function(a, b)
		if a == UNMATCHED then return false end
		if b == UNMATCHED then return true end
		return a < b
	end)

	local out = {}
	for _, hero in ipairs(hero_order) do
		local abilities = by_hero[hero]
		table.sort(abilities, function(a, b) return a.slot < b.slot end)
		local matched = hero ~= UNMATCHED
		out[#out + 1] = "\n== "
			.. (matched and frame:expandTemplate{ title = "HeroIcon", args = { hero } } or hero)
			.. " =="
		for _, ability in ipairs(abilities) do
			out[#out + 1] = "=== "
				.. (matched and frame:expandTemplate{ title = "AbilityIcon", args = { ability.name } } or ability.name)
				.. " ==="
			for _, path in ipairs(ability.paths) do
				out[#out + 1] = render_line(frame, ability.name, path)
			end
		end
	end
	return table.concat(out, "\n")
end

return p