savedCardsInfos = savedDoubleFacedCards.get(rarity);
if (savedCardsInfos == null) {
CardCriteria criteria = new CardCriteria();
diff --git a/Mage.Sets/src/mage/sets/Starter2000.java b/Mage.Sets/src/mage/sets/Starter2000.java
index 1e3a3f1c95c..ef18ba34486 100644
--- a/Mage.Sets/src/mage/sets/Starter2000.java
+++ b/Mage.Sets/src/mage/sets/Starter2000.java
@@ -13,10 +13,6 @@ public final class Starter2000 extends ExpansionSet {
private static final Starter2000 instance = new Starter2000();
- /**
- *
- * @return
- */
public static Starter2000 getInstance() {
return instance;
}
diff --git a/Mage.Sets/src/mage/sets/TherosBeyondDeath.java b/Mage.Sets/src/mage/sets/TherosBeyondDeath.java
new file mode 100644
index 00000000000..1fde557d95a
--- /dev/null
+++ b/Mage.Sets/src/mage/sets/TherosBeyondDeath.java
@@ -0,0 +1,68 @@
+package mage.sets;
+
+import mage.cards.ExpansionSet;
+import mage.constants.Rarity;
+import mage.constants.SetType;
+
+/**
+ * @author TheElk801
+ */
+public final class TherosBeyondDeath extends ExpansionSet {
+
+ private static final TherosBeyondDeath instance = new TherosBeyondDeath();
+
+ public static TherosBeyondDeath getInstance() {
+ return instance;
+ }
+
+ private TherosBeyondDeath() {
+ super("Theros Beyond Death", "THB", ExpansionSet.buildDate(2020, 1, 24), SetType.EXPANSION);
+ this.blockName = "Theros Beyond Death";
+ this.hasBoosters = true;
+ this.numBoosterLands = 1;
+ this.numBoosterCommon = 10;
+ this.numBoosterUncommon = 3;
+ this.numBoosterRare = 1;
+ this.ratioBoosterMythic = 8;
+ this.maxCardNumberInBooster = 254;
+
+ cards.add(new SetCardInfo("Ashiok, Nightmare Muse", 208, Rarity.MYTHIC, mage.cards.a.AshiokNightmareMuse.class));
+ cards.add(new SetCardInfo("Ashiok, Sculptor of Fears", 274, Rarity.MYTHIC, mage.cards.a.AshiokSculptorOfFears.class));
+ cards.add(new SetCardInfo("Athreos, Shroud-Veiled", 269, Rarity.MYTHIC, mage.cards.a.AthreosShroudVeiled.class));
+ cards.add(new SetCardInfo("Commanding Presence", 7, Rarity.UNCOMMON, mage.cards.c.CommandingPresence.class));
+ cards.add(new SetCardInfo("Daxos, Blessed by the Sun", 9, Rarity.UNCOMMON, mage.cards.d.DaxosBlessedByTheSun.class));
+ cards.add(new SetCardInfo("Deathbellow War Cry", 294, Rarity.RARE, mage.cards.d.DeathbellowWarCry.class));
+ cards.add(new SetCardInfo("Demon of Loathing", 292, Rarity.RARE, mage.cards.d.DemonOfLoathing.class));
+ cards.add(new SetCardInfo("Eidolon of Philosophy", 48, Rarity.COMMON, mage.cards.e.EidolonOfPhilosophy.class));
+ cards.add(new SetCardInfo("Elspeth, Sun's Nemesis", 14, Rarity.MYTHIC, mage.cards.e.ElspethSunsNemesis.class));
+ cards.add(new SetCardInfo("Elspeth, Undaunted Hero", 270, Rarity.MYTHIC, mage.cards.e.ElspethUndauntedHero.class));
+ cards.add(new SetCardInfo("Forest", 254, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS));
+ cards.add(new SetCardInfo("Grasping Giant", 288, Rarity.RARE, mage.cards.g.GraspingGiant.class));
+ cards.add(new SetCardInfo("Hero of the Winds", 23, Rarity.UNCOMMON, mage.cards.h.HeroOfTheWinds.class));
+ cards.add(new SetCardInfo("Indomitable Will", 25, Rarity.COMMON, mage.cards.i.IndomitableWill.class));
+ cards.add(new SetCardInfo("Inevitable End", 102, Rarity.UNCOMMON, mage.cards.i.InevitableEnd.class));
+ cards.add(new SetCardInfo("Ironscale Hydra", 296, Rarity.RARE, mage.cards.i.IronscaleHydra.class));
+ cards.add(new SetCardInfo("Island", 251, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS));
+ cards.add(new SetCardInfo("Klothys's Design", 176, Rarity.UNCOMMON, mage.cards.k.KlothyssDesign.class));
+ cards.add(new SetCardInfo("Klothys, God of Destiny", 220, Rarity.MYTHIC, mage.cards.k.KlothysGodOfDestiny.class));
+ cards.add(new SetCardInfo("Leonin of the Lost Pride", 28, Rarity.COMMON, mage.cards.l.LeoninOfTheLostPride.class));
+ cards.add(new SetCardInfo("Memory Drain", 54, Rarity.COMMON, mage.cards.m.MemoryDrain.class));
+ cards.add(new SetCardInfo("Mire's Grasp", 106, Rarity.COMMON, mage.cards.m.MiresGrasp.class));
+ cards.add(new SetCardInfo("Mountain", 253, Rarity.LAND, mage.cards.basiclands.Mountain.class, FULL_ART_BFZ_VARIOUS));
+ cards.add(new SetCardInfo("Nyxborn Colossus", 191, Rarity.COMMON, mage.cards.n.NyxbornColossus.class));
+ cards.add(new SetCardInfo("Nyxborn Courser", 29, Rarity.COMMON, mage.cards.n.NyxbornCourser.class));
+ cards.add(new SetCardInfo("Plains", 250, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS));
+ cards.add(new SetCardInfo("Revoke Existence", 34, Rarity.COMMON, mage.cards.r.RevokeExistence.class));
+ cards.add(new SetCardInfo("Serpent of Yawning Depths", 291, Rarity.RARE, mage.cards.s.SerpentOfYawningDepths.class));
+ cards.add(new SetCardInfo("Setessan Champion", 198, Rarity.RARE, mage.cards.s.SetessanChampion.class));
+ cards.add(new SetCardInfo("Sphinx Mindbreaker", 290, Rarity.RARE, mage.cards.s.SphinxMindbreaker.class));
+ cards.add(new SetCardInfo("Staggering Insight", 228, Rarity.UNCOMMON, mage.cards.s.StaggeringInsight.class));
+ cards.add(new SetCardInfo("Swamp", 252, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS));
+ cards.add(new SetCardInfo("Terror of Mount Velus", 295, Rarity.RARE, mage.cards.t.TerrorOfMountVelus.class));
+ cards.add(new SetCardInfo("The Akroan War", 124, Rarity.RARE, mage.cards.t.TheAkroanWar.class));
+ cards.add(new SetCardInfo("Treeshaker Chimera", 297, Rarity.RARE, mage.cards.t.TreeshakerChimera.class));
+ cards.add(new SetCardInfo("Underworld Rage-Hound", 163, Rarity.COMMON, mage.cards.u.UnderworldRageHound.class));
+ cards.add(new SetCardInfo("Underworld Sentinel", 293, Rarity.RARE, mage.cards.u.UnderworldSentinel.class));
+ cards.add(new SetCardInfo("Victory's Envoy", 289, Rarity.RARE, mage.cards.v.VictorysEnvoy.class));
+ }
+}
diff --git a/Mage.Sets/src/mage/sets/ThroneOfEldraine.java b/Mage.Sets/src/mage/sets/ThroneOfEldraine.java
index 2a8c83a7695..25112e0fafd 100644
--- a/Mage.Sets/src/mage/sets/ThroneOfEldraine.java
+++ b/Mage.Sets/src/mage/sets/ThroneOfEldraine.java
@@ -327,8 +327,5 @@ public final class ThroneOfEldraine extends ExpansionSet {
cards.add(new SetCardInfo("Worthy Knight", 36, Rarity.RARE, mage.cards.w.WorthyKnight.class));
cards.add(new SetCardInfo("Yorvo, Lord of Garenbrig", 185, Rarity.RARE, mage.cards.y.YorvoLordOfGarenbrig.class));
cards.add(new SetCardInfo("Youthful Knight", 37, Rarity.COMMON, mage.cards.y.YouthfulKnight.class));
-
- // This is here to prevent the incomplete adventure implementation from causing problems and will be removed
- cards.removeIf(setCardInfo -> AdventureCard.class.isAssignableFrom(setCardInfo.getCardClass()));
}
}
diff --git a/Mage.Sets/src/mage/sets/ThroneOfEldraineCollectorsEdition.java b/Mage.Sets/src/mage/sets/ThroneOfEldraineCollectorsEdition.java
index 9606cef6f0c..7edb2592788 100644
--- a/Mage.Sets/src/mage/sets/ThroneOfEldraineCollectorsEdition.java
+++ b/Mage.Sets/src/mage/sets/ThroneOfEldraineCollectorsEdition.java
@@ -111,8 +111,5 @@ public final class ThroneOfEldraineCollectorsEdition extends ExpansionSet {
cards.add(new SetCardInfo("Witch's Vengeance", 358, Rarity.RARE, mage.cards.w.WitchsVengeance.class));
cards.add(new SetCardInfo("Worthy Knight", 341, Rarity.RARE, mage.cards.w.WorthyKnight.class));
cards.add(new SetCardInfo("Yorvo, Lord of Garenbrig", 376, Rarity.RARE, mage.cards.y.YorvoLordOfGarenbrig.class));
-
- // This is here to prevent the incomplete adventure implementation from causing problems and will be removed
- cards.removeIf(setCardInfo -> AdventureCard.class.isAssignableFrom(setCardInfo.getCardClass()));
}
}
diff --git a/Mage.Tests/pom.xml b/Mage.Tests/pom.xml
index 27539086b71..4ec098108db 100644
--- a/Mage.Tests/pom.xml
+++ b/Mage.Tests/pom.xml
@@ -6,7 +6,7 @@
org.mage
mage-root
- 1.4.40
+ 1.4.41
mage-tests
diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java
index 268b58f7ef2..db8796ba96f 100644
--- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/CastCreaturesTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.AI.basic;
import mage.constants.PhaseStep;
@@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBaseAI;
/**
- *
* @author LevelX2
*/
public class CastCreaturesTest extends CardTestPlayerBaseAI {
@@ -173,7 +171,7 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI {
/**
* Tests that the creature is cast if enough mana is available.
- *
+ *
* Once Ammit Eternal is cast against a computer AI opponent, the AI just
* decides to sit there and only play basic lands. I've sat there and decked
* it because it just plays lands. It's like it views giving the Ammit
@@ -194,8 +192,11 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Ammit Eternal");
+
+ setStrictChooseMode(true);
setStopAt(3, PhaseStep.END_TURN);
execute();
+ assertAllCommandsUsed();
assertPermanentCount(playerB, "Ammit Eternal", 1);
diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/ChooseTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ChooseTargetTest.java
new file mode 100644
index 00000000000..c9071775bcd
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ChooseTargetTest.java
@@ -0,0 +1,58 @@
+package org.mage.test.AI.basic;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author JayDi85
+ */
+public class ChooseTargetTest extends CardTestPlayerBase {
+
+ @Test
+ public void test_chooseTargetCard_Manual() {
+ // At the beginning of your end step, choose a creature card in an opponent's graveyard, then that player chooses a creature card in your graveyard.
+ // You may return those cards to the battlefield under their owners' control.
+ addCard(Zone.BATTLEFIELD, playerA, "Dawnbreak Reclaimer", 1);
+ //
+ addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 1);
+ addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion", 1);
+
+ setChoice(playerA, "Silvercoat Lion");
+ setChoice(playerB, "Silvercoat Lion");
+ setChoice(playerA, "Yes");
+
+ setStrictChooseMode(true);
+ setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Silvercoat Lion", 1);
+ assertPermanentCount(playerB, "Silvercoat Lion", 1);
+ }
+
+ @Test
+ public void test_chooseTargetCard_AI() {
+ // At the beginning of your end step, choose a creature card in an opponent's graveyard, then that player chooses a creature card in your graveyard.
+ // You may return those cards to the battlefield under their owners' control.
+ addCard(Zone.BATTLEFIELD, playerA, "Dawnbreak Reclaimer", 1);
+ //
+ addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 1);
+ addCard(Zone.GRAVEYARD, playerB, "Silvercoat Lion", 1);
+
+ // AI must choose itself (strict mode must be disabled)
+ //setChoice(playerA, "Silvercoat Lion");
+ //setChoice(playerB, "Silvercoat Lion");
+ //setChoice(playerA, "Yes");
+
+ //setStrictChooseMode(true);
+ setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Silvercoat Lion", 1);
+ assertPermanentCount(playerB, "Silvercoat Lion", 1);
+ }
+
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java
new file mode 100644
index 00000000000..fd345c9e9be
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/ExileTargetTest.java
@@ -0,0 +1,40 @@
+package org.mage.test.AI.basic;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestCommander4Players;
+
+/**
+ * @author JayDi85
+ */
+public class ExileTargetTest extends CardTestCommander4Players {
+
+ // Player order: A -> D -> C -> B
+ @Test
+ public void test_chooseOpponentTargets() {
+ // AI sometimes chooses own permanents in multiplayer game instead opponents
+
+ // When Oblivion Ring enters the battlefield, exile another target nonland permanent.
+ // When Oblivion Ring leaves the battlefield, return the exiled card to the battlefield under its owner’s control.
+ addCard(Zone.HAND, playerA, "Oblivion Ring", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
+ //
+ addCard(Zone.BATTLEFIELD, playerA, "Apex Altisaur", 1); // 10/10
+ addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); // 2/2
+ addCard(Zone.BATTLEFIELD, playerC, "Balduvian Bears", 1); // 2/2
+
+ // must select opponent's Balduvian Bears
+ // showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Oblivion Ring");
+ //addTarget(playerA, "Balduvian Bears"); // disable to activate AI target choose
+
+ // showAvaileableAbilities("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ //setStrictChooseMode(true); // disable strict mode to activate AI for choosing
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerC, "Balduvian Bears", 0);
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java
index 72d1166e1ac..8c24dff2651 100644
--- a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetPriorityTest.java
@@ -21,7 +21,6 @@ import org.mage.test.serverside.base.CardTestPlayerBaseAI;
public class TargetPriorityTest extends CardTestPlayerBaseAI {
// TODO: enable _target_ tests after computerPlayer.chooseTarget will be reworks like chooseTargetAmount
-
@Test
@Ignore
public void test_target_PriorityKillByBigPT() {
@@ -116,7 +115,6 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
checkPermanentCounters("counters", 1, PhaseStep.BEGIN_COMBAT, playerB, "Balduvian Bears", CounterType.P1P1, 2);
// AI cast avatar on turn 3 and target 1 creature to kil by 3 damage
-
//setStrictChooseMode(true); // AI
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
@@ -144,8 +142,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
addCard(Zone.BATTLEFIELD, playerB, "Battering Sliver", 1 * cardsMultiplier); // 4/4 with ability
//castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt");
-
- showHand("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
+// showHand("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
@@ -159,9 +156,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
*/
}
-
// TARGET AMOUNT
-
@Test
public void test_targetAmount_PriorityKillByBigPT() {
addCard(Zone.HAND, playerA, "Flames of the Firebrand"); // damage 3
@@ -279,7 +274,7 @@ public class TargetPriorityTest extends CardTestPlayerBaseAI {
// must damage x3 Balduvian Bears by -1 to keep alive
checkDamage("pt after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", 1);
- showBattlefield("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ // showBattlefield("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
diff --git a/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetRequiredTest.java b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetRequiredTest.java
new file mode 100644
index 00000000000..8feb009fc4b
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/AI/basic/TargetRequiredTest.java
@@ -0,0 +1,101 @@
+package org.mage.test.AI.basic;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author JayDi85
+ */
+public class TargetRequiredTest extends CardTestPlayerBase {
+
+ /*
+ Redcap must sacrifice target land -- it's required target, but AI don't known about that
+ (target can be copied as new target in effect's code)
+ */
+
+ @Test
+ public void test_chooseBadTargetOnSacrifice_WithTargets_User() {
+ // Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
+ addCard(Zone.HAND, playerA, "Redcap Melee", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
+ addTarget(playerA, "Mountain");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertGraveyardCount(playerA, "Redcap Melee", 1);
+ assertGraveyardCount(playerA, "Mountain", 1);
+ assertPermanentCount(playerA, "Mountain", 3 - 1);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+ }
+
+ @Test
+ public void test_chooseBadTargetOnSacrifice_WithTargets_AI() {
+ // Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
+ addCard(Zone.HAND, playerA, "Redcap Melee", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
+ //addTarget(playerA, "Mountain"); AI must select targets
+
+ //setStrictChooseMode(true); AI must select targets
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertGraveyardCount(playerA, "Redcap Melee", 1);
+ assertGraveyardCount(playerA, "Mountain", 1);
+ assertPermanentCount(playerA, "Mountain", 3 - 1);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+ }
+
+ @Test
+ public void test_chooseBadTargetOnSacrifice_WithoutTargets_User() {
+ // Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
+ addCard(Zone.HAND, playerA, "Redcap Melee", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Atarka Monument", 1); // {T}: Add {R} or {G}.
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
+ //addTarget(playerA, "Mountain"); no lands to sacrifice
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertGraveyardCount(playerA, "Redcap Melee", 1);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+ }
+
+ @Test
+ public void test_chooseBadTargetOnSacrifice_WithoutTargets_AI() {
+ // Redcap Melee deals 4 damage to target creature or planeswalker. If a nonred permanent is dealt damage this way, you sacrifice a land.
+ addCard(Zone.HAND, playerA, "Redcap Melee", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Atarka Monument", 1); // {T}: Add {R} or {G}.
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Redcap Melee", "Silvercoat Lion");
+ //addTarget(playerA, "Mountain"); no lands to sacrifice
+
+ //setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertGraveyardCount(playerA, "Redcap Melee", 1);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java
index e1ddf99d6d1..8c900e619c0 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/activated/LightningStormTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.abilities.activated;
import mage.constants.PhaseStep;
@@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author LevelX2
*/
public class LightningStormTest extends CardTestPlayerBase {
@@ -16,10 +14,9 @@ public class LightningStormTest extends CardTestPlayerBase {
* So, this just happened to me. My opponent cast Lightning Storm and while
* it was on the stack I couldn't use the ability despite having land in
* hand which isn't something I've had an issue with before.
- *
+ *
* My opponent had a Leyline of Sanctity in play, so perhaps that was
* causing the issue somehow? Does anyone want to try and replicate it?
- *
*/
@Test
public void ActivateByBothPlayersTest() {
@@ -31,22 +28,32 @@ public class LightningStormTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Mountain");
addCard(Zone.HAND, playerB, "Mountain");
+ // A activate
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Storm", playerB);
+
+ // B discard and re-target
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Discard");
- setChoice(playerB, "playerA");
+ setChoice(playerB, "Mountain");
+ setChoice(playerB, "Yes");
+ addTarget(playerB, playerA);
+
+ // A discard and re-target
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Discard");
- setChoice(playerA, "playerB");
+ setChoice(playerA, "Mountain");
+ setChoice(playerA, "Yes");
+ addTarget(playerA, playerB);
+ setStrictChooseMode(true);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
-
execute();
+ assertAllCommandsUsed();
assertGraveyardCount(playerA, "Lightning Storm", 1);
assertGraveyardCount(playerB, "Mountain", 1);
assertGraveyardCount(playerA, "Mountain", 1);
assertLife(playerA, 20);
- assertLife(playerB, 13);
+ assertLife(playerB, 20 - 3 - 2 - 2);
}
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java
index 07cf2e1902d..2d610e86c6b 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/EchoTest.java
@@ -36,7 +36,7 @@ public class EchoTest extends CardTestPlayerBase {
// cast Avalanche Riders and destroy forest
- addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Avalanche Riders");
addTarget(playerA, "Forest");
@@ -45,10 +45,19 @@ public class EchoTest extends CardTestPlayerBase {
activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}");
activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}");
activateManaAbility(3, PhaseStep.UPKEEP, playerA, "{T}: Add {W}");
- castSpell(3, PhaseStep.UPKEEP, playerA, "Restoration Angel", null, "Echo {3}{R} (At the beginning of your upkeep, if this came under your control since the beginning of your last upkeep, sacrifice it unless you pay its echo cost.)");
+ castSpell(3, PhaseStep.UPKEEP, playerA, "Restoration Angel");
+ addTarget(playerA, "Avalanche Riders");
setChoice(playerA, "Yes"); // raider do restore
+
+ // Avalanche Riders triggered again
+ addTarget(playerA, "Forest");
+
+ // but no echo for blinked rider
+
+ setStrictChooseMode(true);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
+ assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
@@ -56,7 +65,8 @@ public class EchoTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Avalanche Riders", 1);
assertPermanentCount(playerA, "Restoration Angel", 1);
- assertPermanentCount(playerB, "Forest", 0);
+ assertPermanentCount(playerA, "Forest", 0);
+ assertGraveyardCount(playerA, "Forest", 2);
assertTappedCount("Plains", true, 4);
assertTappedCount("Mountain", true, 0);
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java
index 304e2f324cb..44e1dde0c88 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/KickerWithFlashbackTest.java
@@ -11,17 +11,13 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class KickerWithFlashbackTest extends CardTestPlayerBase {
// https://github.com/magefree/mage/issues/5389
-
-
// Burst Lightning {R}
// Kicker {4} (You may pay an additional {4} as you cast this spell.)
// Burst Lightning deals 2 damage to any target. If this spell was kicked, it deals 4 damage to that permanent or player instead.
-
// Snapcaster Mage {1}{U}
// Flash
// When Snapcaster Mage enters the battlefield, target instant or sorcery card in your graveyard gains flashback until end of turn.
// The flashback cost is equal to its mana cost. (You may cast that card from your graveyard for its flashback cost. Then exile it.)
-
@Test
public void test_SimpleKicker() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
@@ -79,7 +75,7 @@ public class KickerWithFlashbackTest extends CardTestPlayerBase {
addTarget(playerA, "Burst Lightning");
// cast burst by flashback
- showAvaileableAbilities("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ // showAvaileableAbilities("after", 1, PhaseStep.BEGIN_COMBAT, playerA);
activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Flashback");
setChoice(playerA, "Yes"); // use kicker
addTarget(playerA, playerB);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java
index 55b83475619..30dc896c76c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ManifestTest.java
@@ -213,8 +213,8 @@ public class ManifestTest extends CardTestPlayerBase {
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Reality Shift", "Silvercoat Lion");
- showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
- showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
+ // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
+ // showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Silence the Believers", EmptyNames.FACE_DOWN_CREATURE.toString());
setStopAt(1, PhaseStep.END_TURN);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java
index 50bf73e391a..dc8ced15240 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/MorphTest.java
@@ -362,10 +362,10 @@ public class MorphTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sagu Mauler", NO_TARGET, "Sagu Mauler", StackClause.WHILE_NOT_ON_STACK);
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
- showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
+ // showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Echoing Decay", EmptyNames.FACE_DOWN_CREATURE.toString());
- showBattlefield("A battle after", 1, PhaseStep.END_TURN, playerA);
+ // showBattlefield("A battle after", 1, PhaseStep.END_TURN, playerA);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
@@ -540,14 +540,14 @@ public class MorphTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Fury");
setChoice(playerA, "Yes"); // cast it face down as 2/2 creature
- showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
- showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
+// showBattlefield("A battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
+// showBattlefield("B battle", 1, PhaseStep.POSTCOMBAT_MAIN, playerB);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Supplant Form");
addTarget(playerB, EmptyNames.FACE_DOWN_CREATURE.toString());
- showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA);
- showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB);
+// showBattlefield("A battle end", 1, PhaseStep.END_TURN, playerA);
+// showBattlefield("B battle end", 1, PhaseStep.END_TURN, playerB);
setStopAt(1, PhaseStep.END_TURN);
execute();
@@ -717,14 +717,20 @@ public class MorphTest extends CardTestPlayerBase {
* not work correctly. When Vesuvan Shapeshifter turns face up and becomes a
* copy of the targeted creature, it should still be in the state of
* "turning face up", thus triggering the ability of the Brine Elemental.
+ *
+ * combo: Vesuvan Shapeshifter + Brine Elemental Brine Elemental in play,
+ * Vesuvan Shapeshifter in hand 1) Cast Vesuvan Shapeshifter face-down. 2)
+ * Flip Vesuvan Shapeshifter for its morph cost, copying Brine Elemental.
+ * Your opponent skips his next untap. 3) During your upkeep, flip Vesuvan
+ * Shapeshifter face-down. 4) Repeat from 2.
*/
@Test
public void testVesuvanShapeshifter() {
// Morph {5}{U}{U}
// When Brine Elemental is turned face up, each opponent skips their next untap step.
- addCard(Zone.HAND, playerA, "Brine Elemental"); // Creature {4}{U}{U} 5/4
- addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
+ addCard(Zone.BATTLEFIELD, playerA, "Brine Elemental"); // Creature {4}{U}{U} 5/4
+ //addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
// As Vesuvan Shapeshifter enters the battlefield or is turned face up, you may choose another creature on the battlefield.
// If you do, until Vesuvan Shapeshifter is turned face down, it becomes a copy of that creature
@@ -733,23 +739,23 @@ public class MorphTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Vesuvan Shapeshifter"); // Creature 0/0
addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
- castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brine Elemental");
- setChoice(playerA, "No"); // cast it normally
-
+ // 1. Cast Vesuvan as face-down
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Vesuvan Shapeshifter");
- setChoice(playerB, "Yes");
+ setChoice(playerB, "Yes"); // cast as face-down
+ // 2. Moth Vesuvan and copy brine
activateAbility(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "{1}{U}: Turn this face-down permanent");
- setChoice(playerB, "Brine Elemental");
+ addTarget(playerB, "Brine Elemental");
+ // No face up trigger and choose from Vesuvan
+ // But brine's trigger must works on next turn 3 (skip untap)
+ setStrictChooseMode(true);
setStopAt(2, PhaseStep.END_TURN);
-
execute();
+ assertAllCommandsUsed();
assertPermanentCount(playerA, "Brine Elemental", 1);
-
assertPermanentCount(playerB, "Brine Elemental", 1);
-
Assert.assertTrue("Skip next turn has to be added to TurnMods", currentGame.getState().getTurnMods().size() == 1);
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java
index 59d579afa95..c67b2d8bed4 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/TransformTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.abilities.keywords;
import mage.constants.PhaseStep;
@@ -362,4 +361,54 @@ public class TransformTest extends CardTestPlayerBase {
}
+ /**
+ * Having cast Phantasmal Image copying my opponent's flipped Thing in the
+ * Ice, I was left with a 0/4 Awoken Horror.
+ *
+ * https://github.com/magefree/mage/issues/5893
+ *
+ * The transform effect on the stack should fizzle. The card brought back
+ * from Exile should be a new object unless I am interpreting the rules
+ * incorrectly. The returned permanent uses the same GUID.
+ */
+ @Test
+ public void testCopyTransformedThingInTheIce() {
+ // Defender
+ // Thing in the Ice enters the battlefield with four ice counters on it.
+ // Whenever you cast an instant or sorcery spell, remove an ice counter from Thing in the Ice. Then if it has no ice counters on it, transform it.
+ addCard(Zone.HAND, playerA, "Thing in the Ice"); // Creature {1}{U}
+ // Creatures you control get +1/+0 until end of turn.
+ addCard(Zone.HAND, playerA, "Banners Raised", 4); // Creature {R}
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
+
+ // You may have Phantasmal Image enter the battlefield as a copy of any creature
+ // on the battlefield, except it's an Illusion in addition to its other types and
+ // it has "When this creature becomes the target of a spell or ability, sacrifice it."
+ addCard(Zone.HAND, playerB, "Phantasmal Image", 1); // Creature {1}{U}
+ addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thing in the Ice");
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised");
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised");
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised");
+ castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Banners Raised");
+
+ castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Phantasmal Image");
+ addTarget(playerB, "Awoken Horror");
+
+ setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertGraveyardCount(playerA, "Banners Raised", 4);
+ assertPermanentCount(playerA, "Thing in the Ice", 0);
+ assertPermanentCount(playerA, "Awoken Horror", 1);
+ assertPowerToughness(playerA, "Awoken Horror", 7, 8);
+
+ assertPermanentCount(playerB, "Awoken Horror", 1);
+ assertPowerToughness(playerB, "Awoken Horror", 7, 8);
+
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java
new file mode 100644
index 00000000000..b4013c696b1
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/PlayTopCardFromLibraryTest.java
@@ -0,0 +1,128 @@
+package org.mage.test.cards.asthough;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ * @author JayDi85
+ */
+public class PlayTopCardFromLibraryTest extends CardTestPlayerBase {
+
+ /*
+ Bolas's Citadel
+ {3}{B}{B}{B}
+ You may look at the top card of your library any time.
+ You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
+ {T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.
+ */
+
+ @Test
+ public void test_CreaturePlay() {
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears"); // 2 CMC
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Balduvian Bears", 1);
+ assertLife(playerA, 20 - 2);
+ }
+
+ @Test
+ public void test_CreaturePlay2() {
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Balduvian Bears", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Vizier of the Menagerie", 1);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Balduvian Bears", 1);
+ }
+
+ @Test
+ public void test_ManaCostmodifications() {
+ //
+ // {5}{B}{B}
+ // You may cast Scourge of Nel Toth from your graveyard by paying {B}{B} and sacrificing two creatures rather than paying its mana cost.
+ addCard(Zone.GRAVEYARD, playerA, "Scourge of Nel Toth", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Kitesail Corsair", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Scourge of Nel Toth");
+ setChoice(playerA, "Kitesail Corsair");
+ setChoice(playerA, "Kitesail Corsair");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Scourge of Nel Toth", 1);
+ assertLife(playerA, 20);
+ }
+
+ @Test
+ public void test_SplitRightPlay() {
+ // https://github.com/magefree/mage/issues/5912
+ // Bolas's citadel requires you to pay mana instead of life for a split card on top of library.
+ //
+ // Steps to reproduce:
+ //
+ // Bolas's Citadel in play, Revival//Revenge on top of library.
+ // Cast Revenge, choose target
+ // receive prompt to pay 4WB.
+ //
+ // Expected outcome
+ //
+ // No prompt for mana payment, payment of six life instead.
+
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
+
+ // Double your life total. Target opponent loses half their life, rounded up.
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revenge", playerB); // {4}{W}{B} = 6 life
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertLife(playerA, (20 - 6) * 2);
+ assertLife(playerB, 20 / 2);
+ }
+
+ @Test
+ public void test_SplitLeftPlay() {
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Revival // Revenge", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
+ addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 1);
+
+ // Return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revival", "Balduvian Bears"); // {W/B}{W/B} = 2 life
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertLife(playerA, 20 - 2);
+ assertLife(playerB, 20);
+ assertGraveyardCount(playerA, "Balduvian Bears", 0);
+ assertPermanentCount(playerA, "Balduvian Bears", 1);
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java
index 1d117fc88be..3176f1d128c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/asthough/SpendOtherManaTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.asthough;
import mage.constants.PhaseStep;
@@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author LevelX2
*/
public class SpendOtherManaTest extends CardTestPlayerBase {
@@ -47,7 +45,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
/**
* Tron mana doesn't work with Oath of Nissa. (e.g. can't cast Chandra,
* Flamecaller with Urza's Tower, Power Plant, and Mine.)
- *
+ *
* AI don't get the Planeswalker as playable card (probably because of the
* as thought effect)
*/
@@ -76,7 +74,7 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
* I was unable to cast Nissa, Voice of Zendikar using black mana with Oath
* of Nissa in play. Pretty sure Oath is working usually, so here were the
* conditions in my game:
- *
+ *
* -Cast Dark Petition with spell mastery -Attempt to cast Nissa, Voice of
* Zendikar using the triple black mana from Dark Petition
*/
@@ -122,20 +120,84 @@ public class SpendOtherManaTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerA, "Hostage Taker"); // {2}{U}{B}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hostage Taker");
- setChoice(playerA, "Silvercoat Lion");
+ addTarget(playerA, "Silvercoat Lion");
+ // red mana must be used as any mana
activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool
activateManaAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{T}: Add {R}."); // red mana to pool
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Silvercoat Lion"); // cast it from exile with red mana from pool
+ setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
+ assertAllCommandsUsed();
assertPermanentCount(playerA, "Hostage Taker", 1);
assertTappedCount("Mountain", true, 4);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
-
}
+ @Test
+ public void test_QuicksilverElemental_Normal() {
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
+
+ // {U}: Quicksilver Elemental gains all activated abilities of target creature until end of turn.
+ // You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver Elemental’s abilities.
+ addCard(Zone.BATTLEFIELD, playerA, "Quicksilver Elemental"); // Creature {1}{W}
+ // {R}, {T}: Anaba Shaman deals 1 damage to any target.
+ addCard(Zone.BATTLEFIELD, playerB, "Anaba Shaman");
+
+ // gain abilities
+ checkPlayableAbility("must not have", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", false);
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}:", "Anaba Shaman");
+
+ // use ability
+ checkPlayableAbility("must have new ability", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", true);
+ activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", playerB);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20 - 1);
+ }
+
+ @Test
+ public void test_QuicksilverElemental_Flicker() {
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
+
+ // {U}: Quicksilver Elemental gains all activated abilities of target creature until end of turn.
+ // You may spend blue mana as though it were mana of any color to pay the activation costs of Quicksilver Elemental’s abilities.
+ addCard(Zone.BATTLEFIELD, playerA, "Quicksilver Elemental"); // Creature {1}{W}
+ // {R}, {T}: Anaba Shaman deals 1 damage to any target.
+ addCard(Zone.BATTLEFIELD, playerB, "Anaba Shaman");
+ // Exile target nontoken permanent, then return it to the battlefield under its owner’s control.
+ addCard(Zone.HAND, playerA, "Flicker"); // {1}{W}
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
+
+ // gain abilities
+ checkPlayableAbility("must not have", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", false);
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{U}:", "Anaba Shaman");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPlayableAbility("must have new ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{R}, {T}:", true);
+
+ // renew target
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Flicker", "Anaba Shaman");
+
+ // use ability
+ checkPlayableAbility("must save ability", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", true);
+ activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{R}, {T}:", playerB);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertGraveyardCount(playerA, "Flicker", 1);
+ assertLife(playerA, 20);
+ assertLife(playerB, 20 - 1);
+ }
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java
index e5ce6a0800f..1f3373df032 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/AngelOfJubilationTest.java
@@ -83,11 +83,13 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Food Chain");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}, Sacrifice a permanent you control: Return target creature to its owner's hand.");
- playerB.addChoice("Food Chain");
- playerA.addTarget("Angel of Jubilation");
+ addTarget(playerB, "Angel of Jubilation"); // return to hand
+ setChoice(playerB, "Food Chain"); // cacrifice cost
+ setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
+ assertAllCommandsUsed();
assertPermanentCount(playerA, "Angel of Jubilation", 0);
assertPermanentCount(playerB, "Food Chain", 0);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java
index 0b0ea380119..4caf5347b13 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java
@@ -12,13 +12,12 @@ import org.mage.test.serverside.base.CardTestCommander4Players;
public class CommandersCastTest extends CardTestCommander4Players {
// Player order: A -> D -> C -> B
-
@Test
public void test_CastToBattlefieldOneTime() {
addCard(Zone.COMMAND, playerA, "Balduvian Bears", 1); // {1}{G}, 2/2, commander
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
- showCommand("commanders", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showCommand("commanders", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
setStopAt(1, PhaseStep.END_TURN);
@@ -71,7 +70,7 @@ public class CommandersCastTest extends CardTestCommander4Players {
public void test_PlayAsLandOneTime() {
addCard(Zone.COMMAND, playerA, "Academy Ruins", 1);
- showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins");
//castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Academy Ruins");
@@ -114,8 +113,7 @@ public class CommandersCastTest extends CardTestCommander4Players {
waitStackResolved(5, PhaseStep.POSTCOMBAT_MAIN);
checkPermanentCount("after cast 2", 5, PhaseStep.POSTCOMBAT_MAIN, playerA, "Academy Ruins", 1);
- showBattlefield("end battlefield", 5, PhaseStep.END_TURN, playerA);
-
+// showBattlefield("end battlefield", 5, PhaseStep.END_TURN, playerA);
setStopAt(5, PhaseStep.END_TURN);
setStrictChooseMode(true);
execute();
@@ -261,7 +259,7 @@ public class CommandersCastTest extends CardTestCommander4Players {
addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 2);
// cast overload
- showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Weapon Surge with overload");
setChoice(playerA, "Yes"); // move to command zone
checkAbility("after", 1, PhaseStep.BEGIN_COMBAT, playerA, "Balduvian Bears", FirstStrikeAbility.class, true);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java
index 42c8b0bb6f7..2a7e23d40af 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/ConditionalPreventionTest.java
@@ -134,4 +134,60 @@ public class ConditionalPreventionTest extends CardTestPlayerBase {
assertHandCount(playerA, "Lightning Bolt", 0);
}
+ @Test
+ public void test_PrentableCombatDamage() {
+ // Prevent all damage that would be dealt to creatures.
+ addCard(Zone.BATTLEFIELD, playerA, "Bubble Matrix", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // player A must do damage
+ attack(1, playerA, "Balduvian Bears", playerB);
+
+ // player B can't do damage (bears must block and safe)
+ attack(4, playerB, "Balduvian Bears", playerA);
+ block(4, playerA, "Balduvian Bears", "Balduvian Bears");
+
+ setStrictChooseMode(true);
+ setStopAt(4, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Balduvian Bears", 1);
+ assertPermanentCount(playerB, "Balduvian Bears", 1);
+ assertLife(playerA, 20);
+ assertLife(playerB, 20 - 2);
+ }
+
+ @Test
+ public void test_UnpreventableCombatDamage() {
+ // Combat damage that would be dealt by creatures you control can't be prevented.
+ addCard(Zone.BATTLEFIELD, playerB, "Questing Beast", 1);
+ //
+ // Prevent all damage that would be dealt to creatures.
+ addCard(Zone.BATTLEFIELD, playerA, "Bubble Matrix", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
+ //
+ addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // player A must do damage
+ attack(1, playerA, "Balduvian Bears", playerB);
+
+ // player B must be prevented by Bubble Matrix, but can't (Questing Beast)
+ // a -> b -- can't do damage (matrix)
+ // b -> a -- can do damage (matrix -> quest)
+ attack(4, playerB, "Balduvian Bears", playerA);
+ block(4, playerA, "Balduvian Bears", "Balduvian Bears");
+
+ setStrictChooseMode(true);
+ setStopAt(4, PhaseStep.END_TURN);
+ execute();
+ assertAllCommandsUsed();
+
+ assertPermanentCount(playerA, "Balduvian Bears", 0);
+ assertPermanentCount(playerB, "Balduvian Bears", 1);
+ assertLife(playerA, 20);
+ assertLife(playerB, 20 - 2);
+ }
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java
index b25fd55e46e..4a981b00069 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/EndOfTurnMultiOpponentsTest.java
@@ -153,4 +153,58 @@ public class EndOfTurnMultiOpponentsTest extends CardTestMultiPlayerBaseWithRang
assertAllCommandsUsed();
}
+ // leaved players
+ // 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
+ // or until a specific point in that turn will last until that turn would have begun.
+ // They neither expire immediately nor last indefinitely.
+ @Test
+ public void test_UntilYourNextTurnMulti_Leaved() {
+ // Player order: A -> D -> C -> B
+ addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilYourNextTurn)));
+
+ EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN);
+ EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 2, playerD, true, PhaseStep.END_TURN);
+ EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 3, playerC, true, PhaseStep.END_TURN);
+ EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 4, playerB, true, PhaseStep.END_TURN);
+ EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 5, playerD, true, null);
+
+ addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1);
+ addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1);
+ addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1);
+ addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1);
+ //
+ // When Eye of Doom enters the battlefield, each player chooses a nonland permanent and puts a doom counter on it.
+ addCard(Zone.HAND, playerC, "Eye of Doom", 1);
+ addCard(Zone.BATTLEFIELD, playerC, "Forest", 4);
+
+ checkPlayerInGame("A must plays in 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, true);
+ attack(1, playerA, cardBear2);
+
+ checkPlayerInGame("A must plays in 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
+ attack(2, playerD, cardBear2);
+
+ checkPlayerInGame("A must plays in 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerC, playerA, true);
+ attack(3, playerC, cardBear2);
+ concede(3, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPlayerInGame("A must leaved in 3 after", 3, PhaseStep.POSTCOMBAT_MAIN, playerC, playerA, false);
+
+ // test PlayerList.getNext processing
+ // play Eye of Doom, ask all players to put doom counter
+ castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Eye of Doom");
+ addTarget(playerC, cardBear2);
+ addTarget(playerB, cardBear2);
+ //addTarget(playerA, cardBear2); // leaved
+ addTarget(playerD, cardBear2);
+
+ checkPlayerInGame("A must leaved in 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, playerA, false);
+ attack(4, playerB, cardBear2);
+ checkPlayerInGame("A must leaved in 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerD, playerA, false);
+ attack(5, playerD, cardBear2);
+
+ setStopAt(5, PhaseStep.CLEANUP);
+ setStrictChooseMode(true);
+ execute();
+ assertAllCommandsUsed();
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MerfolkTricksterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MerfolkTricksterTest.java
new file mode 100644
index 00000000000..1a9e2e6c5c1
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/MerfolkTricksterTest.java
@@ -0,0 +1,137 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package org.mage.test.cards.continuous;
+
+import mage.abilities.Abilities;
+import mage.abilities.AbilitiesImpl;
+import mage.abilities.Ability;
+import mage.abilities.keyword.FlashAbility;
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import mage.counters.CounterType;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ *
+ * @author drmDev
+ */
+public class MerfolkTricksterTest extends CardTestPlayerBase {
+
+ /*
+ Merfolk Trickster (UU)
+ Creature Merfolk Wizard
+ Flash
+ When Merfolk Trickster enters the battlefield, tap target creature an opponent controls. It loses all abilities until end of turn.
+ */
+ public final String mTrickster = "Merfolk Trickster";
+
+ @Test
+ public void test_TricksterAndFlyer_FlyingRemoved() {
+ addCard(Zone.BATTLEFIELD, playerA, "Flying Men"); // (U) 1/1 flyer
+ addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
+ addCard(Zone.HAND, playerB, mTrickster);
+
+ attack(1, playerA, "Flying Men");
+ castSpell(1, PhaseStep.DECLARE_BLOCKERS, playerB, mTrickster);
+ addTarget(playerB, "Flying Men");
+
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 19);
+ assertTappedCount("Island", true, 2);
+ assertTapped("Flying Men", true);
+
+ Abilities noAbilities = new AbilitiesImpl<>();
+ assertAbilities(playerA, "Flying Men", noAbilities); // no abilities, empty list
+
+ Abilities flashAbility = new AbilitiesImpl<>();
+ flashAbility.add(FlashAbility.getInstance());
+ assertAbilities(playerB, mTrickster, flashAbility); // has flash
+
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_TricksterAndFlyerBlocked_FlyingRemovedAndBlocked() {
+ addCard(Zone.BATTLEFIELD, playerA, "Flying Men"); // (U) 1/1 flyer
+ addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
+ addCard(Zone.HAND, playerB, mTrickster);
+
+ attack(1, playerA, "Flying Men");
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, mTrickster);
+ addTarget(playerB, "Flying Men");
+ block(1, playerB, mTrickster, "Flying Men");
+
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ assertTappedCount("Island", true, 2);
+ assertGraveyardCount(playerA, "Flying Men", 1);
+ assertPermanentCount(playerB, mTrickster, 1);
+ assertDamageReceived(playerB, mTrickster, 1);
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_TricksterBlocksFootlightFiend_Survives() {
+ addCard(Zone.BATTLEFIELD, playerA, "Footlight Fiend"); // (R/B) 1/1 on death pings any target for 1
+ addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
+ addCard(Zone.HAND, playerB, mTrickster);
+
+ attack(1, playerA, "Footlight Fiend");
+ castSpell(1, PhaseStep.DECLARE_ATTACKERS, playerB, mTrickster);
+ addTarget(playerB, "Footlight Fiend");
+ block(1, playerB, mTrickster, "Footlight Fiend");
+ addTarget(playerA, mTrickster);
+
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ assertTappedCount("Island", true, 2);
+ assertPermanentCount(playerB, mTrickster, 1);
+ assertDamageReceived(playerB, mTrickster, 1);
+ //assertAllCommandsUsed(); // uncommenting this will force a failure since PlayerA cannot do a command to target Trickster, as expected
+ }
+
+ @Test
+ public void test_TricksterBlocksTibaltToken_Survives() {
+ /*
+ Tibalt, Rakish Instigator (2R)
+ Legendary Planeswalker Tibalt
+ Your opponents can't gain life.
+ -2: Create a 1/1 red Devil creature token with "When this creature dies, it deals 1 damage to any target."
+ */
+ addCard(Zone.BATTLEFIELD, playerA, "Tibalt, Rakish Instigator");
+ addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
+ addCard(Zone.HAND, playerB, mTrickster);
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2:");
+
+ attack(3, playerA, "Devil");
+ castSpell(3, PhaseStep.DECLARE_ATTACKERS, playerB, mTrickster);
+ addTarget(playerB, "Devil");
+ block(3, playerB, mTrickster, "Devil");
+ addTarget(playerA, mTrickster);
+
+ setStopAt(3, PhaseStep.END_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ assertCounterCount("Tibalt, Rakish Instigator", CounterType.LOYALTY, 3);
+ assertTappedCount("Island", true, 2);
+ assertPermanentCount(playerB, mTrickster, 1);
+ assertDamageReceived(playerB, mTrickster, 1);
+ // assertAllCommandsUsed(); // uncommenting this should force a failure since PlayerA cannot do a command to target Trickster, as expected
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PraetorsGraspTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PraetorsGraspTest.java
index f21418d1c99..941ca49a460 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PraetorsGraspTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PraetorsGraspTest.java
@@ -22,8 +22,7 @@ public class PraetorsGraspTest extends CardTestPlayerBase {
addTarget(playerA, playerB);
addTarget(playerA, "Mountain");
- showAvaileableAbilities("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
-
+ // showAvaileableAbilities("after", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java
index 681b4981bf3..3afca6ea16b 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/PsychicIntrusionTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.continuous;
import mage.constants.PhaseStep;
@@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author LevelX2
*/
@@ -23,20 +21,22 @@ public class PsychicIntrusionTest extends CardTestPlayerBase {
// Target opponent reveals their hand. You choose a nonland card from that player's
// graveyard or hand and exile it. You may cast that card for as long as it remains exiled,
// and you may spend mana as though it were mana of any color to cast that spell.
- addCard(Zone.HAND, playerA, "Psychic Intrusion", 1);
+ addCard(Zone.HAND, playerA, "Psychic Intrusion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
-
- addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1);
+
+ addCard(Zone.HAND, playerB, "Elspeth, Sun's Champion", 1); // {4}{W}{W}
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB);
- addTarget(playerA, "Elspeth, Sun's Champion");
-
+ setChoice(playerA, "Elspeth, Sun's Champion");
+
// cast from exile with any mana
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Elspeth, Sun's Champion");
-
+
+ setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
+ assertAllCommandsUsed();
assertGraveyardCount(playerA, "Psychic Intrusion", 1);
assertHandCount(playerB, "Elspeth, Sun's Champion", 0);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/control/GainControlDiedCastAgainTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/control/GainControlDiedCastAgainTest.java
index 66977a02087..43cf81ec556 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/control/GainControlDiedCastAgainTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/control/GainControlDiedCastAgainTest.java
@@ -42,10 +42,12 @@ public class GainControlDiedCastAgainTest extends CardTestPlayerBase {
attack(2, playerB, "Elesh Norn, Grand Cenobite");
block(2, playerA, "Keiga, the Tide Star", "Elesh Norn, Grand Cenobite");
- addTarget(playerB, "Elesh Norn, Grand Cenobite");
+ addTarget(playerA, "Elesh Norn, Grand Cenobite");
+ setStrictChooseMode(true);
setStopAt(2, PhaseStep.POSTCOMBAT_MAIN);
execute();
+ assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 20);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java
index b94640fc882..e3d6030976b 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/CopySpellTest.java
@@ -77,11 +77,14 @@ public class CopySpellTest extends CardTestPlayerBase {
assertAbility(playerB, "Silvercoat Lion", FlyingAbility.getInstance(), false);
}
- /**
+ /*
* Reported bug: "Silverfur Partisan and fellow wolves did not trigger off
* of copies of Strength of Arms made by Zada, Hedron Grinder. Not sure
* about other spells, but I imagine similar results."
- */
+
+ // Perhaps someone knows the correct implementation for this test.
+ // Just target the Silverfur Partisan and hit done
+ // This test works fine in game. The @Ignore would not work for me either.
@Test
public void ZadaHedronSilverfurPartisan() {
@@ -98,18 +101,17 @@ public class CopySpellTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
- //castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Village Messenger");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Giant Growth", "Zada, Hedron Grinder");
-
- setStopAt(1, PhaseStep.BEGIN_COMBAT);
- execute();
+ addTarget(playerA, "Silverfur Partisan");
assertGraveyardCount(playerA, "Giant Growth", 1);
assertPowerToughness(playerA, "Silverfur Partisan", 5, 5);
assertPowerToughness(playerA, "Zada, Hedron Grinder", 6, 6);
assertPermanentCount(playerA, "Wolf", 1); // created from Silverfur ability
}
-
+ */
+
+
@Test
public void ZadaHedronGrinderBoostWithCharm() {
// Choose two -
@@ -159,12 +161,12 @@ public class CopySpellTest extends CardTestPlayerBase {
* modal the player announces the mode choice (see rule 700.2). If the
* player wishes to splice any cards onto the spell (see rule 702.46), they
* reveal those cards in their hand. 706.10. To copy a spell, activated
- * ability, or triggered ability means to put a copy of it onto the stack;
- * a copy of a spell isn't cast and a copy of an activated ability isn't
+ * ability, or triggered ability means to put a copy of it onto the stack; a
+ * copy of a spell isn't cast and a copy of an activated ability isn't
* activated. A copy of a spell or ability copies both the characteristics
* of the spell or ability and all decisions made for it, including modes,
- * targets, the value of X, and additional or alternative costs.
- * (See rule 601, “Casting Spells.”)
+ * targets, the value of X, and additional or alternative costs. (See rule
+ * 601, “Casting Spells.”)
*/
@Test
public void ZadaHedronGrinderAndSplicedSpell() {
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java
index b11ff198525..fc014230648 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/copy/SparkDoubleTest.java
@@ -1,5 +1,9 @@
package org.mage.test.cards.copy;
+import mage.abilities.Ability;
+import mage.abilities.common.SimpleActivatedAbility;
+import mage.abilities.costs.mana.ManaCostsImpl;
+import mage.abilities.effects.common.CreateTokenCopyTargetEffect;
import mage.abilities.keyword.VigilanceAbility;
import mage.constants.CardType;
import mage.constants.PhaseStep;
@@ -7,6 +11,7 @@ import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
+import mage.target.TargetPermanent;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@@ -154,4 +159,87 @@ public class SparkDoubleTest extends CardTestPlayerBase {
Assert.assertEquals("must add 1 loyalty", 4 + 1, spark.getCounters(currentGame).getCount(CounterType.LOYALTY));
Assert.assertEquals("must add 1 creature counter", 1, spark.getCounters(currentGame).getCount(CounterType.P1P1));
}
+
+ @Test
+ public void test_CopyOfSparksCopy_BySpell() {
+
+ /*
+ Spark Double isn’t legendary if it copies a legendary permanent, and this exception is copiable.
+ If something else copies Spark Double later, that copy also won’t be legendary.
+ If you control two or more permanents with the same name but only one is legendary, the “legend rule” doesn’t apply. (2019-05-03)
+
+ it's applier copy check
+ */
+ //
+ addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U}
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
+ //
+ addCard(Zone.BATTLEFIELD, playerA, "Akroma, Angel of Wrath", 1); // legendary
+ //
+ // Create a 1/1 white Bird creature token with flying, then populate. (Create a token that’s a copy of a creature token you control.)
+ addCard(Zone.HAND, playerA, "Eyes in the Skies"); // {3}{W}
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
+ //
+ // Create a token that’s a copy of target creature you control.
+ addCard(Zone.HAND, playerA, "Quasiduplicate"); // {1}{U}{U}
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
+
+ // make copy of legendary creature (it's not legendary now)
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double");
+ setChoice(playerA, "Yes");
+ setChoice(playerA, "Akroma, Angel of Wrath");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPermanentCount("must have copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", 2);
+
+ // make copy of copy by CreateTokenCopyTargetEffect
+// showBattlefield("before last copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Quasiduplicate");
+ addTarget(playerA, "Akroma, Angel of Wrath[only copy]");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPermanentCount("must have copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", 3);
+
+// showBattlefield("after all", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_CopyOfSparksCopy_ByAbility() {
+ Ability ability = new SimpleActivatedAbility(new CreateTokenCopyTargetEffect(), new ManaCostsImpl(""));
+ ability.addTarget(new TargetPermanent());
+ addCustomCardWithAbility("copy", playerA, ability);
+
+ addCard(Zone.HAND, playerA, "Spark Double"); // {3}{U}
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
+ //
+ addCard(Zone.BATTLEFIELD, playerA, "Akroma, Angel of Wrath", 1); // legendary
+ //
+ // Create a 1/1 white Bird creature token with flying, then populate. (Create a token that’s a copy of a creature token you control.)
+ addCard(Zone.HAND, playerA, "Eyes in the Skies"); // {3}{W}
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
+
+ // make copy of legendary creature (it's not legendary now)
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Spark Double");
+ setChoice(playerA, "Yes");
+ setChoice(playerA, "Akroma, Angel of Wrath");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPermanentCount("must have copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", 2);
+
+ // make copy of copy by CreateTokenCopyTargetEffect
+ // showBattlefield("before last copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("before last copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create a token that");
+ addTarget(playerA, "Akroma, Angel of Wrath[only copy]");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ checkPermanentCount("must have copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", 3);
+
+ // showBattlefield("after all", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java
new file mode 100644
index 00000000000..6a888285cb1
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/adventure/AdventureCardsTest.java
@@ -0,0 +1,641 @@
+package org.mage.test.cards.cost.adventure;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import mage.counters.CounterType;
+import mage.game.permanent.Permanent;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+public class AdventureCardsTest extends CardTestPlayerBase {
+
+ String abilityBrazenBorrowerMainCast = "Cast Brazen Borrower";
+ String abilityBrazenBorrowerAdventureCast = "Cast Petty Theft";
+
+ @Test
+ public void testCastTreatsToShare() {
+ /*
+ * Curious Pair {1}{G}
+ * Creature — Human Peasant
+ * 1/3
+ * ----
+ * Treats to Share {G}
+ * Sorcery — Adventure
+ * Create a Food token.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCantCastTreatsToShareTwice() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertActionsCount(playerA, 1);
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastCuriousPair() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 0);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastTreatsToShareAndCuriousPair() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastTreatsToShareWithEdgewallInnkeeper() {
+ /*
+ * Edgewall Innkeeper {G}
+ * Creature — Human Peasant
+ * Whenever you cast a creature spell that has an Adventure, draw a card.
+ * 1/1
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastCuriousPairWithEdgewallInnkeeper() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertHandCount(playerA, 1);
+ assertPermanentCount(playerA, "Food", 0);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastTreatsToShareAndCuriousPairWithEdgewallInnkeeper() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Edgewall Innkeeper");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 1);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastCuriousPairWithMysteriousPathlighter() {
+ setStrictChooseMode(true);
+
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Mysterious Pathlighter");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 0);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertPowerToughness(playerA, "Curious Pair", 2, 4);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastMemoryTheft() {
+ /*
+ * Memory Theft {2}{B}
+ * Sorcery
+ * Target opponent reveals their hand. You choose a nonland card from it. That player discards that card.
+ * You may put a card that has an Adventure that player owns from exile into that player's graveyard.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ addCard(Zone.HAND, playerA, "Opt");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+
+ addCard(Zone.BATTLEFIELD, playerB, "Swamp");
+ addCard(Zone.BATTLEFIELD, playerB, "Swamp");
+ addCard(Zone.BATTLEFIELD, playerB, "Swamp");
+ addCard(Zone.HAND, playerB, "Memory Theft");
+ castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Memory Theft", playerA);
+ playerB.addChoice("Opt");
+ playerB.addChoice("Curious Pair");
+
+ setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertHandCount(playerA, 0);
+ assertExileCount(playerA, "Curious Pair", 0);
+ assertGraveyardCount(playerA, 2);
+ }
+
+ @Test
+ public void testCastTreatsToShareWithLuckyClover() {
+ /*
+ * Lucky Clover {2}
+ * Artifact
+ * Whenever you cast an Adventure instant or sorcery spell, copy it. You may choose new targets for the copy.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Lucky Clover");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 2);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCastTreatsToShareAndCopy() {
+ /*
+ * Fork {R}{R}
+ * Instant
+ * Copy target instant or sorcery spell, except that the copy is red. You may choose new targets for the copy.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain");
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ addCard(Zone.HAND, playerA, "Fork");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Fork", "Treats to Share");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 2);
+ assertPermanentCount(playerA, 5);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, 1);
+ assertGraveyardCount(playerA, "Fork", 1);
+ assertGraveyardCount(playerA, 1);
+ }
+
+ @Test
+ public void testCastTreatsToShareAndCounter() {
+ /*
+ * Counterspell {U}{U}
+ * Instant
+ * Counter target spell.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerB, "Island");
+ addCard(Zone.BATTLEFIELD, playerB, "Island");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+ addCard(Zone.HAND, playerB, "Counterspell");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Counterspell", "Treats to Share");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 0);
+ assertPermanentCount(playerA, 1);
+ assertExileCount(playerA, 0);
+ assertGraveyardCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 1);
+ assertGraveyardCount(playerB, "Counterspell", 1);
+ assertGraveyardCount(playerB, 1);
+ }
+
+ @Test
+ public void testCastOpponentsHandTreatsToShare() {
+ /*
+ * Psychic Intrusion {3}{U}{B}
+ * Sorcery
+ * Target opponent reveals their hand. You choose a nonland card from that player's graveyard or hand and exile it.
+ * You may cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any color to cast that spell.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 6);
+ addCard(Zone.HAND, playerA, "Psychic Intrusion");
+ addCard(Zone.HAND, playerB, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Psychic Intrusion", playerB);
+ setChoice(playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertHandCount(playerB, 0);
+ assertPermanentCount(playerB, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, 0);
+ assertExileCount(playerB, 0);
+ assertGraveyardCount(playerA, "Psychic Intrusion", 1);
+ assertGraveyardCount(playerA, 1);
+ }
+
+ @Test
+ public void testMultipleAdventures() {
+ /*
+ * Eager Cadet
+ * Creature — Human Soldier
+ * 1/1
+ */
+ /*
+ * Rimrock Knight {1}{R}
+ * Creature — Dwarf Knight
+ * Rimrock Knight can't block.
+ * 3/1
+ * ----
+ * Boulder Rush {R}
+ * Instant — Adventure
+ * Target creature gets +2/+0 until end of turn.
+ */
+
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 6);
+ addCard(Zone.BATTLEFIELD, playerA, "Eager Cadet");
+ addCard(Zone.HAND, playerA, "Rimrock Knight", 2);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boulder Rush", "Eager Cadet");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boulder Rush", "Eager Cadet");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight");
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Rimrock Knight", 2);
+ assertPermanentCount(playerA, "Eager Cadet", 1);
+ assertPowerToughness(playerA, "Eager Cadet", 5, 1);
+ assertExileCount(playerA, 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testRimrockKnightPermanentText() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
+ addCard(Zone.HAND, playerA, "Rimrock Knight");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rimrock Knight");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Rimrock Knight", 1);
+ assertExileCount(playerA, 0);
+ assertGraveyardCount(playerA, 0);
+
+ Permanent rimrock = getPermanent("Rimrock Knight");
+ Assert.assertEquals(rimrock.getRules(currentGame).get(0), "{this} can't block.");
+ }
+
+ /*
+ * Tests for Rule 601.3e:
+ * 601.3e If a rule or effect states that only an alternative set of characteristics or a subset of characteristics
+ * are considered to determine if a card or copy of a card is legal to cast, those alternative characteristics
+ * replace the object’s characteristics prior to determining whether the player may begin to cast it.
+ * Example: Garruk’s Horde says, in part, “You may cast the top card of your library if it’s a creature card.” If
+ * you control Garruk’s Horde and the top card of your library is a noncreature card with morph, you may cast it
+ * using its morph ability.
+ * Example: Melek, Izzet Paragon says, in part, “You may cast the top card of your library if it’s an instant or
+ * sorcery card.” If you control Melek, Izzet Paragon and the top card of your library is Giant Killer, an
+ * adventurer creature card whose Adventure is an instant named Chop Down, you may cast Chop Down but not Giant
+ * Killer. If instead you control Garruk’s Horde and the top card of your library is Giant Killer, you may cast
+ * Giant Killer but not Chop Down.
+ */
+ @Test
+ public void testCastTreatsToShareWithMelek() {
+ /*
+ * Melek, Izzet Paragon {4}{U}{R}
+ * Legendary Creature — Weird Wizard
+ * Play with the top card of your library revealed.
+ * You may cast the top card of your library if it's an instant or sorcery card.
+ * Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy.
+ * 2/4
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Melek, Izzet Paragon");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, 4);
+ assertPermanentCount(playerA, "Food", 2);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCantCastCuriousPairWithMelek() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Melek, Izzet Paragon");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertActionsCount(playerA, 1);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertLibraryCount(playerA, 1);
+ }
+
+ @Test
+ public void testCastCuriousPairWithGarruksHorde() {
+ /*
+ * Garruk's Horde {5}{G}{G}
+ * Creature — Beast
+ * Trample
+ * Play with the top card of your library revealed.
+ * You may cast the top card of your library if it's a creature card.
+ * 7/7
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Garruk's Horde");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 0);
+ assertPermanentCount(playerA, "Curious Pair", 1);
+ assertExileCount(playerA, 0);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void testCantCastTreatsToShareWithGarruksHorde() {
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Garruk's Horde");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Curious Pair");
+
+ // showAvaileableAbilities("abils", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertActionsCount(playerA, 1);
+ assertPermanentCount(playerA, "Food", 0);
+ assertLibraryCount(playerA, 1);
+ }
+
+ @Test
+ //@Ignore("Not yet working correctly.")
+ public void testCastTreatsToShareWithWrennAndSixEmblem() {
+ /*
+ * Wrenn and Six {R}{G}
+ * Legendary Planeswalker — Wrenn
+ * +1: Return up to one target land card from your graveyard to your hand.
+ * −1: Wrenn and Six deals 1 damage to any target.
+ * −7: You get an emblem with "Instant and sorcery cards in your graveyard have retrace."
+ * Loyalty: 3
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Wrenn and Six");
+ addCard(Zone.GRAVEYARD, playerA, "Curious Pair");
+ addCard(Zone.HAND, playerA, "Forest"); // pay for retrace
+
+ addCounters(1, PhaseStep.UPKEEP, playerA, "Wrenn and Six", CounterType.LOYALTY, 5);
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-7: You get an emblem");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("abils", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+
+ // retrace - You may cast this card from your graveyard by discarding a land card as an additional cost to cast it
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ setChoice(playerA, "Forest");
+
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertPermanentCount(playerA, "Wrenn and Six", 1);
+ assertEmblemCount(playerA, 1);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, "Forest", 1);
+ assertGraveyardCount(playerA, 1);
+ }
+
+ @Test
+ public void testCastTreatsToShareWithTeferiTimeRaveler() {
+ /*
+ * Teferi, Time Raveler {1}{W}{U}
+ * Legendary Planeswalker — Teferi
+ * Each opponent can cast spells only any time they could cast a sorcery.
+ * +1: Until your next turn, you may cast sorcery spells as though they had flash.
+ * −3: Return up to one target artifact, creature, or enchantment to its owner's hand. Draw a card.
+ * Loyalty: 4
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Teferi, Time Raveler");
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.HAND, playerA, "Curious Pair");
+
+ activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1: Until your next");
+ waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("abils", 1, PhaseStep.BEGIN_COMBAT, playerA);
+ castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Treats to Share");
+
+ setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, 3);
+ assertPermanentCount(playerA, "Food", 1);
+ assertPermanentCount(playerA, "Curious Pair", 0);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA, 0);
+ }
+
+ @Test
+ public void test_PlayableAbiities_NoneByMana() {
+
+ addCard(Zone.HAND, playerA, "Brazen Borrower", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
+ addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // no playable by mana
+ checkPlayableAbility("main", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerMainCast, false);
+ checkPlayableAbility("adventure", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerAdventureCast, false);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_PlayableAbiities_NoneByTarget() {
+ // Brazen Borrower {1}{U}{U}
+ // Petty Theft {1}{U} Return target nonland permanent an opponent controls to its owner’s hand.
+
+ addCard(Zone.HAND, playerA, "Brazen Borrower", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
+ //addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // no playable by wrong target
+ checkPlayableAbility("main", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerMainCast, false);
+ checkPlayableAbility("adventure", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerAdventureCast, false);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_PlayableAbiities_OnlyAdventure() {
+ // Brazen Borrower {1}{U}{U}
+ // Petty Theft {1}{U} Return target nonland permanent an opponent controls to its owner’s hand.
+
+ addCard(Zone.HAND, playerA, "Brazen Borrower", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
+ addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // only adventure
+ checkPlayableAbility("main", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerMainCast, false);
+ checkPlayableAbility("adventure", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerAdventureCast, true);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+ assertAllCommandsUsed();
+ }
+
+ @Test
+ public void test_PlayableAbiities_All() {
+ // Brazen Borrower {1}{U}{U}
+ // Petty Theft {1}{U} Return target nonland permanent an opponent controls to its owner’s hand.
+
+ addCard(Zone.HAND, playerA, "Brazen Borrower", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
+ addCard(Zone.BATTLEFIELD, playerB, "Balduvian Bears", 1);
+
+ // all
+ checkPlayableAbility("main", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerMainCast, true);
+ checkPlayableAbility("adventure", 1, PhaseStep.PRECOMBAT_MAIN, playerA, abilityBrazenBorrowerAdventureCast, true);
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+ assertAllCommandsUsed();
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java
new file mode 100644
index 00000000000..ee0d7c27d68
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/alternate/BolassCitadelTest.java
@@ -0,0 +1,61 @@
+package org.mage.test.cards.cost.alternate;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+public class BolassCitadelTest extends CardTestPlayerBase {
+ @Test
+ public void testCastEagerCadet() {
+ /*
+ * Eager Cadet
+ * Creature — Human Soldier
+ * 1/1
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Forest");
+ addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Eager Cadet");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Eager Cadet");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Eager Cadet", 1);
+ assertGraveyardCount(playerA,0);
+ assertLife(playerA, 19);
+ }
+
+ @Test
+ public void testCastAdventure() {
+ /*
+ * Curious Pair {1}{G}
+ * Creature — Human Peasant
+ * 1/3
+ * ----
+ * Treats to Share {G}
+ * Sorcery — Adventure
+ * Create a Food token.
+ */
+ setStrictChooseMode(true);
+ addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel");
+ removeAllCardsFromLibrary(playerA);
+ addCard(Zone.LIBRARY, playerA, "Curious Pair");
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertAllCommandsUsed();
+ assertHandCount(playerA, 0);
+ assertPermanentCount(playerA, "Food", 1);
+ assertExileCount(playerA, "Curious Pair", 1);
+ assertGraveyardCount(playerA,0);
+ assertLife(playerA, 19);
+ }
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java
index 6db1d8e5728..1cdb2b42801 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/OathOfLiegesTest.java
@@ -13,7 +13,6 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
//addCard(Zone.BATTLEFIELD, playerA, "Hypersonic Dragon", 1); // can cast spells at any time
//addCard(Zone.HAND, playerA, "Breath of Life", 1); // {3}{W} // return creatures
//addCard(Zone.HAND, playerA, "Replenish", 1); // {3}{W} // return all enchantments
-
@Test
public void testOath_OwnCardTriggersOnOwnTurn() {
// A
@@ -140,7 +139,7 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// turn 1 - A
// cast oath A
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish");
- showBattlefield("A perms", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
+// showBattlefield("A perms", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
// cast oath copy
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Copy Enchantment");
setChoice(playerA, "Yes"); // use copy effect
@@ -194,15 +193,14 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
// turn 1 - A
// nothing
-
// turn 2 - B
// cast oath A
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Replenish");
// cast oath copy by opponent
- showBattlefield("A perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerA);
- showBattlefield("B perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
- showAvaileableAbilities("B abils", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
- showHand("B hand", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
+ // showBattlefield("A perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerA);
+ // showBattlefield("B perms", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
+ // showAvaileableAbilities("B abils", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
+ // showHand("B hand", 2, PhaseStep.POSTCOMBAT_MAIN, playerB);
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerB, "Copy Enchantment");
setChoice(playerB, "Yes"); // use copy effect
setChoice(playerB, "Oath of Lieges"); // target for copy
@@ -210,7 +208,7 @@ public class OathOfLiegesTest extends CardTestPlayerBase {
checkPermanentCount("B have 1 oath", 2, PhaseStep.END_TURN, playerA, "Oath of Lieges", 1);
checkPermanentCount("A have 10 plains", 2, PhaseStep.END_TURN, playerA, "Plains", 10);
checkPermanentCount("B have 12 plains", 2, PhaseStep.END_TURN, playerB, "Plains", 12);
- showLibrary("lib B", 2, PhaseStep.END_TURN, playerB);
+ // showLibrary("lib B", 2, PhaseStep.END_TURN, playerB);
// turn 3 - A
// oath A triggers for A and activates
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java
index 882c8e1efb8..5bc8cac2f13 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/enchantments/StarfieldOfNyxTest.java
@@ -7,6 +7,7 @@ import mage.constants.Zone;
import mage.filter.Filter;
import mage.game.permanent.Permanent;
import org.junit.Assert;
+import org.junit.Ignore;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
@@ -117,4 +118,48 @@ public class StarfieldOfNyxTest extends CardTestPlayerBase {
Assert.assertFalse(emrakul.getAbilities().contains(FlyingAbility.getInstance())); // loses flying though
}
+
+ /**
+ * So Starfield of Nyx in play. Detention Sphere with a Song of the Dryads
+ * under it. Wrath of god on the stack. In response I Parallax Wave Honden
+ * of life's Web, Mirrari's Wake, Sphere of Safety, Aura Shards, and
+ * Enchantress's Presence to save them. Wrath resolves and Song of the
+ * Dryads come back along with the 5 named enchantments. Opp targets
+ * Starfield of Nyx with Song of the Dryads. When song of the dryads
+ * attaches all my enchantments stay creatures but as 1/1's instead of cmc.
+ * I untap draw and cast Humility. All my 1/1 enchantments die while opp's
+ * bruna, light of alabaster keeps her abilities. After mirrari's wake dies
+ * due to this I still have double mana. So yea, something broke big time
+ * there.
+ */
+ @Test
+ @Ignore
+ public void testStarfieldOfNyxAndSongOfTheDryads() {
+ // Nontoken creatures you control get +1/+1 and have vigilance.
+ addCard(Zone.BATTLEFIELD, playerA, "Always Watching", 5);
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
+ // At the beginning of your upkeep, you may return target enchantment card from your graveyard to the battlefield.
+ // As long as you control five or more enchantments, each other non-Aura enchantment you control is a creature in
+ // addition to its other types and has base power and base toughness each equal to its converted mana cost.
+ addCard(Zone.HAND, playerA, "Starfield of Nyx"); // "{4}{W}"
+
+ addCard(Zone.BATTLEFIELD, playerB, "Forest", 3);
+ // Enchanted permanent is a colorless Forest land.
+ addCard(Zone.HAND, playerB, "Song of the Dryads"); // Enchant Permanent {2}{G}
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Starfield of Nyx");
+ castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Song of the Dryads", "Starfield of Nyx");
+
+ setStopAt(2, PhaseStep.BEGIN_COMBAT);
+ execute();
+
+ assertPermanentCount(playerA, "Always Watching", 5);
+ assertPermanentCount(playerB, "Song of the Dryads", 1);
+
+ assertPowerToughness(playerA, "Always Watching", 0, 0, Filter.ComparisonScope.All);
+
+ assertPermanentCount(playerA, "Forest", 1);
+
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaPoolTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaPoolTest.java
index 3cccb1cdf06..7e3d76fa305 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaPoolTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/mana/ManaPoolTest.java
@@ -241,7 +241,7 @@ public class ManaPoolTest extends CardTestPlayerBase {
checkManaPool("mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 4);
// use for ability
- showAvaileableAbilities("before ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("before ability", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{X}:", playerB);
setChoice(playerA, "X=3");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java
index 3458edce5d9..af517d7c5bd 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/restriction/CantAttackTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.restriction;
import mage.constants.PhaseStep;
@@ -140,56 +139,56 @@ public class CantAttackTest extends CardTestPlayerBase {
assertTapped("Silvercoat Lion", false);
assertPowerToughness(playerB, "Silvercoat Lion", 4, 4);
}
-
+
/*
Reported bug: Medomai was able to attack on an extra turn when cheated into play.
- */
+ */
@Test
public void testMedomaiShouldNotAttackOnExtraTurns() {
-
+
/*
Medomai the Ageless {4}{W}{U}
Legendary Creature — Sphinx 4/4
Flying
Whenever Medomai the Ageless deals combat damage to a player, take an extra turn after this one.
Medomai the Ageless can't attack during extra turns.
- */
+ */
String medomai = "Medomai the Ageless";
-
+
/*
Cauldron Dance {4}{B}{R} Instant
Cast Cauldron Dance only during combat.
Return target creature card from your graveyard to the battlefield. That creature gains haste. Return it to your hand at the beginning of the next end step.
You may put a creature card from your hand onto the battlefield. That creature gains haste. Its controller sacrifices it at the beginning of the next end step.
- */
+ */
String cDance = "Cauldron Dance";
- String dBlade = "Doom Blade"; // {1}{B} instant destroy target creature
+ String dBlade = "Doom Blade"; // {1}{B} instant destroy target creature
addCard(Zone.BATTLEFIELD, playerA, medomai);
addCard(Zone.HAND, playerA, dBlade);
addCard(Zone.HAND, playerA, cDance);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
-
+
// attack with Medomai, connect, and destroy him after combat
attack(1, playerA, medomai);
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, dBlade, medomai);
-
+
// next turn granted, return Medomai to field with Cauldron and try to attack again
castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, cDance);
addTarget(playerA, medomai);
attack(2, playerA, medomai);
-
+
// medomai should not have been allowed to attack, but returned to hand at beginning of next end step still
setStopAt(2, PhaseStep.END_TURN);
execute();
-
+
assertLife(playerB, 16); // one hit from medomai
assertGraveyardCount(playerA, dBlade, 1);
assertGraveyardCount(playerA, cDance, 1);
assertGraveyardCount(playerA, medomai, 0);
assertHandCount(playerA, medomai, 1);
}
-
+
@Test
public void basicMedomaiTestForExtraTurn() {
/*
@@ -198,184 +197,248 @@ public class CantAttackTest extends CardTestPlayerBase {
Flying
Whenever Medomai the Ageless deals combat damage to a player, take an extra turn after this one.
Medomai the Ageless can't attack during extra turns.
- */
+ */
String medomai = "Medomai the Ageless";
-
+
/*
Exquisite Firecraft {1}{R}{R}
Sorcery
Exquisite Firecraft deals 4 damage to any target.
- */
+ */
String eFirecraft = "Exquisite Firecraft";
-
+
addCard(Zone.BATTLEFIELD, playerA, medomai);
addCard(Zone.HAND, playerA, eFirecraft);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 3);
-
+
// attack with medomai, get extra turn, confirm cannot attack again with medomai and can cast sorcery
attack(1, playerA, medomai);
attack(2, playerA, medomai); // should not be allowed to
castSpell(2, PhaseStep.POSTCOMBAT_MAIN, playerA, eFirecraft, playerB);
-
+
setStopAt(2, PhaseStep.END_TURN);
execute();
-
+
assertLife(playerB, 12); // 1 hit from medomai and firecraft = 8 damage
assertGraveyardCount(playerA, eFirecraft, 1);
assertPermanentCount(playerA, medomai, 1);
}
-
+
@Test
- public void sphereOfSafetyPaidCostAllowsAttack() {
+ public void sphereOfSafetyPaidCostAllowsAttack() {
/*
Sphere of Safety {4}{W}
Enchantment
Creatures can't attack you or a planeswalker you control unless their controller pays {X} for each of those creatures, where X is the number of enchantments you control.
- */
+ */
String sphere = "Sphere of Safety";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, sphere);
addCard(Zone.BATTLEFIELD, playerA, "Forest");
-
+
attack(1, playerA, memnite);
setChoice(playerA, "Yes");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, sphere, 1);
assertLife(playerB, 19); // took the hit from memnite
assertTapped("Forest", true); // forest had to be tapped
}
-
+
@Test
public void sphereOfSafetyCostNotPaid_NoAttackAllowed() {
/*
Sphere of Safety {4}{W}
Enchantment
Creatures can't attack you or a planeswalker you control unless their controller pays {X} for each of those creatures, where X is the number of enchantments you control.
- */
+ */
String sphere = "Sphere of Safety";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, sphere);
addCard(Zone.BATTLEFIELD, playerA, "Forest");
-
+
attack(1, playerA, memnite);
setChoice(playerA, "No");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, sphere, 1);
assertLife(playerB, 20); // no damage went through, did not elect to pay
assertTapped("Forest", false); // forest not tapped
}
-
+
@Test
- public void collectiveResistanceCostPaid_AttackAllowed()
- {
+ public void collectiveResistanceCostPaid_AttackAllowed() {
/*
Collective Restraint {3}{U}
Enchantment
Domain — Creatures can't attack you unless their controller pays {X} for each creature they control that's attacking you, where X is the number of basic land types among lands you control.
- */
+ */
String cRestraint = "Collective Restraint";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, cRestraint);
addCard(Zone.BATTLEFIELD, playerB, "Island"); // 1 basic land type = pay 1 to attack
addCard(Zone.BATTLEFIELD, playerA, "Forest");
-
+
attack(1, playerA, memnite);
setChoice(playerA, "Yes");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, cRestraint, 1);
assertLife(playerB, 19); // took the hit from memnite
assertTapped("Forest", true); // forest had to be tapped
}
-
+
@Test
- public void collectiveResistanceCostNotPaid_NoAttackAllowed()
- {
+ public void collectiveResistanceCostNotPaid_NoAttackAllowed() {
/*
Collective Restraint {3}{U}
Enchantment
Domain — Creatures can't attack you unless their controller pays {X} for each creature they control that's attacking you, where X is the number of basic land types among lands you control.
- */
+ */
String cRestraint = "Collective Restraint";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, cRestraint);
addCard(Zone.BATTLEFIELD, playerB, "Island"); // 1 basic land type = pay 1 to attack
addCard(Zone.BATTLEFIELD, playerA, "Forest");
-
+
attack(1, playerA, memnite);
setChoice(playerA, "No");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, cRestraint, 1);
assertLife(playerB, 20); // no damage went through, did not elect to pay
assertTapped("Forest", false); // forest not tapped
}
-
+
@Test
- public void ghostlyPrison_PaidCost_AllowsAttack() {
+ public void ghostlyPrison_PaidCost_AllowsAttack() {
/*
Ghostly Prison {2}{W}
Enchantment
Creatures can't attack you unless their controller pays {2} for each creature they control that's attacking you.
- */
+ */
String gPrison = "Ghostly Prison";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, gPrison);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
-
+
attack(1, playerA, memnite);
setChoice(playerA, "Yes");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, gPrison, 1);
assertLife(playerB, 19); // took the hit from memnite
assertTappedCount("Forest", true, 2); // forests had to be tapped
}
-
+
@Test
public void ghostlyPrison_CostNotPaid_NoAttackAllowed() {
/*
Ghostly Prison {2}{W}
Enchantment
Creatures can't attack you unless their controller pays {2} for each creature they control that's attacking you.
- */
+ */
String gPrison = "Ghostly Prison";
String memnite = "Memnite";
-
+
addCard(Zone.BATTLEFIELD, playerA, memnite); // {0} 1/1
addCard(Zone.BATTLEFIELD, playerB, gPrison);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
-
+
attack(1, playerA, memnite);
setChoice(playerA, "No");
-
+
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
-
+
assertPermanentCount(playerB, gPrison, 1);
assertLife(playerB, 20); // no damage went through, did not elect to pay
assertTapped("Forest", false); // no forests tapped
}
+
+ @Test
+ public void OpportunisticDragon() {
+ // Flying
+ // When Opportunistic Dragon enters the battlefield, choose target Human or artifact an opponent controls. For as long as Opportunistic Dragon remains on the battlefield, gain control of that permanent, it loses all abilities, and it can't attack or block.
+ addCard(Zone.HAND, playerA, "Opportunistic Dragon"); // Creature {2}{R}{R}
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
+ addCard(Zone.BATTLEFIELD, playerA, "Desperate Castaways"); // Creature - Human Pirate 2/3
+
+ // Other Pirates you control get +1/+1.
+ // At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
+ addCard(Zone.BATTLEFIELD, playerB, "Admiral Beckett Brass"); // Creature {1}{B}{B}{R}
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Opportunistic Dragon");
+ addTarget(playerA, "Admiral Beckett Brass");
+
+ attack(3, playerA, "Admiral Beckett Brass"); // Can't attack
+
+ setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+
+ assertPermanentCount(playerA, "Opportunistic Dragon", 1);
+ assertPermanentCount(playerA, "Admiral Beckett Brass", 1);
+ assertPowerToughness(playerA, "Desperate Castaways", 2, 3);
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ }
+
+ /* Opportunistic Dragon - can't block/can't attack effect did not end when opportunistic dragon was exiled */
+ @Test
+ public void OpportunisticDragonEndEffects() {
+ // Flying
+ // When Opportunistic Dragon enters the battlefield, choose target Human or artifact an opponent controls. For as long as Opportunistic Dragon remains on the battlefield, gain control of that permanent, it loses all abilities, and it can't attack or block.
+ addCard(Zone.HAND, playerA, "Opportunistic Dragon"); // Creature {2}{R}{R}
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
+ addCard(Zone.BATTLEFIELD, playerA, "Desperate Castaways"); // Creature - Human Pirate 2/3
+
+ // Other Pirates you control get +1/+1.
+ // At the beginning of your end step, gain control of target nonland permanent controlled by a player who was dealt combat damage by three or more Pirates this turn.
+ addCard(Zone.BATTLEFIELD, playerB, "Admiral Beckett Brass"); // Creature {1}{B}{B}{R} 3/3
+ addCard(Zone.BATTLEFIELD, playerB, "Desperate Castaways"); // Creature - Human Pirate 2/3
+ // Destroy target nonartifact, nonblack creature. It can't be regenerated.
+ addCard(Zone.HAND, playerB, "Terror"); // Instant {1}{B}
+ addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Opportunistic Dragon");
+ addTarget(playerA, "Admiral Beckett Brass");
+
+ attack(3, playerA, "Admiral Beckett Brass"); // Can't attack
+ castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Terror", "Opportunistic Dragon");
+
+ attack(4, playerB, "Admiral Beckett Brass"); // Can attack again
+
+ setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
+ execute();
+
+ assertGraveyardCount(playerB, "Terror", 1);
+ assertGraveyardCount(playerA, "Opportunistic Dragon", 1);
+ assertPermanentCount(playerB, "Admiral Beckett Brass", 1);
+ assertPowerToughness(playerB, "Desperate Castaways", 3, 4);
+
+ assertLife(playerA, 17);
+ assertLife(playerB, 20);
+ }
+
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java
index bdde801cd48..4b9330199d9 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/FiendOfTheShadowsTest.java
@@ -6,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* also tests regenerate and tests that permanents with protection can be
* sacrificed
*
@@ -46,10 +45,13 @@ public class FiendOfTheShadowsTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Swamp");
attack(1, playerA, "Fiend of the Shadows");
+ addTarget(playerB, "Swamp");
playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Swamp");
+ setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
+ assertAllCommandsUsed();
assertLife(playerA, 20);
assertLife(playerB, 17);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java
index efb30757b70..489e3310ebb 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/MisdirectionTest.java
@@ -99,6 +99,7 @@ public class MisdirectionTest extends CardTestPlayerBase {
}
// check to change target permanent creature legal to to a creature the opponent of the spell controller controls
+ // target to illegal target can't be tested
@Test
public void test_ChangePublicExecution() {
// Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
@@ -129,41 +130,5 @@ public class MisdirectionTest extends CardTestPlayerBase {
assertGraveyardCount(playerB, "Custodian of the Trove", 1);
assertPermanentCount(playerB, "Pillarfield Ox", 1);
assertPowerToughness(playerB, "Pillarfield Ox", 0, 4);
-
- }
-
- // check to change target permanent creature not legal to to a creature the your opponent controls
- @Test
- public void test_ChangePublicExecution2() {
- // Destroy target creature an opponent controls. Each other creature that player controls gets -2/-0 until end of turn.
- addCard(Zone.HAND, playerA, "Public Execution");
- addCard(Zone.BATTLEFIELD, playerA, "Swamp", 6);
- addCard(Zone.BATTLEFIELD, playerA, "Keeper of the Lens", 1);
- /*
- Misdirection {3}{U}{U}
- Instant
- You may exile a blue card from your hand rather than pay Misdirection's mana cost.
- Change the target of target spell with a single target.
- */
- addCard(Zone.HAND, playerB, "Misdirection");
- addCard(Zone.BATTLEFIELD, playerB, "Pillarfield Ox", 1);
- addCard(Zone.BATTLEFIELD, playerB, "Custodian of the Trove", 1); // 4/3
- addCard(Zone.BATTLEFIELD, playerB, "Island", 5);
-
- castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Public Execution", "Custodian of the Trove");
- castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerB, "Misdirection", "Public Execution", "Public Execution");
-
- setStopAt(1, PhaseStep.BEGIN_COMBAT);
- execute();
- assertAllCommandsUsed();
-
- assertGraveyardCount(playerA, "Public Execution", 1);
- assertGraveyardCount(playerB, "Misdirection", 1);
- assertPermanentCount(playerA, "Keeper of the Lens", 1);
-
- assertPermanentCount(playerB, "Pillarfield Ox", 1);
- assertPowerToughness(playerB, "Pillarfield Ox", 0, 4);
-
- assertGraveyardCount(playerB, "Custodian of the Trove", 1);
}
}
\ No newline at end of file
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/ThousandYearStormTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/ThousandYearStormTest.java
index 1e0198075d6..f777c75bab2 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/ThousandYearStormTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/ThousandYearStormTest.java
@@ -15,7 +15,6 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
Enchantment
Whenever you cast an instant or sorcery spell, copy it for each other instant and sorcery spell you’ve cast before it this turn. You may choose new targets for the copies.
*/
-
@Test
public void test_CalcBeforeStorm() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
@@ -233,7 +232,6 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Augmenting Automaton");
// turn 1
-
// 1a
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
checkLife("0x copy", 1, PhaseStep.BEGIN_COMBAT, playerB, 20 - 3);
@@ -255,7 +253,6 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
assertAllCommandsUsed();
}
-
@Test
public void test_WaitStackResolvedWithBolts() {
addCard(Zone.BATTLEFIELD, playerB, "Mountain", 5);
@@ -282,6 +279,8 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
You control enchanted permanent.
Cycling {2} ({2}, Discard this card: Draw a card.)
*/
+ // Test fails sometimes with the following message:
+ // java.lang.AssertionError: b 0x copy after control - PlayerA have wrong life: 20 <> 17 expected:<17> but was:<20>
@Test
public void test_GetControlNotCounts() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
@@ -296,7 +295,6 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
addCard(Zone.HAND, playerB, "Lay Claim");
// turn 2
-
// pump card for A
// 1
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
@@ -313,7 +311,6 @@ public class ThousandYearStormTest extends CardTestPlayerBase {
checkLife("b 0x copy after control", 3, PhaseStep.UPKEEP, playerA, 20 - 3);
// turn 4
-
// pump for B
// 1
castSpell(4, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", playerA);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DecimatorBeetleTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DecimatorBeetleTest.java
index 2354b76a18f..8519ec3ecaa 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DecimatorBeetleTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/akh/DecimatorBeetleTest.java
@@ -7,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author escplan9
*/
public class DecimatorBeetleTest extends CardTestPlayerBase {
@@ -19,29 +18,33 @@ When Decimator Beetle enters the battlefield, put a -1/-1 counter on target crea
Whenever Decimator Beetle attacks, remove a -1/-1 counter from target creature you control and put a -1/-1 counter on up to one target creature defending player controls.
*/
private final String decimator = "Decimator Beetle";
-
+
@Test
public void targetOpponentCreatureWithDecimator() {
-
+
String grizzly = "Grizzly Bears"; // {1}{G} 2/2
String hillGiant = "Hill Giant"; // {3}{R} 3/3
-
+
addCard(Zone.HAND, playerA, decimator);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
addCard(Zone.BATTLEFIELD, playerA, grizzly);
addCard(Zone.BATTLEFIELD, playerB, hillGiant);
-
+
+ // put -1/-1 on own creature
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, decimator);
addTarget(playerA, grizzly);
-
+
+ // remove -1/-1 from own creature and put to defender control
attack(3, playerA, decimator);
- addTarget(playerA, grizzly);
- addTarget(playerA, hillGiant);
-
+ addTarget(playerA, grizzly); // remove
+ addTarget(playerA, hillGiant); // put
+
+ setStrictChooseMode(true);
setStopAt(3, PhaseStep.END_COMBAT);
execute();
-
+ assertAllCommandsUsed();
+
assertPowerToughness(playerA, grizzly, 2, 2); // had -1/-1 counter, but removed on attack
assertPowerToughness(playerB, hillGiant, 2, 2); // gets -1/-1 counter from decimator attack ability
assertCounterCount(playerA, grizzly, CounterType.M1M1, 0);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tor/LaquatussChampionTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tor/LaquatussChampionTest.java
index 1c7bbed6a3a..6ec74d61d52 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/single/tor/LaquatussChampionTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tor/LaquatussChampionTest.java
@@ -67,6 +67,7 @@ public class LaquatussChampionTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 7);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 7);
addCard(Zone.HAND, playerA, "Laquatus's Champion");
+ // Destroy target creature. It can't be regenerated.
addCard(Zone.HAND, playerA, "Terminate");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Laquatus's Champion");
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java
index f4192157f7e..821d98c3113 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/EnterLeaveBattlefieldExileTargetTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.triggers;
import mage.constants.PhaseStep;
@@ -7,7 +6,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author LevelX2
*/
public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase {
@@ -25,9 +23,12 @@ public class EnterLeaveBattlefieldExileTargetTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Angel of Serenity");
addTarget(playerA, "Silvercoat Lion^Pillarfield Ox");
+ setChoice(playerA, "Yes");
+ setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
+ assertAllCommandsUsed();
assertPermanentCount(playerA, "Angel of Serenity", 1);
assertExileCount("Silvercoat Lion", 1);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java
index af57b93243b..976276de27c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/SpellskiteTest.java
@@ -233,7 +233,7 @@ public class SpellskiteTest extends CardTestPlayerBase {
once for each instances of the word “target” in the text
of a spell or ability. In this case, the target can't be changed
due to Spellskite already being a target.
- */
+ */
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
@@ -258,8 +258,6 @@ public class SpellskiteTest extends CardTestPlayerBase {
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{U/P}: Change a target", "Fiery Justice", "Fiery Justice");
setChoice(playerB, "Yes"); // pay 2 life
- showBattlefield("B battle", 1, PhaseStep.BEGIN_COMBAT, playerB);
- showGraveyard("B grave", 1, PhaseStep.BEGIN_COMBAT, playerB);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
@@ -274,7 +272,7 @@ public class SpellskiteTest extends CardTestPlayerBase {
public void testThatSplitDamageCanGetRedirected() {
/* Standard redirect test
The Spellskite should die from the 5 damage that was redirected to it
- */
+ */
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java
index 4a66a8e5baf..2152c47107a 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/ZadaHedronGrinderTest.java
@@ -1,4 +1,3 @@
-
package org.mage.test.cards.triggers;
import mage.abilities.keyword.TrampleAbility;
@@ -8,7 +7,6 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- *
* @author LevelX2
*/
public class ZadaHedronGrinderTest extends CardTestPlayerBase {
@@ -43,7 +41,70 @@ public class ZadaHedronGrinderTest extends CardTestPlayerBase {
assertAbility(playerA, "Zada, Hedron Grinder", TrampleAbility.getInstance(), true);
assertPowerToughness(playerA, "Silvercoat Lion", 4, 2);
assertAbility(playerA, "Silvercoat Lion", TrampleAbility.getInstance(), true);
+ }
+ @Test
+ public void testTargetsByTestPlayer() {
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
+
+ // Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for
+ // each other creature you control that the spell could target. Each copy targets a different one of those creatures.
+ addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3
+ addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
+ //
+ // Put a +1/+1 counter on target creature. That creature gains reach until end of turn.
+ addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G}
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
+
+ // cast
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder");
+ addTarget(playerA, "Balduvian Bears^Silvercoat Lion");
+
+ setStrictChooseMode(true);
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertGraveyardCount(playerA, "Arbor Armament", 1);
+ assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1);
+ assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1);
+ assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1);
+ }
+
+ @Test
+ public void testTargetsByAI() {
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
+
+ // Whenever you cast an instant or sorcery spell that targets only Zada, Hedron Grinder, copy that spell for
+ // each other creature you control that the spell could target. Each copy targets a different one of those creatures.
+ addCard(Zone.BATTLEFIELD, playerA, "Zada, Hedron Grinder", 1); // 3/3
+ addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
+ addCard(Zone.BATTLEFIELD, playerA, "Balduvian Bears", 1);
+ //
+ // Put a +1/+1 counter on target creature. That creature gains reach until end of turn.
+ addCard(Zone.HAND, playerA, "Arbor Armament", 1); // {G}
+ addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
+
+ // cast
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Arbor Armament", "Zada, Hedron Grinder");
+ //addTarget(playerA, "Balduvian Bears^Silvercoat Lion");
+
+ //setStrictChooseMode(true); // no strict mode for AI
+ setStopAt(1, PhaseStep.BEGIN_COMBAT);
+ execute();
+ assertAllCommandsUsed();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertGraveyardCount(playerA, "Arbor Armament", 1);
+ assertPowerToughness(playerA, "Zada, Hedron Grinder", 3 + 1, 3 + 1);
+ assertPowerToughness(playerA, "Silvercoat Lion", 2 + 1, 2 + 1);
+ assertPowerToughness(playerA, "Balduvian Bears", 2 + 1, 2 + 1);
}
}
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..a8d5eb50e5c
--- /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.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java
index 02a8f7760c2..02ca241e295 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/BrainMaggotTest.java
@@ -12,8 +12,8 @@ public class BrainMaggotTest extends CardTestPlayerBase {
/**
* When Brain Maggot enters the battlefield, target opponent reveals their
- * hand and you choose a nonland card from it. Exile that card until
- * Brain Maggot leaves the battlefield.
+ * hand and you choose a nonland card from it. Exile that card until Brain
+ * Maggot leaves the battlefield.
*/
@Test
public void testCardFromHandWillBeExiled() {
@@ -48,7 +48,7 @@ public class BrainMaggotTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Brain Maggot");
addTarget(playerA, playerB);
setChoice(playerA, "Bloodflow Connoisseur");
- showExile("exile", 1, PhaseStep.BEGIN_COMBAT, playerB);
+// showExile("exile", 1, PhaseStep.BEGIN_COMBAT, playerB);
checkExileCount("blood must be in exile", 1, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 1);
// return
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ElendaTheDuskRoseTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ElendaTheDuskRoseTest.java
new file mode 100644
index 00000000000..db65bc50928
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/ElendaTheDuskRoseTest.java
@@ -0,0 +1,168 @@
+package org.mage.test.cards.triggers.dies;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import mage.filter.Filter;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ *
+ *
+ * @author LevelX2
+ */
+public class ElendaTheDuskRoseTest extends CardTestPlayerBase {
+
+ @Test
+ public void testAddCounter() {
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
+
+ addCard(Zone.HAND, playerA, "Lightning Bolt", 1);
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+ // Lifelink
+ // Whenever another creature dies, put a +1/+1 counter on Elenda, The Dusk Rose.
+ // When Elenda dies, create X 1/1 white Vampire creature tokens with lifelink, where X is Elenda's power.
+ addCard(Zone.HAND, playerA, "Elenda, the Dusk Rose", 1); // {2}{W}{B} 1/1
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elenda, the Dusk Rose");
+
+ castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion");
+
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertPermanentCount(playerA, "Elenda, the Dusk Rose", 1);
+
+ assertGraveyardCount(playerA, "Lightning Bolt", 1);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+
+ assertPowerToughness(playerA, "Elenda, the Dusk Rose", 2, 2);
+ }
+
+ @Test
+ public void testCreateVampireTokens() {
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
+
+ addCard(Zone.HAND, playerA, "Lightning Bolt", 2);
+ addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
+ // Lifelink
+ // Whenever another creature dies, put a +1/+1 counter on Elenda, The Dusk Rose.
+ // When Elenda dies, create X 1/1 white Vampire creature tokens with lifelink, where X is Elenda's power.
+ addCard(Zone.HAND, playerA, "Elenda, the Dusk Rose", 1); // {2}{W}{B} 1/1
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elenda, the Dusk Rose");
+
+ castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion");
+
+ castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Elenda, the Dusk Rose");
+
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertPermanentCount(playerA, "Elenda, the Dusk Rose", 0);
+
+ assertGraveyardCount(playerA, "Lightning Bolt", 2);
+ assertGraveyardCount(playerB, "Silvercoat Lion", 1);
+ assertGraveyardCount(playerA, "Elenda, the Dusk Rose", 1);
+
+ assertPermanentCount(playerA, "Vampire", 2);
+ assertPowerToughness(playerA, "Vampire", 1, 1, Filter.ComparisonScope.All);
+ }
+
+ @Test
+ public void testKillAndReanimate() {
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); /// 2/2
+ // Whenever a creature is put into your graveyard from the battlefield, you may sacrifice Angelic Renewal. If you do, return that card to the battlefield.
+ addCard(Zone.BATTLEFIELD, playerA, "Angelic Renewal", 1);
+
+ addCard(Zone.BATTLEFIELD, playerB, "Mountain", 2);
+ addCard(Zone.HAND, playerB, "Lightning Bolt", 2);
+
+ // Lifelink
+ // Whenever another creature dies, put a +1/+1 counter on Elenda, The Dusk Rose.
+ // When Elenda dies, create X 1/1 white Vampire creature tokens with lifelink, where X is Elenda's power.
+ addCard(Zone.HAND, playerA, "Elenda, the Dusk Rose", 1); // {2}{W}{B} 1/1
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elenda, the Dusk Rose");
+
+ castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion");
+
+ castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Elenda, the Dusk Rose");
+ setChoice(playerA, "No"); // use Angelic Renewal on Silvercoat Lion
+ setChoice(playerA, "Yes"); // use Angelic Renewal on Elenda, the Dusk Rose
+
+ setStopAt(3, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertPermanentCount(playerA, "Elenda, the Dusk Rose", 1);
+ assertPowerToughness(playerA, "Elenda, the Dusk Rose", 1, 1);
+
+ assertGraveyardCount(playerB, "Lightning Bolt", 2);
+ assertGraveyardCount(playerA, "Silvercoat Lion", 1);
+ assertGraveyardCount(playerA, "Elenda, the Dusk Rose", 0);
+
+ assertPermanentCount(playerA, "Vampire", 2);
+ assertPowerToughness(playerA, "Vampire", 1, 1, Filter.ComparisonScope.All);
+ }
+
+ @Test
+ public void testKillMultipleAndReanimate() {
+ addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
+ addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1); /// 2/2
+ // Whenever a creature is put into your graveyard from the battlefield, you may sacrifice Angelic Renewal. If you do, return that card to the battlefield.
+ addCard(Zone.BATTLEFIELD, playerA, "Angelic Renewal", 1);
+
+ addCard(Zone.BATTLEFIELD, playerB, "Mountain", 3);
+ // Sweltering Suns deals 3 damage to each creature.
+ // Cycling {3} ({3}, Discard this card: Draw a card.)
+ addCard(Zone.HAND, playerB, "Sweltering Suns", 1); // Sorcery {1}{R}{R}
+
+ // Lifelink
+ // Whenever another creature dies, put a +1/+1 counter on Elenda, The Dusk Rose.
+ // When Elenda dies, create X 1/1 white Vampire creature tokens with lifelink, where X is Elenda's power.
+ addCard(Zone.HAND, playerA, "Elenda, the Dusk Rose", 1); // {2}{W}{B} 1/1
+
+ castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Elenda, the Dusk Rose");
+
+ castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sweltering Suns");
+
+ setChoice(playerA, "Yes"); // use Angelic Renewal on Elenda, the Dusk Rose
+ setChoice(playerA, "No"); // use Angelic Renewal on Silvercoat Lion
+
+ setStopAt(2, PhaseStep.END_TURN);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+
+ assertGraveyardCount(playerA, "Angelic Renewal", 1);
+
+ assertPermanentCount(playerA, "Silvercoat Lion", 0);
+ assertPermanentCount(playerA, "Elenda, the Dusk Rose", 1);
+ assertPowerToughness(playerA, "Elenda, the Dusk Rose", 1, 1);
+
+ assertGraveyardCount(playerB, "Sweltering Suns", 1);
+ assertGraveyardCount(playerA, "Silvercoat Lion", 1);
+ assertGraveyardCount(playerA, "Elenda, the Dusk Rose", 0);
+
+ assertPermanentCount(playerA, "Vampire", 1);
+ assertPowerToughness(playerA, "Vampire", 1, 1, Filter.ComparisonScope.All);
+ }
+
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/FootlightFiendTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/FootlightFiendTest.java
new file mode 100644
index 00000000000..9865e2e7398
--- /dev/null
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/FootlightFiendTest.java
@@ -0,0 +1,33 @@
+package org.mage.test.cards.triggers.dies;
+
+import mage.constants.PhaseStep;
+import mage.constants.Zone;
+import org.junit.Test;
+import org.mage.test.serverside.base.CardTestPlayerBase;
+
+/**
+ *
+ * @author drmDev
+ */
+public class FootlightFiendTest extends CardTestPlayerBase {
+
+ @Test
+ public void test_GrizzlyBearBlocksFootlightFiend_BothDie()
+ {
+ addCard(Zone.BATTLEFIELD, playerA, "Footlight Fiend"); // (R/B) 1/1 on death pings any target for 1
+ addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears"); // (G) 2/2
+
+ attack(1, playerA, "Footlight Fiend");
+ block(1, playerB, "Grizzly Bears", "Footlight Fiend");
+ addTarget(playerA, "Grizzly Bears");
+
+ setStopAt(1, PhaseStep.END_COMBAT);
+ execute();
+
+ assertLife(playerA, 20);
+ assertLife(playerB, 20);
+ assertGraveyardCount(playerA, "Footlight Fiend", 1);
+ assertGraveyardCount(playerB, "Grizzly Bears", 1);
+ assertAllCommandsUsed();
+ }
+}
\ No newline at end of file
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java
index 7661906c858..2fd417ba202 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/TidehollowScullerTest.java
@@ -8,14 +8,12 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author LevelX2, JayDi85
*/
-
public class TidehollowScullerTest extends CardTestPlayerBase {
/**
* Test if the same Tidehollow Sculler is cast multiple times, the correct
* corresponding exiled cards are returned
*/
-
@Test
public void test_CastOneCardFromHandWillBeExiled() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
@@ -30,7 +28,6 @@ public class TidehollowScullerTest extends CardTestPlayerBase {
//
addCard(Zone.HAND, playerB, "Bloodflow Connoisseur", 1);
-
// cast and exile from hand
checkHandCardCount("B hand must have blood", 1, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 1);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tidehollow Sculler");
@@ -86,7 +83,7 @@ public class TidehollowScullerTest extends CardTestPlayerBase {
checkPermanentCount("A must have 2 tide", 2, PhaseStep.UPKEEP, playerA, "Tidehollow Sculler", 2);
checkHandCardCount("B hand must have 0 blood", 2, PhaseStep.UPKEEP, playerB, "Bloodflow Connoisseur", 0);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@tide.1");
- showHand("B hand", 2, PhaseStep.BEGIN_COMBAT, playerB);
+// showHand("B hand", 2, PhaseStep.BEGIN_COMBAT, playerB);
checkPermanentCount("A must have 1 tide", 2, PhaseStep.BEGIN_COMBAT, playerA, "Tidehollow Sculler", 1);
checkHandCardCount("B hand must have 1 blood", 2, PhaseStep.BEGIN_COMBAT, playerB, "Bloodflow Connoisseur", 1);
// destroy 2 and return card to hand
@@ -115,4 +112,4 @@ public class TidehollowScullerTest extends CardTestPlayerBase {
}
}
-}
\ No newline at end of file
+}
diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java
index ceb219a29bd..dbd6e9cbec2 100644
--- a/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/cards/triggers/dies/WhisperwoodElementalTest.java
@@ -7,17 +7,18 @@ import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
- * Whisperwood Elemental - Elemental {3}{G}{G}
- * At the beginning of your end step, manifest the top card of your library.
- * Sacrifice Whisperwood Elemental: Until end of turn, face-up, nontoken creatures you control gain "When this creature dies, manifest the top card of your library."
+ * Whisperwood Elemental - Elemental {3}{G}{G} At the beginning of your end
+ * step, manifest the top card of your library. Sacrifice Whisperwood Elemental:
+ * Until end of turn, face-up, nontoken creatures you control gain "When this
+ * creature dies, manifest the top card of your library."
*
* @author LevelX2
*/
public class WhisperwoodElementalTest extends CardTestPlayerBase {
/**
- * Tests that the dies triggered ability of silvercoat lion (gained by sacrificed Whisperwood Elemental)
- * triggers as he dies from Lightning Bolt
+ * Tests that the dies triggered ability of silvercoat lion (gained by
+ * sacrificed Whisperwood Elemental) triggers as he dies from Lightning Bolt
*/
@Test
public void testDiesTriggeredAbility() {
@@ -29,8 +30,6 @@ public class WhisperwoodElementalTest extends CardTestPlayerBase {
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Sacrifice {this}: Until end of turn, face-up, nontoken creatures you control gain \"When this creature dies, manifest the top card of your library.");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Lightning Bolt", "Silvercoat Lion");
- showBattlefield("A battle", 1, PhaseStep.END_TURN, playerA);
- showGraveyard("A grave", 1, PhaseStep.END_TURN, playerA);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
diff --git a/Mage.Tests/src/test/java/org/mage/test/commander/duel/OpalPalaceTest.java b/Mage.Tests/src/test/java/org/mage/test/commander/duel/OpalPalaceTest.java
index 8061ad716d0..e0d3d48582c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/commander/duel/OpalPalaceTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/commander/duel/OpalPalaceTest.java
@@ -27,10 +27,9 @@ public class OpalPalaceTest extends CardTestCommanderDuelBase {
// equal to the number of times it's been cast from the command zone this game.
addCard(Zone.BATTLEFIELD, playerA, "Opal Palace", 1);
- showHand("hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
- showCommand("command", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
- showAvaileableAbilities("abi", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
-
+ // showHand("hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showCommand("command", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
+ // showAvaileableAbilities("abi", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}");
setChoice(playerA, "Opal Palace"); // activate mana replace effect first (+3 counters)
diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java
index 3db7ae92008..2cf830defb4 100644
--- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java
@@ -22,7 +22,10 @@ import org.mage.test.utils.DeckTestUtils;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
/**
* Intended to test Mage server under different load patterns.
@@ -188,7 +191,7 @@ public class LoadTest {
}
}
- public void playTwoAIGame(String deckColors, String deckAllowedSets) {
+ public void playTwoAIGame(String gameName, String deckColors, String deckAllowedSets) {
Assert.assertFalse("need deck colors", deckColors.isEmpty());
Assert.assertFalse("need allowed sets", deckAllowedSets.isEmpty());
@@ -197,7 +200,7 @@ public class LoadTest {
// game by monitor
GameTypeView gameType = monitor.session.getGameTypes().get(0);
- MatchOptions gameOptions = createSimpleGameOptionsForAI(gameType, monitor.session);
+ MatchOptions gameOptions = createSimpleGameOptionsForAI(gameType, monitor.session, gameName);
TableView game = monitor.session.createTable(monitor.roomID, gameOptions);
UUID tableId = game.getTableId();
@@ -214,9 +217,15 @@ public class LoadTest {
// playing until game over
boolean startToWatching = false;
while (true) {
+ GameView gameView = monitor.client.getLastGameView();
+
checkGame = monitor.getTable(tableId);
TableState state = checkGame.get().getTableState();
- logger.warn(state);
+
+ logger.warn(checkGame.get().getTableName()
+ + (gameView != null ? ", turn " + gameView.getTurn() + ", " + gameView.getStep().toString() : "")
+ + (gameView != null ? ", active " + gameView.getActivePlayerName() : "")
+ + ", " + state);
if (state == TableState.FINISHED) {
break;
@@ -227,7 +236,6 @@ public class LoadTest {
startToWatching = true;
}
- GameView gameView = monitor.client.getLastGameView();
if (gameView != null) {
for (PlayerView p : gameView.getPlayers()) {
logger.info(p.getName() + " - Life=" + p.getLife() + "; Lib=" + p.getLibraryCount());
@@ -245,25 +253,33 @@ public class LoadTest {
@Test
@Ignore
public void test_TwoAIPlayGame_One() {
- playTwoAIGame("GR", "GRN");
+ playTwoAIGame("Single AI game", "GR", "GRN");
}
@Test
@Ignore
public void test_TwoAIPlayGame_Multiple() {
- // save random seeds for repeated results
- Integer gamesAmount = 1000;
+ int singleGameSID = 0; // for one game test with same deck
+ int gamesAmount = 1000; // multiple run of one game test
+
+ // save random seeds for repeated results (in decks generating)
List seedsList = new ArrayList<>();
- for (int i = 1; i <= gamesAmount; i++) {
- seedsList.add(RandomUtil.nextInt());
+ if (singleGameSID != 0) {
+ for (int i = 1; i <= gamesAmount; i++) {
+ seedsList.add(singleGameSID);
+ }
+ } else {
+ for (int i = 1; i <= gamesAmount; i++) {
+ seedsList.add(RandomUtil.nextInt());
+ }
}
- for (int i = 1; i <= 1000; i++) {
+ for (int i = 0; i <= seedsList.size() - 1; i++) {
long randomSeed = seedsList.get(i);
- logger.info("RANDOM seed: " + randomSeed);
+ logger.info("Game " + (i + 1) + " of " + seedsList.size() + ", RANDOM seed: " + randomSeed);
RandomUtil.setSeed(randomSeed);
- playTwoAIGame("WGUBR", "SWS");
+ playTwoAIGame("AI game #" + (i + 1), "WGUBR", "ELD");
}
}
@@ -453,8 +469,8 @@ public class LoadTest {
return createSimpleGameOptions("Bots test game", gameTypeView, session, PlayerType.HUMAN);
}
- private MatchOptions createSimpleGameOptionsForAI(GameTypeView gameTypeView, Session session) {
- return createSimpleGameOptions("AI test game", gameTypeView, session, PlayerType.COMPUTER_MAD);
+ private MatchOptions createSimpleGameOptionsForAI(GameTypeView gameTypeView, Session session, String gameName) {
+ return createSimpleGameOptions(gameName, gameTypeView, session, PlayerType.COMPUTER_MAD);
}
private class LoadPlayer {
diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
index 5c0e75f1414..f31dd797f6c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
+++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
@@ -25,6 +25,7 @@ import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
import mage.filter.Filter;
+import mage.filter.FilterMana;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.*;
@@ -40,6 +41,7 @@ import mage.game.draft.Draft;
import mage.game.match.Match;
import mage.game.match.MatchPlayer;
import mage.game.permanent.Permanent;
+import mage.game.permanent.PermanentToken;
import mage.game.stack.StackObject;
import mage.game.tournament.Tournament;
import mage.player.ai.ComputerPlayer;
@@ -59,7 +61,6 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import mage.filter.FilterMana;
import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*;
@@ -452,9 +453,9 @@ public class TestPlayer implements Player {
if (currentTarget.getNumberOfTargets() == 1) {
currentTarget.clearChosen();
}
- if (currentTarget instanceof TargetCreaturePermanentAmount) {
+ if (currentTarget.getOriginalTarget() instanceof TargetCreaturePermanentAmount) {
// supports only to set the complete amount to one target
- TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget;
+ TargetCreaturePermanentAmount targetAmount = (TargetCreaturePermanentAmount) currentTarget.getOriginalTarget();
targetAmount.setAmount(ability, game);
int amount = targetAmount.getAmountRemaining();
targetAmount.addTarget(id, amount, ability, game);
@@ -652,6 +653,13 @@ public class TestPlayer implements Player {
wasProccessed = true;
}
+ // check playable ability: ability text, must have
+ if (params[0].equals(CHECK_COMMAND_PLAYABLE_ABILITY) && params.length == 3) {
+ assertPlayableAbility(action, game, computerPlayer, params[1], Boolean.parseBoolean(params[2]));
+ actions.remove(action);
+ wasProccessed = true;
+ }
+
// check battlefield count: target player, card name, count
if (params[0].equals(CHECK_COMMAND_PERMANENT_COUNT) && params.length == 4) {
assertPermanentCount(action, game, game.getPlayer(UUID.fromString(params[1])), params[2], Integer.parseInt(params[3]));
@@ -879,11 +887,14 @@ public class TestPlayer implements Player {
System.out.println("Total permanents: " + cards.size());
List data = cards.stream()
- .map(c -> (c.getIdName()
- + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
- + (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
- + ", " + (c.isTapped() ? "Tapped" : "Untapped")
- + (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName())
+ .map(c -> (
+ ((c instanceof PermanentToken) ? "[T] " : "[C] ")
+ + c.getIdName()
+ + (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "")
+ + " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
+ + (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
+ + ", " + (c.isTapped() ? "Tapped" : "Untapped")
+ + (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName())
))
.sorted()
.collect(Collectors.toList());
@@ -1000,6 +1011,30 @@ public class TestPlayer implements Player {
}
}
+ private void assertPlayableAbility(PlayerAction action, Game game, Player player, String abilityStartText, boolean mustHave) {
+ boolean founded = false;
+ for (Ability ability : computerPlayer.getPlayable(game, true)) {
+ if (ability.toString().startsWith(abilityStartText)) {
+ founded = true;
+ break;
+ }
+ }
+
+ if (mustHave && !founded) {
+ printStart(action.getActionName());
+ printAbilities(game, computerPlayer.getPlayable(game, true));
+ printEnd();
+ Assert.fail("Must have playable ability, but not found: " + abilityStartText);
+ }
+
+ if (!mustHave && founded) {
+ printStart(action.getActionName());
+ printAbilities(game, computerPlayer.getPlayable(game, true));
+ printEnd();
+ Assert.fail("Must not have playable ability, but found: " + abilityStartText);
+ }
+ }
+
private void assertPermanentCount(PlayerAction action, Game game, Player player, String permanentName, int count) {
int foundedCount = 0;
for (Permanent perm : game.getBattlefield().getAllPermanents()) {
@@ -1418,7 +1453,8 @@ public class TestPlayer implements Player {
private void chooseStrictModeFailed(Game game, String reason) {
if (strictChooseMode) {
- Assert.fail("Missing target/choice def for turn " + game.getTurnNum() + ", " + game.getStep().getType().name() + ": " + reason);
+ Assert.fail("Missing target/choice def for turn " + game.getTurnNum() + ", " + this.getName() + ", "
+ + game.getStep().getType().name() + ": " + reason);
}
}
@@ -1488,6 +1524,11 @@ public class TestPlayer implements Player {
@Override
public boolean choose(Outcome outcome, Target target, UUID sourceId, Game game, Map options) {
+ UUID abilityControllerId = computerPlayer.getId();
+ if (target.getTargetController() != null && target.getAbilityController() != null) {
+ abilityControllerId = target.getAbilityController();
+ }
+
if (!choices.isEmpty()) {
List usedChoices = new ArrayList<>();
@@ -1499,12 +1540,13 @@ public class TestPlayer implements Player {
source = stackObject.getStackAbility();
}
- if ((target instanceof TargetPermanent) || (target instanceof TargetPermanentOrPlayer)) { // player target not implemted yet
+ if ((target.getOriginalTarget() instanceof TargetPermanent)
+ || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)) { // player target not implemted yet
FilterPermanent filterPermanent;
- if (target instanceof TargetPermanentOrPlayer) {
- filterPermanent = ((TargetPermanentOrPlayer) target).getFilterPermanent();
+ if (target.getOriginalTarget() instanceof TargetPermanentOrPlayer) {
+ filterPermanent = ((TargetPermanentOrPlayer) target.getOriginalTarget()).getFilterPermanent();
} else {
- filterPermanent = ((TargetPermanent) target).getFilter();
+ filterPermanent = ((TargetPermanent) target.getOriginalTarget()).getFilter();
}
for (String choose2 : choices) {
String[] targetList = choose2.split("\\^");
@@ -1522,13 +1564,12 @@ public class TestPlayer implements Player {
targetName = targetName.substring(0, targetName.length() - 11);
}
}
- for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, getId(), sourceId, game)) {
+ for (Permanent permanent : game.getBattlefield().getActivePermanents(filterPermanent, abilityControllerId, sourceId, game)) {
if (target.getTargets().contains(permanent.getId())) {
continue;
}
if (permanent.getName().equals(targetName)) {
-
- if (target.isNotTarget() || target.canTarget(computerPlayer.getId(), permanent.getId(), source, game)) {
+ if (target.isNotTarget() || target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) {
target.add(permanent.getId(), game);
targetFound = true;
@@ -1536,7 +1577,7 @@ public class TestPlayer implements Player {
}
}
} else if ((permanent.getName() + '-' + permanent.getExpansionSetCode()).equals(targetName)) {
- if (target.isNotTarget() || target.canTarget(computerPlayer.getId(), permanent.getId(), source, game)) {
+ if (target.isNotTarget() || target.canTarget(abilityControllerId, permanent.getId(), source, game)) {
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) {
target.add(permanent.getId(), game);
targetFound = true;
@@ -1557,7 +1598,7 @@ public class TestPlayer implements Player {
for (Player player : game.getPlayers().values()) {
for (String choose2 : choices) {
if (player.getName().equals(choose2)) {
- if (target.canTarget(computerPlayer.getId(), player.getId(), null, game) && !target.getTargets().contains(player.getId())) {
+ if (target.canTarget(abilityControllerId, player.getId(), null, game) && !target.getTargets().contains(player.getId())) {
target.add(player.getId(), game);
choices.remove(choose2);
return true;
@@ -1568,7 +1609,7 @@ public class TestPlayer implements Player {
}
// TODO: add same choices fixes for other target types (one choice must uses only one time for one target)
- if (target instanceof TargetCard) {
+ if (target.getOriginalTarget() instanceof TargetCard) {
// one choice per target
// only unique targets
//TargetCard targetFull = ((TargetCard) target);
@@ -1588,7 +1629,7 @@ public class TestPlayer implements Player {
CheckOneChoice:
for (String possibleChoice : possibleChoices) {
- Set possibleCards = target.possibleTargets(sourceId, target.getTargetController() == null ? getId() : target.getTargetController(), game);
+ Set possibleCards = target.possibleTargets(sourceId, abilityControllerId, game);
CheckTargetsList:
for (UUID targetId : possibleCards) {
MageObject targetObject = game.getObject(targetId);
@@ -1634,10 +1675,10 @@ public class TestPlayer implements Player {
}
}
- if (target instanceof TargetSource) {
+ if (target.getOriginalTarget() instanceof TargetSource) {
Set possibleTargets;
- TargetSource t = ((TargetSource) target);
- possibleTargets = t.possibleTargets(sourceId, computerPlayer.getId(), game);
+ TargetSource t = ((TargetSource) target.getOriginalTarget());
+ possibleTargets = t.possibleTargets(sourceId, abilityControllerId, game);
for (String choose2 : choices) {
String[] targetList = choose2.split("\\^");
boolean targetFound = false;
@@ -1697,18 +1738,20 @@ public class TestPlayer implements Player {
// how to fix: change target definition for addTarget in test's code or update choose from targets implementation in TestPlayer
if ((foundMulti && !canMulti) || (foundSpecialStart && !canSpecialStart) || (foundSpecialClose && !canSpecialClose) || (foundEquals && !canEquals)) {
- Assert.fail("Targets list was setup by addTarget with " + targets + ", but target definition [" + targetDefinition + "]"
+ Assert.fail(this.getName() + " - Targets list was setup by addTarget with " + targets + ", but target definition [" + targetDefinition + "]"
+ " is not supported by [" + canSupportChars + "] for target class " + needTarget.getClass().getSimpleName());
}
}
@Override
public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) {
+ UUID abilityControllerId = computerPlayer.getId();
+ if (target.getTargetController() != null && target.getAbilityController() != null) {
+ abilityControllerId = target.getAbilityController();
+ }
+ UUID sourceId = source != null ? source.getSourceId() : null;
+
if (!targets.isEmpty()) {
- UUID abilityControllerId = computerPlayer.getId();
- if (target.getTargetController() != null && target.getAbilityController() != null) {
- abilityControllerId = target.getAbilityController();
- }
// do not select
if (targets.get(0).equals(TARGET_SKIP)) {
@@ -1718,11 +1761,11 @@ public class TestPlayer implements Player {
}
// player
- if (target instanceof TargetPlayer
- || target instanceof TargetAnyTarget
- || target instanceof TargetCreatureOrPlayer
- || target instanceof TargetPermanentOrPlayer
- || target instanceof TargetDefender) {
+ if (target.getOriginalTarget() instanceof TargetPlayer
+ || target.getOriginalTarget() instanceof TargetAnyTarget
+ || target.getOriginalTarget() instanceof TargetCreatureOrPlayer
+ || target.getOriginalTarget() instanceof TargetPermanentOrPlayer
+ || target.getOriginalTarget() instanceof TargetDefender) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "=");
if (targetDefinition.startsWith("targetPlayer=")) {
@@ -1737,15 +1780,14 @@ public class TestPlayer implements Player {
}
}
}
-
}
// permanent in battlefield
- if ((target instanceof TargetPermanent)
- || (target instanceof TargetPermanentOrPlayer)
- || (target instanceof TargetAnyTarget)
- || (target instanceof TargetCreatureOrPlayer)
- || (target instanceof TargetDefender)) {
+ if ((target.getOriginalTarget() instanceof TargetPermanent)
+ || (target.getOriginalTarget() instanceof TargetPermanentOrPlayer)
+ || (target.getOriginalTarget() instanceof TargetAnyTarget)
+ || (target.getOriginalTarget() instanceof TargetCreatureOrPlayer)
+ || (target.getOriginalTarget() instanceof TargetDefender)) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^[]");
String[] targetList = targetDefinition.split("\\^");
@@ -1763,7 +1805,7 @@ public class TestPlayer implements Player {
targetName = targetName.substring(0, targetName.length() - 11);
}
}
- Filter filter = target.getFilter();
+ Filter filter = target.getOriginalTarget().getFilter();
if (filter instanceof FilterCreatureOrPlayer) {
filter = ((FilterCreatureOrPlayer) filter).getCreatureFilter();
}
@@ -1776,18 +1818,17 @@ public class TestPlayer implements Player {
if (filter instanceof FilterPlaneswalkerOrPlayer) {
filter = ((FilterPlaneswalkerOrPlayer) filter).getFilterPermanent();
}
- for (Permanent permanent : game.getBattlefield().getAllActivePermanents((FilterPermanent) filter, game)) {
+ for (Permanent permanent : game.getBattlefield().getActivePermanents((FilterPermanent) filter, abilityControllerId, sourceId, game)) {
if (permanent.getName().equals(targetName) || (permanent.getName() + '-' + permanent.getExpansionSetCode()).equals(targetName)) {
if (target.canTarget(abilityControllerId, permanent.getId(), source, game) && !target.getTargets().contains(permanent.getId())) {
if ((permanent.isCopy() && !originOnly) || (!permanent.isCopy() && !copyOnly)) {
target.add(permanent.getId(), game);
targetFound = true;
- break;
+ break; // return to for (String targetName
}
}
}
}
-
}
if (targetFound) {
targets.remove(targetDefinition);
@@ -1797,18 +1838,18 @@ public class TestPlayer implements Player {
}
// card in hand
- if (target instanceof TargetCardInHand) {
+ if (target.getOriginalTarget() instanceof TargetCardInHand) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
boolean targetFound = false;
for (String targetName : targetList) {
- for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target).getFilter(), game)) {
+ for (Card card : computerPlayer.getHand().getCards(((TargetCardInHand) target.getOriginalTarget()).getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.add(card.getId(), game);
targetFound = true;
- break;
+ break; // return to for (String targetName
}
}
}
@@ -1821,8 +1862,8 @@ public class TestPlayer implements Player {
}
// card in exile
- if (target instanceof TargetCardInExile) {
- TargetCardInExile targetFull = (TargetCardInExile) target;
+ if (target.getOriginalTarget() instanceof TargetCardInExile) {
+ TargetCardInExile targetFull = (TargetCardInExile) target.getOriginalTarget();
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
@@ -1830,10 +1871,10 @@ public class TestPlayer implements Player {
for (String targetName : targetList) {
for (Card card : game.getExile().getCards(targetFull.getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
- if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) {
- targetFull.add(card.getId(), game);
+ if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
+ target.add(card.getId(), game);
targetFound = true;
- break;
+ break; // return to for (String targetName
}
}
}
@@ -1858,7 +1899,7 @@ public class TestPlayer implements Player {
if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !targetFull.getTargets().contains(card.getId())) {
targetFull.add(card.getId(), game);
targetFound = true;
- break;
+ break; // return to for (String targetName
}
}
}
@@ -1872,22 +1913,22 @@ public class TestPlayer implements Player {
// card in graveyard
- if (target instanceof TargetCardInOpponentsGraveyard
- || target instanceof TargetCardInYourGraveyard
- || target instanceof TargetCardInGraveyard
- || target instanceof TargetCardInGraveyardOrBattlefield) {
- TargetCard targetFull = (TargetCard) target;
+ if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard
+ || target.getOriginalTarget() instanceof TargetCardInYourGraveyard
+ || target.getOriginalTarget() instanceof TargetCardInGraveyard
+ || target.getOriginalTarget() instanceof TargetCardInGraveyardOrBattlefield) {
+ TargetCard targetFull = (TargetCard) target.getOriginalTarget();
List needPlayers = game.getState().getPlayersInRange(getId(), game).toList();
// fix for opponent graveyard
- if (target instanceof TargetCardInOpponentsGraveyard) {
+ if (target.getOriginalTarget() instanceof TargetCardInOpponentsGraveyard) {
// current player remove
Assert.assertTrue(needPlayers.contains(getId()));
needPlayers.remove(getId());
Assert.assertFalse(needPlayers.contains(getId()));
}
// fix for your graveyard
- if (target instanceof TargetCardInYourGraveyard) {
+ if (target.getOriginalTarget() instanceof TargetCardInYourGraveyard) {
// only current player
Assert.assertTrue(needPlayers.contains(getId()));
needPlayers.clear();
@@ -1906,17 +1947,16 @@ public class TestPlayer implements Player {
Player player = game.getPlayer(playerId);
for (Card card : player.getGraveyard().getCards(targetFull.getFilter(), game)) {
if (card.getName().equals(targetName) || (card.getName() + '-' + card.getExpansionSetCode()).equals(targetName)) {
- if (targetFull.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
+ if (target.canTarget(abilityControllerId, card.getId(), source, game) && !target.getTargets().contains(card.getId())) {
target.add(card.getId(), game);
targetFound = true;
- break IterateGraveyards;
+ break IterateGraveyards; // return to for (String targetName
}
}
}
}
}
-
if (targetFound) {
targets.remove(targetDefinition);
return true;
@@ -1926,7 +1966,7 @@ public class TestPlayer implements Player {
}
// stack
- if (target instanceof TargetSpell) {
+ if (target.getOriginalTarget() instanceof TargetSpell) {
for (String targetDefinition : targets) {
checkTargetDefinitionMarksSupport(target, targetDefinition, "^");
String[] targetList = targetDefinition.split("\\^");
@@ -1934,9 +1974,11 @@ public class TestPlayer implements Player {
for (String targetName : targetList) {
for (StackObject stackObject : game.getStack()) {
if (stackObject.getName().equals(targetName)) {
- target.add(stackObject.getId(), game);
- targetFound = true;
- break;
+ if (target.canTarget(abilityControllerId, stackObject.getId(), source, game) && !target.getTargets().contains(stackObject.getId())) {
+ target.add(stackObject.getId(), game);
+ targetFound = true;
+ break; // return to for (String targetName
+ }
}
}
}
@@ -1954,13 +1996,13 @@ public class TestPlayer implements Player {
String message;
if (source != null) {
- message = "Targets list was setup by addTarget with " + targets + ", but not used in ["
+ message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used in ["
+ "card " + source.getSourceObject(game)
+ " -> ability " + source.getClass().getSimpleName() + " (" + source.getRule().substring(0, Math.min(20, source.getRule().length()) - 1) + "..." + ")"
+ " -> target " + target.getClass().getSimpleName() + " (" + target.getMessage() + ")"
+ "]";
} else {
- message = "Targets list was setup by addTarget with " + targets + ", but not used in ["
+ message = this.getName() + " - Targets list was setup by addTarget with " + targets + ", but not used in ["
+ "card XXX"
+ " -> target " + target.getClass().getSimpleName() + " (" + target.getMessage() + ")"
+ "]";
@@ -2161,11 +2203,6 @@ public class TestPlayer implements Player {
return computerPlayer.getCounters();
}
- @Override
- public void otherPlayerLeftGame(Game game) {
- computerPlayer.otherPlayerLeftGame(game);
- }
-
@Override
public void beginTurn(Game game) {
checkLegalMovesThisTurn(game);
@@ -2343,20 +2380,25 @@ public class TestPlayer implements Player {
}
@Override
- public UUID getCastSourceIdWithAlternateMana() {
+ public Set getCastSourceIdWithAlternateMana() {
return computerPlayer.getCastSourceIdWithAlternateMana();
}
@Override
- public ManaCosts getCastSourceIdManaCosts() {
+ public Map> getCastSourceIdManaCosts() {
return computerPlayer.getCastSourceIdManaCosts();
}
@Override
- public Costs getCastSourceIdCosts() {
+ public Map> getCastSourceIdCosts() {
return computerPlayer.getCastSourceIdCosts();
}
+ @Override
+ public void clearCastSourceIdManaCosts() {
+ computerPlayer.clearCastSourceIdManaCosts();
+ }
+
@Override
public boolean isInPayManaMode() {
return computerPlayer.isInPayManaMode();
@@ -2859,7 +2901,7 @@ public class TestPlayer implements Player {
}
@Override
- public Set getPlayableObjects(Game game, Zone zone) {
+ public Map getPlayableObjects(Game game, Zone zone) {
return computerPlayer.getPlayableObjects(game, zone);
}
@@ -3463,7 +3505,7 @@ public class TestPlayer implements Player {
public void setChooseStrictMode(boolean enable) {
this.strictChooseMode = enable;
}
-
+
@Override
public void addPhyrexianToColors(FilterMana colors) {
computerPlayer.addPhyrexianToColors(colors);
@@ -3478,7 +3520,7 @@ public class TestPlayer implements Player {
public FilterMana getPhyrexianColors() {
return computerPlayer.getPhyrexianColors();
}
-
+
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
return card.getSpellAbility();
diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
index 858e3e81353..13dce614832 100644
--- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
+++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java
@@ -55,6 +55,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
public static final String CHECK_COMMAND_DAMAGE = "DAMAGE";
public static final String CHECK_COMMAND_LIFE = "LIFE";
public static final String CHECK_COMMAND_ABILITY = "ABILITY";
+ public static final String CHECK_COMMAND_PLAYABLE_ABILITY = "PLAYABLE_ABILITY";
public static final String CHECK_COMMAND_PERMANENT_COUNT = "PERMANENT_COUNT";
public static final String CHECK_COMMAND_PERMANENT_COUNTERS = "PERMANENT_COUNTERS";
public static final String CHECK_COMMAND_EXILE_COUNT = "EXILE_COUNT";
@@ -318,6 +319,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
check(checkName, turnNum, step, player, CHECK_COMMAND_ABILITY, permanentName, abilityClass.getName(), mustHave.toString());
}
+ public void checkPlayableAbility(String checkName, int turnNum, PhaseStep step, TestPlayer player, String abilityStartText, Boolean mustHave) {
+ check(checkName, turnNum, step, player, CHECK_COMMAND_PLAYABLE_ABILITY, abilityStartText, mustHave.toString());
+ }
+
public void checkPermanentCount(String checkName, int turnNum, PhaseStep step, TestPlayer player, String permanentName, Integer count) {
//Assert.assertNotEquals("", permanentName);
checkPermanentCount(checkName, turnNum, step, player, player, permanentName, count);
diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java
index 3feddbd4eba..abbbb3e1562 100644
--- a/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/serverside/deck/DeckValidatorTest.java
@@ -28,14 +28,14 @@ public class DeckValidatorTest extends MageTestBase {
int number;
- public CardNameAmount(String setCode, int cardNumber, int number) {
+ CardNameAmount(String setCode, int cardNumber, int number) {
this.name = "";
this.setCode = setCode;
this.cardNumber = String.valueOf(cardNumber);
this.number = number;
}
- public CardNameAmount(String name, int number) {
+ CardNameAmount(String name, int number) {
this.name = name;
this.number = number;
}
@@ -48,11 +48,11 @@ public class DeckValidatorTest extends MageTestBase {
return number;
}
- public String getSetCode() {
+ String getSetCode() {
return setCode;
}
- public String getCardNumber() {
+ String getCardNumber() {
return cardNumber;
}
@@ -359,6 +359,7 @@ public class DeckValidatorTest extends MageTestBase {
cardinfo = CardRepository.instance.findCard(cardNameAmount.getName());
}
for (int i = 0; i < cardNameAmount.getNumber(); i++) {
+ assert cardinfo != null;
deckToTest.getCards().add(cardinfo.getCard());
}
}
@@ -372,6 +373,7 @@ public class DeckValidatorTest extends MageTestBase {
cardinfo = CardRepository.instance.findCard(cardNameAmount.getName());
}
for (int i = 0; i < cardNameAmount.getNumber(); i++) {
+ assert cardinfo != null;
deckToTest.getSideboard().add(cardinfo.getCard());
}
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java
index e3347d4ce3b..810dae41b6c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java
+++ b/Mage.Tests/src/test/java/org/mage/test/sets/BoosterGenerationTest.java
@@ -5,6 +5,7 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.keyword.PartnerWithAbility;
import mage.cards.Card;
@@ -143,6 +144,15 @@ public class BoosterGenerationTest extends MageTestBase {
assertTrue(allCards.stream().anyMatch(card -> card.getCardType().contains(CardType.LAND) && card.getRarity().equals(Rarity.COMMON)));
}
+ @Test
+ public void testWarOfTheSpark_EveryBoosterContainsPlaneswalker() {
+ for (int i = 0; i < 10; i++) {
+ List booster = WarOfTheSpark.getInstance().createBooster();
+ // check that booster contains a planeswalker
+ assertTrue(booster.stream().anyMatch(MageObject::isPlaneswalker));
+ }
+ }
+
@Test
public void testDominaria_EveryBoosterContainsLegendaryCreature() {
for (int i = 0; i < 10; i++) {
@@ -160,6 +170,14 @@ public class BoosterGenerationTest extends MageTestBase {
}
}
+ @Test
+ public void testModernHorizons_BoosterMustHaveOneSnowLand() {
+ for (int i = 0; i < 10; i++) {
+ List booster = ModernHorizons.getInstance().createBooster();
+ assertTrue("Modern Horizon's booster must contain 1 snow covered land", booster.stream().anyMatch(card -> card.isBasic() && card.getName().startsWith("Snow-Covered ")));
+ }
+ }
+
@Test
public void testMastersEditionII_BoosterMustHaveOneSnowLand() {
for (int i = 0; i < 10; i++) {
@@ -171,7 +189,7 @@ public class BoosterGenerationTest extends MageTestBase {
@Test
public void testBattlebond_BoosterMustHaveOneLand() {
for (int i = 0; i < 10; i++) {
- List booster = Coldsnap.getInstance().createBooster();
+ List booster = Battlebond.getInstance().createBooster();
assertTrue("battlebond's booster must contain 1 land", booster.stream().anyMatch(card -> card.isBasic() && card.isLand()));
}
}
diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
index 97465f17704..d745681ea4c 100644
--- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
+++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java
@@ -19,8 +19,8 @@ import mage.counters.Counter;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
-import mage.filter.FilterPermanent;
import mage.filter.FilterMana;
+import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.Graveyard;
import mage.game.Table;
@@ -403,11 +403,6 @@ public class PlayerStub implements Player {
return false;
}
- @Override
- public void otherPlayerLeftGame(Game game) {
-
- }
-
@Override
public ManaPool getManaPool() {
return null;
@@ -1048,7 +1043,7 @@ public class PlayerStub implements Player {
}
@Override
- public Set getPlayableObjects(Game game, Zone zone) {
+ public Map getPlayableObjects(Game game, Zone zone) {
return null;
}
@@ -1208,7 +1203,7 @@ public class PlayerStub implements Player {
}
@Override
- public UUID getCastSourceIdWithAlternateMana() {
+ public Set getCastSourceIdWithAlternateMana() {
return null;
}
@@ -1218,15 +1213,20 @@ public class PlayerStub implements Player {
}
@Override
- public ManaCosts getCastSourceIdManaCosts() {
+ public Map> getCastSourceIdCosts() {
return null;
}
@Override
- public Costs getCastSourceIdCosts() {
+ public Map> getCastSourceIdManaCosts() {
return null;
}
+ @Override
+ public void clearCastSourceIdManaCosts() {
+
+ }
+
@Override
public void addPermissionToShowHandCards(UUID watcherUserId) {
@@ -1374,19 +1374,19 @@ public class PlayerStub implements Player {
@Override
public void addPhyrexianToColors(FilterMana colors) {
-
+
}
@Override
public void removePhyrexianFromColors(FilterMana colors) {
-
+
}
@Override
public FilterMana getPhyrexianColors() {
return (new FilterMana());
}
-
+
@Override
public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) {
return card.getSpellAbility();
diff --git a/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java
index 13d6915f92f..a140bac5f7f 100644
--- a/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java
+++ b/Mage.Tests/src/test/java/org/mage/test/testapi/TestAliases.java
@@ -11,7 +11,6 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author JayDi85
*/
-
public class TestAliases extends CardTestPlayerBase {
@Test
@@ -54,7 +53,7 @@ public class TestAliases extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Swamp@battle", 1);
addCard(Zone.GRAVEYARD, playerA, "Swamp@grave", 1);
- showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA);
+// showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA);
checkAliasZone("lib", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "lib", Zone.LIBRARY);
checkAliasZone("hand", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "hand", Zone.HAND);
checkAliasZone("battle", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "battle", Zone.BATTLEFIELD);
@@ -77,8 +76,8 @@ public class TestAliases extends CardTestPlayerBase {
checkPermanentCount("Plains must exists", 1, PhaseStep.UPKEEP, playerB, "Plains", 5);
checkPermanentCount("Mountain must exists", 1, PhaseStep.UPKEEP, playerB, "Mountain", 5);
//
- showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA);
- showAliases("B aliases", 1, PhaseStep.UPKEEP, playerB);
+// showAliases("A aliases", 1, PhaseStep.UPKEEP, playerA);
+// showAliases("B aliases", 1, PhaseStep.UPKEEP, playerB);
// A
checkAliasZone("Swamp must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp", Zone.BATTLEFIELD, false);
checkAliasZone("Swamp.1 must not", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Swamp.1", Zone.BATTLEFIELD, false);
@@ -111,7 +110,7 @@ public class TestAliases extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@lion.3");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "@lion.5");
- showAliases("A aliases", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
+// showAliases("A aliases", 1, PhaseStep.POSTCOMBAT_MAIN, playerA);
checkAliasZone("1", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.1", Zone.BATTLEFIELD, false);
checkAliasZone("2", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.2", Zone.BATTLEFIELD, true);
checkAliasZone("3", 1, PhaseStep.POSTCOMBAT_MAIN, playerA, "lion.3", Zone.BATTLEFIELD, false);
@@ -125,4 +124,4 @@ public class TestAliases extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Lightning Bolt", 3);
assertGraveyardCount(playerA, "Silvercoat Lion", 3);
}
-}
\ No newline at end of file
+}
diff --git a/Mage.Verify/pom.xml b/Mage.Verify/pom.xml
index fd3116701f4..8516f71beb1 100644
--- a/Mage.Verify/pom.xml
+++ b/Mage.Verify/pom.xml
@@ -6,7 +6,7 @@
org.mage
mage-root
- 1.4.40
+ 1.4.41
mage-verify
@@ -49,7 +49,7 @@
org.mage
mage-client
- 1.4.40
+ 1.4.41
diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
index d5ec909522d..9026b1f8cbc 100644
--- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
+++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java
@@ -765,7 +765,7 @@ public class VerifyCardDataTest {
// debug only: show direct card info (takes it from class file, not from db repository)
String cardName = "Essence Capture";
CardScanner.scan();
- CardSetInfo testSet = new CardSetInfo("test", "test", "123", Rarity.COMMON);
+ CardSetInfo testSet = new CardSetInfo(cardName, "test", "123", Rarity.COMMON);
CardInfo cardInfo = CardRepository.instance.findCard(cardName);
Card card = CardImpl.createCard(cardInfo.getClassName(), testSet);
card.getRules().stream().forEach(System.out::println);
diff --git a/Mage/pom.xml b/Mage/pom.xml
index fc994fff7d2..12350d02341 100644
--- a/Mage/pom.xml
+++ b/Mage/pom.xml
@@ -6,7 +6,7 @@
org.mage
mage-root
- 1.4.40
+ 1.4.41
mage
diff --git a/Mage/src/main/java/mage/abilities/Ability.java b/Mage/src/main/java/mage/abilities/Ability.java
index 386f8d56c7f..f14c95b6341 100644
--- a/Mage/src/main/java/mage/abilities/Ability.java
+++ b/Mage/src/main/java/mage/abilities/Ability.java
@@ -1,5 +1,8 @@
package mage.abilities;
+import java.io.Serializable;
+import java.util.List;
+import java.util.UUID;
import mage.MageObject;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostAdjuster;
@@ -23,10 +26,6 @@ import mage.target.Targets;
import mage.target.targetadjustment.TargetAdjuster;
import mage.watchers.Watcher;
-import java.io.Serializable;
-import java.util.List;
-import java.util.UUID;
-
/**
* Practically everything in the game is started from an Ability. This interface
* describes what an Ability is composed of at the highest level.
@@ -47,8 +46,10 @@ public interface Ability extends Controllable, Serializable {
*
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
- * @see mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
- * @see mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
+ * @see
+ * mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
+ * @see
+ * mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
*/
void newId();
@@ -57,8 +58,10 @@ public interface Ability extends Controllable, Serializable {
*
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
- * @see mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
- * @see mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
+ * @see
+ * mage.game.GameImpl#addTriggeredAbility(mage.abilities.TriggeredAbility)
+ * @see
+ * mage.game.GameImpl#addDelayedTriggeredAbility(mage.abilities.DelayedTriggeredAbility)
*/
void newOriginalId();
@@ -264,15 +267,16 @@ public interface Ability extends Controllable, Serializable {
/**
* Activates this ability prompting the controller to pay any mandatory
*
- * @param game A reference the {@link Game} for which this ability should be
- * activated within.
+ * @param game A reference the {@link Game} for which this ability should be
+ * activated within.
* @param noMana Whether or not {@link ManaCosts} have to be paid.
* @return True if this ability was successfully activated.
* @see mage.players.PlayerImpl#cast(mage.abilities.SpellAbility,
* mage.game.Game, boolean)
* @see mage.players.PlayerImpl#playAbility(mage.abilities.ActivatedAbility,
* mage.game.Game)
- * @see mage.players.PlayerImpl#triggerAbility(mage.abilities.TriggeredAbility,
+ * @see
+ * mage.players.PlayerImpl#triggerAbility(mage.abilities.TriggeredAbility,
* mage.game.Game)
*/
boolean activate(Game game, boolean noMana);
@@ -286,7 +290,8 @@ public interface Ability extends Controllable, Serializable {
*
* @param game The {@link Game} for which this ability resolves within.
* @return Whether or not this ability successfully resolved.
- * @see mage.players.PlayerImpl#playManaAbility(mage.abilities.mana.ManaAbility,
+ * @see
+ * mage.players.PlayerImpl#playManaAbility(mage.abilities.mana.ManaAbility,
* mage.game.Game)
* @see mage.players.PlayerImpl#specialAction(mage.abilities.SpecialAction,
* mage.game.Game)
@@ -461,15 +466,6 @@ public interface Ability extends Controllable, Serializable {
*/
String getGameLogMessage(Game game);
- /**
- * Used to deactivate cost modification logic of ability activation for some
- * special handling (e.g. FlashbackAbility gets cost modifiaction twice
- * because of how it's handled now)
- *
- * @param active execute no cost modification
- */
- void setCostModificationActive(boolean active);
-
boolean activateAlternateOrAdditionalCosts(MageObject sourceObject, boolean noMana, Player controller, Game game);
/**
diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java
index 1f365f13e22..fedd2613df4 100644
--- a/Mage/src/main/java/mage/abilities/AbilityImpl.java
+++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java
@@ -1,5 +1,9 @@
package mage.abilities;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.costs.*;
@@ -17,7 +21,6 @@ import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.Card;
import mage.cards.SplitCard;
import mage.constants.*;
-import mage.filter.FilterMana;
import mage.game.Game;
import mage.game.command.Emblem;
import mage.game.command.Plane;
@@ -35,11 +38,6 @@ import mage.util.ThreadLocalStringBuilder;
import mage.watchers.Watcher;
import org.apache.log4j.Logger;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -66,7 +64,6 @@ public abstract class AbilityImpl implements Ability {
protected boolean ruleAtTheTop = false;
protected boolean ruleVisible = true;
protected boolean ruleAdditionalCostsVisible = true;
- protected boolean costModificationActive = true;
protected boolean activated = false;
protected boolean worksFaceDown = false;
protected int sourceObjectZoneChangeCounter;
@@ -116,7 +113,6 @@ public abstract class AbilityImpl implements Ability {
this.ruleAtTheTop = ability.ruleAtTheTop;
this.ruleVisible = ability.ruleVisible;
this.ruleAdditionalCostsVisible = ability.ruleAdditionalCostsVisible;
- this.costModificationActive = ability.costModificationActive;
this.worksFaceDown = ability.worksFaceDown;
this.abilityWord = ability.abilityWord;
this.sourceObjectZoneChangeCounter = ability.sourceObjectZoneChangeCounter;
@@ -356,30 +352,9 @@ public abstract class AbilityImpl implements Ability {
}
//20101001 - 601.2e
- if (costModificationActive) {
-
- // TODO: replace all AdjustingSourceCosts abilities to continuus effect, see Affinity example
- //20100716 - 601.2e
- if (sourceObject != null) {
- sourceObject.adjustCosts(this, game);
- if (sourceObject instanceof Card) {
- for (Ability ability : ((Card) sourceObject).getAbilities(game)) {
- if (ability instanceof AdjustingSourceCosts) {
- ((AdjustingSourceCosts) ability).adjustCosts(this, game);
- }
- }
- } else {
- for (Ability ability : sourceObject.getAbilities()) {
- if (ability instanceof AdjustingSourceCosts) {
- ((AdjustingSourceCosts) ability).adjustCosts(this, game);
- }
- }
- }
- }
-
+ if (sourceObject != null) {
+ sourceObject.adjustCosts(this, game); // still needed
game.getContinuousEffects().costModification(this, game);
- } else {
- costModificationActive = true;
}
UUID activatorId = controllerId;
@@ -542,15 +517,14 @@ public abstract class AbilityImpl implements Ability {
/**
* 601.2b If a cost that will be paid as the spell is being cast includes
- * Phyrexian mana symbols, the player announces whether they intend to
- * pay 2 life or the corresponding colored mana cost for each of those
- * symbols.
+ * Phyrexian mana symbols, the player announces whether they intend to pay 2
+ * life or the corresponding colored mana cost for each of those symbols.
*/
private void handlePhyrexianManaCosts(Game game, UUID sourceId, Player controller) {
Iterator costIterator = manaCostsToPay.iterator();
while (costIterator.hasNext()) {
ManaCost cost = costIterator.next();
-
+
if (cost instanceof PhyrexianManaCost) {
PhyrexianManaCost phyrexianManaCost = (PhyrexianManaCost) cost;
PayLifeCost payLifeCost = new PayLifeCost(2);
@@ -1191,11 +1165,6 @@ public abstract class AbilityImpl implements Ability {
return sb.toString();
}
- @Override
- public void setCostModificationActive(boolean active) {
- this.costModificationActive = active;
- }
-
@Override
public boolean getWorksFaceDown() {
return worksFaceDown;
diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbility.java b/Mage/src/main/java/mage/abilities/ActivatedAbility.java
index 152f05242f6..4601964969b 100644
--- a/Mage/src/main/java/mage/abilities/ActivatedAbility.java
+++ b/Mage/src/main/java/mage/abilities/ActivatedAbility.java
@@ -1,4 +1,3 @@
-
package mage.abilities;
import java.util.UUID;
@@ -62,14 +61,6 @@ public interface ActivatedAbility extends Ability {
@Override
ActivatedAbility copy();
- /**
- * Set a flag to know, that the ability is only created adn used to check
- * what's playbable for the player.
- */
- void setCheckPlayableMode();
-
- boolean isCheckPlayableMode();
-
void setMaxActivationsPerTurn(int maxActivationsPerTurn);
int getMaxActivationsPerTurn(Game game);
diff --git a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java
index f0ba91bf64f..d54e2f27eb9 100644
--- a/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java
+++ b/Mage/src/main/java/mage/abilities/ActivatedAbilityImpl.java
@@ -46,11 +46,9 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
protected TimingRule timing = TimingRule.INSTANT;
protected TargetController mayActivate = TargetController.YOU;
protected UUID activatorId;
- protected boolean checkPlayableMode;
protected ActivatedAbilityImpl(AbilityType abilityType, Zone zone) {
super(abilityType, zone);
- this.checkPlayableMode = false;
}
public ActivatedAbilityImpl(final ActivatedAbilityImpl ability) {
@@ -58,7 +56,6 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
timing = ability.timing;
mayActivate = ability.mayActivate;
activatorId = ability.activatorId;
- checkPlayableMode = ability.checkPlayableMode;
maxActivationsPerTurn = ability.maxActivationsPerTurn;
condition = ability.condition;
}
@@ -262,16 +259,6 @@ public abstract class ActivatedAbilityImpl extends AbilityImpl implements Activa
this.timing = timing;
}
- @Override
- public void setCheckPlayableMode() {
- checkPlayableMode = true;
- }
-
- @Override
- public boolean isCheckPlayableMode() {
- return checkPlayableMode;
- }
-
protected boolean hasMoreActivationsThisTurn(Game game) {
if (getMaxActivationsPerTurn(game) == Integer.MAX_VALUE) {
return true;
diff --git a/Mage/src/main/java/mage/abilities/SpellAbility.java b/Mage/src/main/java/mage/abilities/SpellAbility.java
index 0a76898c13f..26f0bd32b66 100644
--- a/Mage/src/main/java/mage/abilities/SpellAbility.java
+++ b/Mage/src/main/java/mage/abilities/SpellAbility.java
@@ -1,5 +1,7 @@
package mage.abilities;
+import java.util.Optional;
+import java.util.UUID;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.costs.Cost;
@@ -15,9 +17,6 @@ import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
-import java.util.Optional;
-import java.util.UUID;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -63,7 +62,10 @@ public class SpellAbility extends ActivatedAbilityImpl {
*/
public boolean spellCanBeActivatedRegularlyNow(UUID playerId, Game game) {
MageObject object = game.getObject(sourceId);
- if ((Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) {
+ if (object == null) {
+ return false;
+ }
+ if (game.getState().getValue("CastFromExileEnabled" + object.getId()) != null) {
return (Boolean) game.getState().getValue("CastFromExileEnabled" + object.getId()); // card like Chandra, Torch of Defiance +1 loyal ability)
}
return null != game.getContinuousEffects().asThough(sourceId, AsThoughEffectType.CAST_AS_INSTANT, this, playerId, game) // check this first to allow Offering in main phase
@@ -88,7 +90,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
}
}
// Check if rule modifying events prevent to cast the spell in check playable mode
- if (this.isCheckPlayableMode()) {
+ if (game.inCheckPlayableState()) {
if (game.getContinuousEffects().preventedByRuleModification(
GameEvent.getEvent(GameEvent.EventType.CAST_SPELL, this.getId(), this.getSourceId(), playerId), this, game, true)) {
return ActivationStatus.getFalse();
@@ -98,7 +100,7 @@ public class SpellAbility extends ActivatedAbilityImpl {
if (getSpellAbilityType() == SpellAbilityType.BASE_ALTERNATE) {
Player player = game.getPlayer(playerId);
if (player != null
- && getSourceId().equals(player.getCastSourceIdWithAlternateMana())) {
+ && player.getCastSourceIdWithAlternateMana().contains(getSourceId())) {
return ActivationStatus.getFalse();
}
}
diff --git a/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java b/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java
index f646dcaa880..5af021988bf 100644
--- a/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java
+++ b/Mage/src/main/java/mage/abilities/abilityword/ConstellationAbility.java
@@ -12,22 +12,29 @@ import mage.game.permanent.Permanent;
/**
* Constellation
*
- *
* @author LevelX2
*/
public class ConstellationAbility extends TriggeredAbilityImpl {
+ private final boolean thisOr;
+
public ConstellationAbility(Effect effect) {
this(effect, false);
}
public ConstellationAbility(Effect effect, boolean optional) {
+ this(effect, optional, true);
+ }
+
+ public ConstellationAbility(Effect effect, boolean optional, boolean thisOr) {
super(Zone.BATTLEFIELD, effect, optional);
+ this.thisOr = thisOr;
}
public ConstellationAbility(final ConstellationAbility ability) {
super(ability);
+ this.thisOr = ability.thisOr;
}
@Override
@@ -42,17 +49,17 @@ public class ConstellationAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
- if (event.getPlayerId().equals(this.getControllerId())) {
- Permanent permanent = game.getPermanent(event.getTargetId());
- if (permanent != null && permanent.isEnchantment()) {
- return true;
- }
+ if (!event.getPlayerId().equals(this.getControllerId())) {
+ return false;
}
- return false;
+ Permanent permanent = game.getPermanent(event.getTargetId());
+ return permanent != null && permanent.isEnchantment();
}
@Override
public String getRule() {
- return new StringBuilder("Constellation — Whenever {this} or another enchantment enters the battlefield under your control, ").append(super.getRule()).toString();
+ return "Constellation — Whenever "
+ + (thisOr ? "{this} or another" : "an")
+ + " enchantment enters the battlefield under your control, " + super.getRule();
}
}
diff --git a/Mage/src/main/java/mage/abilities/common/BeginningOfUntapTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/BeginningOfUntapTriggeredAbility.java
index bc685c9fa73..89d9f87241d 100644
--- a/Mage/src/main/java/mage/abilities/common/BeginningOfUntapTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/BeginningOfUntapTriggeredAbility.java
@@ -10,11 +10,9 @@ import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
-import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
- *
* @author Jeff
*/
public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
@@ -59,8 +57,8 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
return yours;
case NOT_YOU:
- Player controller = game.getPlayer(this.getControllerId());
- if (controller != null && controller.getInRange().contains(event.getPlayerId()) && !event.getPlayerId().equals(this.getControllerId())) {
+ if (game.getState().getPlayersInRange(this.getControllerId(), game).contains(event.getPlayerId())
+ && !event.getPlayerId().equals(this.getControllerId())) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
@@ -80,8 +78,7 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
break;
case ANY:
- controller = game.getPlayer(this.getControllerId());
- if (controller != null && controller.getInRange().contains(event.getPlayerId())) {
+ if (game.getState().getPlayersInRange(this.getControllerId(), game).contains(event.getPlayerId())) {
if (getTargets().isEmpty()) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getPlayerId()));
@@ -89,6 +86,7 @@ public class BeginningOfUntapTriggeredAbility extends TriggeredAbilityImpl {
}
return true;
}
+ break;
}
return false;
}
diff --git a/Mage/src/main/java/mage/abilities/common/ControllerPlaysLandTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ControllerPlaysLandTriggeredAbility.java
index 252203870dc..f741ee9feb5 100644
--- a/Mage/src/main/java/mage/abilities/common/ControllerPlaysLandTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/ControllerPlaysLandTriggeredAbility.java
@@ -14,7 +14,6 @@ import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent;
/**
- *
* @author jeffwadsworth
*/
public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@@ -35,7 +34,7 @@ public class ControllerPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
- return land.getControllerId().equals(controllerId);
+ return land != null && land.getControllerId().equals(controllerId);
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/common/DiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DiesTriggeredAbility.java
index 2334c8e7890..8d82bd7d776 100644
--- a/Mage/src/main/java/mage/abilities/common/DiesTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/DiesTriggeredAbility.java
@@ -1,4 +1,3 @@
-
package mage.abilities.common;
import mage.MageObject;
@@ -34,11 +33,11 @@ public class DiesTriggeredAbility extends ZoneChangeTriggeredAbility {
if (before == null) {
return false;
}
- if (!(before instanceof PermanentToken) && !this.hasSourceObjectAbility(game, before, event)) {
+ if (!this.hasSourceObjectAbility(game, before, event)) { // the permanent does not have the ability so no trigger
return false;
}
- // check now it is in graveyard
- if (before.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(sourceId)) {
+ // check now it is in graveyard if it is no token
+ if (!(before instanceof PermanentToken) && before.getZoneChangeCounter(game) + 1 == game.getState().getZoneChangeCounter(sourceId)) {
Zone after = game.getState().getZone(sourceId);
return after != null && Zone.GRAVEYARD.match(after);
} else {
diff --git a/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java b/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java
new file mode 100644
index 00000000000..3635991e15b
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/common/EscapesWithAbility.java
@@ -0,0 +1,88 @@
+package mage.abilities.common;
+
+import mage.abilities.Ability;
+import mage.abilities.SpellAbility;
+import mage.abilities.effects.EntersBattlefieldEffect;
+import mage.abilities.effects.OneShotEffect;
+import mage.abilities.keyword.EscapeAbility;
+import mage.constants.AbilityType;
+import mage.constants.Outcome;
+import mage.counters.CounterType;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+import mage.util.CardUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author TheElk801
+ */
+public class EscapesWithAbility extends EntersBattlefieldAbility {
+
+ private final int counters;
+
+ public EscapesWithAbility(int counters) {
+ super(new EscapesWithEffect(counters), false);
+ this.counters = counters;
+ }
+
+ private EscapesWithAbility(final EscapesWithAbility ability) {
+ super(ability);
+ this.counters = ability.counters;
+ }
+
+ @Override
+ public EscapesWithAbility copy() {
+ return new EscapesWithAbility(this);
+ }
+
+ @Override
+ public String getRule() {
+ return "{this} escapes with " + CardUtil.numberToText(counters, "a")
+ + " +1/+1 counter" + (counters > 1 ? 's' : "") + " on it.";
+ }
+}
+
+class EscapesWithEffect extends OneShotEffect {
+
+ private final int counter;
+
+ EscapesWithEffect(int counter) {
+ super(Outcome.BoostCreature);
+ this.counter = counter;
+ }
+
+ private EscapesWithEffect(final EscapesWithEffect effect) {
+ super(effect);
+ this.counter = effect.counter;
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Permanent permanent = game.getPermanent(source.getSourceId());
+ if (permanent == null && source.getAbilityType() == AbilityType.STATIC) {
+ permanent = game.getPermanentEntering(source.getSourceId());
+ }
+ if (permanent == null) {
+ return false;
+ }
+ SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY);
+ if (!(spellAbility instanceof EscapeAbility)
+ || !spellAbility.getSourceId().equals(source.getSourceId())
+ || permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()
+ || !spellAbility.getSourceId().equals(source.getSourceId())) {
+ return false;
+ }
+ List appliedEffects = (ArrayList) this.getValue("appliedEffects");
+ permanent.addCounters(CounterType.P1P1.createInstance(counter), source, game, appliedEffects);
+ return true;
+ }
+
+ @Override
+ public EscapesWithEffect copy() {
+ return new EscapesWithEffect(this);
+ }
+
+}
diff --git a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java
index 753a2907169..75d32f71e9a 100644
--- a/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/ExploitCreatureTriggeredAbility.java
@@ -1,4 +1,3 @@
-
package mage.abilities.common;
import mage.MageObject;
@@ -8,6 +7,7 @@ import mage.constants.SetTargetPointer;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
+import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
@@ -44,14 +44,18 @@ public class ExploitCreatureTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
- if (event.getTargetId().equals(getSourceId()) && event.getSourceId().equals(getSourceId())) {
- if (!this.hasSourceObjectAbility(game, source, event)) {
- return false;
+ Permanent sourcePermanent = null;
+ if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) {
+ sourcePermanent = game.getPermanent(getSourceId());
+ } else {
+ if (game.getShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) {
+ sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
}
- this.setControllerId(event.getPlayerId());
- return true; // if Exploits creature sacrifices itself, exploit triggers
}
- return super.isInUseableZone(game, source, event);
+ if (sourcePermanent == null) {
+ return false;
+ }
+ return hasSourceObjectAbility(game, sourcePermanent, event);
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java
index e59ceb25243..a300de53419 100644
--- a/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/GodEternalDiesTriggeredAbility.java
@@ -1,5 +1,6 @@
package mage.abilities.common;
+import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
@@ -17,8 +18,6 @@ import mage.players.Player;
* @author TheElk801
*/
public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
-
- Boolean applied;
public GodEternalDiesTriggeredAbility() {
super(Zone.ALL, null, true);
@@ -33,7 +32,7 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
if (event.getType() == GameEvent.EventType.ZONE_CHANGE) {
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
return zEvent.getFromZone() == Zone.BATTLEFIELD
- && (zEvent.getToZone() == Zone.GRAVEYARD
+ && (zEvent.getToZone() == Zone.GRAVEYARD
|| zEvent.getToZone() == Zone.EXILED);
}
return false;
@@ -41,24 +40,29 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
- applied = false;
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (zEvent.getTargetId().equals(this.getSourceId())) {
- Permanent permanent = game.getPermanentOrLKIBattlefield(this.getSourceId());
- // for cases where its triggered ability is removed, ex: Kasmina's Transmutation
- if (permanent != null) {
- for (Ability a : permanent.getAbilities()) {
- if (a instanceof GodEternalDiesTriggeredAbility) {
- applied = true;
- }
- }
- }
- if (applied) {
this.getEffects().clear();
this.addEffect(new GodEternalEffect(new MageObjectReference(zEvent.getTarget(), game)));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isInUseableZone(Game game, MageObject source, GameEvent event) {
+ Permanent sourcePermanent = null;
+ if (game.getState().getZone(getSourceId()) == Zone.BATTLEFIELD) {
+ sourcePermanent = game.getPermanent(getSourceId());
+ } else {
+ if (game.getShortLivingLKI(getSourceId(), Zone.BATTLEFIELD)) {
+ sourcePermanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD);
}
}
- return applied;
+ if (sourcePermanent == null) {
+ return false;
+ }
+ return hasSourceObjectAbility(game, sourcePermanent, event);
}
@Override
@@ -68,8 +72,8 @@ public class GodEternalDiesTriggeredAbility extends TriggeredAbilityImpl {
@Override
public String getRule() {
- return "When {this} dies or is put into exile from the battlefield, " +
- "you may put it into its owner's library third from the top.";
+ return "When {this} dies or is put into exile from the battlefield, "
+ + "you may put it into its owner's library third from the top.";
}
}
@@ -104,4 +108,4 @@ class GodEternalEffect extends OneShotEffect {
}
return player.putCardOnTopXOfLibrary(card, game, source, 3);
}
-}
\ No newline at end of file
+}
diff --git a/Mage/src/main/java/mage/abilities/common/OpponentPlaysLandTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OpponentPlaysLandTriggeredAbility.java
index 7249b6679e4..d8c7ba42e06 100644
--- a/Mage/src/main/java/mage/abilities/common/OpponentPlaysLandTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/OpponentPlaysLandTriggeredAbility.java
@@ -13,7 +13,6 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
/**
- *
* @author jeffwadsworth
*/
public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@@ -34,7 +33,7 @@ public class OpponentPlaysLandTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent land = game.getPermanent(event.getTargetId());
- return game.getOpponents(controllerId).contains(land.getControllerId());
+ return land != null && game.getOpponents(controllerId).contains(land.getControllerId());
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/common/TurnedFaceUpAllTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TurnedFaceUpAllTriggeredAbility.java
index b28e5385833..6b954afe1a9 100644
--- a/Mage/src/main/java/mage/abilities/common/TurnedFaceUpAllTriggeredAbility.java
+++ b/Mage/src/main/java/mage/abilities/common/TurnedFaceUpAllTriggeredAbility.java
@@ -1,5 +1,3 @@
-
-
package mage.abilities.common;
import mage.MageObject;
@@ -14,7 +12,6 @@ import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
- *
* @author LevelX2
*/
@@ -27,7 +24,7 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
this(effect, filter, false);
}
- public TurnedFaceUpAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean setTargetPointer) {
+ public TurnedFaceUpAllTriggeredAbility(Effect effect, FilterPermanent filter, boolean setTargetPointer) {
this(Zone.BATTLEFIELD, effect, filter, setTargetPointer, false);
}
@@ -60,7 +57,7 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
if (!event.getTargetId().equals(getSourceId())) {
MageObject sourceObj = this.getSourceObject(game);
if (sourceObj != null) {
- if (sourceObj instanceof Card && ((Card)sourceObj).isFaceDown(game)) {
+ if (sourceObj instanceof Card && ((Card) sourceObj).isFaceDown(game)) {
// if face down and it's not itself that is turned face up, it does not trigger
return false;
}
@@ -70,9 +67,9 @@ public class TurnedFaceUpAllTriggeredAbility extends TriggeredAbilityImpl {
}
}
Permanent permanent = game.getPermanent(event.getTargetId());
- if (filter.match(permanent, getSourceId(), getControllerId(), game)) {
+ if (permanent != null && filter.match(permanent, getSourceId(), getControllerId(), game)) {
if (setTargetPointer) {
- for (Effect effect: getEffects()) {
+ for (Effect effect : getEffects()) {
effect.setTargetPointer(new FixedTarget(event.getTargetId()));
}
}
diff --git a/Mage/src/main/java/mage/abilities/condition/common/PlayLandCondition.java b/Mage/src/main/java/mage/abilities/condition/common/PlayLandCondition.java
index cab3b23a32b..b528a8dca6e 100644
--- a/Mage/src/main/java/mage/abilities/condition/common/PlayLandCondition.java
+++ b/Mage/src/main/java/mage/abilities/condition/common/PlayLandCondition.java
@@ -1,20 +1,23 @@
-package mage.abilities.condition.common;
-
-import mage.abilities.Ability;
-import mage.abilities.condition.Condition;
-import mage.game.Game;
-import mage.watchers.common.PlayLandWatcher;
-
-/**
- * @author jeffwadsworth
- */
-public enum PlayLandCondition implements Condition {
- instance;
-
- @Override
- public boolean apply(Game game, Ability source) {
- PlayLandWatcher watcher = game.getState().getWatcher(PlayLandWatcher.class);
- return watcher != null
- && watcher.landPlayed(source.getControllerId());
- }
-}
+package mage.abilities.condition.common;
+
+import mage.abilities.Ability;
+import mage.abilities.condition.Condition;
+import mage.game.Game;
+import mage.watchers.common.PlayLandWatcher;
+
+/**
+ * @author jeffwadsworth
+ */
+public enum PlayLandCondition implements Condition {
+ instance;
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ if (game.getTurn().getPhase() == null) { // only for getFrameColor for River of Tears before game started
+ return false;
+ }
+ PlayLandWatcher watcher = game.getState().getWatcher(PlayLandWatcher.class);
+ return watcher != null
+ && watcher.landPlayed(source.getControllerId());
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/costs/AdjustingSourceCosts.java b/Mage/src/main/java/mage/abilities/costs/AdjustingSourceCosts.java
deleted file mode 100644
index 34d951a97cf..00000000000
--- a/Mage/src/main/java/mage/abilities/costs/AdjustingSourceCosts.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package mage.abilities.costs;
-
-import mage.abilities.Ability;
-import mage.game.Game;
-
-/**
- * Interface for abilities that adjust source and only source costs. For the
- * cases when some permanent adjusts costs of other spells use
- * {@link mage.abilities.effects.CostModificationEffect}.
- *
- * Example of such source costs adjusting:
- * {@link mage.abilities.keyword.AffinityForArtifactsAbility}
- *
- * @author nantuko
- */
-@Deprecated
-// replace all AdjustingSourceCosts with "extends CostModificationEffectImpl with zone.ALL" (see Affinity example)
-@FunctionalInterface
-public interface AdjustingSourceCosts {
-
- void adjustCosts(Ability ability, Game game);
-}
diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInAllGraveyardsCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInAllGraveyardsCount.java
index cd07e0a9cf9..72cd99216cb 100644
--- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInAllGraveyardsCount.java
+++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/CardsInAllGraveyardsCount.java
@@ -1,4 +1,3 @@
-
package mage.abilities.dynamicvalue.common;
import java.util.UUID;
@@ -25,7 +24,7 @@ public class CardsInAllGraveyardsCount implements DynamicValue {
this.filter = filter;
}
- public CardsInAllGraveyardsCount(CardsInAllGraveyardsCount dynamicValue) {
+ public CardsInAllGraveyardsCount(final CardsInAllGraveyardsCount dynamicValue) {
this.filter = dynamicValue.filter.copy();
}
diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SourcePermanentPowerCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SourcePermanentPowerCount.java
index f55eb7a3f87..af4effc0f04 100644
--- a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SourcePermanentPowerCount.java
+++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SourcePermanentPowerCount.java
@@ -3,6 +3,7 @@ package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
+import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
@@ -29,7 +30,10 @@ public class SourcePermanentPowerCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
- Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(sourceAbility.getSourceId());
+ Permanent sourcePermanent = game.getPermanent(sourceAbility.getSourceId());
+ if (sourcePermanent == null || sourcePermanent.getZoneChangeCounter(game) > sourceAbility.getSourceObjectZoneChangeCounter()) {
+ sourcePermanent = (Permanent) game.getLastKnownInformation(sourceAbility.getSourceId(), Zone.BATTLEFIELD);
+ }
if (sourcePermanent != null
&& (allowNegativeValues || sourcePermanent.getPower().getValue() >= 0)) {
return sourcePermanent.getPower().getValue();
diff --git a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java
index 03ed5c0b3d9..a21b0e3a25e 100644
--- a/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java
+++ b/Mage/src/main/java/mage/abilities/effects/AsThoughEffectImpl.java
@@ -1,6 +1,5 @@
package mage.abilities.effects;
-import java.util.UUID;
import mage.abilities.Ability;
import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
@@ -8,8 +7,9 @@ import mage.constants.EffectType;
import mage.constants.Outcome;
import mage.game.Game;
+import java.util.UUID;
+
/**
- *
* @author BetaSteward_at_googlemail.com
*/
public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements AsThoughEffect {
@@ -29,10 +29,11 @@ public abstract class AsThoughEffectImpl extends ContinuousEffectImpl implements
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
+ // affectedControllerId = player to check
if (getAsThoughEffectType().equals(AsThoughEffectType.LOOK_AT_FACE_DOWN)) {
return applies(objectId, source, playerId, game);
} else {
- return applies(objectId, source, affectedAbility.getControllerId(), game);
+ return applies(objectId, source, playerId, game);
}
}
diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java
index e414bfb3c17..4355daf9b4e 100644
--- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java
+++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffectImpl.java
@@ -217,7 +217,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
boolean canDelete = false;
Player player = game.getPlayer(startingControllerId);
- // discard on start of turn for leave player
+ // discard on start of turn for leaved player
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
diff --git a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java
index 5d3456dc21f..296f931fdb0 100644
--- a/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java
+++ b/Mage/src/main/java/mage/abilities/effects/ContinuousEffects.java
@@ -1,14 +1,15 @@
package mage.abilities.effects;
+import java.io.Serializable;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.*;
import mage.abilities.effects.common.continuous.BecomesFaceDownCreatureEffect;
import mage.abilities.effects.common.continuous.CommanderReplacementEffect;
-import mage.cards.Card;
-import mage.cards.Cards;
-import mage.cards.CardsImpl;
-import mage.cards.SplitCardHalf;
+import mage.cards.*;
import mage.constants.*;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicate;
@@ -26,11 +27,6 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
import org.apache.log4j.Logger;
-import java.io.Serializable;
-import java.util.*;
-import java.util.Map.Entry;
-import java.util.stream.Collectors;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -336,7 +332,7 @@ public class ContinuousEffects implements Serializable {
}
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
//get all applicable transient Replacement effects
- for (Iterator iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
+ for (Iterator iterator = replacementEffects.iterator(); iterator.hasNext();) {
ReplacementEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@@ -369,7 +365,7 @@ public class ContinuousEffects implements Serializable {
}
}
- for (Iterator iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
+ for (Iterator iterator = preventionEffects.iterator(); iterator.hasNext();) {
PreventionEffect effect = iterator.next();
if (!effect.checksEventType(event, game)) {
continue;
@@ -508,9 +504,20 @@ public class ContinuousEffects implements Serializable {
UUID idToCheck;
if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof SplitCardHalf) {
idToCheck = ((SplitCardHalf) affectedAbility.getSourceObject(game)).getParentCard().getId();
+ } else if (affectedAbility != null && affectedAbility.getSourceObject(game) instanceof AdventureCardSpell
+ && type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
+ && type != AsThoughEffectType.CAST_AS_INSTANT) {
+ // adventure spell uses alternative characteristics for spell/stack
+ idToCheck = ((AdventureCardSpell) affectedAbility.getSourceObject(game)).getParentCard().getId();
} else {
- if (game.getObject(objectId) instanceof SplitCardHalf) {
- idToCheck = ((SplitCardHalf) game.getObject(objectId)).getParentCard().getId();
+ Card card = game.getCard(objectId);
+ if (card instanceof SplitCardHalf) {
+ idToCheck = ((SplitCardHalf) card).getParentCard().getId();
+ } else if (card instanceof AdventureCardSpell
+ && type != AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE
+ && type != AsThoughEffectType.CAST_AS_INSTANT) {
+ // adventure spell uses alternative characteristics for spell/stack
+ idToCheck = ((AdventureCardSpell) card).getParentCard().getId();
} else {
idToCheck = objectId;
}
@@ -712,10 +719,10 @@ public class ContinuousEffects implements Serializable {
* Checks if an event won't happen because of an rule modifying effect
*
* @param event
- * @param targetAbility ability the event is attached to. can be null.
+ * @param targetAbility ability the event is attached to. can be null.
* @param game
* @param checkPlayableMode true if the event does not really happen but
- * it's checked if the event would be replaced
+ * it's checked if the event would be replaced
* @return
*/
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean checkPlayableMode) {
@@ -729,10 +736,7 @@ public class ContinuousEffects implements Serializable {
if (effect.getDuration() != Duration.OneUse || !effect.isUsed()) {
effect.setValue("targetAbility", targetAbility);
if (effect.applies(event, sourceAbility, game)) {
- if (targetAbility instanceof ActivatedAbility && ((ActivatedAbility) targetAbility).isCheckPlayableMode()) {
- checkPlayableMode = true;
- }
- if (!checkPlayableMode) {
+ if (!game.inCheckPlayableState()) {
String message = effect.getInfoMessage(sourceAbility, event, game);
if (message != null && !message.isEmpty()) {
if (effect.sendMessageToUser()) {
@@ -762,7 +766,7 @@ public class ContinuousEffects implements Serializable {
do {
Map> rEffects = getApplicableReplacementEffects(event, game);
// Remove all consumed effects (ability dependant)
- for (Iterator it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
+ for (Iterator it1 = rEffects.keySet().iterator(); it1.hasNext();) {
ReplacementEffect entry = it1.next();
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
Set consumedAbilitiesIds = consumed.get(entry.getId());
@@ -953,7 +957,7 @@ public class ContinuousEffects implements Serializable {
if (!waitingEffects.isEmpty()) {
// check if waiting effects can be applied now
- for (Iterator>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
+ for (Iterator>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
Map.Entry> entry = iterator.next();
if (appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
appliedAbilities = appliedEffectAbilities.get(entry.getKey());
diff --git a/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java
index 5575a214741..182b6e583f3 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/ClashEffect.java
@@ -1,7 +1,5 @@
-
package mage.abilities.effects.common;
-import java.io.ObjectStreamException;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@@ -20,6 +18,8 @@ import mage.players.PlayerList;
import mage.target.Target;
import mage.target.common.TargetOpponent;
+import java.io.ObjectStreamException;
+
/**
* 1. The controller of the spell or ability chooses an opponent. (This doesn't
* target the opponent.) 2. Each player involved in the clash reveals the top
@@ -36,7 +36,7 @@ import mage.target.common.TargetOpponent;
* 7. The clash spell or ability finishes resolving. That usually involves a
* bonus gained by the controller of the clash spell or ability if they won
* the clash. 8. Abilities that triggered during the clash are put on the stack.
- *
+ *
* There are no draws or losses in a clash. Either you win it or you don't. Each
* spell or ability with clash says what happens if you (the controller of that
* spell or ability) win the clash. Typically, if you don't win the clash,
@@ -148,7 +148,7 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
if (cardOpponent != null && current.getId().equals(opponent.getId())) {
topOpponent = current.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
}
- nextPlayer = playerList.getNext(game);
+ nextPlayer = playerList.getNext(game, false);
} while (nextPlayer != null && !nextPlayer.getId().equals(game.getActivePlayerId()));
// put the cards back to library
if (cardController != null) {
diff --git a/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java
index 3841ff8c3fb..dccf10bc179 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/CopySpellForEachItCouldTargetEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common;
import mage.MageItem;
@@ -11,6 +10,7 @@ import mage.filter.FilterImpl;
import mage.filter.FilterInPlay;
import mage.filter.predicate.mageobject.FromSetPredicate;
import mage.game.Game;
+import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.Target;
@@ -29,7 +29,8 @@ public abstract class CopySpellForEachItCouldTargetEffect ex
public CopySpellForEachItCouldTargetEffect(FilterInPlay filter) {
super(Outcome.Copy);
- this.staticText = "copy the spell for each other " + filter.getMessage() + " that spell could target. Each copy targets a different one";
+ this.staticText = "copy the spell for each other " + filter.getMessage()
+ + " that spell could target. Each copy targets a different one";
this.filter = filter;
}
@@ -67,7 +68,8 @@ public abstract class CopySpellForEachItCouldTargetEffect ex
for (TargetAddress addr : TargetAddress.walk(spell)) {
Target targetInstance = addr.getTarget(spell);
if (targetInstance.getNumberOfTargets() > 1) {
- throw new UnsupportedOperationException("Changing Target instances with multiple targets is unsupported");
+ throw new UnsupportedOperationException("Changing Target instances "
+ + "with multiple targets is unsupported");
}
if (changeTarget(targetInstance, game, source)) {
targetsToBeChanged.add(addr);
@@ -142,25 +144,28 @@ public abstract class CopySpellForEachItCouldTargetEffect ex
FilterInPlay setFilter = filter.copy();
setFilter.add(new FromSetPredicate(targetCopyMap.keySet()));
Target target = new TargetWithAdditionalFilter(sampleTarget, setFilter);
+ target.setNotTarget(false); // it is targeted, not chosen
target.setMinNumberOfTargets(0);
target.setMaxNumberOfTargets(1);
- target.setTargetName(filter.getMessage() + " that " + spell.getLogName() + " could target (" + targetCopyMap.size() + " remaining)");
-
+ target.setTargetName(filter.getMessage() + " that " + spell.getLogName()
+ + " could target (" + targetCopyMap.size() + " remaining)");
// shortcut if there's only one possible target remaining
if (targetCopyMap.size() > 1
&& target.canChoose(spell.getId(), player.getId(), game)) {
- player.choose(Outcome.Neutral, target, spell.getId(), game);
+ // The original "source" is not applicable here due to the spell being a copy. ie: Zada, Hedron Grinder
+ player.chooseTarget(Outcome.Neutral, target, spell.getSpellAbility(), game); // not source, but the spell that is copied
}
Collection chosenIds = target.getTargets();
if (chosenIds.isEmpty()) {
chosenIds = targetCopyMap.keySet();
}
-
List toDelete = new ArrayList<>();
for (UUID chosenId : chosenIds) {
Spell chosenCopy = targetCopyMap.get(chosenId);
if (chosenCopy != null) {
game.getStack().push(chosenCopy);
+ game.fireEvent(new GameEvent(GameEvent.EventType.COPIED_STACKOBJECT,
+ chosenCopy.getId(), spell.getId(), source.getControllerId()));
toDelete.add(chosenId);
madeACopy = true;
}
@@ -254,8 +259,8 @@ class TargetWithAdditionalFilter extends TargetImpl {
}
@Override
- public int getMaxNumberOfTargets() {
- return originalTarget.getMaxNumberOfTargets();
+ public int getMinNumberOfTargets() {
+ return originalTarget.getMinNumberOfTargets();
}
@Override
@@ -263,6 +268,11 @@ class TargetWithAdditionalFilter extends TargetImpl {
originalTarget.setMinNumberOfTargets(minNumberOfTargets);
}
+ @Override
+ public int getMaxNumberOfTargets() {
+ return originalTarget.getMaxNumberOfTargets();
+ }
+
@Override
public void setMaxNumberOfTargets(int maxNumberOfTargets) {
originalTarget.setMaxNumberOfTargets(maxNumberOfTargets);
@@ -323,7 +333,8 @@ class TargetWithAdditionalFilter extends TargetImpl {
@Override
public FilterInPlay getFilter() {
- return new CompoundFilter((FilterInPlay) originalTarget.getFilter(), additionalFilter, originalTarget.getFilter().getMessage());
+ return new CompoundFilter((FilterInPlay) originalTarget.getFilter(),
+ additionalFilter, originalTarget.getFilter().getMessage());
}
@Override
diff --git a/Mage/src/main/java/mage/abilities/effects/common/CouncilsDilemmaVoteEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CouncilsDilemmaVoteEffect.java
index 27b66c74672..e1630720175 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/CouncilsDilemmaVoteEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/CouncilsDilemmaVoteEffect.java
@@ -1,14 +1,12 @@
-
-
package mage.abilities.effects.common;
+import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
-
-import java.util.UUID;
+import mage.players.Players;
/**
* @author JRHerlehy
@@ -16,6 +14,8 @@ import java.util.UUID;
public abstract class CouncilsDilemmaVoteEffect extends OneShotEffect {
protected int voteOneCount = 0, voteTwoCount = 0;
+ protected final Players choiceOneVoters = new Players();
+ protected final Players choiceTwoVoters = new Players();
public CouncilsDilemmaVoteEffect(Outcome outcome) {
super(outcome);
@@ -29,11 +29,15 @@ public abstract class CouncilsDilemmaVoteEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
- if (player.chooseUse(Outcome.Vote, "Choose " + choiceOne + '?', source, game)) {
+ if (player.chooseUse(Outcome.Vote,
+ "Choose " + choiceOne + " or " + choiceTwo + "?",
+ source.getRule(), choiceOne, choiceTwo, source, game)) {
voteOneCount++;
+ choiceOneVoters.addPlayer(player);
game.informPlayers(player.getName() + " has voted for " + choiceOne);
} else {
voteTwoCount++;
+ choiceTwoVoters.addPlayer(player);
game.informPlayers(player.getName() + " has voted for " + choiceTwo);
}
}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java
index 7618dbd63bd..acc93941dca 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/CreateTokenCopyTargetEffect.java
@@ -7,7 +7,6 @@ import mage.abilities.Mode;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
-import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
@@ -136,6 +135,8 @@ public class CreateTokenCopyTargetEffect extends OneShotEffect {
} else {
permanent = game.getPermanentOrLKIBattlefield(targetId);
}
+
+ // can target card or permanent
Card copyFrom;
ApplyToPermanent applier = new EmptyApplyToPermanent();
if (permanent != null) {
diff --git a/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java
new file mode 100644
index 00000000000..89ad63e0abb
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/effects/common/ExileAdventureSpellEffect.java
@@ -0,0 +1,107 @@
+package mage.abilities.effects.common;
+
+import mage.abilities.Ability;
+import mage.abilities.MageSingleton;
+import mage.abilities.effects.AsThoughEffectImpl;
+import mage.abilities.effects.ContinuousEffect;
+import mage.abilities.effects.OneShotEffect;
+import mage.cards.AdventureCardSpell;
+import mage.cards.Card;
+import mage.constants.AsThoughEffectType;
+import mage.constants.Duration;
+import mage.constants.Outcome;
+import mage.game.ExileZone;
+import mage.game.Game;
+import mage.game.stack.Spell;
+import mage.players.Player;
+import mage.target.targetpointer.FixedTarget;
+import mage.util.CardUtil;
+
+import java.util.UUID;
+
+/**
+ * @author phulin
+ */
+public class ExileAdventureSpellEffect extends OneShotEffect implements MageSingleton {
+
+ private static final ExileAdventureSpellEffect instance = new ExileAdventureSpellEffect();
+
+ public static ExileAdventureSpellEffect getInstance() {
+ return instance;
+ }
+
+ public static UUID adventureExileId(UUID controllerId, Game game) {
+ return CardUtil.getExileZoneId(controllerId.toString() + "- On an Adventure", game);
+ }
+
+ private ExileAdventureSpellEffect() {
+ super(Outcome.Exile);
+ staticText = "";
+ }
+
+ @Override
+ public ExileAdventureSpellEffect copy() {
+ return instance;
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Player controller = game.getPlayer(source.getControllerId());
+ if (controller != null) {
+ Spell spell = game.getStack().getSpell(source.getId());
+ if (spell != null && !spell.isCopy()) {
+ Card spellCard = spell.getCard();
+ if (spellCard instanceof AdventureCardSpell) {
+ UUID exileId = adventureExileId(controller.getId(), game);
+ game.getExile().createZone(exileId, "On an Adventure");
+ AdventureCardSpell adventureSpellCard = (AdventureCardSpell) spellCard;
+ Card parentCard = adventureSpellCard.getParentCard();
+ if (controller.moveCardsToExile(parentCard, source, game, true, exileId, "On an Adventure")) {
+ ContinuousEffect effect = new AdventureCastFromExileEffect();
+ effect.setTargetPointer(new FixedTarget(parentCard.getId(), game));
+ game.addEffect(effect, source);
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
+
+class AdventureCastFromExileEffect extends AsThoughEffectImpl {
+
+ public AdventureCastFromExileEffect() {
+ super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.Custom, Outcome.Benefit);
+ staticText = "Then exile this card. You may cast the creature later from exile.";
+ }
+
+ public AdventureCastFromExileEffect(final AdventureCastFromExileEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ return true;
+ }
+
+ @Override
+ public AdventureCastFromExileEffect copy() {
+ return new AdventureCastFromExileEffect(this);
+ }
+
+ @Override
+ public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
+ UUID targetId = getTargetPointer().getFirst(game, source);
+ ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(affectedControllerId, game));
+ if (targetId == null) {
+ this.discard();
+ } else if (objectId.equals(targetId)
+ && affectedControllerId.equals(source.getControllerId())
+ && adventureExileZone.contains(objectId)) {
+ Card card = game.getCard(objectId);
+ return card != null;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageToAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageToAllEffect.java
index 93394e11808..b0d619a381e 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageToAllEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/PreventAllDamageToAllEffect.java
@@ -1,44 +1,46 @@
-
-
package mage.abilities.effects.common;
-import java.util.UUID;
+import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
-import mage.filter.FilterInPlay;
-import mage.filter.common.FilterCreatureOrPlayer;
-import mage.filter.common.FilterCreaturePermanent;
+import mage.filter.FilterPermanent;
+import mage.filter.FilterPlayer;
+import mage.filter.common.FilterPermanentOrPlayer;
import mage.filter.predicate.other.PlayerIdPredicate;
+import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
-import mage.game.permanent.Permanent;
-import mage.players.Player;
+
+import java.util.UUID;
/**
- *
* @author BetaSteward_at_googlemail.com
*/
public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
- protected FilterInPlay filter;
-
- public PreventAllDamageToAllEffect(Duration duration, FilterCreaturePermanent filter) {
- this(duration, createFilter(filter));
+ protected FilterPermanentOrPlayer filter;
+
+ public PreventAllDamageToAllEffect(Duration duration, FilterPermanent filterPermanent) {
+ this(duration, createFilter(filterPermanent, null));
}
- public PreventAllDamageToAllEffect(Duration duration, FilterInPlay filter) {
+ public PreventAllDamageToAllEffect(Duration duration, FilterPermanent filterPermanent, boolean onlyCombat) {
+ this(duration, createFilter(filterPermanent, null), onlyCombat);
+ }
+
+ public PreventAllDamageToAllEffect(Duration duration, FilterPermanentOrPlayer filter) {
this(duration, filter, false);
}
- public PreventAllDamageToAllEffect(Duration duration, FilterInPlay filter, boolean onlyCombat) {
+ public PreventAllDamageToAllEffect(Duration duration, FilterPermanentOrPlayer filter, boolean onlyCombat) {
super(duration, Integer.MAX_VALUE, onlyCombat);
this.filter = filter;
staticText = "Prevent all "
- + (onlyCombat ? "combat ":"")
- + "damage that would be dealt to "
+ + (onlyCombat ? "combat " : "")
+ + "damage that would be dealt to "
+ filter.getMessage()
- + (duration.toString().isEmpty() ?"": ' ' + duration.toString());
+ + (duration.toString().isEmpty() ? "" : ' ' + duration.toString());
}
public PreventAllDamageToAllEffect(final PreventAllDamageToAllEffect effect) {
@@ -46,13 +48,25 @@ public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
this.filter = effect.filter.copy();
}
- private static FilterInPlay createFilter(FilterCreaturePermanent filter) {
- FilterCreatureOrPlayer newfilter = new FilterCreatureOrPlayer(filter.getMessage());
- newfilter.setCreatureFilter(filter);
- newfilter.getPlayerFilter().add(new PlayerIdPredicate(UUID.randomUUID()));
- return newfilter;
+ private static FilterPermanentOrPlayer createFilter(FilterPermanent filterPermanent, FilterPlayer filterPlayer) {
+ String message = String.join(
+ " and ",
+ filterPermanent != null ? filterPermanent.getMessage() : "",
+ filterPlayer != null ? filterPlayer.getMessage() : "");
+ FilterPermanent filter1 = filterPermanent;
+ if (filter1 == null) {
+ filter1 = new FilterPermanent();
+ filter1.add(new PermanentIdPredicate(UUID.randomUUID())); // disable filter
+ }
+ FilterPlayer filter2 = filterPlayer;
+ if (filter2 == null) {
+ filter2 = new FilterPlayer();
+ filter2.add(new PlayerIdPredicate(UUID.randomUUID())); // disable filter
+ }
+
+ return new FilterPermanentOrPlayer(message, filter1, filter2);
}
-
+
@Override
public PreventAllDamageToAllEffect copy() {
return new PreventAllDamageToAllEffect(this);
@@ -66,17 +80,9 @@ public class PreventAllDamageToAllEffect extends PreventionEffectImpl {
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
if (super.applies(event, source, game)) {
- Permanent permanent = game.getPermanent(event.getTargetId());
- if (permanent != null) {
- if (filter.match(permanent, source.getSourceId(), source.getControllerId(), game)) {
- return true;
- }
- }
- else {
- Player player = game.getPlayer(event.getTargetId());
- if (player != null && filter.match(player, source.getSourceId(), source.getControllerId(), game)) {
- return true;
- }
+ MageObject object = game.getObject(event.getTargetId());
+ if (object != null) {
+ return filter.match(object, source.getSourceId(), source.getControllerId(), game);
}
}
return false;
diff --git a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetMultiAmountEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetMultiAmountEffect.java
index 0e832aeac6e..7aa79be735a 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetMultiAmountEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/PreventDamageToTargetMultiAmountEffect.java
@@ -1,8 +1,5 @@
package mage.abilities.effects.common;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.Mode;
@@ -10,14 +7,19 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.PreventionEffectImpl;
import mage.constants.Duration;
import mage.game.Game;
+import mage.game.events.DamageEvent;
import mage.game.events.GameEvent;
+import mage.game.events.PreventDamageEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetAmount;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
/**
- *
* @author LevelX2
*/
public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl {
@@ -77,7 +79,7 @@ public class PreventDamageToTargetMultiAmountEffect extends PreventionEffectImpl
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
int targetAmount = targetAmountMap.get(event.getTargetId());
- GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, event.getTargetId(), source.getSourceId(), source.getControllerId(), event.getAmount(), false);
+ GameEvent preventEvent = new PreventDamageEvent(event.getTargetId(), source.getSourceId(), source.getControllerId(), event.getAmount(), ((DamageEvent) event).isCombatDamage());
if (!game.replaceEvent(preventEvent)) {
if (event.getAmount() >= targetAmount) {
int damage = targetAmount;
diff --git a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java
index 51252895a9d..574c005917f 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/ReturnToHandTargetEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common;
import java.util.ArrayList;
@@ -68,9 +67,10 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
for (UUID targetId : targetPointer.getTargets(game, source)) {
MageObject mageObject = game.getObject(targetId);
if (mageObject != null) {
- if (mageObject instanceof Spell && mageObject.isCopy()) {
+ if (mageObject instanceof Spell
+ && mageObject.isCopy()) {
copyIds.add(targetId);
- } else {
+ } else if (mageObject instanceof Card) {
cards.add((Card) mageObject);
}
}
@@ -83,7 +83,8 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
}
@Override
- public String getText(Mode mode) {
+ public String getText(Mode mode
+ ) {
if (staticText != null && !staticText.isEmpty()) {
return staticText;
}
@@ -93,7 +94,8 @@ public class ReturnToHandTargetEffect extends OneShotEffect {
Target target = mode.getTargets().get(0);
StringBuilder sb = new StringBuilder("return ");
if (target.getNumberOfTargets() == 0 && target.getMaxNumberOfTargets() > 0) {
- sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(" target ").append(target.getTargetName()).append(" to their owners' hand");
+ sb.append("up to ").append(CardUtil.numberToText(target.getMaxNumberOfTargets())).append(" target ")
+ .append(target.getTargetName()).append(" to their owners' hand");
return sb.toString();
} else {
if (target.getNumberOfTargets() > 1) {
diff --git a/Mage/src/main/java/mage/abilities/effects/common/RevealLibraryPutIntoHandEffect.java b/Mage/src/main/java/mage/abilities/effects/common/RevealLibraryPutIntoHandEffect.java
index 7272cc71dee..f5243a07733 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/RevealLibraryPutIntoHandEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/RevealLibraryPutIntoHandEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common;
import java.util.Set;
@@ -73,7 +72,7 @@ public class RevealLibraryPutIntoHandEffect extends OneShotEffect {
Set cardsList = cards.getCards(game);
Cards cardsToHand = new CardsImpl();
for (Card card : cardsList) {
- if (filter.match(card, game)) {
+ if (filter.match(card, source.getSourceId(), controller.getId(), game)) {
cardsToHand.add(card);
cards.remove(card);
}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java b/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java
index 62f74e35980..074459a3850 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/WishEffect.java
@@ -1,8 +1,5 @@
-
package mage.abilities.effects.common;
-import java.util.List;
-import java.util.Set;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
@@ -16,8 +13,10 @@ import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
+import java.util.List;
+import java.util.Set;
+
/**
- *
* @author Styxo
*/
public class WishEffect extends OneShotEffect {
@@ -73,7 +72,7 @@ public class WishEffect extends OneShotEffect {
if (controller.chooseUse(Outcome.Benefit, choiceText, source, game)) {
Cards cards = controller.getSideboard();
List exile = game.getExile().getAllCards(game);
- boolean noTargets = cards.isEmpty() && (alsoFromExile ? exile.isEmpty() : true);
+ boolean noTargets = cards.isEmpty() && (!alsoFromExile || exile.isEmpty());
if (noTargets) {
game.informPlayer(controller, "You have no cards outside the game" + (alsoFromExile ? " or in exile" : "") + '.');
return true;
@@ -96,7 +95,7 @@ public class WishEffect extends OneShotEffect {
return true;
}
- TargetCard target = new TargetCard(Zone.OUTSIDE, filter);
+ TargetCard target = new TargetCard(Zone.ALL, filter);
target.setNotTarget(true);
if (controller.choose(Outcome.Benefit, filteredCards, target, game)) {
Card card = controller.getSideboard().get(target.getFirstTarget(), game);
diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java
index 4901a687ec1..8729a75af15 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/CantCastMoreThanOneSpellEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java
index 129c58657ac..c418dadd407 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainControlTargetEffect.java
@@ -2,6 +2,7 @@ package mage.abilities.effects.common.continuous;
import java.util.UUID;
import mage.abilities.Ability;
+import mage.abilities.ActivatedAbility;
import mage.abilities.Mode;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
@@ -23,6 +24,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
protected UUID controllingPlayerId;
private boolean fixedControl;
+ private boolean firstControlChange = true;
public GainControlTargetEffect(Duration duration) {
this(duration, false, null);
@@ -77,31 +79,44 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
- boolean targetStillExists = false;
+ boolean oneTargetStillExists = false;
for (UUID permanentId : getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
- targetStillExists = true;
+ oneTargetStillExists = true;
if (!permanent.isControlledBy(controllingPlayerId)) {
GameEvent loseControlEvent = GameEvent.getEvent(GameEvent.EventType.LOSE_CONTROL, permanentId, source.getId(), permanent.getControllerId());
if (game.replaceEvent(loseControlEvent)) {
return false;
}
+ boolean controlChanged = false;
if (controllingPlayerId != null) {
- permanent.changeControllerId(controllingPlayerId, game);
- permanent.getAbilities().setControllerId(controllingPlayerId);
+ if (permanent.changeControllerId(controllingPlayerId, game)) {
+ permanent.getAbilities().setControllerId(controllingPlayerId);
+ controlChanged = true;
+ }
} else {
- permanent.changeControllerId(source.getControllerId(), game);
- permanent.getAbilities().setControllerId(source.getControllerId());
+ if (permanent.changeControllerId(source.getControllerId(), game)) {
+ permanent.getAbilities().setControllerId(source.getControllerId());
+ controlChanged = true;
+ }
+ }
+ if (source instanceof ActivatedAbility
+ && firstControlChange && !controlChanged) {
+ // If it was not possible to get control of target permanent by the activated ability the first time it took place
+ // the effect failed (e.g. because of Guardian Beast) and must be discarded
+ // This does not handle correctly multiple targets at once
+ discard();
}
}
}
}
// no valid target exists and the controller is no longer in the game, effect can be discarded
- if (!targetStillExists
+ if (!oneTargetStillExists
|| !controller.isInGame()) {
discard();
}
+ firstControlChange = false;
return true;
}
discard(); // controller no longer exists
diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java
index 35e63cc3619..2d369f0f4d8 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/PlayTheTopCardEffect.java
@@ -1,7 +1,5 @@
-
package mage.abilities.effects.common.continuous;
-import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.cards.Card;
@@ -11,6 +9,9 @@ import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.players.Player;
+import mage.util.CardUtil;
+
+import java.util.UUID;
/**
* @author nantuko
@@ -47,16 +48,23 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
- Card cardOnTop = game.getCard(objectId);
- if (cardOnTop != null
- && affectedControllerId.equals(source.getControllerId())
- && cardOnTop.isOwnedBy(source.getControllerId())
- && (!cardOnTop.getManaCost().isEmpty() || cardOnTop.isLand())
- && filter.match(cardOnTop, game)) {
- Player player = game.getPlayer(cardOnTop.getOwnerId());
- if (player != null && cardOnTop.equals(player.getLibrary().getFromTop(game))) {
- return true;
- }
+ return applies(objectId, null, source, game, affectedControllerId);
+ }
+
+ @Override
+ public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
+ Card cardToCheck = game.getCard(objectId);
+ objectId = CardUtil.getMainCardId(game, objectId); // for split cards
+
+ if (cardToCheck != null
+ && playerId.equals(source.getControllerId())
+ && cardToCheck.isOwnedBy(source.getControllerId())
+ && (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
+ && filter.match(cardToCheck, game)) {
+ Player player = game.getPlayer(cardToCheck.getOwnerId());
+
+ UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
+ return objectId.equals(needCardID);
}
return false;
}
diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java
index 4947acadf22..3c79a578252 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionAllEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
@@ -6,7 +5,6 @@ import java.util.Set;
import java.util.UUID;
import mage.Mana;
import mage.abilities.Ability;
-import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.cards.Card;
import mage.choices.ChoiceImpl;
@@ -70,11 +68,9 @@ public class SpellsCostReductionAllEffect extends CostModificationEffectImpl {
@Override
public boolean apply(Game game, Ability source, Ability abilityToModify) {
if (upTo) {
- if (abilityToModify instanceof ActivatedAbility) {
- if (((ActivatedAbility) abilityToModify).isCheckPlayableMode()) {
- CardUtil.reduceCost(abilityToModify, this.amount);
- return true;
- }
+ if (game.inCheckPlayableState()) {
+ CardUtil.reduceCost(abilityToModify, this.amount);
+ return true;
}
Mana mana = abilityToModify.getManaCostsToPay().getMana();
int reduceMax = mana.getGeneric();
diff --git a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java
index a58c1a2cb0f..b4d57f89812 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/cost/SpellsCostReductionControllerEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common.cost;
import java.util.LinkedHashSet;
@@ -6,7 +5,6 @@ import java.util.Set;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
-import mage.abilities.ActivatedAbility;
import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
@@ -85,7 +83,7 @@ public class SpellsCostReductionControllerEffect extends CostModificationEffectI
return false;
}
int reduce = reduceMax;
- if (!(abilityToModify instanceof ActivatedAbility) || !((ActivatedAbility) abilityToModify).isCheckPlayableMode()) {
+ if (!game.inCheckPlayableState()) {
ChoiceImpl choice = new ChoiceImpl(false);
Set set = new LinkedHashSet<>();
for (int i = 0; i <= amount; i++) {
diff --git a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java
index 64c7abd88e5..1b5ba2047d6 100644
--- a/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java
+++ b/Mage/src/main/java/mage/abilities/effects/common/counter/AddCountersSourceEffect.java
@@ -1,4 +1,3 @@
-
package mage.abilities.effects.common.counter;
import java.util.ArrayList;
@@ -96,7 +95,9 @@ public class AddCountersSourceEffect extends OneShotEffect {
if (permanent == null && source.getAbilityType() == AbilityType.STATIC) {
permanent = game.getPermanentEntering(source.getSourceId());
}
- if (permanent != null) {
+ if (permanent != null
+ && (source.getSourceObjectZoneChangeCounter() == 0 // from static ability
+ || source.getSourceObjectZoneChangeCounter() == permanent.getZoneChangeCounter(game))) { // prevent to add counters to later source objects
if (counter != null) {
Counter newCounter = counter.copy();
int countersToAdd = amount.calculate(game, source, this);
diff --git a/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java b/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java
new file mode 100644
index 00000000000..78961979a87
--- /dev/null
+++ b/Mage/src/main/java/mage/abilities/keyword/EscapeAbility.java
@@ -0,0 +1,63 @@
+package mage.abilities.keyword;
+
+import mage.abilities.SpellAbility;
+import mage.abilities.costs.Cost;
+import mage.abilities.costs.common.ExileFromGraveCost;
+import mage.abilities.costs.mana.ManaCostsImpl;
+import mage.cards.Card;
+import mage.constants.SpellAbilityType;
+import mage.constants.Zone;
+import mage.filter.FilterCard;
+import mage.filter.predicate.permanent.AnotherPredicate;
+import mage.target.common.TargetCardInYourGraveyard;
+import mage.util.CardUtil;
+
+/**
+ * @author TheElk801
+ */
+public class EscapeAbility extends SpellAbility {
+
+ private static final FilterCard filter = new FilterCard();
+
+ static {
+ filter.add(AnotherPredicate.instance);
+ }
+
+ private final String manaCost;
+ private final int exileCount;
+
+ public EscapeAbility(Card card, String manaCost, int exileCount) {
+ super(new ManaCostsImpl(manaCost), card.getName() + " with escape");
+ this.newId();
+ this.zone = Zone.GRAVEYARD;
+ this.spellAbilityType = SpellAbilityType.BASE_ALTERNATE;
+ this.manaCost = manaCost;
+ this.exileCount = exileCount;
+
+ Cost cost = new ExileFromGraveCost(new TargetCardInYourGraveyard(exileCount, filter));
+ cost.setText("");
+ this.addCost(cost);
+ }
+
+ private EscapeAbility(final EscapeAbility ability) {
+ super(ability);
+ this.manaCost = ability.manaCost;
+ this.exileCount = ability.exileCount;
+ }
+
+ @Override
+ public EscapeAbility copy() {
+ return new EscapeAbility(this);
+ }
+
+ @Override
+ public String getRule(boolean all) {
+ return getRule();
+ }
+
+ @Override
+ public String getRule() {
+ return "Escape — " + this.manaCost + ", Exile " + CardUtil.numberToText(this.exileCount) +
+ " other cards from your graveyard. (You may cast this card from your graveyard for its escape cost.)";
+ }
+}
diff --git a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java
index 8d1906594fc..a99f166e0bc 100644
--- a/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/HideawayAbility.java
@@ -1,5 +1,6 @@
package mage.abilities.keyword;
+import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.StaticAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
@@ -23,8 +24,6 @@ import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
-import java.util.UUID;
-
/**
* @author LevelX2
*
@@ -100,6 +99,7 @@ class HideawayExileEffect extends OneShotEffect {
cards.addAll(controller.getLibrary().getTopCards(game, 4));
if (!cards.isEmpty()) {
TargetCard target1 = new TargetCard(Zone.LIBRARY, filter1);
+ target1.setNotTarget(true);
if (controller.choose(Outcome.Detriment, cards, target1, game)) {
Card card = cards.get(target1.getFirstTarget(), game);
if (card != null) {
@@ -121,7 +121,7 @@ class HideawayLookAtFaceDownCardEffect extends AsThoughEffectImpl {
public HideawayLookAtFaceDownCardEffect() {
super(AsThoughEffectType.LOOK_AT_FACE_DOWN, Duration.EndOfGame, Outcome.Benefit);
- staticText = "You may look at cards exiled with {this}";
+ staticText = "You may look at the cards exiled with {this}";
}
private HideawayLookAtFaceDownCardEffect(final HideawayLookAtFaceDownCardEffect effect) {
diff --git a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java
index c6c26c43cdd..e4cf7c34a0a 100644
--- a/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/OfferingAbility.java
@@ -1,5 +1,6 @@
package mage.abilities.keyword;
+import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
@@ -17,8 +18,6 @@ import mage.target.Target;
import mage.target.common.TargetControlledCreaturePermanent;
import mage.util.CardUtil;
-import java.util.UUID;
-
/**
* 702.46. Offering # 702.46a Offering is a static ability of a card that
* functions in any zone from which the card can be cast. "[Subtype] offering"
@@ -121,7 +120,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
if (game.getBattlefield().count(((OfferingAbility) source).getFilter(), source.getSourceId(), source.getControllerId(), game) > 0) {
- if (CardUtil.isCheckPlayableMode(affectedAbility)) {
+ if (game.inCheckPlayableState()) {
return true;
}
FilterControlledCreaturePermanent filter = ((OfferingAbility) source).getFilter();
@@ -130,7 +129,7 @@ class OfferingAsThoughEffect extends AsThoughEffectImpl {
return false;
}
Player player = game.getPlayer(source.getControllerId());
- if (player != null && !CardUtil.isCheckPlayableMode(affectedAbility)
+ if (player != null && !game.inCheckPlayableState()
&& player.chooseUse(Outcome.Benefit, "Offer a " + filter.getMessage() + " to cast " + spellToCast.getName() + '?', source, game)) {
Target target = new TargetControlledCreaturePermanent(1, 1, filter, true);
player.chooseTarget(Outcome.Sacrifice, target, source, game);
@@ -193,7 +192,7 @@ class OfferingCostReductionEffect extends CostModificationEffectImpl {
@Override
public boolean applies(Ability abilityToModify, Ability source, Game game) {
- if (CardUtil.isCheckPlayableMode(abilityToModify)) { // Cost modifaction does not work correctly for checking available spells
+ if (game.inCheckPlayableState()) { // Cost modifaction does not work correctly for checking available spells
return false;
}
if (abilityToModify.getId().equals(spellAbilityId) && abilityToModify instanceof SpellAbility) {
diff --git a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java
index 3f0c3037ddc..48de601e55e 100644
--- a/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/ReplicateAbility.java
@@ -1,16 +1,10 @@
-
package mage.abilities.keyword;
-import java.util.Iterator;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.TriggeredAbilityImpl;
-import mage.abilities.costs.Cost;
-import mage.abilities.costs.Costs;
-import mage.abilities.costs.OptionalAdditionalCost;
-import mage.abilities.costs.OptionalAdditionalCostImpl;
-import mage.abilities.costs.OptionalAdditionalSourceCosts;
+import mage.abilities.costs.*;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
@@ -23,8 +17,9 @@ import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
+import java.util.Iterator;
+
/**
- *
* @author LevelX2
*/
public class ReplicateAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
@@ -91,12 +86,12 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
String times = "";
if (additionalCost.isRepeatable()) {
int numActivations = additionalCost.getActivateCount();
- times = Integer.toString(numActivations + 1) + (numActivations == 0 ? " time " : " times ");
+ times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
}
if (additionalCost.canPay(ability, sourceId, controllerId, game)
&& player.chooseUse(Outcome.Benefit, new StringBuilder("Pay ").append(times).append(additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
additionalCost.activate();
- for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext();) {
+ for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
@@ -170,7 +165,7 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
- if (((ReplicateAbility) ability).isActivated()) {
+ if (ability.isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ReplicateSpell", spell);
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
@@ -213,7 +208,7 @@ class ReplicateCopyEffect extends OneShotEffect {
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
- if (((ReplicateAbility) ability).isActivated()) {
+ if (ability.isActivated()) {
((ReplicateAbility) ability).resetReplicate();
}
}
diff --git a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java
index 8408cd0fc3f..fb90104d2d1 100644
--- a/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java
+++ b/Mage/src/main/java/mage/abilities/keyword/TransformAbility.java
@@ -1,4 +1,3 @@
-
package mage.abilities.keyword;
import mage.abilities.Ability;
@@ -66,8 +65,8 @@ public class TransformAbility extends SimpleStaticAbility {
for (Ability ability : sourceCard.getAbilities()) {
permanent.addAbility(ability, game);
}
- permanent.getPower().setValue(sourceCard.getPower().getValue());
- permanent.getToughness().setValue(sourceCard.getToughness().getValue());
+ permanent.getPower().modifyBaseValue(sourceCard.getPower().getValue());
+ permanent.getToughness().modifyBaseValue(sourceCard.getToughness().getValue());
permanent.setTransformable(sourceCard.isTransformable());
}
}
diff --git a/Mage/src/main/java/mage/cards/AdventureCard.java b/Mage/src/main/java/mage/cards/AdventureCard.java
index af213591bb5..d100ae27705 100644
--- a/Mage/src/main/java/mage/cards/AdventureCard.java
+++ b/Mage/src/main/java/mage/cards/AdventureCard.java
@@ -1,8 +1,14 @@
package mage.cards;
+import mage.abilities.Abilities;
+import mage.abilities.AbilitiesImpl;
+import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.constants.CardType;
+import mage.constants.Zone;
+import mage.game.Game;
+import java.util.List;
import java.util.UUID;
/**
@@ -10,17 +16,92 @@ import java.util.UUID;
*/
public abstract class AdventureCard extends CardImpl {
- protected SpellAbility adventureSpellAbility = new SpellAbility(null, null);
+ /* The adventure spell card, i.e. Swift End. */
+ protected Card spellCard;
- public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] typesLeft, CardType[] typesRight, String costsLeft, String adventureName, String costsRight) {
- super(ownerId, setInfo, typesLeft, costsLeft);
+ public AdventureCard(UUID ownerId, CardSetInfo setInfo, CardType[] types, CardType[] typesSpell, String costs, String adventureName, String costsSpell) {
+ super(ownerId, setInfo, types, costs);
+ this.spellCard = new AdventureCardSpellImpl(ownerId, setInfo, adventureName, typesSpell, costsSpell, this);
}
public AdventureCard(AdventureCard card) {
super(card);
+ this.spellCard = card.getSpellCard().copy();
+ ((AdventureCardSpell) this.spellCard).setParentCard(this);
}
- public SpellAbility getAdventureSpellAbility() {
- return adventureSpellAbility;
+ public Card getSpellCard() {
+ return spellCard;
+ }
+
+ @Override
+ public void assignNewId() {
+ super.assignNewId();
+ spellCard.assignNewId();
+ }
+
+ @Override
+ public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List appliedEffects) {
+ if (super.moveToZone(toZone, sourceId, game, flag, appliedEffects)) {
+ game.getState().setZone(getSpellCard().getId(), toZone);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setZone(Zone zone, Game game) {
+ super.setZone(zone, game);
+ game.setZone(getSpellCard().getId(), zone);
+ }
+
+ @Override
+ public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List appliedEffects) {
+ if (super.moveToExile(exileId, name, sourceId, game, appliedEffects)) {
+ Zone currentZone = game.getState().getZone(getId());
+ game.getState().setZone(getSpellCard().getId(), currentZone);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean cast(Game game, Zone fromZone, SpellAbility ability, UUID controllerId) {
+ switch (ability.getSpellAbilityType()) {
+ case ADVENTURE_SPELL:
+ return this.getSpellCard().cast(game, fromZone, ability, controllerId);
+ default:
+ this.getSpellCard().getSpellAbility().setControllerId(controllerId);
+ return super.cast(game, fromZone, ability, controllerId);
+ }
+ }
+
+ @Override
+ public Abilities getAbilities() {
+ Abilities allAbilities = new AbilitiesImpl<>();
+ allAbilities.addAll(spellCard.getAbilities());
+ allAbilities.addAll(super.getAbilities());
+ return allAbilities;
+ }
+
+ @Override
+ public Abilities getAbilities(Game game) {
+ Abilities allAbilities = new AbilitiesImpl<>();
+ allAbilities.addAll(spellCard.getAbilities(game));
+ allAbilities.addAll(super.getAbilities(game));
+ return allAbilities;
+ }
+
+ public Abilities getSharedAbilities() {
+ // abilities without spellcard
+ return super.getAbilities();
+ }
+
+ @Override
+ public void setOwnerId(UUID ownerId) {
+ super.setOwnerId(ownerId);
+ abilities.setControllerId(ownerId);
+ spellCard.getAbilities().setControllerId(ownerId);
+ spellCard.setOwnerId(ownerId);
}
}
diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpell.java b/Mage/src/main/java/mage/cards/AdventureCardSpell.java
new file mode 100644
index 00000000000..5b873acc8d3
--- /dev/null
+++ b/Mage/src/main/java/mage/cards/AdventureCardSpell.java
@@ -0,0 +1,20 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package mage.cards;
+
+/**
+ *
+ * @author phulin
+ */
+public interface AdventureCardSpell extends Card {
+
+ @Override
+ AdventureCardSpell copy();
+
+ void setParentCard(AdventureCard card);
+
+ AdventureCard getParentCard();
+}
diff --git a/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java
new file mode 100644
index 00000000000..f14cc4bfb58
--- /dev/null
+++ b/Mage/src/main/java/mage/cards/AdventureCardSpellImpl.java
@@ -0,0 +1,154 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package mage.cards;
+
+import mage.abilities.Modes;
+import mage.abilities.SpellAbility;
+import mage.abilities.effects.common.ExileAdventureSpellEffect;
+import mage.constants.CardType;
+import mage.constants.SpellAbilityType;
+import mage.constants.SubType;
+import mage.constants.Zone;
+import mage.game.ExileZone;
+import mage.game.Game;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author phulin
+ */
+public class AdventureCardSpellImpl extends CardImpl implements AdventureCardSpell {
+
+ private AdventureCard adventureCardParent;
+
+ public AdventureCardSpellImpl(UUID ownerId, CardSetInfo setInfo, String adventureName, CardType[] cardTypes, String costs, AdventureCard adventureCardParent) {
+ super(ownerId, setInfo, cardTypes, costs, SpellAbilityType.ADVENTURE_SPELL);
+ this.subtype.add(SubType.ADVENTURE);
+
+ AdventureCardSpellAbility newSpellAbility = new AdventureCardSpellAbility(getSpellAbility());
+ newSpellAbility.setName(adventureName, costs);
+ newSpellAbility.addEffect(ExileAdventureSpellEffect.getInstance());
+ newSpellAbility.setCardName(adventureName);
+ this.replaceSpellAbility(newSpellAbility);
+ spellAbility = newSpellAbility;
+
+ this.setName(adventureName);
+ this.adventureCardParent = adventureCardParent;
+ }
+
+ public AdventureCardSpellImpl(final AdventureCardSpellImpl card) {
+ super(card);
+ this.adventureCardParent = card.adventureCardParent;
+ }
+
+ @Override
+ public UUID getOwnerId() {
+ return adventureCardParent.getOwnerId();
+ }
+
+ @Override
+ public String getImageName() {
+ return adventureCardParent.getImageName();
+ }
+
+ @Override
+ public String getExpansionSetCode() {
+ return adventureCardParent.getExpansionSetCode();
+ }
+
+ @Override
+ public String getCardNumber() {
+ return adventureCardParent.getCardNumber();
+ }
+
+ @Override
+ public boolean moveToZone(Zone toZone, UUID sourceId, Game game, boolean flag, List appliedEffects) {
+ return adventureCardParent.moveToZone(toZone, sourceId, game, flag, appliedEffects);
+ }
+
+ @Override
+ public boolean moveToExile(UUID exileId, String name, UUID sourceId, Game game, List appliedEffects) {
+ return adventureCardParent.moveToExile(exileId, name, sourceId, game, appliedEffects);
+ }
+
+ @Override
+ public AdventureCard getMainCard() {
+ return adventureCardParent;
+ }
+
+ @Override
+ public void setZone(Zone zone, Game game) {
+ game.setZone(adventureCardParent.getId(), zone);
+ game.setZone(adventureCardParent.getSpellCard().getId(), zone);
+ }
+
+ @Override
+ public AdventureCardSpell copy() {
+ return new AdventureCardSpellImpl(this);
+ }
+
+ @Override
+ public void setParentCard(AdventureCard card) {
+ this.adventureCardParent = card;
+ }
+
+ @Override
+ public AdventureCard getParentCard() {
+ return this.adventureCardParent;
+ }
+}
+
+class AdventureCardSpellAbility extends SpellAbility {
+ public AdventureCardSpellAbility(final SpellAbility ability) {
+ super(ability);
+ }
+
+ @Override
+ public ActivationStatus canActivate(UUID playerId, Game game) {
+ ExileZone adventureExileZone = game.getExile().getExileZone(ExileAdventureSpellEffect.adventureExileId(playerId, game));
+ Card spellCard = game.getCard(this.getSourceId());
+ if (spellCard instanceof AdventureCardSpell) {
+ Card card = ((AdventureCardSpell) spellCard).getParentCard();
+ if (adventureExileZone != null && adventureExileZone.contains(card.getId())) {
+ return ActivationStatus.getFalse();
+ }
+ }
+ return super.canActivate(playerId, game);
+ }
+
+ public void setName(String name, String costs) {
+ this.name = "Adventure — " + name + " " + costs;
+ }
+
+ @Override
+ public String getRule(boolean all) {
+ return this.getRule();
+ }
+
+ @Override
+ public String getRule() {
+ StringBuilder sbRule = new StringBuilder();
+ sbRule.append("Adventure — ");
+ sbRule.append(this.getCardName());
+ sbRule.append(" ");
+ sbRule.append(manaCosts.getText());
+ sbRule.append(" — ");
+ Modes modes = this.getModes();
+ if (modes.size() <= 1) {
+ sbRule.append(modes.getMode().getEffects().getTextStartingUpperCase(modes.getMode()));
+ } else {
+ sbRule.append(getModes().getText());
+ }
+ sbRule.append(" (Then exile this card. You may cast the creature later from exile.)");
+ return sbRule.toString();
+ }
+
+ @Override
+ public SpellAbility copy() {
+ return new AdventureCardSpellAbility(this);
+ }
+}
\ No newline at end of file
diff --git a/Mage/src/main/java/mage/cards/Card.java b/Mage/src/main/java/mage/cards/Card.java
index bac8f59d403..a4ea5154a5f 100644
--- a/Mage/src/main/java/mage/cards/Card.java
+++ b/Mage/src/main/java/mage/cards/Card.java
@@ -150,7 +150,7 @@ public interface Card extends MageObject {
/**
*
- * @return The main card of a split half card, otherwise the card itself is
+ * @return The main card of a split half card or adventure spell card, otherwise the card itself is
* returned
*/
Card getMainCard();
diff --git a/Mage/src/main/java/mage/cards/CardImpl.java b/Mage/src/main/java/mage/cards/CardImpl.java
index d903a30b0b3..4648b7980a3 100644
--- a/Mage/src/main/java/mage/cards/CardImpl.java
+++ b/Mage/src/main/java/mage/cards/CardImpl.java
@@ -230,7 +230,7 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
@Override
public List getRules() {
try {
- return abilities.getRules(this.getName());
+ return getAbilities().getRules(this.getName());
} catch (Exception e) {
logger.info("Exception in rules generation for card: " + this.getName(), e);
}
@@ -505,6 +505,9 @@ public abstract class CardImpl extends MageObjectImpl implements Card {
stackObject = game.getStack().getSpell(((SplitCard) this).getRightHalfCard().getId());
}
}
+ if (stackObject == null && (this instanceof AdventureCard)) {
+ stackObject = game.getStack().getSpell(((AdventureCard) this).getSpellCard().getId());
+ }
if (stackObject == null) {
stackObject = game.getStack().getSpell(getId());
}
diff --git a/Mage/src/main/java/mage/cards/ExpansionSet.java b/Mage/src/main/java/mage/cards/ExpansionSet.java
index 6d63757c9e5..f1879539936 100644
--- a/Mage/src/main/java/mage/cards/ExpansionSet.java
+++ b/Mage/src/main/java/mage/cards/ExpansionSet.java
@@ -1,5 +1,8 @@
package mage.cards;
+import java.io.Serializable;
+import java.util.*;
+import java.util.stream.Collectors;
import mage.ObjectColor;
import mage.abilities.Ability;
import mage.abilities.keyword.PartnerWithAbility;
@@ -12,10 +15,6 @@ import mage.util.CardUtil;
import mage.util.RandomUtil;
import org.apache.log4j.Logger;
-import java.io.Serializable;
-import java.util.*;
-import java.util.stream.Collectors;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -270,8 +269,8 @@ public abstract class ExpansionSet implements Serializable {
}
protected boolean validateColors(List booster) {
- List magicColors =
- Arrays.asList(ObjectColor.WHITE, ObjectColor.BLUE, ObjectColor.BLACK, ObjectColor.RED, ObjectColor.GREEN);
+ List magicColors
+ = Arrays.asList(ObjectColor.WHITE, ObjectColor.BLUE, ObjectColor.BLACK, ObjectColor.RED, ObjectColor.GREEN);
// all cards colors
Map colorWeight = new HashMap<>();
@@ -354,7 +353,6 @@ public abstract class ExpansionSet implements Serializable {
addToBooster(booster, commons);
}
-
List rares = getCardsByRarity(Rarity.RARE);
List mythics = getCardsByRarity(Rarity.MYTHIC);
for (int i = 0; i < numBoosterRare; i++) {
@@ -384,6 +382,19 @@ public abstract class ExpansionSet implements Serializable {
}
}
}
+
+ if (numBoosterLands > 0) {
+ List specialLands = getSpecialLand();
+ List basicLands = getCardsByRarity(Rarity.LAND);
+ for (int i = 0; i < numBoosterLands; i++) {
+ if (ratioBoosterSpecialLand > 0 && RandomUtil.nextInt(ratioBoosterSpecialLand) < ratioBoosterSpecialLandNumerator && specialLands != null) {
+ addToBooster(booster, specialLands);
+ } else {
+ addToBooster(booster, basicLands);
+ }
+ }
+ }
+
return booster;
}
diff --git a/Mage/src/main/java/mage/constants/AsThoughEffectType.java b/Mage/src/main/java/mage/constants/AsThoughEffectType.java
index de81133a057..808a99277d8 100644
--- a/Mage/src/main/java/mage/constants/AsThoughEffectType.java
+++ b/Mage/src/main/java/mage/constants/AsThoughEffectType.java
@@ -1,7 +1,6 @@
package mage.constants;
/**
- *
* @author North
*/
public enum AsThoughEffectType {
@@ -19,15 +18,29 @@ public enum AsThoughEffectType {
BLOCK_FORESTWALK,
DAMAGE_NOT_BLOCKED,
BE_BLOCKED,
- PLAY_FROM_NOT_OWN_HAND_ZONE, // do not use dialogs in "applies" method for that type of effect (it calls multiple times)
+
+ // PLAY_FROM_NOT_OWN_HAND_ZONE + CAST_AS_INSTANT:
+ // 1. Do not use dialogs in "applies" method for that type of effect (it calls multiple times and will freeze the game)
+ // 2. All effects in "applies" must checks affectedControllerId.equals(source.getControllerId()) (if not then all players will be able to play it)
+ // 3. Target points to mainCard, but card's characteristics from objectId (split, adventure)
+ // TODO: search all PLAY_FROM_NOT_OWN_HAND_ZONE and CAST_AS_INSTANT effects and add support of mainCard and objectId
+ PLAY_FROM_NOT_OWN_HAND_ZONE,
CAST_AS_INSTANT,
+
ACTIVATE_AS_INSTANT,
DAMAGE,
SHROUD,
HEXPROOF,
PAY_0_ECHO,
LOOK_AT_FACE_DOWN,
+
+ // SPEND_OTHER_MANA:
+ // 1. It's uses for mana calcs at any zone, not stack only
+ // 2. Compare zone change counter as "objectZCC <= targetZCC + 1"
+ // 3. Compare zone with original (like exiled) and stack, not stack only
+ // TODO: search all SPEND_ONLY_MANA effects and improve counters compare as SPEND_OTHER_MANA
SPEND_OTHER_MANA,
+
SPEND_ONLY_MANA,
TARGET
}
diff --git a/Mage/src/main/java/mage/constants/SpellAbilityType.java b/Mage/src/main/java/mage/constants/SpellAbilityType.java
index 509796a2a7a..22427d77be4 100644
--- a/Mage/src/main/java/mage/constants/SpellAbilityType.java
+++ b/Mage/src/main/java/mage/constants/SpellAbilityType.java
@@ -15,7 +15,7 @@ public enum SpellAbilityType {
SPLIT_RIGHT("RightSplit SpellAbility"),
MODE("Mode SpellAbility"),
SPLICE("Spliced SpellAbility"),
- ADVENTURE("Adventure SpellAbility");
+ ADVENTURE_SPELL("Adventure SpellAbility");
private final String text;
diff --git a/Mage/src/main/java/mage/constants/SubType.java b/Mage/src/main/java/mage/constants/SubType.java
index e74f2a85523..c4a92c2d34a 100644
--- a/Mage/src/main/java/mage/constants/SubType.java
+++ b/Mage/src/main/java/mage/constants/SubType.java
@@ -110,6 +110,7 @@ public enum SubType {
// D
DATHOMIRIAN("Dathomirian", SubTypeSet.CreatureType, true), // Star Wars
DAUTHI("Dauthi", SubTypeSet.CreatureType),
+ DEMIGOD("Demigod", SubTypeSet.CreatureType),
DEMON("Demon", SubTypeSet.CreatureType),
DESERTER("Deserter", SubTypeSet.CreatureType),
DEVIL("Devil", SubTypeSet.CreatureType),
diff --git a/Mage/src/main/java/mage/counters/CounterType.java b/Mage/src/main/java/mage/counters/CounterType.java
index 401631f7801..9f310038009 100644
--- a/Mage/src/main/java/mage/counters/CounterType.java
+++ b/Mage/src/main/java/mage/counters/CounterType.java
@@ -21,6 +21,7 @@ public enum CounterType {
CARRION("carrion"),
CHARGE("charge"),
CHIP("chip"),
+ COIN("coin"),
CORPSE("corpse"),
CREDIT("credit"),
CRYSTAL("crystal"),
diff --git a/Mage/src/main/java/mage/filter/StaticFilters.java b/Mage/src/main/java/mage/filter/StaticFilters.java
index 2888e8840a4..68097a1fa22 100644
--- a/Mage/src/main/java/mage/filter/StaticFilters.java
+++ b/Mage/src/main/java/mage/filter/StaticFilters.java
@@ -15,6 +15,7 @@ import mage.filter.predicate.mageobject.CardTypePredicate;
import mage.filter.predicate.mageobject.MulticoloredPredicate;
import mage.filter.predicate.mageobject.SubtypePredicate;
import mage.filter.predicate.mageobject.SupertypePredicate;
+import mage.filter.predicate.other.PlayerPredicate;
import mage.filter.predicate.permanent.AnotherPredicate;
import mage.filter.predicate.permanent.AttackingPredicate;
import mage.filter.predicate.permanent.ControllerPredicate;
@@ -55,6 +56,12 @@ public final class StaticFilters {
FILTER_CARD_CARDS.setLockedFilter(true);
}
+ public static final FilterCard FILTER_CARD_ENTCHANTMENT = new FilterEnchantmentCard();
+
+ static {
+ FILTER_CARD_ENTCHANTMENT.setLockedFilter(true);
+ }
+
public static final FilterArtifactCard FILTER_CARD_ARTIFACT = new FilterArtifactCard();
static {
@@ -73,6 +80,12 @@ public final class StaticFilters {
FILTER_CARD_CREATURE.setLockedFilter(true);
}
+ public static final FilterCreatureCard FILTER_CARD_CREATURES = new FilterCreatureCard("creature cards");
+
+ static {
+ FILTER_CARD_CREATURES.setLockedFilter(true);
+ }
+
public static final FilterCreatureCard FILTER_CARD_CREATURE_A = new FilterCreatureCard("a creature card");
static {
@@ -133,7 +146,6 @@ public final class StaticFilters {
FILTER_CARD_A_NON_LAND.setLockedFilter(true);
}
-
public static final FilterNonlandCard FILTER_CARDS_NON_LAND = new FilterNonlandCard("nonland cards");
static {
@@ -188,6 +200,13 @@ public final class StaticFilters {
FILTER_ARTIFACT_CREATURE_PERMANENT.setLockedFilter(true);
}
+ public static final FilterControlledArtifactPermanent FILTER_ARTIFACTS_NON_CREATURE = new FilterControlledArtifactPermanent("Noncreature artifacts");
+
+ static {
+ FILTER_ARTIFACTS_NON_CREATURE.add(Predicates.not(new CardTypePredicate(CardType.CREATURE)));
+ FILTER_ARTIFACTS_NON_CREATURE.setLockedFilter(true);
+ }
+
public static final FilterPermanent FILTER_PERMANENT_ARTIFACT_OR_CREATURE = new FilterPermanent("artifact or creature");
static {
@@ -589,4 +608,10 @@ public final class StaticFilters {
FILTER_CARD_ARTIFACT_OR_CREATURE.setLockedFilter(true);
}
+ public static final FilterPlayer FILTER_PLAYER_CONTROLLER = new FilterPlayer("you");
+
+ static {
+ FILTER_PLAYER_CONTROLLER.add(new PlayerPredicate(TargetController.YOU));
+ FILTER_PLAYER_CONTROLLER.setLockedFilter(true);
+ }
}
diff --git a/Mage/src/main/java/mage/filter/common/FilterOtherCreatureSharingCreatureSubtype.java b/Mage/src/main/java/mage/filter/common/FilterOtherCreatureSharingCreatureSubtype.java
new file mode 100644
index 00000000000..2fc7fe5de45
--- /dev/null
+++ b/Mage/src/main/java/mage/filter/common/FilterOtherCreatureSharingCreatureSubtype.java
@@ -0,0 +1,45 @@
+
+package mage.filter.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import mage.constants.SubType;
+import mage.constants.SubTypeSet;
+import mage.filter.predicate.Predicates;
+import mage.filter.predicate.mageobject.SubtypePredicate;
+import mage.filter.predicate.permanent.PermanentIdPredicate;
+import mage.game.Game;
+import mage.game.permanent.Permanent;
+
+/**
+ *
+ * @author tschroeder
+ */
+
+public class FilterOtherCreatureSharingCreatureSubtype extends FilterCreaturePermanent {
+
+ public FilterOtherCreatureSharingCreatureSubtype(Permanent creature, Game game) {
+ super("creature sharing a creature type with " + creature.toString());
+
+ List subtypePredicates = new ArrayList<>();
+ for (SubType subtype : creature.getSubtype(game)) {
+ if (subtype.getSubTypeSet() == SubTypeSet.CreatureType) {
+ subtypePredicates.add(new SubtypePredicate(subtype));
+ }
+ }
+ this.add(Predicates.and(
+ Predicates.or(subtypePredicates),
+ Predicates.not(new PermanentIdPredicate(creature.getId()))
+ ));
+ }
+
+ public FilterOtherCreatureSharingCreatureSubtype(final FilterOtherCreatureSharingCreatureSubtype filter) {
+ super(filter);
+ }
+
+ @Override
+ public FilterOtherCreatureSharingCreatureSubtype copy() {
+ return new FilterOtherCreatureSharingCreatureSubtype(this);
+ }
+}
diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/AdventurePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/AdventurePredicate.java
index 90927d92ca6..98544c800f6 100644
--- a/Mage/src/main/java/mage/filter/predicate/mageobject/AdventurePredicate.java
+++ b/Mage/src/main/java/mage/filter/predicate/mageobject/AdventurePredicate.java
@@ -1,19 +1,27 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
+import mage.cards.AdventureCard;
+import mage.cards.Card;
import mage.filter.predicate.Predicate;
import mage.game.Game;
+import mage.game.stack.Spell;
/**
* @author TheElk801
- * TODO: make this actually work
*/
public enum AdventurePredicate implements Predicate {
instance;
@Override
public boolean apply(MageObject input, Game game) {
- return false;
+ if (input instanceof Spell) {
+ return ((Spell) input).getCard() instanceof AdventureCard;
+ } else if (input instanceof Card) {
+ return input instanceof AdventureCard;
+ } else {
+ return false;
+ }
}
@Override
diff --git a/Mage/src/main/java/mage/filter/predicate/other/CardTextPredicate.java b/Mage/src/main/java/mage/filter/predicate/other/CardTextPredicate.java
index e09d8560049..1c2af8afdbd 100644
--- a/Mage/src/main/java/mage/filter/predicate/other/CardTextPredicate.java
+++ b/Mage/src/main/java/mage/filter/predicate/other/CardTextPredicate.java
@@ -3,6 +3,8 @@ package mage.filter.predicate.other;
import java.util.HashMap;
import java.util.Locale;
+
+import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.SplitCard;
import mage.constants.SubType;
@@ -76,6 +78,14 @@ public class CardTextPredicate implements Predicate {
}
}
}
+ if (input instanceof AdventureCard) {
+ for (String rule : ((AdventureCard) input).getSpellCard().getRules(game)) {
+ if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
+ found = true;
+ break;
+ }
+ }
+ }
for (String rule : input.getRules(game)) {
if (rule.toLowerCase(Locale.ENGLISH).contains(token)) {
found = true;
diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java
index af5ce5a1ead..2da26d73c92 100644
--- a/Mage/src/main/java/mage/game/Game.java
+++ b/Mage/src/main/java/mage/game/Game.java
@@ -128,10 +128,8 @@ public interface Game extends MageItem, Serializable {
return player.getInRange().stream()
.filter(opponentId -> !opponentId.equals(playerId))
.collect(Collectors.toSet());
-
}
-
default boolean isActivePlayer(UUID playerId) {
return getActivePlayerId() != null && getActivePlayerId().equals(playerId);
}
@@ -202,7 +200,11 @@ public interface Game extends MageItem, Serializable {
boolean isSimulation();
- void setSimulation(boolean simulation);
+ void setSimulation(boolean checkPlayableState);
+
+ boolean inCheckPlayableState();
+
+ void setCheckPlayableState(boolean checkPlayableState);
MageObject getLastKnownInformation(UUID objectId, Zone zone);
diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java
index c0c8c8a6865..4f038a6b1f3 100644
--- a/Mage/src/main/java/mage/game/GameImpl.java
+++ b/Mage/src/main/java/mage/game/GameImpl.java
@@ -1,5 +1,9 @@
package mage.game;
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.*;
+import java.util.Map.Entry;
import mage.MageException;
import mage.MageObject;
import mage.abilities.*;
@@ -29,6 +33,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;
@@ -64,12 +69,6 @@ import mage.util.functions.ApplyToPermanent;
import mage.watchers.common.*;
import org.apache.log4j.Logger;
-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 {
private static final int ROLLBACK_TURNS_MAX = 4;
@@ -77,7 +76,9 @@ public abstract class GameImpl implements Game, Serializable {
private static final Logger logger = Logger.getLogger(GameImpl.class);
private transient Object customData;
+
protected boolean simulation = false;
+ protected boolean checkPlayableState = false;
protected final UUID id;
@@ -163,6 +164,7 @@ public abstract class GameImpl implements Game, Serializable {
this.state = game.state.copy();
this.gameCards = game.gameCards;
this.simulation = game.simulation;
+ this.checkPlayableState = game.checkPlayableState;
this.gameOptions = game.gameOptions;
this.lki.putAll(game.lki);
this.lkiExtended.putAll(game.lkiExtended);
@@ -188,6 +190,16 @@ public abstract class GameImpl implements Game, Serializable {
this.simulation = simulation;
}
+ @Override
+ public void setCheckPlayableState(boolean checkPlayableState) {
+ this.checkPlayableState = checkPlayableState;
+ }
+
+ @Override
+ public boolean inCheckPlayableState() {
+ return checkPlayableState;
+ }
+
@Override
public UUID getId() {
return id;
@@ -230,6 +242,12 @@ public abstract class GameImpl implements Game, Serializable {
gameCards.put(rightCard.getId(), rightCard);
state.addCard(rightCard);
}
+ if (card instanceof AdventureCard) {
+ Card spellCard = ((AdventureCard) card).getSpellCard();
+ spellCard.setOwnerId(ownerId);
+ gameCards.put(spellCard.getId(), spellCard);
+ state.addCard(spellCard);
+ }
}
}
@@ -445,7 +463,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;
}
@@ -738,7 +766,7 @@ public abstract class GameImpl implements Game, Serializable {
state.getTurn().resumePlay(this, wasPaused);
if (!isPaused() && !checkIfGameIsOver()) {
endOfTurn();
- player = playerList.getNext(this);
+ player = playerList.getNext(this, true);
state.setTurnNum(state.getTurnNum() + 1);
}
}
@@ -763,7 +791,7 @@ public abstract class GameImpl implements Game, Serializable {
if (!playExtraTurns()) {
break;
}
- playerByOrder = playerList.getNext(this);
+ playerByOrder = playerList.getNext(this, true);
state.setPlayerByOrderId(playerByOrder.getId());
}
}
@@ -1296,6 +1324,8 @@ public abstract class GameImpl implements Game, Serializable {
} else {
throw new MageException("Error in testclass");
}
+ } finally {
+ setCheckPlayableState(false);
}
state.getPlayerList().getNext();
}
@@ -1307,6 +1337,7 @@ public abstract class GameImpl implements Game, Serializable {
} finally {
resetLKI();
clearAllBookmarks();
+ setCheckPlayableState(false);
}
}
@@ -1562,7 +1593,7 @@ public abstract class GameImpl implements Game, Serializable {
}
newBluePrint.assignNewId();
if (copyFromPermanent.isTransformed()) {
- TransformAbility.transform(newBluePrint, copyFromPermanent.getSecondCardFace(), this);
+ TransformAbility.transform(newBluePrint, newBluePrint.getSecondCardFace(), this);
}
}
if (applier != null) {
@@ -1578,7 +1609,7 @@ public abstract class GameImpl implements Game, Serializable {
Ability newAbility = source.copy();
newEffect.init(newAbility, this);
- // If there are already copy effects with dration = Custom to the same object, remove the existing effects because they no longer have any effect
+ // If there are already copy effects with duration = Custom to the same object, remove the existing effects because they no longer have any effect
if (duration == Duration.Custom) {
for (Effect effect : getState().getContinuousEffects().getLayeredEffects(this)) {
if (effect instanceof CopyEffect) {
@@ -1767,7 +1798,7 @@ public abstract class GameImpl implements Game, Serializable {
Iterator copiedCards = this.getState().getCopiedCards().iterator();
while (copiedCards.hasNext()) {
Card card = copiedCards.next();
- if (card instanceof SplitCardHalf) {
+ if (card instanceof SplitCardHalf || card instanceof AdventureCardSpell) {
continue; // only the main card is moves, not the halves
}
Zone zone = state.getZone(card.getId());
@@ -2428,8 +2459,8 @@ public abstract class GameImpl implements Game, Serializable {
* exist. Then, if there are any objects still controlled by that player,
* those objects are exiled. This is not a state-based action. It happens as
* soon as the player leaves the game. If the player who left the game had
- * priority at the time they left, priority passes to the next player
- * in turn order who's still in the game. #
+ * priority at the time they left, priority passes to the next player in
+ * turn order who's still in the game. #
*
* @param playerId
*/
@@ -2466,7 +2497,6 @@ public abstract class GameImpl implements Game, Serializable {
perm.removeFromCombat(this, true);
}
toOutside.add(perm);
-// it.remove();
} else if (perm.isControlledBy(player.getId())) {
// and any effects which give that player control of any objects or players end
Effects:
@@ -2563,7 +2593,7 @@ public abstract class GameImpl implements Game, Serializable {
if (!isActivePlayer(playerId)) {
setMonarchId(null, getActivePlayerId());
} else {
- Player nextPlayer = getPlayerList().getNext(this);
+ Player nextPlayer = getPlayerList().getNext(this, true);
if (nextPlayer != null) {
setMonarchId(null, nextPlayer.getId());
}
@@ -2622,7 +2652,7 @@ public abstract class GameImpl implements Game, Serializable {
return result;
}
DamageEvent damageEvent = (DamageEvent) event;
- GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, damageEvent.getTargetId(), damageEvent.getSourceId(), source.getControllerId(), damageEvent.getAmount(), false);
+ GameEvent preventEvent = new PreventDamageEvent(damageEvent.getTargetId(), damageEvent.getSourceId(), source.getControllerId(), damageEvent.getAmount(), damageEvent.isCombatDamage());
if (game.replaceEvent(preventEvent)) {
result.setReplaced(true);
return result;
diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java
index 21efc769a75..b43dc75c4cf 100644
--- a/Mage/src/main/java/mage/game/GameState.java
+++ b/Mage/src/main/java/mage/game/GameState.java
@@ -5,6 +5,7 @@ import mage.abilities.*;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffects;
import mage.abilities.effects.Effect;
+import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.SplitCard;
import mage.constants.Zone;
@@ -634,6 +635,9 @@ public class GameState implements Serializable, Copyable {
* Returns a list of all active players of the game in range of playerId,
* also setting the playerId to the first/current player of the list. Also
* returning the other players in turn order.
+ *
+ * Not safe for continuous effects, see rule 800.4k (effects must work until end of turn even after player leaves)
+ * Use Player.InRange() to find active players list at the start of the turn
*
* @param playerId
* @param game
@@ -644,7 +648,7 @@ public class GameState implements Serializable, Copyable {
Player currentPlayer = game.getPlayer(playerId);
if (currentPlayer != null) {
for (Player player : players.values()) {
- if (!player.hasLeft() && !player.hasLost() && currentPlayer.getInRange().contains(player.getId())) {
+ if (player.isInGame() && currentPlayer.getInRange().contains(player.getId())) {
newPlayerList.add(player.getId());
}
}
@@ -811,6 +815,9 @@ public class GameState implements Serializable, Copyable {
removeCopiedCard(((SplitCard) card).getLeftHalfCard());
removeCopiedCard(((SplitCard) card).getRightHalfCard());
}
+ if (card instanceof AdventureCard) {
+ removeCopiedCard(((AdventureCard) card).getSpellCard());
+ }
}
/**
@@ -1166,6 +1173,11 @@ public class GameState implements Serializable, Copyable {
copiedCards.put(rightCard.getId(), rightCard);
addCard(rightCard);
}
+ if (copiedCard instanceof AdventureCard) {
+ Card spellCard = ((AdventureCard) copiedCard).getSpellCard();
+ copiedCards.put(spellCard.getId(), spellCard);
+ addCard(spellCard);
+ }
return copiedCard;
}
diff --git a/Mage/src/main/java/mage/game/combat/Combat.java b/Mage/src/main/java/mage/game/combat/Combat.java
index acf6d191f33..91874cdd0dc 100644
--- a/Mage/src/main/java/mage/game/combat/Combat.java
+++ b/Mage/src/main/java/mage/game/combat/Combat.java
@@ -1236,7 +1236,7 @@ public class Combat implements Serializable, Copyable {
case LEFT:
players = game.getState().getPlayerList(attackingPlayerId);
while (attackingPlayer.isInGame()) {
- Player opponent = players.getNext(game);
+ Player opponent = players.getNext(game, false);
if (attackingPlayer.hasOpponent(opponent.getId(), game)) {
attackablePlayers.add(opponent.getId());
break;
diff --git a/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java b/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java
index 8ca6f0b2125..cda33d8faa7 100644
--- a/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java
+++ b/Mage/src/main/java/mage/game/command/emblems/WrennAndSixEmblem.java
@@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.RetraceAbility;
+import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.constants.*;
import mage.game.Game;
@@ -42,7 +43,14 @@ class WrennAndSixEmblemEffect extends ContinuousEffectImpl {
}
for (UUID cardId : controller.getGraveyard()) {
Card card = game.getCard(cardId);
- if (card == null || !card.isInstantOrSorcery()) {
+ if (card == null) {
+ continue;
+ }
+ if (card instanceof AdventureCard) {
+ // Adventure cards are castable per https://twitter.com/elishffrn/status/1179047911729946624
+ card = ((AdventureCard) card).getSpellCard();
+ }
+ if (!card.isInstantOrSorcery()) {
continue;
}
Ability ability = new RetraceAbility(card);
diff --git a/Mage/src/main/java/mage/game/draft/RateCard.java b/Mage/src/main/java/mage/game/draft/RateCard.java
index aa83b500529..4ad85a91041 100644
--- a/Mage/src/main/java/mage/game/draft/RateCard.java
+++ b/Mage/src/main/java/mage/game/draft/RateCard.java
@@ -13,9 +13,6 @@ import mage.constants.Outcome;
import mage.constants.SubType;
import mage.target.Target;
import mage.target.TargetPermanent;
-import mage.target.common.TargetAttackingCreature;
-import mage.target.common.TargetAttackingOrBlockingCreature;
-import mage.target.common.TargetCreaturePermanent;
import mage.target.common.TargetPlayerOrPlaneswalker;
import org.apache.log4j.Logger;
@@ -136,7 +133,7 @@ public final class RateCard {
private static int isEffectRemoval(Card card, Ability ability, Effect effect) {
if (effect.getOutcome() == Outcome.Removal) {
- log.debug("Found removal: " + card.getName());
+ // found removal
return 1;
}
//static List removalEffects =[BoostTargetEffect,BoostEnchantedEffect]
@@ -153,7 +150,7 @@ public final class RateCard {
if (effect.getOutcome() == Outcome.Damage || effect instanceof DamageTargetEffect) {
for (Target target : ability.getTargets()) {
if (!(target instanceof TargetPlayerOrPlaneswalker)) {
- log.debug("Found damage dealer: " + card.getName());
+ // found damage dealer
return 1;
}
}
@@ -163,11 +160,8 @@ public final class RateCard {
effect instanceof ExileTargetEffect ||
effect instanceof ExileUntilSourceLeavesEffect) {
for (Target target : ability.getTargets()) {
- if (target instanceof TargetCreaturePermanent ||
- target instanceof TargetAttackingCreature ||
- target instanceof TargetAttackingOrBlockingCreature ||
- target instanceof TargetPermanent) {
- log.debug("Found destroyer/exiler: " + card.getName());
+ if (target instanceof TargetPermanent) {
+ // found destroyer/exiler
return 1;
}
}
diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java
index 71ea031c5e3..fb854bcdf1c 100644
--- a/Mage/src/main/java/mage/game/events/GameEvent.java
+++ b/Mage/src/main/java/mage/game/events/GameEvent.java
@@ -22,6 +22,7 @@ public class GameEvent implements Serializable {
// for counters: event is result of effect (+1 from planeswalkers is cost, not effect)
// for combat damage: event is preventable damage
// for discard: event is result of effect (1) or result of cost (0)
+ // for prevent damage: try to prevent combat damage (1) or other damage (0)
protected boolean flag;
protected String data;
protected Zone zone;
diff --git a/Mage/src/main/java/mage/game/events/PreventDamageEvent.java b/Mage/src/main/java/mage/game/events/PreventDamageEvent.java
new file mode 100644
index 00000000000..49d9623afa8
--- /dev/null
+++ b/Mage/src/main/java/mage/game/events/PreventDamageEvent.java
@@ -0,0 +1,17 @@
+package mage.game.events;
+
+import java.util.UUID;
+
+/**
+ * @author JayDi85
+ */
+public class PreventDamageEvent extends GameEvent {
+
+ public PreventDamageEvent(UUID targetId, UUID sourceId, UUID playerId, int damageToPrevent, boolean isCombatDamage) {
+ super(EventType.PREVENT_DAMAGE, targetId, sourceId, playerId, damageToPrevent, isCombatDamage);
+ }
+
+ public boolean isCombatDamage() {
+ return flag;
+ }
+}
diff --git a/Mage/src/main/java/mage/game/permanent/Battlefield.java b/Mage/src/main/java/mage/game/permanent/Battlefield.java
index a4445c6c642..178880baa6f 100644
--- a/Mage/src/main/java/mage/game/permanent/Battlefield.java
+++ b/Mage/src/main/java/mage/game/permanent/Battlefield.java
@@ -1,4 +1,3 @@
-
package mage.game.permanent;
import mage.abilities.keyword.PhasingAbility;
@@ -6,7 +5,6 @@ import mage.constants.CardType;
import mage.constants.RangeOfInfluence;
import mage.filter.FilterPermanent;
import mage.game.Game;
-import mage.players.Player;
import java.io.Serializable;
import java.util.*;
@@ -85,8 +83,8 @@ public class Battlefield implements Serializable {
&& permanent.isPhasedIn())
.count();
} else {
- Set range = game.getPlayer(sourcePlayerId).getInRange();
- return (int) field.values()
+ List range = game.getState().getPlayersInRange(sourcePlayerId, game);
+ return (int) field.values()
.stream()
.filter(permanent -> range.contains(permanent.getControllerId())
&& filter.match(permanent, sourceId, sourcePlayerId, game)
@@ -150,7 +148,7 @@ public class Battlefield implements Serializable {
&& permanent.isPhasedIn()).count() >= num;
} else {
- Set range = game.getPlayer(sourcePlayerId).getInRange();
+ List range = game.getState().getPlayersInRange(sourcePlayerId, game);
return field.values().stream()
.filter(permanent -> range.contains(permanent.getControllerId())
&& filter.match(permanent, null, sourcePlayerId, game)
@@ -235,7 +233,7 @@ public class Battlefield implements Serializable {
/**
* Returns all {@link Permanent} on the battlefield that match the supplied
- * filter. This method ignores the range of influence.
+ * filter. This method ignores the range of influence. It's ignore controllers preficate
*
* @param filter
* @param game
@@ -298,12 +296,8 @@ public class Battlefield implements Serializable {
.filter(perm -> perm.isPhasedIn() && filter.match(perm, sourceId, sourcePlayerId, game))
.collect(Collectors.toList());
} else {
- Player player = game.getPlayer(sourcePlayerId);
- if(player == null){
- return Collections.emptyList();
- }
- Set range = player.getInRange();
- return field.values()
+ List range = game.getState().getPlayersInRange(sourcePlayerId, game);
+ return field.values()
.stream()
.filter(perm -> perm.isPhasedIn() && range.contains(perm.getControllerId())
&& filter.match(perm, sourceId, sourcePlayerId, game)).collect(Collectors.toList());
@@ -323,7 +317,7 @@ public class Battlefield implements Serializable {
if (game.getRangeOfInfluence() == RangeOfInfluence.ALL) {
return getAllActivePermanents();
} else {
- Set range = game.getPlayer(sourcePlayerId).getInRange();
+ List range = game.getState().getPlayersInRange(sourcePlayerId, game);
return field.values()
.stream()
.filter(perm -> perm.isPhasedIn()
diff --git a/Mage/src/main/java/mage/game/permanent/PermanentCard.java b/Mage/src/main/java/mage/game/permanent/PermanentCard.java
index f6436ee4511..1f2bf0ccfb1 100644
--- a/Mage/src/main/java/mage/game/permanent/PermanentCard.java
+++ b/Mage/src/main/java/mage/game/permanent/PermanentCard.java
@@ -1,15 +1,20 @@
package mage.game.permanent;
+import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Abilities;
import mage.abilities.Ability;
+import mage.abilities.SpellAbility;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.keyword.TransformAbility;
+import mage.cards.AdventureCard;
import mage.cards.Card;
import mage.cards.LevelerCard;
+import mage.constants.SpellAbilityType;
import mage.game.Game;
import mage.game.events.ZoneChangeEvent;
@@ -87,6 +92,18 @@ public class PermanentCard extends PermanentImpl {
} else {
this.abilities = card.getAbilities().copy();
}
+ if (card instanceof AdventureCard) {
+ // Adventure card spell abilities should not appear on permanents.
+ List toRemove = new ArrayList();
+ for (Ability ability : this.abilities) {
+ if (ability instanceof SpellAbility) {
+ if (((SpellAbility) ability).getSpellAbilityType() == SpellAbilityType.ADVENTURE_SPELL) {
+ toRemove.add(ability);
+ }
+ }
+ }
+ toRemove.forEach(ability -> this.abilities.remove(ability));
+ }
this.abilities.setControllerId(this.controllerId);
this.abilities.setSourceId(objectId);
this.cardType.clear();
diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java
index 4537ecf18bb..a40e9aa5ad1 100644
--- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java
+++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java
@@ -665,6 +665,13 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public boolean changeControllerId(UUID controllerId, Game game) {
Player newController = game.getPlayer(controllerId);
+ // For each control change compared to last controler send a GAIN_CONTROL replace event to be able to prevent the gain control (e.g. Guardian Beast)
+ if (beforeResetControllerId != controllerId) {
+ GameEvent gainControlEvent = GameEvent.getEvent(GameEvent.EventType.GAIN_CONTROL, this.getId(), null, controllerId);
+ if (game.replaceEvent(gainControlEvent)) {
+ return false;
+ }
+ }
GameEvent loseControlEvent = GameEvent.getEvent(GameEvent.EventType.LOSE_CONTROL, this.getId(), null, controllerId);
@@ -963,7 +970,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
private int checkProtectionAbilities(GameEvent event, UUID sourceId, Game game) {
MageObject source = game.getObject(sourceId);
if (source != null && hasProtectionFrom(source, game)) {
- GameEvent preventEvent = new GameEvent(GameEvent.EventType.PREVENT_DAMAGE, this.objectId, sourceId, this.controllerId, event.getAmount(), false);
+ GameEvent preventEvent = new PreventDamageEvent(this.objectId, sourceId, this.controllerId, event.getAmount(), ((DamageEvent) event).isCombatDamage());
if (!game.replaceEvent(preventEvent)) {
int preventedDamage = event.getAmount();
event.setAmount(0);
diff --git a/Mage/src/main/java/mage/game/permanent/token/AshiokNightmareMuseToken.java b/Mage/src/main/java/mage/game/permanent/token/AshiokNightmareMuseToken.java
new file mode 100644
index 00000000000..5678ce5f7f0
--- /dev/null
+++ b/Mage/src/main/java/mage/game/permanent/token/AshiokNightmareMuseToken.java
@@ -0,0 +1,79 @@
+package mage.game.permanent.token;
+
+import mage.MageInt;
+import mage.abilities.Ability;
+import mage.abilities.common.AttacksOrBlocksTriggeredAbility;
+import mage.abilities.effects.OneShotEffect;
+import mage.cards.Card;
+import mage.constants.CardType;
+import mage.constants.Outcome;
+import mage.constants.SubType;
+import mage.constants.Zone;
+import mage.game.Game;
+import mage.players.Player;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author TheElk801
+ */
+public final class AshiokNightmareMuseToken extends TokenImpl {
+
+ public AshiokNightmareMuseToken() {
+ super("Nightmare", "2/3 blue and black Nightmare creature token with " +
+ "\"Whenever this creature attacks or blocks, each opponent exiles the top two cards of their library.\"");
+ cardType.add(CardType.CREATURE);
+ color.setBlue(true);
+ color.setBlack(true);
+ subtype.add(SubType.NIGHTMARE);
+ power = new MageInt(2);
+ toughness = new MageInt(3);
+ this.addAbility(new AttacksOrBlocksTriggeredAbility(new AshiokNightmareMuseTokenEffect(), false));
+ }
+
+ private AshiokNightmareMuseToken(final AshiokNightmareMuseToken token) {
+ super(token);
+ }
+
+ public AshiokNightmareMuseToken copy() {
+ return new AshiokNightmareMuseToken(this);
+ }
+}
+
+class AshiokNightmareMuseTokenEffect extends OneShotEffect {
+
+ AshiokNightmareMuseTokenEffect() {
+ super(Outcome.Benefit);
+ staticText = "each opponent exiles the top two cards of their library.";
+ }
+
+ private AshiokNightmareMuseTokenEffect(final AshiokNightmareMuseTokenEffect effect) {
+ super(effect);
+ }
+
+ @Override
+ public AshiokNightmareMuseTokenEffect copy() {
+ return new AshiokNightmareMuseTokenEffect(this);
+ }
+
+ @Override
+ public boolean apply(Game game, Ability source) {
+ Player player = game.getPlayer(source.getControllerId());
+ if (player == null) {
+ return false;
+ }
+ Set cards = game
+ .getOpponents(source.getControllerId())
+ .stream()
+ .map(game::getPlayer)
+ .filter(Objects::nonNull)
+ .map(Player::getLibrary)
+ .map(library -> library.getTopCards(game, 2))
+ .flatMap(Collection::stream)
+ .collect(Collectors.toSet());
+ return player.moveCards(cards, Zone.EXILED, source, game);
+ }
+}
\ No newline at end of file
diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java
index 295c229e4ac..e6436d7eae5 100644
--- a/Mage/src/main/java/mage/game/stack/StackAbility.java
+++ b/Mage/src/main/java/mage/game/stack/StackAbility.java
@@ -1,5 +1,9 @@
package mage.game.stack;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
import mage.MageInt;
import mage.MageObject;
import mage.ObjectColor;
@@ -30,11 +34,6 @@ import mage.util.GameLog;
import mage.util.SubTypeList;
import mage.watchers.Watcher;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.UUID;
-
/**
* @author BetaSteward_at_googlemail.com
*/
@@ -481,11 +480,6 @@ public class StackAbility extends StackObjImpl implements Ability {
throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates.
}
- @Override
- public void setCostModificationActive(boolean active) {
- throw new UnsupportedOperationException("Not supported. Only neede for flashbacked spells");
- }
-
@Override
public boolean getWorksFaceDown() {
return this.ability.getWorksFaceDown();
diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java
index 1e8ddbb897f..989f14df13d 100644
--- a/Mage/src/main/java/mage/players/Player.java
+++ b/Mage/src/main/java/mage/players/Player.java
@@ -20,8 +20,8 @@ import mage.counters.Counter;
import mage.counters.Counters;
import mage.designations.Designation;
import mage.designations.DesignationType;
-import mage.filter.FilterPermanent;
import mage.filter.FilterMana;
+import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.Graveyard;
import mage.game.Table;
@@ -75,7 +75,7 @@ public interface Player extends MageItem, Copyable {
void setLife(int life, Game game, UUID sourceId);
/**
- * @param amount amount of life loss
+ * @param amount amount of life loss
* @param game
* @param atCombat was the source combat damage
* @return
@@ -211,13 +211,6 @@ public interface Player extends MageItem, Copyable {
*/
boolean canRespond();
- /**
- * Called if other player left the game
- *
- * @param game
- */
- void otherPlayerLeftGame(Game game);
-
ManaPool getManaPool();
Set getInRange();
@@ -351,7 +344,7 @@ public interface Player extends MageItem, Copyable {
* @param source
* @param game
* @param targetPlayerId player whose library will be searched
- * @param triggerEvents whether searching will trigger any game events
+ * @param triggerEvents whether searching will trigger any game events
* @return true if search was successful
*/
boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId, boolean triggerEvents);
@@ -372,23 +365,23 @@ public interface Player extends MageItem, Copyable {
/**
* Plays a card if possible
*
- * @param card the card that can be cast
+ * @param card the card that can be cast
* @param game
- * @param noMana if it's a spell i can be cast without paying mana
+ * @param noMana if it's a spell i can be cast without paying mana
* @param ignoreTiming if it's cast during the resolution of another spell
- * no sorcery or play land timing restriction are checked. For a land it has
- * to be the turn of the player playing that card.
- * @param reference mage object that allows to play the card
+ * no sorcery or play land timing restriction are checked. For a land it has
+ * to be the turn of the player playing that card.
+ * @param reference mage object that allows to play the card
* @return
*/
boolean playCard(Card card, Game game, boolean noMana, boolean ignoreTiming, MageObjectReference reference);
/**
- * @param card the land card to play
+ * @param card the land card to play
* @param game
* @param ignoreTiming false - it won't be checked if the stack is empty and
- * you are able to play a Sorcery. It's still checked, if you are able to
- * play a land concerning the number of lands you already played.
+ * you are able to play a Sorcery. It's still checked, if you are able to
+ * play a land concerning the number of lands you already played.
* @return
*/
boolean playLand(Card card, Game game, boolean ignoreTiming);
@@ -534,11 +527,11 @@ public interface Player extends MageItem, Copyable {
/**
* Moves the cards from cards to the bottom of the players library.
*
- * @param cards - list of cards that have to be moved
- * @param game - game
+ * @param cards - list of cards that have to be moved
+ * @param game - game
* @param anyOrder - true if player can determine the order of the cards
- * else random order
- * @param source - source ability
+ * else random order
+ * @param source - source ability
* @return
*/
boolean putCardsOnBottomOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@@ -559,10 +552,10 @@ public interface Player extends MageItem, Copyable {
/**
* Moves the cards from cards to the top of players library.
*
- * @param cards - list of cards that have to be moved
- * @param game - game
+ * @param cards - list of cards that have to be moved
+ * @param game - game
* @param anyOrder - true if player can determine the order of the cards
- * @param source - source ability
+ * @param source - source ability
* @return
*/
boolean putCardsOnTopOfLibrary(Cards cards, Game game, Ability source, boolean anyOrder);
@@ -593,8 +586,8 @@ public interface Player extends MageItem, Copyable {
/**
* Choose the order in which blockers get damage assigned to
*
- * @param blockers list of blockers where to choose the next one from
- * @param combatGroup the concerning combat group
+ * @param blockers list of blockers where to choose the next one from
+ * @param combatGroup the concerning combat group
* @param blockerOrder the already set order of blockers
* @param game
* @return blocker next to add to the blocker order
@@ -637,7 +630,7 @@ public interface Player extends MageItem, Copyable {
List getPlayableOptions(Ability ability, Game game);
- Set getPlayableObjects(Game game, Zone zone);
+ Map getPlayableObjects(Game game, Zone zone);
LinkedHashMap getUseableActivatedAbilities(MageObject object, Zone zone, Game game);
@@ -664,7 +657,7 @@ public interface Player extends MageItem, Copyable {
* @param card
* @param game
* @param abilitiesToActivate extra info about abilities that can be
- * activated on NO option
+ * activated on NO option
* @return player looked at the card
*/
boolean lookAtFaceDownCard(Card card, Game game, int abilitiesToActivate);
@@ -737,11 +730,11 @@ public interface Player extends MageItem, Copyable {
* @param toZone
* @param source
* @param game
- * @param tapped the cards are tapped on the battlefield
- * @param faceDown the cards are face down in the to zone
- * @param byOwner the card is moved (or put onto battlefield) by the owner
- * of the card and if target zone is battlefield controls the permanent
- * (instead of the controller of the source)
+ * @param tapped the cards are tapped on the battlefield
+ * @param faceDown the cards are face down in the to zone
+ * @param byOwner the card is moved (or put onto battlefield) by the owner
+ * of the card and if target zone is battlefield controls the permanent
+ * (instead of the controller of the source)
* @param appliedEffects
* @return
*/
@@ -777,7 +770,7 @@ public interface Player extends MageItem, Copyable {
* list of applied effects is not saved
*
* @param card
- * @param exileId exile zone id (optional)
+ * @param exileId exile zone id (optional)
* @param exileName name of exile zone (optional)
* @param sourceId
* @param game
@@ -819,7 +812,7 @@ public interface Player extends MageItem, Copyable {
* @param sourceId
* @param game
* @param fromZone if null, this info isn't postet
- * @param toTop to the top of the library else to the bottom
+ * @param toTop to the top of the library else to the bottom
* @param withName show the card name in the log
* @return
*/
@@ -844,18 +837,20 @@ public interface Player extends MageItem, Copyable {
* without mana (null) or the mana set to manaCosts instead of its normal
* mana costs.
*
- * @param sourceId the source that can be cast without mana
+ * @param sourceId the source that can be cast without mana
* @param manaCosts alternate ManaCost, null if it can be cast without mana
- * cost
- * @param costs alternate other costs you need to pay
+ * cost
+ * @param costs alternate other costs you need to pay
*/
void setCastSourceIdWithAlternateMana(UUID sourceId, ManaCosts manaCosts, Costs costs);
- UUID getCastSourceIdWithAlternateMana();
+ Set