Answer for shop copiers before the player pays
A purchase always lands in the rightmost slot (buy_from_shop emplaces at the end, button_callbacks.lua:2279), which the two copiers experience in opposite ways: a bought Blueprint arrives copying nothing, a bought Brainstorm copies the leftmost joker immediately. The buy-area tooltip now says which, by name — JCA.copy_source_if_bought resolves the arrival target (chaining through a leftmost copier exactly like copy_source), and Blueprint's line names the joker to slot it against after buying. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,13 @@ Three-layer split; `JCA` is the single global namespace:
|
|||||||
nothing. Do not rebuild the neighbour lookup with the `a and b or c` idiom:
|
nothing. Do not rebuild the neighbour lookup with the `a and b or c` idiom:
|
||||||
for a rightmost Blueprint the right-hand slot is nil and the idiom falls
|
for a rightmost Blueprint the right-hand slot is nil and the idiom falls
|
||||||
through to the leftmost joker, confidently reporting the wrong card.
|
through to the leftmost joker, confidently reporting the wrong card.
|
||||||
|
Shop copiers get the same treatment *before* purchase via
|
||||||
|
`JCA.copy_source_if_bought(key)`: a purchase always lands in the
|
||||||
|
**rightmost** slot (`buy_from_shop` → `G.jokers:emplace`,
|
||||||
|
`button_callbacks.lua:2279`; emplace appends, `cardarea.lua:53`), so a
|
||||||
|
bought Blueprint starts dead (buy-area tooltip names the joker to slot it
|
||||||
|
against) while a bought Brainstorm copies the leftmost joker immediately
|
||||||
|
(tooltip says which, chaining through a leftmost copier like copy_source).
|
||||||
- `JCA.is_eternal(card)` reads `card.ability.eternal` — the flag vanilla itself
|
- `JCA.is_eternal(card)` reads `card.ability.eternal` — the flag vanilla itself
|
||||||
uses for the badge (`card.lua:1141`). **Never call `SMODS.is_eternal()` from
|
uses for the badge (`card.lua:1141`). **Never call `SMODS.is_eternal()` from
|
||||||
an advisor path**: it runs a full `SMODS.calculate_context` per call
|
an advisor path**: it runs a full `SMODS.calculate_context` per call
|
||||||
|
|||||||
@@ -220,6 +220,30 @@ function JCA.copy_source(card)
|
|||||||
return nil, 'none', chain
|
return nil, 'none', chain
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- What a copier still IN THE SHOP would copy if bought right now. A purchase
|
||||||
|
-- always lands in the RIGHTMOST slot (buy_from_shop hands the card to
|
||||||
|
-- G.jokers:emplace, button_callbacks.lua:2279, and emplace appends,
|
||||||
|
-- cardarea.lua:53) -- so a bought Blueprint arrives with no right-hand
|
||||||
|
-- neighbour and copies nothing until the player reorders, while a bought
|
||||||
|
-- Brainstorm reads the leftmost joker the moment it lands. Same returns as
|
||||||
|
-- copy_source; a leftmost joker that is itself a copier is followed through
|
||||||
|
-- copy_source so the chain resolves to the joker actually copied.
|
||||||
|
function JCA.copy_source_if_bought(key)
|
||||||
|
if not (COPIERS[key] and G.jokers and G.jokers.cards) then return nil, 'none', {} end
|
||||||
|
if COPIERS[key] == 'right' then return nil, 'none', {} end
|
||||||
|
local target = G.jokers.cards[1]
|
||||||
|
local center = target and target.config and target.config.center
|
||||||
|
if not center then return nil, 'none', {} end
|
||||||
|
if COPIERS[center.key] then
|
||||||
|
local final, status, chain = JCA.copy_source(target)
|
||||||
|
table.insert(chain, 1, target)
|
||||||
|
return final, status, chain
|
||||||
|
end
|
||||||
|
if not center.blueprint_compat then return target, 'incompatible', {} end
|
||||||
|
if target.debuff then return target, 'debuffed', {} end
|
||||||
|
return target, 'ok', {}
|
||||||
|
end
|
||||||
|
|
||||||
-- Ceremonial Dagger is positional too, and it is the dangerous one: when the
|
-- Ceremonial Dagger is positional too, and it is the dangerous one: when the
|
||||||
-- blind is selected it DESTROYS the joker to its right and eats twice its sell
|
-- blind is selected it DESTROYS the joker to its right and eats twice its sell
|
||||||
-- value as Mult (card.lua:2945). The card text says so; what it cannot tell you
|
-- value as Mult (card.lua:2945). The card text says so; what it cannot tell you
|
||||||
@@ -633,6 +657,42 @@ local function tooltip_rows(card)
|
|||||||
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
|
||||||
|
|
||||||
|
-- Copiers in the shop: a purchase always lands rightmost (see
|
||||||
|
-- copy_source_if_bought), so say what THIS board hands the copier the
|
||||||
|
-- moment it arrives -- by name, before the player pays. Explanations,
|
||||||
|
-- not verdicts, so learning mode keeps them.
|
||||||
|
if in_buy_area(card) and COPIERS[key] then
|
||||||
|
if key == 'j_blueprint' then
|
||||||
|
-- The caution above already says a rightmost Blueprint is dead;
|
||||||
|
-- add the one thing it cannot know: which joker to slot it against.
|
||||||
|
local best = JCA.best_copy_target(card)
|
||||||
|
if best then
|
||||||
|
text_row('Fix after buying: slot it just left of '
|
||||||
|
.. name_of(best.config.center.key), G.C.GREEN)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local target, status, chain = JCA.copy_source_if_bought(key)
|
||||||
|
local via = ''
|
||||||
|
for _, link in ipairs(chain) do
|
||||||
|
via = via .. name_of(link.config.center.key) .. ' -> '
|
||||||
|
end
|
||||||
|
local tname = target and name_of(target.config.center.key)
|
||||||
|
if status == 'ok' then
|
||||||
|
text_row('Will copy ' .. via .. tname .. ' as soon as it lands', G.C.GREEN)
|
||||||
|
elseif status == 'incompatible' then
|
||||||
|
text_row('Will copy ' .. via .. tname .. ' - which cannot be copied', G.C.RED)
|
||||||
|
elseif status == 'debuffed' then
|
||||||
|
text_row('Will copy ' .. via .. tname .. ' - debuffed, so nothing', G.C.RED)
|
||||||
|
else
|
||||||
|
local best = JCA.best_copy_target(card)
|
||||||
|
text_row(best
|
||||||
|
and ('Will copy nothing - make ' .. name_of(best.config.center.key)
|
||||||
|
.. ' your leftmost joker first')
|
||||||
|
or 'Will copy nothing - no copyable joker on your board', G.C.RED)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Run context: this deck, these hand levels. Facts that frame the tags,
|
-- Run context: this deck, these hand levels. Facts that frame the tags,
|
||||||
-- never part of the score; capped at two lines so multi-suit jokers do
|
-- never part of the score; capped at two lines so multi-suit jokers do
|
||||||
-- not flood the tooltip. Fixed iteration order keeps hovers stable.
|
-- not flood the tooltip. Fixed iteration order keeps hovers stable.
|
||||||
|
|||||||
@@ -352,6 +352,38 @@ local ring = field('j_brainstorm', 'j_blueprint')
|
|||||||
for _, c in ipairs(ring) do c.config.center.blueprint_compat = true end
|
for _, c in ipairs(ring) do c.config.center.blueprint_compat = true end
|
||||||
eq(JCA.copy_source(ring[2]), nil, 'a Blueprint/Brainstorm ring resolves to nothing')
|
eq(JCA.copy_source(ring[2]), nil, 'a Blueprint/Brainstorm ring resolves to nothing')
|
||||||
|
|
||||||
|
-- A copier still in the SHOP is positional before it is even bought: a purchase
|
||||||
|
-- always lands in the rightmost slot (buy_from_shop emplaces at the end,
|
||||||
|
-- button_callbacks.lua:2279), so a bought Blueprint starts dead while a bought
|
||||||
|
-- Brainstorm reads the leftmost joker immediately.
|
||||||
|
field('j_baron', 'j_mime')
|
||||||
|
eq(JCA.copy_source_if_bought('j_blueprint'), nil,
|
||||||
|
'a bought Blueprint arrives rightmost, copying nothing')
|
||||||
|
eq(select(2, JCA.copy_source_if_bought('j_blueprint')), 'none',
|
||||||
|
'and reports the empty slot, not an error')
|
||||||
|
|
||||||
|
local arrival = field('j_baron', 'j_mime')
|
||||||
|
arrival[1].config.center.blueprint_compat = true
|
||||||
|
local bought, bought_status = JCA.copy_source_if_bought('j_brainstorm')
|
||||||
|
eq(bought, arrival[1], 'a bought Brainstorm copies the leftmost joker on arrival')
|
||||||
|
eq(bought_status, 'ok', 'and reports it as live')
|
||||||
|
|
||||||
|
-- The leftmost joker being a copier chains exactly like an owned Brainstorm:
|
||||||
|
-- the answer is the joker at the end, and the walked copier is recorded.
|
||||||
|
local shop_chain = field('j_blueprint', 'j_baron')
|
||||||
|
for _, c in ipairs(shop_chain) do c.config.center.blueprint_compat = true end
|
||||||
|
local final_b, st_b, via_b = JCA.copy_source_if_bought('j_brainstorm')
|
||||||
|
eq(final_b, shop_chain[2], 'a leftmost Blueprint chains to the joker it copies')
|
||||||
|
eq(st_b, 'ok', 'and reports it as live')
|
||||||
|
eq(#via_b, 1, 'and records the copier walked through')
|
||||||
|
eq(via_b[1], shop_chain[1], 'which is the leftmost Blueprint')
|
||||||
|
|
||||||
|
field('j_baron', 'j_mime')
|
||||||
|
G.jokers.cards = {}
|
||||||
|
eq(JCA.copy_source_if_bought('j_brainstorm'), nil,
|
||||||
|
'an empty board gives a bought Brainstorm nothing to copy')
|
||||||
|
eq(JCA.copy_source_if_bought('j_baron'), nil, 'a non-copier has no arrival answer')
|
||||||
|
|
||||||
-- A dead copier's fix is a reorder, so the advisor names the best target on
|
-- A dead copier's fix is a reorder, so the advisor names the best target on
|
||||||
-- the board: copyable (blueprint_compat for TRUTH, not debuffed, not another
|
-- the board: copyable (blueprint_compat for TRUTH, not debuffed, not another
|
||||||
-- copier), preferring jokers the db marks as prime copy targets.
|
-- copier), preferring jokers the db marks as prime copy targets.
|
||||||
|
|||||||
Reference in New Issue
Block a user