From 8a4a23bb8f4eb95ddf58f5a333b9053c50617449 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 25 Jun 2024 18:25:08 +0400 Subject: [PATCH] game: improved cost tags to support card hints on stack (example: evidence, fixes #12522); --- .../cost/additional/CollectEvidenceTest.java | 53 +++++++++++++++++++ .../common/CollectedEvidenceCondition.java | 3 +- .../abilities/hint/common/EvidenceHint.java | 2 +- Mage/src/main/java/mage/util/CardUtil.java | 22 ++++++-- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java index 7b267a5f052..2a8ef06e381 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/additional/CollectEvidenceTest.java @@ -1,8 +1,14 @@ package org.mage.test.cards.cost.additional; +import mage.abilities.Ability; +import mage.abilities.condition.common.CollectedEvidenceCondition; +import mage.abilities.keyword.CollectEvidenceAbility; +import mage.cards.Card; import mage.constants.PhaseStep; import mage.constants.Zone; import mage.counters.CounterType; +import mage.game.stack.StackObject; +import org.junit.Assert; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; @@ -22,6 +28,7 @@ public class CollectEvidenceTest extends CardTestPlayerBase { private static final String sprite = "Crimestopper Sprite"; private static final String monitor = "Surveillance Monitor"; private static final String unraveler = "Conspiracy Unraveler"; + private static final String bite = "Bite Down on Crime"; @Test public void testNoPay() { @@ -310,4 +317,50 @@ public class CollectEvidenceTest extends CardTestPlayerBase { assertExileCount(ogre, 4); assertPermanentCount(playerA, "Colossal Dreadmaw", 1); } + + @Test + public void testNonDirectConditionalOnStack() { + // evidence conditional must work on stack from non spell abilities too + // cost tags related bug: https://github.com/magefree/mage/issues/12522 + + // As an additional cost to cast this spell, you may collect evidence 6. This spell costs {2} less to cast if evidence was collected. + // Target creature you control gets +2/+0 until end of turn. + // It deals damage equal to its power to target creature you don't control. + addCard(Zone.HAND, playerA, bite); // {3}{G} + addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerB, "Augmenting Automaton"); + addCard(Zone.GRAVEYARD, playerA, ogre, 2); // {2}{R} + + // before + runCode("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + Card card = playerA.getHand().getCards(game).stream().filter(c -> c.getName().equals(bite)).findFirst().orElse(null); + Assert.assertNotNull(card); + Ability ability = card.getAbilities(game).stream().filter(a -> a instanceof CollectEvidenceAbility).findFirst().orElse(null); + Assert.assertNotNull(ability); + Assert.assertFalse("Evidence must not be collected before usage", CollectedEvidenceCondition.instance.apply(game, ability)); + }); + + // on cast + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, bite); + setChoice(playerA, true); // use alternative cast from bite + addTarget(playerA, "Grizzly Bears"); // boost + addTarget(playerA, "Augmenting Automaton"); // damage target + setChoice(playerA, ogre, 2); // pay for collect evidence + runCode("on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { + StackObject object = game.getStack().peekFirst(); + Assert.assertNotNull(object); + Ability ability = object.getAbilities().stream().filter(a -> a instanceof CollectEvidenceAbility).findFirst().orElse(null); + Assert.assertNotNull(ability); + Assert.assertTrue("Evidence must be collected on stack", CollectedEvidenceCondition.instance.apply(game, ability)); + }); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertExileCount(ogre, 2); + assertPermanentCount(playerA, "Grizzly Bears", 1); + assertGraveyardCount(playerB, "Augmenting Automaton", 1); + } } diff --git a/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java index 9ff596d1e75..377e456eeaf 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CollectedEvidenceCondition.java @@ -21,6 +21,7 @@ public enum CollectedEvidenceCondition implements Condition { @Override public String toString() { - return "Evidence was collected"; + // must use "used" instead "collected" because it can be visible as card hint on stack before real collect + return "Evidence was used"; } } diff --git a/Mage/src/main/java/mage/abilities/hint/common/EvidenceHint.java b/Mage/src/main/java/mage/abilities/hint/common/EvidenceHint.java index a1bdb54bf9d..046f54d737f 100644 --- a/Mage/src/main/java/mage/abilities/hint/common/EvidenceHint.java +++ b/Mage/src/main/java/mage/abilities/hint/common/EvidenceHint.java @@ -14,7 +14,7 @@ public class EvidenceHint extends ConditionHint { private final int needAmount; public EvidenceHint(int needAmount) { - super(CollectedEvidenceCondition.instance, "evidence was collected"); + super(CollectedEvidenceCondition.instance); this.needAmount = needAmount; } diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 884c1fa231d..634a12b7181 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1732,11 +1732,27 @@ public final class CardUtil { */ public static Map getSourceCostsTagsMap(Game game, Ability source) { Map costTags; + + // from spell ability - direct access costTags = source.getCostsTagMap(); - if (costTags == null && source.getSourcePermanentOrLKI(game) != null) { - costTags = game.getPermanentCostsTags().get(CardUtil.getSourceStackMomentReference(game, source)); + if (costTags != null) { + return costTags; } - return costTags; + + // from any ability after resolve - access by permanent + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent != null) { + costTags = game.getPermanentCostsTags().get(CardUtil.getSourceStackMomentReference(game, source)); + return costTags; + } + + // from any ability before resolve (on stack) - access by spell ability + MageObject sourceObject = source.getSourceObject(game); + if (sourceObject instanceof Spell) { + return ((Spell) sourceObject).getSpellAbility().getCostsTagMap(); + } + + return null; } /**