Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/src/adapter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,7 @@ export type WaitingFor =
// `keyword.type` mirrors engine `AlternativeCastKeyword` (game_state.rs) 1:1.
// Keep this union exhaustive with the engine enum so the modal's keyword
// switch is type-checked against every variant the engine can emit.
| { type: "AlternativeCastChoice"; data: { player: PlayerId; object_id: ObjectId; card_id: CardId; payment_mode?: CastPaymentMode; keyword: { type: "Warp" } | { type: "Evoke" } | { type: "Emerge" } | { type: "Dash" } | { type: "Blitz" } | { type: "Overload" } | { type: "Bestow" } | { type: "Awaken" } | { type: "Cleave" } | { type: "MoreThanMeetsTheEye" } | { type: "Impending" } | { type: "Prototype" } | { type: "Mutate" } | { type: "Spectacle" } | { type: "Prowl" } | { type: "FaceDown" }; normal_cost: ManaCost; alternative_cost: ManaCost | null; alternative_additional_cost: SerializedAbilityCost | null } }
| { type: "AlternativeCastChoice"; data: { player: PlayerId; object_id: ObjectId; card_id: CardId; payment_mode?: CastPaymentMode; keyword: { type: "Warp" } | { type: "Evoke" } | { type: "Emerge" } | { type: "Dash" } | { type: "Blitz" } | { type: "Overload" } | { type: "Bestow" } | { type: "Awaken" } | { type: "Cleave" } | { type: "MoreThanMeetsTheEye" } | { type: "Impending" } | { type: "Prototype" } | { type: "Mutate" } | { type: "Spectacle" } | { type: "Prowl" } | { type: "FaceDown" }; normal_cost: ManaCost; alternative_cost: ManaCost | null; alternative_additional_cost: SerializedAbilityCost | null; alternative_additional_cost_description: string | null } }
// CR 702.140c + CR 730.2a: mutating creature spell resolving with a legal
// target — controller chooses to put it on top of or under the target creature.
| { type: "MutateMergeChoice"; data: { player: PlayerId; merging_id: ObjectId; target_id: ObjectId } }
Expand Down
17 changes: 12 additions & 5 deletions client/src/components/modal/AlternativeCostModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface KeywordCopy {
function keywordCopy(
keyword: Keyword,
cardName: string,
alternativeAdditionalCostDescription: string | null,
t: TFunction<"game">,
): KeywordCopy {
switch (keyword) {
Expand All @@ -55,15 +56,18 @@ function keywordCopy(
showOracleText: true,
subtitle: t("alternativeCost.evokeSubtitle", { name: cardName }),
};
// CR 702.119a-c: Emerge sacrifice a creature while casting; the emerge
// cost is reduced by that creature's mana value (handled engine-side).
// CR 702.119a-b: Emerge's required sacrifice quality is supplied by the
// engine; the modal must not infer it from the typed cost filter.
case "Emerge":
return {
eyebrow: t("alternativeCost.emergeEyebrow"),
normalLabel: t("alternativeCost.emergeNormalLabel"),
altLabel: t("alternativeCost.emergeAltLabel"),
showOracleText: true,
subtitle: t("alternativeCost.emergeSubtitle", { name: cardName }),
subtitle: t("alternativeCost.emergeSubtitle", {
name: cardName,
sacrifice: alternativeAdditionalCostDescription ?? t("alternativeCost.emergeFallbackSacrifice"),
}),
};
// CR 702.109a: Dash — like Warp, the rider (haste + end-step return to hand)
// lives on the keyword itself and doesn't change the spell's printed text.
Expand Down Expand Up @@ -243,6 +247,7 @@ export function AlternativeCostModal() {
normalCost={data.normal_cost}
alternativeCost={data.alternative_cost}
alternativeAdditionalCost={data.alternative_additional_cost}
alternativeAdditionalCostDescription={data.alternative_additional_cost_description}
dispatch={dispatch}
/>
);
Expand All @@ -254,13 +259,15 @@ function AlternativeCostContent({
normalCost,
alternativeCost,
alternativeAdditionalCost,
alternativeAdditionalCostDescription,
dispatch,
}: {
objectId: number;
keyword: Keyword;
normalCost: ManaCost;
alternativeCost: ManaCost | null;
alternativeAdditionalCost: SerializedAbilityCost | null;
alternativeAdditionalCostDescription: string | null;
dispatch: (action: GameAction) => Promise<unknown>;
}) {
const { t } = useTranslation("game");
Expand All @@ -269,7 +276,7 @@ function AlternativeCostContent({
if (!obj) return null;

const cardName = obj.name;
const copy = keywordCopy(keyword, cardName, t);
const copy = keywordCopy(keyword, cardName, alternativeAdditionalCostDescription, t);

return (
<DialogShell
Expand Down Expand Up @@ -315,7 +322,7 @@ function AlternativeCostContent({
)}
{alternativeAdditionalCost && (
<span className="ml-2 text-xs text-slate-300">
{describeAdditionalCost(alternativeAdditionalCost, t)}
{alternativeAdditionalCostDescription ?? describeAdditionalCost(alternativeAdditionalCost, t)}
</span>
)}
{copy.altSuffix && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ type AltKeyword = Extract<
{ type: "AlternativeCastChoice" }
>["data"]["keyword"]["type"];

function setSpectacleChoice(keyword: AltKeyword) {
function setSpectacleChoice(
keyword: AltKeyword,
alternativeAdditionalCostDescription: string | null = null,
) {
const waitingFor: WaitingFor = {
type: "AlternativeCastChoice",
data: {
Expand All @@ -45,6 +48,7 @@ function setSpectacleChoice(keyword: AltKeyword) {
normal_cost: { type: "Cost", shards: ["Red"], generic: 3 },
alternative_cost: RED_COST,
alternative_additional_cost: null,
alternative_additional_cost_description: alternativeAdditionalCostDescription,
},
};

Expand Down Expand Up @@ -121,4 +125,11 @@ describe("AlternativeCostModal", () => {
).toBeInTheDocument();
},
);

it("renders Emerge's engine-provided sacrifice description", () => {
setSpectacleChoice("Emerge", "an artifact");
render(<AlternativeCostModal />);

expect(screen.getByText(/sacrificing an artifact/i)).toBeInTheDocument();
});
});
3 changes: 2 additions & 1 deletion client/src/i18n/locales/de/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Auftauchen",
"emergeNormalLabel": "Normal wirken",
"emergeAltLabel": "Mit Auftauchen wirken",
"emergeSubtitle": "Wirke {{name}} normal oder zahle seine Auftauchen-Kosten, indem du eine Kreatur opferst, was die Kosten um den Manawert dieser Kreatur reduziert.",
"emergeSubtitle": "Wirke {{name}} normal oder zahle seine Auftauchen-Kosten, indem du {{sacrifice}} opferst, was die Kosten um den Manawert dieser bleibenden Karte reduziert.",
"emergeFallbackSacrifice": "eine passende bleibende Karte",
"impendingEyebrow": "Drohend",
"impendingNormalLabel": "Normal wirken",
"impendingAltLabel": "Mit Drohend wirken",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/en/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1798,7 +1798,8 @@
"emergeEyebrow": "Emerge",
"emergeNormalLabel": "Cast Normally",
"emergeAltLabel": "Cast with Emerge",
"emergeSubtitle": "Cast {{name}} normally, or pay its Emerge cost by sacrificing a creature, reducing the cost by that creature's mana value.",
"emergeSubtitle": "Cast {{name}} normally, or pay its Emerge cost by sacrificing {{sacrifice}}, reducing the cost by that permanent's mana value.",
"emergeFallbackSacrifice": "a matching permanent",
"impendingEyebrow": "Impending",
"impendingNormalLabel": "Cast Normally",
"impendingAltLabel": "Cast with Impending",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/es/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Emerger",
"emergeNormalLabel": "Lanzar normalmente",
"emergeAltLabel": "Lanzar con Emerger",
"emergeSubtitle": "Lanza {{name}} normalmente, o paga su coste de Emerger sacrificando una criatura, reduciendo el coste en el valor de maná de esa criatura.",
"emergeSubtitle": "Lanza {{name}} normalmente, o paga su coste de Emerger sacrificando {{sacrifice}}, reduciendo el coste en el valor de maná de ese permanente.",
"emergeFallbackSacrifice": "un permanente que cumpla los requisitos",
"impendingEyebrow": "Inminencia",
"impendingNormalLabel": "Lanzar normalmente",
"impendingAltLabel": "Lanzar con Inminencia",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/fr/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Émergence",
"emergeNormalLabel": "Lancer normalement",
"emergeAltLabel": "Lancer avec Émergence",
"emergeSubtitle": "Lancez {{name}} normalement, ou payez son coût d'Émergence en sacrifiant une créature, ce qui réduit le coût de la valeur de mana de cette créature.",
"emergeSubtitle": "Lancez {{name}} normalement, ou payez son coût d'Émergence en sacrifiant {{sacrifice}}, ce qui réduit le coût de la valeur de mana de ce permanent.",
"emergeFallbackSacrifice": "un permanent correspondant",
"impendingEyebrow": "Imminence",
"impendingNormalLabel": "Lancer normalement",
"impendingAltLabel": "Lancer avec Imminence",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/it/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Emergere",
"emergeNormalLabel": "Lancia normalmente",
"emergeAltLabel": "Lancia con Emergere",
"emergeSubtitle": "Lancia {{name}} normalmente, o paga il suo costo di Emergere sacrificando una creatura, riducendo il costo del valore di mana di quella creatura.",
"emergeSubtitle": "Lancia {{name}} normalmente, o paga il suo costo di Emergere sacrificando {{sacrifice}}, riducendo il costo del valore di mana di quel permanente.",
"emergeFallbackSacrifice": "un permanente corrispondente",
"impendingEyebrow": "Incombere",
"impendingNormalLabel": "Lancia normalmente",
"impendingAltLabel": "Lancia con Incombere",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/pl/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Emerge",
"emergeNormalLabel": "Rzuć normalnie",
"emergeAltLabel": "Rzuć z Emerge",
"emergeSubtitle": "Rzuć {{name}} normalnie lub zapłać jego koszt Emerge, poświęcając stwora, co zmniejsza koszt o wartość many tego stwora.",
"emergeSubtitle": "Rzuć {{name}} normalnie lub zapłać jego koszt Emerge, poświęcając {{sacrifice}}, co zmniejsza koszt o wartość many tego permanentu.",
"emergeFallbackSacrifice": "pasujący permanent",
"impendingEyebrow": "Impending",
"impendingNormalLabel": "Rzuć normalnie",
"impendingAltLabel": "Rzuć z Impending",
Expand Down
3 changes: 2 additions & 1 deletion client/src/i18n/locales/pt/game.json
Original file line number Diff line number Diff line change
Expand Up @@ -1754,7 +1754,8 @@
"emergeEyebrow": "Emergir",
"emergeNormalLabel": "Conjurar Normalmente",
"emergeAltLabel": "Conjurar com Emergir",
"emergeSubtitle": "Conjure {{name}} normalmente, ou pague seu custo de Emergir sacrificando uma criatura, reduzindo o custo pelo valor de mana daquela criatura.",
"emergeSubtitle": "Conjure {{name}} normalmente, ou pague seu custo de Emergir sacrificando {{sacrifice}}, reduzindo o custo pelo valor de mana daquele permanente.",
"emergeFallbackSacrifice": "um permanente correspondente",
"impendingEyebrow": "Iminente",
"impendingNormalLabel": "Conjurar Normalmente",
"impendingAltLabel": "Conjurar com Iminente",
Expand Down
4 changes: 2 additions & 2 deletions crates/engine/src/game/ability_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4914,7 +4914,8 @@ fn scan_keyword(kw: &Keyword, mode: ScanMode) -> Axes {
| Keyword::Echo(_)
| Keyword::Buyback(_)
| Keyword::Cycling(_)
| Keyword::Flashback(_) => Axes::CONSERVATIVE,
| Keyword::Flashback(_)
| Keyword::Emerge(_) => Axes::CONSERVATIVE,
// Every other keyword carries a read-free payload (unit / u32 / String /
// ManaCost / value tag): it reads nothing on any axis here. Its cost-read,
// if any, is already captured by `cost_read` above.
Expand Down Expand Up @@ -5000,7 +5001,6 @@ fn scan_keyword(kw: &Keyword, mode: ScanMode) -> Axes {
| Keyword::Madness(_)
| Keyword::Miracle(_)
| Keyword::Dash(_)
| Keyword::Emerge(_)
| Keyword::Harmonize(_)
| Keyword::Foretell(_)
| Keyword::Mutate(_)
Expand Down
Loading
Loading