diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SyrCarahTheBoldTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SyrCarahTheBoldTest.java new file mode 100644 index 00000000000..cd13e24b611 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/damage/SyrCarahTheBoldTest.java @@ -0,0 +1,87 @@ +package org.mage.test.cards.triggers.damage; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class SyrCarahTheBoldTest extends CardTestPlayerBase { + + @Test + public void test_Damage() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromHand(playerA); + // When Syr Carah, the Bold or an instant or sorcery spell you control deals damage to a player, exile the top card of your library. You may play that card this turn. + // {T}: Syr Carah deals 1 damage to any target. + addCard(Zone.BATTLEFIELD, playerA, "Syr Carah, the Bold", 1); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 1); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); + // + addCard(Zone.LIBRARY, playerA, "Swamp", 5); + // + // {1}, {T}, Sacrifice Aeolipile: It deals 2 damage to any target. + addCard(Zone.BATTLEFIELD, playerB, "Aeolipile", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 1); + + // 1 - triggers on ability damage + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: {source} deals", playerB); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkLife("damage 1", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 20 - 1); + + // 2 - triggers on spell damage + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", playerB); + waitStackResolved(1, PhaseStep.POSTCOMBAT_MAIN); + checkLife("damage 2", 1, PhaseStep.POSTCOMBAT_MAIN, playerB, 20 - 1 - 3); + + // 3 - NONE triggers on another ability damage + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}, {T}, Sacrifice", playerA); + checkLife("damage 3", 2, PhaseStep.BEGIN_COMBAT, playerA, 20 - 2); + + // 4 - triggers on combat damage + attack(3, playerA, "Syr Carah, the Bold", playerB); + checkLife("damage 4", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, 20 - 1 - 3 - 3); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } + + @Test + public void test_DamageWithCopyAbility() { + removeAllCardsFromLibrary(playerA); + removeAllCardsFromHand(playerA); + // When Syr Carah, the Bold or an instant or sorcery spell you control deals damage to a player, exile the top card of your library. You may play that card this turn. + // {T}: Syr Carah deals 1 damage to any target. + addCard(Zone.BATTLEFIELD, playerA, "Syr Carah, the Bold", 1); + // + addCard(Zone.LIBRARY, playerA, "Swamp", 5); + // + // {T}: Embermage Goblin deals 1 damage to any target. + addCard(Zone.BATTLEFIELD, playerB, "Embermage Goblin", 1); + // + // Whenever an ability of equipped creature is activated, if it isn't a mana ability, copy that ability. You may choose new targets for the copy. + // Equip 3 + addCard(Zone.BATTLEFIELD, playerB, "Illusionist's Bracers", 1); + addCard(Zone.BATTLEFIELD, playerB, "Island", 3); + + // equip to copy abilities + showAvaileableAbilities("abils", 2, PhaseStep.PRECOMBAT_MAIN, playerB); + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Equip {3}", "Embermage Goblin"); + setChoice(playerB, "No"); // no new target + + // 3 - 2x damage (copy), but no trigger + // java.lang.ClassCastException: mage.game.stack.StackAbility cannot be cast to mage.game.stack.Spell + activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{T}: {source} deals", playerA); + checkLife("damage 3", 2, PhaseStep.END_TURN, playerA, 20 - 1 - 1); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 5209de1f2b4..38b7b03eb1b 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -29,6 +29,7 @@ import mage.filter.Filter; import mage.filter.FilterCard; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledPermanent; import mage.filter.predicate.mageobject.NamePredicate; import mage.filter.predicate.mageobject.SupertypePredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; @@ -45,7 +46,6 @@ import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; import mage.game.stack.Spell; import mage.game.stack.SpellStack; -import mage.game.stack.StackAbility; import mage.game.stack.StackObject; import mage.game.turn.Phase; import mage.game.turn.Step; @@ -69,7 +69,6 @@ import java.io.IOException; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; -import mage.filter.common.FilterControlledPermanent; public abstract class GameImpl implements Game, Serializable { @@ -452,7 +451,17 @@ public abstract class GameImpl implements Game, Serializable { public Spell getSpellOrLKIStack(UUID spellId) { Spell spell = state.getStack().getSpell(spellId); if (spell == null) { - spell = (Spell) this.getLastKnownInformation(spellId, Zone.STACK); + MageObject obj = this.getLastKnownInformation(spellId, Zone.STACK); + if (obj instanceof Spell) { + spell = (Spell) obj; + } else { + if (obj != null) { + // copied activated abilities is StackAbility (not spell) and must be ignored here + // if not then java.lang.ClassCastException: mage.game.stack.StackAbility cannot be cast to mage.game.stack.Spell + // see SyrCarahTheBoldTest as example + // logger.error("getSpellOrLKIStack got non spell id - " + obj.getClass().getName() + " - " + obj.getName(), new Throwable()); + } + } } return spell; } @@ -1471,7 +1480,7 @@ public abstract class GameImpl implements Game, Serializable { /** * @param emblem * @param sourceObject - * @param toPlayerId controller and owner of the emblem + * @param toPlayerId controller and owner of the emblem */ @Override public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) { @@ -1489,8 +1498,8 @@ public abstract class GameImpl implements Game, Serializable { /** * @param plane * @param sourceObject - * @param toPlayerId controller and owner of the plane (may only be one per - * game..) + * @param toPlayerId controller and owner of the plane (may only be one per + * game..) * @return boolean - whether the plane was added successfully or not */ @Override @@ -1719,7 +1728,7 @@ public abstract class GameImpl implements Game, Serializable { break; } // triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature - for (Iterator it = abilities.iterator(); it.hasNext();) { + for (Iterator it = abilities.iterator(); it.hasNext(); ) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -2454,7 +2463,7 @@ public abstract class GameImpl implements Game, Serializable { } //20100423 - 800.4a Set toOutside = new HashSet<>(); - for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) { + for (Iterator it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) { Permanent perm = it.next(); if (perm.isOwnedBy(playerId)) { if (perm.getAttachedTo() != null) { @@ -2497,7 +2506,7 @@ public abstract class GameImpl implements Game, Serializable { player.moveCards(toOutside, Zone.OUTSIDE, null, this); // triggered abilities that don't use the stack have to be executed List abilities = state.getTriggered(player.getId()); - for (Iterator it = abilities.iterator(); it.hasNext();) { + for (Iterator it = abilities.iterator(); it.hasNext(); ) { TriggeredAbility triggeredAbility = it.next(); if (!triggeredAbility.isUsesStack()) { state.removeTriggeredAbility(triggeredAbility); @@ -2517,7 +2526,7 @@ public abstract class GameImpl implements Game, Serializable { // Remove cards from the player in all exile zones for (ExileZone exile : this.getExile().getExileZones()) { - for (Iterator it = exile.iterator(); it.hasNext();) { + for (Iterator it = exile.iterator(); it.hasNext(); ) { Card card = this.getCard(it.next()); if (card != null && card.isOwnedBy(playerId)) { it.remove(); @@ -2527,7 +2536,7 @@ public abstract class GameImpl implements Game, Serializable { //Remove all commander/emblems/plane the player controls boolean addPlaneAgain = false; - for (Iterator it = this.getState().getCommand().iterator(); it.hasNext();) { + for (Iterator it = this.getState().getCommand().iterator(); it.hasNext(); ) { CommandObject obj = it.next(); if (obj.isControlledBy(playerId)) { if (obj instanceof Emblem) {