From 949eb1acb007ad72f85ca5a73426e8255473154e Mon Sep 17 00:00:00 2001 From: Ryan Skeldon Date: Sat, 26 Nov 2016 02:00:30 -0500 Subject: [PATCH 1/6] Implemented Cairn Wanderer --- Mage.Sets/src/mage/cards/c/CairnWanderer.java | 115 ++++++++++++++++ Mage.Sets/src/mage/sets/Lorwyn.java | 3 +- .../cards/single/lrw/CairnWandererTest.java | 124 ++++++++++++++++++ 3 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/c/CairnWanderer.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/lrw/CairnWandererTest.java diff --git a/Mage.Sets/src/mage/cards/c/CairnWanderer.java b/Mage.Sets/src/mage/cards/c/CairnWanderer.java new file mode 100644 index 00000000000..6158d38cb81 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CairnWanderer.java @@ -0,0 +1,115 @@ +package mage.cards.c; + +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.keyword.ChangelingAbility; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FearAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LandwalkAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.ShroudAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Layer; +import mage.constants.Outcome; +import mage.constants.SubLayer; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +/** + * + * @author psykad + */ +public class CairnWanderer extends CardImpl { + public CairnWanderer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}"); + + this.subtype.add("Shapeshifter"); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Changeling + this.addAbility(ChangelingAbility.getInstance()); + + // As long as a creature card with flying is in a graveyard, {this} has flying. The same is true for fear, first strike, double strike, deathtouch, haste, landwalk, lifelink, protection, reach, trample, shroud, and vigilance. + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CairnWandererEffect())); + } + + public CairnWanderer(final CairnWanderer card) { + super(card); + } + + @Override + public CairnWanderer copy() { + return new CairnWanderer(this); + } + + class CairnWandererEffect extends ContinuousEffectImpl { + public CairnWandererEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "As long as a creature card with flying is in a graveyard, {this} has flying. The same is true for fear, first strike, double strike, deathtouch, haste, landwalk, lifelink, protection, reach, trample, shroud, and vigilance."; + } + + public CairnWandererEffect(final CairnWandererEffect effect) { + super(effect); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent perm = game.getPermanent(source.getSourceId()); + + if (perm == null) return false; + + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + + if (player != null) { + for (Card card : player.getGraveyard().getCards(game)) { + if (card.getCardType().contains(CardType.CREATURE)) { + for (Ability ability : card.getAbilities()) { + if (ability instanceof FlyingAbility || + ability instanceof FearAbility || + ability instanceof FirstStrikeAbility || + ability instanceof DoubleStrikeAbility || + ability instanceof DeathtouchAbility || + ability instanceof HasteAbility || + ability instanceof LandwalkAbility || + ability instanceof LifelinkAbility || + ability instanceof ProtectionAbility || + ability instanceof ReachAbility || + ability instanceof TrampleAbility || + ability instanceof ShroudAbility || + ability instanceof VigilanceAbility) { + perm.addAbility(ability, game); + } + } + } + } + } + } + + return true; + } + + @Override + public CairnWandererEffect copy() { + return new CairnWandererEffect(this); + } + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Lorwyn.java b/Mage.Sets/src/mage/sets/Lorwyn.java index 8b99e20d9c3..a911d9f488b 100644 --- a/Mage.Sets/src/mage/sets/Lorwyn.java +++ b/Mage.Sets/src/mage/sets/Lorwyn.java @@ -86,6 +86,7 @@ public class Lorwyn extends ExpansionSet { cards.add(new SetCardInfo("Brion Stoutarm", 246, Rarity.RARE, mage.cards.b.BrionStoutarm.class)); cards.add(new SetCardInfo("Broken Ambitions", 54, Rarity.COMMON, mage.cards.b.BrokenAmbitions.class)); cards.add(new SetCardInfo("Burrenton Forge-Tender", 7, Rarity.UNCOMMON, mage.cards.b.BurrentonForgeTender.class)); + cards.add(new SetCardInfo("Cairn Wanderer", 105, Rarity.RARE, mage.cards.c.CairnWanderer.class)); cards.add(new SetCardInfo("Caterwauling Boggart", 157, Rarity.COMMON, mage.cards.c.CaterwaulingBoggart.class)); cards.add(new SetCardInfo("Ceaseless Searblades", 158, Rarity.UNCOMMON, mage.cards.c.CeaselessSearblades.class)); cards.add(new SetCardInfo("Cenn's Heir", 8, Rarity.COMMON, mage.cards.c.CennsHeir.class)); @@ -260,7 +261,7 @@ public class Lorwyn extends ExpansionSet { cards.add(new SetCardInfo("Profane Command", 135, Rarity.RARE, mage.cards.p.ProfaneCommand.class)); cards.add(new SetCardInfo("Protective Bubble", 80, Rarity.COMMON, mage.cards.p.ProtectiveBubble.class)); cards.add(new SetCardInfo("Prowess of the Fair", 136, Rarity.UNCOMMON, mage.cards.p.ProwessOfTheFair.class)); - cards.add(new SetCardInfo("Purity", 37, Rarity.RARE, mage.cards.p.Purity.class)); + cards.add(new SetCardInfo("Purity", 37, Rarity.RARE, mage.cards.p.Purity.class)); cards.add(new SetCardInfo("Quill-Slinger Boggart", 137, Rarity.COMMON, mage.cards.q.QuillSlingerBoggart.class)); cards.add(new SetCardInfo("Rebellion of the Flamekin", 188, Rarity.UNCOMMON, mage.cards.r.RebellionOfTheFlamekin.class)); cards.add(new SetCardInfo("Ringskipper", 81, Rarity.COMMON, mage.cards.r.Ringskipper.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/lrw/CairnWandererTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/lrw/CairnWandererTest.java new file mode 100644 index 00000000000..2f3e6851fe3 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/lrw/CairnWandererTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.cards.single.lrw; + +import java.util.ArrayList; +import java.util.List; +import mage.abilities.Ability; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.abilities.keyword.FearAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.HasteAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.PlainswalkAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.keyword.ShroudAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.constants.Zone; +import mage.filter.FilterCard; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author psykad + */ +public class CairnWandererTest extends CardTestPlayerBase { + + /* + * Testing: As long as a creature card with flying is in a graveyard, + * {this} has flying. The same is true for fear, first strike, + * double strike, deathtouch, haste, landwalk, lifelink, protection, + * reach, trample, shroud, and vigilance. + */ + @Test + public void TestCairnWandererEffect() { + addCard(Zone.BATTLEFIELD, playerA, "Cairn Wanderer"); + + // Testing FlyingAbility. + addCard(Zone.GRAVEYARD, playerA, "Lantern Kami"); + + // Testing FearAbility. + addCard(Zone.GRAVEYARD, playerA, "Prickly Boggart"); + + // Testing FirstStrikeAbility. + addCard(Zone.GRAVEYARD, playerA, "Serra Zealot"); + + // Testing DoubleStrikeAbility. + addCard(Zone.GRAVEYARD, playerA, "Fencing Ace"); + + // Testing DeathtouchAbility. + addCard(Zone.GRAVEYARD, playerA, "Typhoid Rats"); + + // Testing HasteAbility. + addCard(Zone.GRAVEYARD, playerA, "Raging Goblin"); + + // Testing LandwalkAbility. + addCard(Zone.GRAVEYARD, playerA, "Zodiac Rooster"); + + // Testing LifelinkAbility. + addCard(Zone.GRAVEYARD, playerA, "Trained Caracal"); + + // Testing ProtectionAbility. + addCard(Zone.GRAVEYARD, playerA, "Progenitus"); + + // Testing ReachAbility. + addCard(Zone.GRAVEYARD, playerA, "Tree Monkey"); + + // Testing TrampleAbility. + addCard(Zone.GRAVEYARD, playerA, "Defiant Elf"); + + // Testing ShroudAbility. + addCard(Zone.GRAVEYARD, playerA, "Elvish Lookout"); + + // Testing VigilanceAbility. + addCard(Zone.GRAVEYARD, playerA, "Veteran Cavalier"); + + execute(); + + List abilities = new ArrayList<>(); + abilities.add(FlyingAbility.getInstance()); + abilities.add(FearAbility.getInstance()); + abilities.add(FirstStrikeAbility.getInstance()); + abilities.add(DoubleStrikeAbility.getInstance()); + abilities.add(DeathtouchAbility.getInstance()); + abilities.add(HasteAbility.getInstance()); + abilities.add(LifelinkAbility.getInstance()); + abilities.add(ReachAbility.getInstance()); + abilities.add(ShroudAbility.getInstance()); + abilities.add(TrampleAbility.getInstance()); + abilities.add(VigilanceAbility.getInstance()); + assertAbilities(playerA, "Cairn Wanderer", abilities); + assertAbility(playerA, "Cairn Wanderer", new PlainswalkAbility(), true); + assertAbility(playerA, "Cairn Wanderer", new ProtectionAbility(new FilterCard("everything")), true); // Progenitus - protection from everything. + } +} \ No newline at end of file From ae3750840b6377820ba7d73132cb82918c49bd23 Mon Sep 17 00:00:00 2001 From: Ryan Skeldon Date: Sat, 26 Nov 2016 02:23:27 -0500 Subject: [PATCH 2/6] Added license --- Mage.Sets/src/mage/cards/c/CairnWanderer.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Mage.Sets/src/mage/cards/c/CairnWanderer.java b/Mage.Sets/src/mage/cards/c/CairnWanderer.java index 6158d38cb81..0a48f58f9b0 100644 --- a/Mage.Sets/src/mage/cards/c/CairnWanderer.java +++ b/Mage.Sets/src/mage/cards/c/CairnWanderer.java @@ -1,3 +1,30 @@ +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ package mage.cards.c; import java.util.UUID; From 36d6c006ca0207db70654d9a380cffdbffdf5441 Mon Sep 17 00:00:00 2001 From: MTGfan Date: Sat, 26 Nov 2016 03:57:46 -0500 Subject: [PATCH 3/6] New AttachedPermanentToughnessValue, updated DamageAttachedControllerEffect to take DynamicValue (ex. to work with AttachedPermanentToughnessValue), and updated Creature Bond to use AttachedPermanentToughnessValue and DamageAttachedControllerEffect. Corrected Gaea's Liege, Kor Scythemaster, Soltari Lancer, and Spirit of the Night to use SourceAttackingCondition. Rmoved the AttackingCondition we creature for Gaea's Liege since it duplicated the already existing SourceAttackingCondition. For the other three using SourceAttackingCondition and the minor changes to the code for them should make their code more efficient. --- Mage.Sets/src/mage/cards/c/CreatureBond.java | 187 +++++++----------- Mage.Sets/src/mage/cards/g/GaeasLiege.java | 4 +- .../src/mage/cards/k/KorScythemaster.java | 9 +- Mage.Sets/src/mage/cards/s/SoltariLancer.java | 8 +- .../src/mage/cards/s/SpiritOfTheNight.java | 8 +- .../condition/common/AttackingCondition.java | 30 --- .../AttachedPermanentToughnessValue.java | 41 ++++ .../DamageAttachedControllerEffect.java | 17 +- 8 files changed, 136 insertions(+), 168 deletions(-) delete mode 100644 Mage/src/main/java/mage/abilities/condition/common/AttackingCondition.java create mode 100644 Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessValue.java diff --git a/Mage.Sets/src/mage/cards/c/CreatureBond.java b/Mage.Sets/src/mage/cards/c/CreatureBond.java index 2c022182861..b88a1283b2f 100644 --- a/Mage.Sets/src/mage/cards/c/CreatureBond.java +++ b/Mage.Sets/src/mage/cards/c/CreatureBond.java @@ -1,113 +1,74 @@ -/* - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package mage.cards.c; - -import java.util.UUID; -import mage.abilities.Ability; -import mage.abilities.Mode; -import mage.abilities.common.DiesAttachedTriggeredAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.AttachEffect; -import mage.abilities.keyword.EnchantAbility; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.TargetPermanent; -import mage.target.common.TargetCreaturePermanent; - -/** - * - * @author Styxo - */ -public class CreatureBond extends CardImpl { - - public CreatureBond(UUID ownerId, CardSetInfo setInfo) { - super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); - - this.subtype.add("Aura"); - - // Enchant creature - TargetPermanent auraTarget = new TargetCreaturePermanent(); - this.getSpellAbility().addTarget(auraTarget); - this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit)); - Ability ability = new EnchantAbility(auraTarget.getTargetName()); - this.addAbility(ability); - - // When enchanted creature dies, Creature Bond deals damage equal to that creature's toughness to the creature's controller. - this.addAbility(new DiesAttachedTriggeredAbility(new CreatureBondEffect(), "enchanted creature")); - - } - - public CreatureBond(final CreatureBond card) { - super(card); - } - - @Override - public CreatureBond copy() { - return new CreatureBond(this); - } -} - -class CreatureBondEffect extends OneShotEffect { - - public CreatureBondEffect() { - super(Outcome.Damage); - } - - public CreatureBondEffect(CreatureBondEffect copy) { - super(copy); - } - - @Override - public CreatureBondEffect copy() { - return new CreatureBondEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent creature = (Permanent) getValue("attachedTo"); - if (creature != null) { - Player player = game.getPlayer(creature.getOwnerId()); - if (player != null) { - player.damage(creature.getToughness().getValue(), source.getId(), game, false, true); - return true; - } - } - return false; - } - - @Override - public String getText(Mode mode) { - return "{this} deals damage equal to that creature's toughness to the creature's controller"; - } - -} +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package mage.cards.c; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.common.DiesAttachedTriggeredAbility; +import mage.abilities.dynamicvalue.common.AttachedPermanentToughnessValue; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DamageAttachedControllerEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.target.TargetPermanent; +import mage.target.common.TargetCreaturePermanent; + +/** + * + * @author MTGfan + */ +public class CreatureBond extends CardImpl { + + public CreatureBond(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}"); + + this.subtype.add("Aura"); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + Ability ability = new EnchantAbility(auraTarget.getTargetName()); + this.addAbility(ability); + + // When enchanted creature dies, Creature Bond deals damage equal to that creature's toughness to the creature's controller. + this.addAbility( new DiesAttachedTriggeredAbility(new DamageAttachedControllerEffect(new AttachedPermanentToughnessValue()), "enchanted creature")); + } + + public CreatureBond(final CreatureBond card) { + super(card); + } + + @Override + public CreatureBond copy() { + return new CreatureBond(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GaeasLiege.java b/Mage.Sets/src/mage/cards/g/GaeasLiege.java index 27ca80114fc..6e17bd4ae84 100644 --- a/Mage.Sets/src/mage/cards/g/GaeasLiege.java +++ b/Mage.Sets/src/mage/cards/g/GaeasLiege.java @@ -32,7 +32,7 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.AttackingCondition; +import mage.abilities.condition.common.SourceAttackingCondition; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.dynamicvalue.DynamicValue; @@ -76,7 +76,7 @@ public class GaeasLiege extends CardImpl { this.addAbility(new SimpleStaticAbility(Zone.ALL, new ConditionalContinuousEffect( new SetPowerToughnessSourceEffect(new DefendersForestCount(), Duration.EndOfCombat), new SetPowerToughnessSourceEffect(new PermanentsOnBattlefieldCount(filterLands), Duration.EndOfGame), - AttackingCondition.getInstance(), + SourceAttackingCondition.getInstance(), "As long as {this} isn't attacking, its power and toughness are each equal to the number of Forests you control. As long as {this} is attacking, its power and toughness are each equal to the number of Forests defending player controls."))); // {tap}: Target land becomes a Forest until Gaea's Liege leaves the battlefield. Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesBasicLandTargetEffect(Duration.WhileOnBattlefield, "Forest"), new TapSourceCost()); diff --git a/Mage.Sets/src/mage/cards/k/KorScythemaster.java b/Mage.Sets/src/mage/cards/k/KorScythemaster.java index 4f24620ad87..4befcda12a8 100644 --- a/Mage.Sets/src/mage/cards/k/KorScythemaster.java +++ b/Mage.Sets/src/mage/cards/k/KorScythemaster.java @@ -30,7 +30,7 @@ package mage.cards.k; import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.condition.common.SourceAttackingCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FirstStrikeAbility; @@ -38,7 +38,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterAttackingCreature; /** * @@ -46,8 +45,6 @@ import mage.filter.common.FilterAttackingCreature; */ public class KorScythemaster extends CardImpl { - private static final String rule = "{this} has first strike as long as it's attacking"; - public KorScythemaster(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}"); this.subtype.add("Kor"); @@ -57,9 +54,7 @@ public class KorScythemaster extends CardImpl { this.toughness = new MageInt(1); // Kor Scythemaster has first strike as long as its attacking. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), - new SourceMatchesFilterCondition(new FilterAttackingCreature()), rule); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), SourceAttackingCondition.getInstance(), "{this} has first strike as long as it's attacking"))); } public KorScythemaster(final KorScythemaster card) { diff --git a/Mage.Sets/src/mage/cards/s/SoltariLancer.java b/Mage.Sets/src/mage/cards/s/SoltariLancer.java index 62a139d0ddd..62fb1c03663 100644 --- a/Mage.Sets/src/mage/cards/s/SoltariLancer.java +++ b/Mage.Sets/src/mage/cards/s/SoltariLancer.java @@ -30,7 +30,7 @@ package mage.cards.s; import java.util.UUID; import mage.MageInt; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.SourceMatchesFilterCondition; +import mage.abilities.condition.common.SourceAttackingCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FirstStrikeAbility; @@ -39,7 +39,6 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.Zone; -import mage.filter.common.FilterAttackingCreature; /** * @@ -47,8 +46,6 @@ import mage.filter.common.FilterAttackingCreature; */ public class SoltariLancer extends CardImpl { - private static final String rule = "{this} has first strike as long as it's attacking"; - public SoltariLancer(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{W}"); this.subtype.add("Soltari"); @@ -60,8 +57,7 @@ public class SoltariLancer extends CardImpl { this.addAbility(ShadowAbility.getInstance()); // Soltari Lancer has first strike as long as it's attacking. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), new SourceMatchesFilterCondition(new FilterAttackingCreature()), rule); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), SourceAttackingCondition.getInstance(), "{this} has first strike as long as it's attacking"))); } public SoltariLancer(final SoltariLancer card) { diff --git a/Mage.Sets/src/mage/cards/s/SpiritOfTheNight.java b/Mage.Sets/src/mage/cards/s/SpiritOfTheNight.java index e39967eb971..e186413d9d0 100644 --- a/Mage.Sets/src/mage/cards/s/SpiritOfTheNight.java +++ b/Mage.Sets/src/mage/cards/s/SpiritOfTheNight.java @@ -31,16 +31,15 @@ import mage.constants.CardType; import mage.MageInt; import mage.ObjectColor; import mage.abilities.common.SimpleStaticAbility; -import mage.abilities.condition.common.SourceMatchesFilterCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.Zone; -import mage.filter.common.FilterAttackingCreature; import java.util.UUID; +import mage.abilities.condition.common.SourceAttackingCondition; /** * @@ -48,8 +47,6 @@ import java.util.UUID; */ public class SpiritOfTheNight extends CardImpl { - private static final String rule = "Spirit of the Night has first strike as long as it's attacking"; - public SpiritOfTheNight(UUID ownerId, CardSetInfo setInfo) { super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{6}{B}{B}{B}"); this.supertype.add("Legendary"); @@ -72,8 +69,7 @@ public class SpiritOfTheNight extends CardImpl { this.addAbility(ProtectionAbility.from(ObjectColor.BLACK)); // Spirit of the Night has first strike as long as it's attacking. - ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), new SourceMatchesFilterCondition(new FilterAttackingCreature()), rule); - this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, effect)); + this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new ConditionalContinuousEffect(new GainAbilitySourceEffect(FirstStrikeAbility.getInstance()), SourceAttackingCondition.getInstance(), "{this} has first strike as long as it's attacking"))); } public SpiritOfTheNight(final SpiritOfTheNight card) { diff --git a/Mage/src/main/java/mage/abilities/condition/common/AttackingCondition.java b/Mage/src/main/java/mage/abilities/condition/common/AttackingCondition.java deleted file mode 100644 index a1b315a3b09..00000000000 --- a/Mage/src/main/java/mage/abilities/condition/common/AttackingCondition.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.abilities.condition.common; - -import mage.abilities.Ability; -import mage.abilities.condition.Condition; -import mage.game.Game; -import mage.game.permanent.Permanent; - -/** - * - * @author anonymous - */ -public class AttackingCondition implements Condition { - - private static final AttachedCondition fInstance = new AttachedCondition(); - - public static AttachedCondition getInstance() { - return fInstance; - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - return permanent != null && permanent.isAttacking(); - } -} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessValue.java new file mode 100644 index 00000000000..2dab3cdd799 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AttachedPermanentToughnessValue.java @@ -0,0 +1,41 @@ +/* + * 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.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * + * @author MTGfan + */ +public class AttachedPermanentToughnessValue implements DynamicValue { + + @Override + public int calculate(Game game, Ability source, Effect effect) { + Permanent enchantment = game.getPermanentOrLKIBattlefield(source.getSourceId()); + Permanent enchanted = game.getPermanentOrLKIBattlefield(enchantment.getAttachedTo()); + return enchanted.getToughness().getValue(); + } + + @Override + public AttachedPermanentToughnessValue copy(){ + return new AttachedPermanentToughnessValue(); + } + + @Override + public String toString() { + return "equal to"; + } + + @Override + public String getMessage() { + return "that creature's toughness"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/DamageAttachedControllerEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DamageAttachedControllerEffect.java index b248481aad9..4db95ed083f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DamageAttachedControllerEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DamageAttachedControllerEffect.java @@ -30,6 +30,8 @@ package mage.abilities.effects.common; import mage.abilities.Ability; import mage.abilities.Mode; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.game.Game; @@ -42,9 +44,14 @@ import mage.players.Player; */ public class DamageAttachedControllerEffect extends OneShotEffect { - protected int amount; + protected DynamicValue amount; public DamageAttachedControllerEffect(int amount) { + super(Outcome.Damage); + this.amount = new StaticValue(amount); + } + + public DamageAttachedControllerEffect(DynamicValue amount) { super(Outcome.Damage); this.amount = amount; } @@ -71,7 +78,7 @@ public class DamageAttachedControllerEffect extends OneShotEffect { } Player player = game.getPlayer(enchanted.getControllerId()); if(player != null) { - player.damage(amount, source.getSourceId(), game, false, true); + player.damage(amount.calculate(game, source, this), source.getSourceId(), game, false, true); return true; } return false; @@ -82,7 +89,9 @@ public class DamageAttachedControllerEffect extends OneShotEffect { if (staticText != null && !staticText.isEmpty()) { return staticText; } - return "{this} deals " + amount + " damage to that creature's controller"; + if ("equal to".equals(amount.toString())) { + return "{this} deals damage " + amount + " that creatures toughness to that creature's controller"; + } + return "{this} deals " + amount + " damage to that creature's controller"; } - } From 946b72336171d44508cb78f7c8e41728e96b2c5d Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 26 Nov 2016 10:22:23 +0100 Subject: [PATCH 4/6] * Fixed possible null pointer exceptions. --- .../abilities/effects/common/combat/CantAttackYouAllEffect.java | 2 +- .../java/mage/abilities/effects/keyword/ManifestEffect.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java index 1dbf2cf026f..c681328c3cc 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/combat/CantAttackYouAllEffect.java @@ -76,7 +76,7 @@ public class CantAttackYouAllEffect extends RestrictionEffect { @Override public boolean canAttack(Permanent attacker, UUID defenderId, Ability source, Game game) { return (!alsoPlaneswalker && !defenderId.equals(source.getControllerId())) - || (alsoPlaneswalker && !game.getCombat().getDefendingPlayerId(attacker.getId(), game).equals(source.getControllerId())); + || (alsoPlaneswalker && !(source.getControllerId().equals(game.getCombat().getDefendingPlayerId(attacker.getId(), game)))); } @Override diff --git a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java index b64d9f792fe..206eb12a417 100644 --- a/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/keyword/ManifestEffect.java @@ -79,7 +79,7 @@ public class ManifestEffect extends OneShotEffect { for (Card card : cards) { ManaCosts manaCosts = null; if (card.getCardType().contains(CardType.CREATURE)) { - manaCosts = card.getSpellAbility().getManaCosts(); + manaCosts = card.getSpellAbility() != null ? card.getSpellAbility().getManaCosts() : null; if (manaCosts == null) { manaCosts = new ManaCostsImpl("{0}"); } From 26e3cde8b38c50cebd7309394c82d9dbb78a619a Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 26 Nov 2016 10:38:48 +0100 Subject: [PATCH 5/6] * Some minor chnages to Cairn Wanderer. --- Mage.Sets/src/mage/cards/c/CairnWanderer.java | 70 +++++++++++-------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CairnWanderer.java b/Mage.Sets/src/mage/cards/c/CairnWanderer.java index 0a48f58f9b0..720866bacd2 100644 --- a/Mage.Sets/src/mage/cards/c/CairnWanderer.java +++ b/Mage.Sets/src/mage/cards/c/CairnWanderer.java @@ -30,6 +30,7 @@ package mage.cards.c; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.MageSingleton; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.keyword.ChangelingAbility; @@ -64,17 +65,18 @@ import mage.players.Player; * @author psykad */ public class CairnWanderer extends CardImpl { + public CairnWanderer(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}"); - + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}"); + this.subtype.add("Shapeshifter"); this.power = new MageInt(4); - this.toughness = new MageInt(4); - + this.toughness = new MageInt(4); + // Changeling this.addAbility(ChangelingAbility.getInstance()); - - // As long as a creature card with flying is in a graveyard, {this} has flying. The same is true for fear, first strike, double strike, deathtouch, haste, landwalk, lifelink, protection, reach, trample, shroud, and vigilance. + + // As long as a creature card with flying is in a graveyard, Cairn Wanderer has flying. The same is true for fear, first strike, double strike, deathtouch, haste, landwalk, lifelink, protection, reach, trample, shroud, and vigilance. this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new CairnWandererEffect())); } @@ -86,51 +88,57 @@ public class CairnWanderer extends CardImpl { public CairnWanderer copy() { return new CairnWanderer(this); } - + class CairnWandererEffect extends ContinuousEffectImpl { + public CairnWandererEffect() { super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); staticText = "As long as a creature card with flying is in a graveyard, {this} has flying. The same is true for fear, first strike, double strike, deathtouch, haste, landwalk, lifelink, protection, reach, trample, shroud, and vigilance."; } - + public CairnWandererEffect(final CairnWandererEffect effect) { super(effect); } @Override public boolean apply(Game game, Ability source) { - Permanent perm = game.getPermanent(source.getSourceId()); - - if (perm == null) return false; - + Permanent sourcePermanent = game.getPermanent(source.getSourceId()); + + if (sourcePermanent == null) { + return false; + } + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { Player player = game.getPlayer(playerId); - + if (player != null) { for (Card card : player.getGraveyard().getCards(game)) { if (card.getCardType().contains(CardType.CREATURE)) { - for (Ability ability : card.getAbilities()) { - if (ability instanceof FlyingAbility || - ability instanceof FearAbility || - ability instanceof FirstStrikeAbility || - ability instanceof DoubleStrikeAbility || - ability instanceof DeathtouchAbility || - ability instanceof HasteAbility || - ability instanceof LandwalkAbility || - ability instanceof LifelinkAbility || - ability instanceof ProtectionAbility || - ability instanceof ReachAbility || - ability instanceof TrampleAbility || - ability instanceof ShroudAbility || - ability instanceof VigilanceAbility) { - perm.addAbility(ability, game); + for (Ability ability : card.getAbilities(game)) { + if (ability instanceof MageSingleton) { + if (ability instanceof FlyingAbility + || ability instanceof FearAbility + || ability instanceof FirstStrikeAbility + || ability instanceof DoubleStrikeAbility + || ability instanceof DeathtouchAbility + || ability instanceof HasteAbility + || ability instanceof LifelinkAbility + || ability instanceof ReachAbility + || ability instanceof TrampleAbility + || ability instanceof ShroudAbility + || ability instanceof VigilanceAbility) { + sourcePermanent.addAbility(ability, game); + } + } else if (ability instanceof ProtectionAbility + || ability instanceof LandwalkAbility) { + sourcePermanent.addAbility(ability, game); } } } } } } - + return true; } @@ -138,5 +146,5 @@ public class CairnWanderer extends CardImpl { public CairnWandererEffect copy() { return new CairnWandererEffect(this); } - } -} \ No newline at end of file + } +} From 8bf299e3421c966bea505c6d4e2dff156ed93c85 Mon Sep 17 00:00:00 2001 From: LevelX2 Date: Sat, 26 Nov 2016 13:46:10 +0100 Subject: [PATCH 6/6] * Fixed and extended extra turn test. --- .../org/mage/test/turnmod/ExtraTurnsTest.java | 196 +++++++++++------- Mage/src/main/java/mage/game/GameImpl.java | 18 +- Mage/src/main/java/mage/game/turn/Phase.java | 10 +- Mage/src/main/java/mage/game/turn/Turn.java | 3 +- 4 files changed, 145 insertions(+), 82 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java index 347e271c08e..c7746838f34 100644 --- a/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/turnmod/ExtraTurnsTest.java @@ -1,70 +1,126 @@ -/* - * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of BetaSteward_at_googlemail.com. - */ -package org.mage.test.turnmod; - -import mage.constants.PhaseStep; -import mage.constants.Zone; -import org.junit.Assert; -import org.junit.Test; -import org.mage.test.serverside.base.CardTestPlayerBase; - -/** - * - * @author LevelX2 - */ -public class ExtraTurnsTest extends CardTestPlayerBase { - - /** - * Emrakul, the Promised End not giving an extra turn when cast in the - * opponent's turn - */ - @Test - public void testEmrakulCastOnOpponentsTurn() { - addCard(Zone.BATTLEFIELD, playerA, "Island", 12); - addCard(Zone.GRAVEYARD, playerA, "Island", 1); - // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. - // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. - // Flying - // Trample - // Protection from instants - addCard(Zone.HAND, playerA, "Emrakul, the Promised End", 1); // {13} - // Flash (You may cast this spell any time you could cast an instant.) - // Creature cards you own that aren't on the battlefield have flash. - // Each opponent can cast spells only any time he or she could cast a sorcery. - addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir", 1); - - castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Promised End"); - - setStopAt(3, PhaseStep.UPKEEP); - execute(); - - assertPermanentCount(playerA, "Emrakul, the Promised End", 1); - - Assert.assertTrue("For extra turn, playerB has to be the active player ", currentGame.getActivePlayerId().equals(playerB.getId())); - } -} +/* + * Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of BetaSteward_at_googlemail.com. + */ +package org.mage.test.turnmod; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author LevelX2 + */ +public class ExtraTurnsTest extends CardTestPlayerBase { + + /** + * Emrakul, the Promised End not giving an extra turn when cast in the + * opponent's turn + */ + @Test + public void testEmrakulCastOnOpponentsTurnCheckTurn3() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 12); + addCard(Zone.GRAVEYARD, playerA, "Island", 1); + // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. + // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. + // Flying + // Trample + // Protection from instants + addCard(Zone.HAND, playerA, "Emrakul, the Promised End", 1); // {13} + // Flash (You may cast this spell any time you could cast an instant.) + // Creature cards you own that aren't on the battlefield have flash. + // Each opponent can cast spells only any time he or she could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Promised End"); + // Turn 4 is the next turn of opponent (player B) that player A controls + // So Turn 5 is the extra turn for player B after Turn 4 + setStopAt(3, PhaseStep.DRAW); + execute(); + + assertPermanentCount(playerA, "Emrakul, the Promised End", 1); + Assert.assertTrue("Turn 3 is no extra turn ", !currentGame.getState().isExtraTurn()); + Assert.assertTrue("For turn " + currentGame.getTurnNum() + ", playerA has to be the active player but active player is: " + + currentGame.getPlayer(currentGame.getActivePlayerId()).getName(), currentGame.getActivePlayerId().equals(playerA.getId())); + } + + @Test + public void testEmrakulCastOnOpponentsTurnCheckTurn4() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 12); + addCard(Zone.GRAVEYARD, playerA, "Island", 1); + // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. + // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. + // Flying + // Trample + // Protection from instants + addCard(Zone.HAND, playerA, "Emrakul, the Promised End", 1); // {13} + // Flash (You may cast this spell any time you could cast an instant.) + // Creature cards you own that aren't on the battlefield have flash. + // Each opponent can cast spells only any time he or she could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Promised End"); + // Turn 4 is the next turn of opponent (player B) that player A controls + // So Turn 5 is the extra turn for player B after Turn 4 + setStopAt(4, PhaseStep.DRAW); + execute(); + + assertPermanentCount(playerA, "Emrakul, the Promised End", 1); + Assert.assertTrue("Turn 4 is a controlled turn ", !playerB.isGameUnderControl()); + Assert.assertTrue("For turn " + currentGame.getTurnNum() + ", playerB has to be the active player but active player is: " + + currentGame.getPlayer(currentGame.getActivePlayerId()).getName(), currentGame.getActivePlayerId().equals(playerB.getId())); + } + + @Test + public void testEmrakulCastOnOpponentsTurnCheckTurn5() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 12); + addCard(Zone.GRAVEYARD, playerA, "Island", 1); + // Emrakul, the Promised End costs {1} less to cast for each card type among cards in your graveyard. + // When you cast Emrakul, you gain control of target opponent during that player's next turn. After that turn, that player takes an extra turn. + // Flying + // Trample + // Protection from instants + addCard(Zone.HAND, playerA, "Emrakul, the Promised End", 1); // {13} + // Flash (You may cast this spell any time you could cast an instant.) + // Creature cards you own that aren't on the battlefield have flash. + // Each opponent can cast spells only any time he or she could cast a sorcery. + addCard(Zone.BATTLEFIELD, playerA, "Teferi, Mage of Zhalfir", 1); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Emrakul, the Promised End"); + // Turn 4 is the next turn of opponent (player B) that player A controls + // So Turn 5 is the extra turn for player B after Turn 4 + setStopOnTurn(5); + execute(); + + assertPermanentCount(playerA, "Emrakul, the Promised End", 1); + Assert.assertTrue("Turn 5 is an extra turn ", currentGame.getState().isExtraTurn()); + Assert.assertTrue("For turn " + currentGame.getTurnNum() + ", playerB has to be the active player but active player is: " + + currentGame.getPlayer(currentGame.getActivePlayerId()).getName(), currentGame.getActivePlayerId().equals(playerB.getId())); + } +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 451f9bbb873..38fa841791f 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -759,14 +759,18 @@ public abstract class GameImpl implements Game, Serializable { Player playerByOrder = getPlayer(playerList.get()); state.setPlayerByOrderId(playerByOrder.getId()); while (!isPaused() && !gameOver(null)) { - playExtraTurns(); + if (!playExtraTurns()) { + break; + } GameEvent event = new GameEvent(GameEvent.EventType.PLAY_TURN, null, null, playerByOrder.getId()); if (!replaceEvent(event)) { if (!playTurn(playerByOrder)) { break; } } - playExtraTurns(); + if (!playExtraTurns()) { + break; + } playerByOrder = playerList.getNext(this); state.setPlayerByOrderId(playerByOrder.getId()); } @@ -792,7 +796,7 @@ public abstract class GameImpl implements Game, Serializable { } } - private void playExtraTurns() { + private boolean playExtraTurns() { //20091005 - 500.7 TurnMod extraTurn = getNextExtraTurn(); while (extraTurn != null) { @@ -805,14 +809,16 @@ public abstract class GameImpl implements Game, Serializable { if (!this.isSimulation()) { informPlayers(extraPlayer.getLogName() + " takes an extra turn"); } - playTurn(extraPlayer); + if (!playTurn(extraPlayer)) { + return false; + } } } extraTurn = getNextExtraTurn(); } state.setTurnId(null); state.setExtraTurn(false); - + return true; } private TurnMod getNextExtraTurn() { @@ -2275,7 +2281,7 @@ public abstract class GameImpl implements Game, Serializable { * * @param playerId */ - protected void leave(UUID playerId) { + protected void leave(UUID playerId) { // needs to be executed from the game thread, not from the concede thread of conceding player! Player player = getPlayer(playerId); if (player == null || player.hasLeft()) { diff --git a/Mage/src/main/java/mage/game/turn/Phase.java b/Mage/src/main/java/mage/game/turn/Phase.java index a474976eaae..4d2d1a0d331 100644 --- a/Mage/src/main/java/mage/game/turn/Phase.java +++ b/Mage/src/main/java/mage/game/turn/Phase.java @@ -133,11 +133,11 @@ public abstract class Phase implements Serializable { } private boolean checkStopOnStepOption(Game game) { - if (game.getOptions().stopOnTurn != null && game.getOptions().stopAtStep == getStep().getType()) { - if (game.getOptions().stopOnTurn <= game.getState().getTurnNum()) { - game.pause(); - return true; - } + if (game.getOptions().stopOnTurn != null + && game.getOptions().stopOnTurn <= game.getState().getTurnNum() + && game.getOptions().stopAtStep == getStep().getType()) { + game.pause(); + return true; } return false; } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index 9239e55e8b6..bf2dc0a8725 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -337,7 +337,8 @@ public class Turn implements Serializable { } private void logStartOfTurn(Game game, Player player) { - StringBuilder sb = new StringBuilder("Turn ").append(game.getState().getTurnNum()).append(" "); + StringBuilder sb = new StringBuilder(game.getState().isExtraTurn() ? "Extra turn" : "Turn "); + sb.append(game.getState().getTurnNum()).append(" "); sb.append(player.getLogName()); sb.append(" ("); int delimiter = game.getPlayers().size() - 1;