feat: add Sets.create and Sets.equippedCounts

This commit is contained in:
dualfroz committed 2026-09-07 22:37:18 +02:00
1 parent 00b89a9133
commit 2eb903beb5
1 file changed
+159
+159
View File
@@ -0,0 +1,159 @@
-- Item sets: matching gear that all share a `set` tag and grants an
-- escalating stat bonus once enough pieces are worn together at once - a
-- fixed weapon/armor/ring/bow catalog of named pieces (see Sets.create),
-- unlike affixes.lua's random roll bolted onto an ordinary base item. The
-- bonus is read live off however many pieces of a given set the wearer
-- currently has equipped (Sets.attackBonus/defenseBonus, wired into
-- combat.lua exactly the way runewords.lua's own live bonus is), so
-- swapping a single piece out mid-run instantly drops back to a lower
-- threshold - or none at all - rather than needing an explicit uninstall
-- step the way a socketed gem or an enchant would.
local Items = require("items")
local Sets = {}
local SLOT_ORDER = { "weapon", "armor", "ring", "bow" }
Sets.catalog = {
{
key = "ironclad", name = "Ironclad",
pieces = {
weapon = { name = "Ironclad Greatsword", power = 3 },
armor = { name = "Ironclad Plate", power = 3 },
ring = { name = "Ironclad Signet", attack = 0, defense = 1 },
},
-- Independent totals, not a stacking ladder: reaching the 3-piece
-- threshold replaces the 2-piece bonus rather than adding to it.
bonuses = {
[2] = { attack = 1, defense = 1 },
[3] = { attack = 2, defense = 3, regen = 1 },
},
},
{
key = "duskrunner", name = "Duskrunner",
pieces = {
weapon = { name = "Duskrunner Fang", power = 2 },
bow = { name = "Duskrunner Bow", power = 2, range = 5 },
ring = { name = "Duskrunner Loop", attack = 1, defense = 0 },
},
bonuses = {
[2] = { attack = 1 },
[3] = { attack = 3, defense = 1 },
},
},
}
Sets.byKey = {}
for _, spec in ipairs(Sets.catalog) do
Sets.byKey[spec.key] = spec
end
-- Every (set, slot) pairing flattened in a fixed order, mirroring
-- artifacts.lua's own Artifacts.order: the list a builder placement rolls
-- a single uniform index into so every piece of every set is equally
-- likely to turn up on a given floor.
Sets.order = {}
for _, spec in ipairs(Sets.catalog) do
for _, slot in ipairs(SLOT_ORDER) do
if spec.pieces[slot] then
Sets.order[#Sets.order + 1] = { set = spec.key, slot = slot }
end
end
end
-- Builds the dropped item for `setKey`'s `slot` piece in the same shape
-- items.lua's own constructors return for that slot, tagged with the
-- `set` field every function below keys its live bonus off.
function Sets.create(setKey, slot)
local spec = Sets.byKey[setKey]
local piece = spec and spec.pieces[slot]
if not piece then return nil end
local item
if slot == "weapon" then item = Items.weapon(piece.name, piece.power)
elseif slot == "armor" then item = Items.armor(piece.name, piece.power)
elseif slot == "ring" then item = Items.ring(piece.name, piece.attack, piece.defense)
elseif slot == "bow" then item = Items.bow(piece.name, piece.power, piece.range)
end
item.set = setKey
return item
end
-- How many of `entity`'s four equip slots currently carry a piece of each
-- set it has at least one piece of, keyed by set key. Walks a fixed slot
-- order rather than pairs() over the entity itself, so the result never
-- depends on Lua's unspecified table iteration order.
function Sets.equippedCounts(entity)
local counts = {}
for _, slot in ipairs(SLOT_ORDER) do
local item = entity[slot]
if item and item.set then
counts[item.set] = (counts[item.set] or 0) + 1
end
end
return counts
end
-- The richest bonus tier `count` pieces of `setKey` qualify for, and the
-- threshold it came from, or nil below its lowest threshold.
function Sets.bonusFor(setKey, count)
local spec = Sets.byKey[setKey]
if not spec or not count or count <= 0 then return nil end
local best, bestThreshold
for threshold, bonus in pairs(spec.bonuses) do
if count >= threshold and (not bestThreshold or threshold > bestThreshold) then
best, bestThreshold = bonus, threshold
end
end
return best, bestThreshold
end
function Sets.attackBonus(entity)
local total = 0
local counts = Sets.equippedCounts(entity)
for _, spec in ipairs(Sets.catalog) do
local bonus = Sets.bonusFor(spec.key, counts[spec.key])
if bonus then total = total + (bonus.attack or 0) end
end
return total
end
function Sets.defenseBonus(entity)
local total = 0
local counts = Sets.equippedCounts(entity)
for _, spec in ipairs(Sets.catalog) do
local bonus = Sets.bonusFor(spec.key, counts[spec.key])
if bonus then total = total + (bonus.defense or 0) end
end
return total
end
-- Hp `entity` regenerates this turn from any active set bonus that grants
-- one, summed the same way attack/defense are above.
function Sets.regenAmount(entity)
local total = 0
local counts = Sets.equippedCounts(entity)
for _, spec in ipairs(Sets.catalog) do
local bonus = Sets.bonusFor(spec.key, counts[spec.key])
if bonus then total = total + (bonus.regen or 0) end
end
return total
end
-- A human-readable "Name Npc" tag for every set currently meeting its
-- lowest threshold, in catalog order - read by render.lua's status panel
-- and by nothing that affects game logic.
function Sets.activeDescriptions(entity)
local counts = Sets.equippedCounts(entity)
local parts = {}
for _, spec in ipairs(Sets.catalog) do
local count = counts[spec.key]
if Sets.bonusFor(spec.key, count) then
parts[#parts + 1] = spec.name .. " " .. count .. "pc"
end
end
if #parts == 0 then return nil end
return table.concat(parts, ", ")
end
return Sets