diff --git a/Mage.Sets/src/mage/sets/gatecrash/CartelAristocrat.java b/Mage.Sets/src/mage/sets/gatecrash/CartelAristocrat.java index 83086c0d761..4ff2be9d3f6 100644 --- a/Mage.Sets/src/mage/sets/gatecrash/CartelAristocrat.java +++ b/Mage.Sets/src/mage/sets/gatecrash/CartelAristocrat.java @@ -32,7 +32,6 @@ import mage.constants.CardType; import mage.constants.Rarity; import mage.constants.Zone; import mage.MageInt; -import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.SacrificeTargetCost; import mage.abilities.effects.common.continuous.GainProtectionFromColorSourceEffect; @@ -44,19 +43,18 @@ import mage.filter.predicate.permanent.AnotherPredicate; import mage.target.common.TargetControlledPermanent; /** - * Gatecrash FAQ (01.2013) - * You choose the color when the ability resolves. + * Gatecrash FAQ (01.2013) You choose the color when the ability resolves. * * @author LevelX2 */ public class CartelAristocrat extends CardImpl { - + private static final FilterControlledPermanent filter = new FilterControlledCreaturePermanent("another creature"); static { filter.add(new AnotherPredicate()); } - + public CartelAristocrat(UUID ownerId) { super(ownerId, 150, "Cartel Aristocrat", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{W}{B}"); this.expansionSetCode = "GTC"; diff --git a/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java new file mode 100644 index 00000000000..eb0c3c4477c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/multiplayer/MyriadTest.java @@ -0,0 +1,123 @@ +/* + * 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.multiplayer; + +import java.io.FileNotFoundException; +import mage.constants.MultiplayerAttackOption; +import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; +import mage.constants.Zone; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestMultiPlayerBase; + +/** + * + * @author LevelX2 + */ +public class MyriadTest extends CardTestMultiPlayerBase { + + @Override + protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { + Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, 0, 40); + // Player order: A -> D -> C -> B + playerA = createPlayer(game, playerA, "PlayerA"); + playerB = createPlayer(game, playerB, "PlayerB"); + playerC = createPlayer(game, playerC, "PlayerC"); + playerD = createPlayer(game, playerD, "PlayerD"); + return game; + } + + /** + * Tests Myriad multiplayer effects Player order: A -> D -> C -> B + */ + @Test + public void CallerOfThePackTest() { + // Trample + // Myriad (Whenever this creature attacks, for each opponent other than the defending player, put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile those tokens at the end of combat.) + addCard(Zone.BATTLEFIELD, playerD, "Caller of the Pack"); // 8/6 + + attack(2, playerD, "Caller of the Pack", playerA); + + setStopAt(2, PhaseStep.DECLARE_BLOCKERS); + execute(); + + assertPermanentCount(playerD, "Caller of the Pack", 3); + } + + @Test + public void CallerOfThePackTestExile() { + // Trample + // Myriad (Whenever this creature attacks, for each opponent other than the defending player, put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile those tokens at the end of combat.) + addCard(Zone.BATTLEFIELD, playerD, "Caller of the Pack"); // 8/6 + + attack(2, playerD, "Caller of the Pack", playerA); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerD, "Caller of the Pack", 1); + + assertLife(playerA, 32); + assertLife(playerB, 32); + assertLife(playerC, 32); + assertLife(playerD, 40); + + } + + @Test + public void CallerOfThePackTestExilePlaneswalker() { + // Trample + // Myriad (Whenever this creature attacks, for each opponent other than the defending player, put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile those tokens at the end of combat.) + addCard(Zone.BATTLEFIELD, playerD, "Caller of the Pack"); // 8/6 + + // +1: You gain 2 life. + // -1: Put a +1/+1 counter on each creature you control. Those creatures gain vigilance until end of turn. + // -6: Put a white Avatar creature token onto the battlefield. It has "This creature's power and toughness are each equal to your life total." + addCard(Zone.BATTLEFIELD, playerA, "Ajani Goldmane"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:"); + + attack(2, playerD, "Caller of the Pack", playerC); + addTarget(playerD, "Ajani Goldmane"); + + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerD, "Caller of the Pack", 1); + assertGraveyardCount(playerA, "Ajani Goldmane", 1); + + assertLife(playerA, 42); + assertLife(playerB, 32); + assertLife(playerC, 32); + assertLife(playerD, 40); + + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java index 36ededfe4a3..863ac2b2565 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestMultiPlayerBase.java @@ -1,22 +1,18 @@ package org.mage.test.serverside.base; -import mage.cards.Card; -import mage.cards.decks.Deck; -import mage.cards.decks.importer.DeckImporterUtil; +import java.io.FileNotFoundException; import mage.constants.MultiplayerAttackOption; import mage.constants.RangeOfInfluence; -import mage.game.*; -import mage.game.permanent.Permanent; -import mage.players.Player; -import org.junit.Assert; +import mage.game.FreeForAll; +import mage.game.Game; +import mage.game.GameException; import org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl; -import java.io.File; -import java.io.FileNotFoundException; - /** - * Base class for testing single cards and effects in multiplayer game. - * For PvP games {@see CardTestPlayerBase} + * Base class for testing single cards and effects in multiplayer game. For PvP + * games { + * + * @see CardTestPlayerBase} * * @author magenoxx_at_gmail.com */ @@ -25,12 +21,12 @@ public abstract class CardTestMultiPlayerBase extends CardTestPlayerAPIImpl { @Override protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException { Game game = new FreeForAll(MultiplayerAttackOption.LEFT, RangeOfInfluence.ONE, 0, 20); - + // Player order: A -> D -> C -> B playerA = createPlayer(game, playerA, "PlayerA"); playerB = createPlayer(game, playerB, "PlayerB"); playerC = createPlayer(game, playerC, "PlayerC"); playerD = createPlayer(game, playerD, "PlayerD"); return game; } - + } diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java index 910f3de6ec4..94e0699a280 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/CardTestPlayerBaseAI.java @@ -55,13 +55,13 @@ public abstract class CardTestPlayerBaseAI extends CardTestPlayerAPIImpl { } @Override - protected TestPlayer createPlayer(String name) { + protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { if (name.equals("PlayerA")) { TestPlayer testPlayer = new TestPlayer(new ComputerPlayer7("PlayerA", RangeOfInfluence.ONE, skill)); testPlayer.setAIPlayer(true); return testPlayer; } - return super.createPlayer(name); + return super.createPlayer(name, rangeOfInfluence); } public void setAISkill(int skill) { diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java index 32805289ed6..6568f377b3f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/MageTestPlayerBase.java @@ -1,15 +1,26 @@ package org.mage.test.serverside.base; -import mage.constants.PhaseStep; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FilenameFilter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import mage.cards.Card; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; +import mage.constants.PhaseStep; import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.game.Game; import mage.game.match.MatchType; import mage.game.permanent.PermanentCard; import mage.game.tournament.TournamentType; +import mage.player.ai.ComputerPlayer; import mage.players.Player; import mage.server.game.GameFactory; import mage.server.util.ConfigSettings; @@ -22,20 +33,13 @@ import org.apache.log4j.Logger; import org.junit.BeforeClass; import org.mage.test.player.TestPlayer; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FilenameFilter; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import mage.player.ai.ComputerPlayer; - /** * Base class for all tests. * * @author ayratn */ public abstract class MageTestPlayerBase { + protected static Logger logger = Logger.getLogger(MageTestPlayerBase.class); public static PluginClassLoader classLoader = new PluginClassLoader(); @@ -62,8 +66,7 @@ public abstract class MageTestPlayerBase { protected static Game currentGame = null; /** - * Player thats starts the game first. - * By default, it is ComputerA. + * Player thats starts the game first. By default, it is ComputerA. */ protected static Player activePlayer = null; @@ -72,6 +75,7 @@ public abstract class MageTestPlayerBase { protected PhaseStep stopAtStep = PhaseStep.UNTAP; protected enum ParserState { + INIT, OPTIONS, EXPECTED @@ -80,16 +84,11 @@ public abstract class MageTestPlayerBase { protected ParserState parserState; /** - * Expected results of the test. - * Read from test case in {@link String} based format: + * Expected results of the test. Read from test case in {@link String} based + * format: *

- * Example: - * turn:1 - * result:won:ComputerA - * life:ComputerA:20 - * life:ComputerB:0 - * battlefield:ComputerB:Tine Shrike:0 - * graveyard:ComputerB:Tine Shrike:1 + * Example: turn:1 result:won:ComputerA life:ComputerA:20 life:ComputerB:0 + * battlefield:ComputerB:Tine Shrike:0 graveyard:ComputerB:Tine Shrike:1 */ protected List expectedResults = new ArrayList<>(); @@ -259,7 +258,7 @@ public abstract class MageTestPlayerBase { logger.warn("Init string wasn't parsed: " + line); } } - + private TestPlayer getPlayer(String name) { switch (name) { case "ComputerA": @@ -282,7 +281,7 @@ public abstract class MageTestPlayerBase { handCards.put(player, hand); return hand; } - + protected List getGraveCards(TestPlayer player) { if (graveyardCards.containsKey(player)) { return graveyardCards.get(player); @@ -319,7 +318,6 @@ public abstract class MageTestPlayerBase { return command; } - private void includeFrom(String line) throws FileNotFoundException { String[] params = line.split(" "); if (params.length == 2) { @@ -340,8 +338,8 @@ public abstract class MageTestPlayerBase { } } - protected TestPlayer createPlayer(String name) { - return new TestPlayer(new ComputerPlayer(name, RangeOfInfluence.ONE)); + protected TestPlayer createPlayer(String name, RangeOfInfluence rangeOfInfluence) { + return new TestPlayer(new ComputerPlayer(name, rangeOfInfluence)); } - + } 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 6e85c699d7c..9a02e9bcf80 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 @@ -12,6 +12,7 @@ import mage.cards.repository.CardRepository; import mage.cards.repository.CardScanner; import mage.constants.CardType; import mage.constants.PhaseStep; +import mage.constants.RangeOfInfluence; import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.Filter; @@ -141,7 +142,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } protected TestPlayer createPlayer(Game game, TestPlayer player, String name, String deckName) throws GameException { - player = createNewPlayer(name); + player = createNewPlayer(name, game.getRangeOfInfluence()); player.setTestMode(true); logger.debug("Loading deck..."); Deck deck = Deck.load(DeckImporterUtil.importDeck(deckName), false, false); @@ -187,8 +188,8 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement } - protected TestPlayer createNewPlayer(String playerName) { - return createPlayer(playerName); + protected TestPlayer createNewPlayer(String playerName, RangeOfInfluence rangeOfInfluence) { + return createPlayer(playerName, rangeOfInfluence); } protected Player getPlayerFromName(String playerName, String line) { diff --git a/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java b/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java index 3d42d6db098..59958328733 100644 --- a/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java +++ b/Mage/src/mage/abilities/effects/common/PutTokenOntoBattlefieldCopyTargetEffect.java @@ -60,6 +60,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { private String additionalSubType; private boolean tapped; private boolean attacking; + private UUID attackedPlayer; public PutTokenOntoBattlefieldCopyTargetEffect() { super(Outcome.PutCreatureInPlay); @@ -68,6 +69,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { this.addedTokenPermanents = new ArrayList<>(); this.number = 1; this.additionalSubType = null; + this.attackedPlayer = null; } public PutTokenOntoBattlefieldCopyTargetEffect(UUID playerId) { @@ -89,8 +91,14 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { * @param additionalCardType the token gains tis card types in addition * @param gainsHaste the token gains haste * @param number number of tokens to put into play + * @param tapped + * @param attacking */ public PutTokenOntoBattlefieldCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number, boolean tapped, boolean attacking) { + this(playerId, additionalCardType, gainsHaste, number, tapped, attacking, null); + } + + public PutTokenOntoBattlefieldCopyTargetEffect(UUID playerId, CardType additionalCardType, boolean gainsHaste, int number, boolean tapped, boolean attacking, UUID attackedPlayer) { super(Outcome.PutCreatureInPlay); this.playerId = playerId; this.additionalCardType = additionalCardType; @@ -99,6 +107,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { this.number = number; this.tapped = tapped; this.attacking = attacking; + this.attackedPlayer = attackedPlayer; } public PutTokenOntoBattlefieldCopyTargetEffect(final PutTokenOntoBattlefieldCopyTargetEffect effect) { @@ -111,6 +120,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { this.additionalSubType = effect.additionalSubType; this.tapped = effect.tapped; this.attacking = effect.attacking; + this.attackedPlayer = effect.attackedPlayer; } @Override @@ -159,7 +169,7 @@ public class PutTokenOntoBattlefieldCopyTargetEffect extends OneShotEffect { token.getSubtype().add(additionalSubType); } } - token.putOntoBattlefield(number, game, source.getSourceId(), playerId == null ? source.getControllerId() : playerId, tapped, attacking); + token.putOntoBattlefield(number, game, source.getSourceId(), playerId == null ? source.getControllerId() : playerId, tapped, attacking, attackedPlayer); for (UUID tokenId : token.getLastAddedTokenIds()) { // by cards like Doubling Season multiple tokens can be added to the battlefield Permanent tokenPermanent = game.getPermanent(tokenId); if (tokenPermanent != null) { diff --git a/Mage/src/mage/abilities/keyword/MyriadAbility.java b/Mage/src/mage/abilities/keyword/MyriadAbility.java new file mode 100644 index 00000000000..f381706d527 --- /dev/null +++ b/Mage/src/mage/abilities/keyword/MyriadAbility.java @@ -0,0 +1,89 @@ +/* + * 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.keyword; + +import java.util.UUID; +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.delayed.AtTheEndOfCombatDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetEffect; +import mage.abilities.effects.common.PutTokenOntoBattlefieldCopyTargetEffect; +import mage.constants.Outcome; +import mage.constants.SetTargetPointer; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.targetpointer.FixedTarget; + +public class MyriadAbility extends AttacksTriggeredAbility { + + public MyriadAbility() { + super(new MyriadEffect(), false, + "Myriad (Whenever this creature attacks, for each opponent other than the defending player, put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile those tokens at the end of combat.)", + SetTargetPointer.PLAYER + ); + } + + public MyriadAbility(final MyriadAbility ability) { + super(ability); + } + + @Override + public MyriadAbility copy() { + return new MyriadAbility(this); + } + +} + +class MyriadEffect extends OneShotEffect { + + public MyriadEffect() { + super(Outcome.Benefit); + this.staticText = "for each opponent other than the defending player, you may put a token that's a copy of this creature onto the battlefield tapped and attacking that player or a planeswalker he or she controls. Exile the tokens at the end of combat"; + } + + public MyriadEffect(final MyriadEffect effect) { + super(effect); + } + + @Override + public MyriadEffect copy() { + return new MyriadEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + Permanent sourceObject = game.getPermanent(source.getSourceId()); + if (controller != null && sourceObject != null) { + for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { + if (playerId != getTargetPointer().getFirst(game, source) && controller.hasOpponent(playerId, game)) { + Player opponent = game.getPlayer(playerId); + if (opponent != null && controller.chooseUse(Outcome.PutCreatureInPlay, + "Put a copy of " + sourceObject.getIdName() + " onto battlefield attacking " + opponent.getName() + "?", source, game)) { + PutTokenOntoBattlefieldCopyTargetEffect effect = new PutTokenOntoBattlefieldCopyTargetEffect(controller.getId(), null, false, 1, true, true, playerId); + effect.setTargetPointer(new FixedTarget(sourceObject, game)); + effect.apply(game, source); + for (Permanent tokenPermanent : effect.getAddedPermanent()) { + ExileTargetEffect exileEffect = new ExileTargetEffect(); + exileEffect.setTargetPointer(new FixedTarget(tokenPermanent, game)); + DelayedTriggeredAbility delayedAbility = new AtTheEndOfCombatDelayedTriggeredAbility(exileEffect); + delayedAbility.setSourceId(source.getSourceId()); + delayedAbility.setControllerId(source.getControllerId()); + delayedAbility.setSourceObject(source.getSourceObject(game), game); + game.addDelayedTriggeredAbility(delayedAbility); + } + } + } + + } + return true; + } + return false; + } +} diff --git a/Mage/src/mage/abilities/keyword/ProvokeAbility.java b/Mage/src/mage/abilities/keyword/ProvokeAbility.java index 94b4c53f65d..d92a4f1642f 100644 --- a/Mage/src/mage/abilities/keyword/ProvokeAbility.java +++ b/Mage/src/mage/abilities/keyword/ProvokeAbility.java @@ -25,7 +25,6 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of BetaSteward_at_googlemail.com. */ - package mage.abilities.keyword; import java.util.UUID; @@ -42,26 +41,24 @@ import mage.game.permanent.Permanent; import mage.target.common.TargetCreaturePermanent; /** - * 702.38. Provoke - * 702.38a Provoke is a triggered ability. “Provoke” means “Whenever this creature attacks, you may - * choose to have target creature defending player controls block this creature this combat - * if able. If you do, untap that creature.” - * 702.38b If a creature has multiple instances of provoke, each triggers separately. + * 702.38. Provoke 702.38a Provoke is a triggered ability. “Provoke” means + * “Whenever this creature attacks, you may choose to have target creature + * defending player controls block this creature this combat if able. If you do, + * untap that creature.” 702.38b If a creature has multiple instances of + * provoke, each triggers separately. * * @author LevelX2 * */ - public class ProvokeAbility extends AttacksTriggeredAbility { - public ProvokeAbility() { + public ProvokeAbility() { super(new UntapTargetEffect(), true, "Provoke (Whenever this attacks, you may have target creature defending player controls untap and block it if able.)"); this.addEffect(new ProvokeRequirementEffect()); } public ProvokeAbility(final ProvokeAbility ability) { super(ability); - this.text = ability.text; } @Override diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index b03625617c6..4d501304bf3 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -212,12 +212,31 @@ public class Combat implements Serializable, Copyable { * @return */ public boolean addAttackingCreature(UUID creatureId, Game game) { + return this.addAttackingCreature(creatureId, game, null); + } + + public boolean addAttackingCreature(UUID creatureId, Game game, UUID playerToAttack) { + Set possibleDefenders; + if (playerToAttack != null) { + possibleDefenders = new HashSet<>(); + for (UUID objectId : defenders) { + Permanent planeswalker = game.getPermanent(objectId); + if (planeswalker != null && planeswalker.getControllerId().equals(playerToAttack)) { + possibleDefenders.add(objectId); + } else if (playerToAttack.equals(objectId)) { + possibleDefenders.add(objectId); + } + } + } else { + possibleDefenders = new HashSet(defenders); + } Player player = game.getPlayer(attackerId); - if (defenders.size() == 1) { - addAttackerToCombat(creatureId, defenders.iterator().next(), game); + if (possibleDefenders.size() == 1) { + addAttackerToCombat(creatureId, possibleDefenders.iterator().next(), game); return true; } else { - TargetDefender target = new TargetDefender(defenders, creatureId); + TargetDefender target = new TargetDefender(possibleDefenders, creatureId); + target.setNotTarget(true); target.setRequired(true); player.chooseTarget(Outcome.Damage, target, null, game); if (target.getFirstTarget() != null) { diff --git a/Mage/src/mage/game/permanent/token/Token.java b/Mage/src/mage/game/permanent/token/Token.java index 964cf59a9e8..772d317b041 100644 --- a/Mage/src/mage/game/permanent/token/Token.java +++ b/Mage/src/mage/game/permanent/token/Token.java @@ -134,6 +134,10 @@ public class Token extends MageObjectImpl { } public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking) { + return putOntoBattlefield(amount, game, sourceId, controllerId, tapped, attacking, null); + } + + public boolean putOntoBattlefield(int amount, Game game, UUID sourceId, UUID controllerId, boolean tapped, boolean attacking, UUID attackedPlayer) { Player controller = game.getPlayer(controllerId); if (controller == null) { return false; @@ -179,7 +183,7 @@ public class Token extends MageObjectImpl { this.lastAddedTokenId = permanent.getId(); game.addSimultaneousEvent(new ZoneChangeEvent(permanent, permanent.getControllerId(), Zone.OUTSIDE, Zone.BATTLEFIELD)); if (attacking && game.getCombat() != null) { - game.getCombat().addAttackingCreature(permanent.getId(), game); + game.getCombat().addAttackingCreature(permanent.getId(), game, attackedPlayer); } if (!game.isSimulation()) { game.informPlayers(controller.getLogName() + " puts a " + permanent.getLogName() + " token onto the battlefield"); diff --git a/Utils/keywords.txt b/Utils/keywords.txt index dd3fd7f5942..72f53816774 100644 --- a/Utils/keywords.txt +++ b/Utils/keywords.txt @@ -44,6 +44,7 @@ Miracle|cost| Mountaincycling|cost| Mountainwalk|new| Morph|card, cost| +Myriad|new| Outlast|cost| Persist|new| Phasing|instance|