Warn about perishable scalers and rental money jokers at buy time

Per-instance sticker cautions, unlike the per-joker CAUTIONS table: the
same joker is a fine buy without the sticker. Perishable debuffs a joker
for good after 5 rounds (Card:calculate_perishable, card.lua:2652), so a
perishable Ride the Bus or Hologram is investment about to be wiped --
the new SCALERS set in synergies.lua lists the jokers whose stored gains
die that way, excluding computed (Bull) and round-scoped (Hit the Road)
values and Egg, whose sell value survives a debuff. Rental charges
every round (card.lua:2647), which can eat a money joker's whole payout.

The scaler-key integrity test caught j_canio for j_caino on the first
run -- the misspelled vanilla keys strike again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-15 19:44:32 -07:00
parent f88b034970
commit 048ab40f85
3 changed files with 69 additions and 0 deletions
+24
View File
@@ -13,6 +13,7 @@ end
local data = assert(SMODS.load_file('synergies.lua'))() local data = assert(SMODS.load_file('synergies.lua'))()
JCA.db = data.jokers JCA.db = data.jokers
JCA.scalers = data.scalers
-- Settings from config.lua, persisted by Steamodded across sessions. -- Settings from config.lua, persisted by Steamodded across sessions.
-- Fallback defaults keep the engine usable outside the game (tests). -- Fallback defaults keep the engine usable outside the game (tests).
@@ -331,6 +332,24 @@ function JCA.is_rental(card)
return not not (card.ability and card.ability.rental) return not not (card.ability and card.ability.rental)
end end
-- Sticker traps on a specific shop card, as caution strings. Per-instance,
-- unlike JCA.cautions (which warns about the joker itself): the same joker is
-- a fine buy without the sticker. Perishable debuffs for good after
-- G.GAME.perishable_rounds (card.lua:2652); rental charges rental_rate ($3)
-- every round (card.lua:2647), which can eat a money joker's whole payout.
function JCA.sticker_cautions(card)
local out = {}
local key = card.config and card.config.center and card.config.center.key
local a = card.ability or {}
if a.perishable and key and JCA.scalers[key] then
out[#out + 1] = 'perishable: gains die in 5 rounds'
end
if a.rental and key and JCA.db[key] and JCA.db[key].gives.money then
out[#out + 1] = 'rental: rent may eat its payout'
end
return out
end
function JCA.is_perished(card) function JCA.is_perished(card)
local a = card.ability local a = card.ability
return not not (a and a.perishable and (a.perish_tally or 0) <= 0) return not not (a and a.perishable and (a.perish_tally or 0) <= 0)
@@ -732,6 +751,11 @@ local function tooltip_rows(card)
end end
local caution = in_buy_area(card) and JCA.cautions[key] local caution = in_buy_area(card) and JCA.cautions[key]
if caution then text_row('Caution: ' .. caution, G.C.RED) end if caution then text_row('Caution: ' .. caution, G.C.RED) end
if in_buy_area(card) then
for _, warn in ipairs(JCA.sticker_cautions(card)) do
text_row('Caution: ' .. warn, G.C.RED)
end
end
-- Copiers in the shop: a purchase always lands rightmost (see -- Copiers in the shop: a purchase always lands rightmost (see
-- copy_source_if_bought), so say what THIS board hands the copier the -- copy_source_if_bought), so say what THIS board hands the copier the
+19
View File
@@ -354,6 +354,21 @@ local CAUTIONS = {
j_gros_michel = '1 in 6 to self-destruct each round', j_gros_michel = '1 in 6 to self-destruct each round',
} }
-- Jokers whose stored ability values grow across rounds (Ride the Bus's Mult,
-- Hologram's xMult...). A PERISHABLE copy of one of these is a trap the card
-- text hides: perishable debuffs the joker for good after 5 rounds
-- (Card:calculate_perishable, card.lua:2652 -> set_debuff), so every round
-- spent growing it is investment about to be wiped. Computed jokers (Bull
-- reads your money, Abstract counts jokers) are excluded -- they lose nothing
-- stored -- as are round-scoped ones (Hit the Road resets each round) and
-- Egg, whose sell value survives a debuff.
local SCALERS = {
'j_ride_the_bus', 'j_green_joker', 'j_obelisk', 'j_hologram',
'j_constellation', 'j_campfire', 'j_vampire', 'j_lucky_cat', 'j_caino',
'j_yorick', 'j_glass', 'j_flash', 'j_red_card', 'j_madness', 'j_trousers',
'j_wee', 'j_square', 'j_runner', 'j_ceremonial', 'j_castle',
}
-- How to phrase a tag link in the tooltip, from the partner's perspective: -- How to phrase a tag link in the tooltip, from the partner's perspective:
-- `give` when the partner provides the tag, `want` when the partner consumes -- `give` when the partner provides the tag, `want` when the partner consumes
-- it, `both` for cluster tags where the pair simply shares the theme. -- it, `both` for cluster tags where the pair simply shares the theme.
@@ -471,5 +486,9 @@ for _, entry in pairs(J) do
entry.gives, entry.wants = g, w entry.gives, entry.wants = g, w
end end
local SCALER_SET = {}
for _, k in ipairs(SCALERS) do SCALER_SET[k] = true end
return { jokers = J, pairs = PAIRS, clashes = CLASHES, cautions = CAUTIONS, return { jokers = J, pairs = PAIRS, clashes = CLASHES, cautions = CAUTIONS,
scalers = SCALER_SET,
tag_text = TAG_TEXT, tag_order = TAG_ORDER, theme_info = THEME_INFO } tag_text = TAG_TEXT, tag_order = TAG_ORDER, theme_info = THEME_INFO }
+26
View File
@@ -658,6 +658,32 @@ eq(JCA.consumable_partners(consumable('Booster', 'Buffoon')), nil,
eq(JCA.consumable_partners({config = {center = {set = 'Voucher'}}}), nil, eq(JCA.consumable_partners({config = {center = {set = 'Voucher'}}}), nil,
'a Voucher yields no consumable advice') 'a Voucher yields no consumable advice')
--------------------------------------------------------------------------------
section('Engine: sticker cautions are per-instance, not per-joker')
--------------------------------------------------------------------------------
-- Every scaler is a real joker; a typo here would silently disarm the caution.
for k in pairs(JCA.scalers) do
ok(JCA.db[k] ~= nil, 'scaler ' .. k .. ' exists in the database')
end
local function stickered(key, ability)
return {config = {center = {key = key}}, ability = ability}
end
eq(#JCA.sticker_cautions(stickered('j_ride_the_bus', {perishable = true})), 1,
'a perishable scaler earns a caution')
eq(#JCA.sticker_cautions(stickered('j_golden', {perishable = true})), 0,
'a perishable static joker loses nothing stored - no caution')
eq(#JCA.sticker_cautions(stickered('j_golden', {rental = true})), 1,
'a rental money joker earns a caution (rent can eat the payout)')
eq(#JCA.sticker_cautions(stickered('j_baron', {rental = true})), 0,
'a rental non-money joker is just a cheap joker - no caution')
eq(#JCA.sticker_cautions(stickered('j_ride_the_bus', {})), 0,
'no sticker, no caution')
eq(#JCA.sticker_cautions(stickered('j_mod_unknown', {perishable = true, rental = true})), 0,
'unknown keys never warn and never error')
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
section('Data: themes are reachable') section('Data: themes are reachable')
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------