game: improved cost tags to support card hints on stack (example: evidence, fixes #12522);

This commit is contained in:
Oleg Agafonov 2024-06-25 18:25:08 +04:00
parent 74b3d26a41
commit 8a4a23bb8f
4 changed files with 75 additions and 5 deletions

View file

@ -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);
}
}

View file

@ -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";
}
}

View file

@ -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;
}

View file

@ -1732,11 +1732,27 @@ public final class CardUtil {
*/
public static Map<String, Object> getSourceCostsTagsMap(Game game, Ability source) {
Map<String, Object> 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;
}
/**