diff --git a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/src/mage/game/OathbreakerFreeForAll.java b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/src/mage/game/OathbreakerFreeForAll.java index 65ac49e5b8d..2c630799ee2 100644 --- a/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/src/mage/game/OathbreakerFreeForAll.java +++ b/Mage.Server.Plugins/Mage.Game.OathbreakerFreeForAll/src/mage/game/OathbreakerFreeForAll.java @@ -117,12 +117,12 @@ public class OathbreakerFreeForAll extends GameCommanderImpl { } @Override - public Set getCommandersIds(Player player, CommanderCardType commanderCardType) { + public Set getCommandersIds(Player player, CommanderCardType commanderCardType, boolean returnAllCardParts) { Set res = new HashSet<>(); if (player != null) { Set commanders = this.playerOathbreakers.getOrDefault(player.getId(), new HashSet<>()); Set spells = this.playerSignatureSpells.getOrDefault(player.getId(), new HashSet<>()); - for (UUID commanderId : super.getCommandersIds(player, commanderCardType)) { + for (UUID commanderId : super.getCommandersIds(player, commanderCardType, returnAllCardParts)) { switch (commanderCardType) { case ANY: res.add(commanderId); @@ -133,6 +133,7 @@ public class OathbreakerFreeForAll extends GameCommanderImpl { } break; case SIGNATURE_SPELL: + // TODO: doesn't filter mdf cards with different sides (creature + spell) if (spells.contains(commanderId)) { res.add(commanderId); } @@ -142,6 +143,6 @@ public class OathbreakerFreeForAll extends GameCommanderImpl { } } } - return res; + return super.filterCommandersBySearchZone(res, returnAllCardParts); } } diff --git a/Mage.Sets/src/mage/cards/c/CaptainVargusWrath.java b/Mage.Sets/src/mage/cards/c/CaptainVargusWrath.java index 7ff3c5eecb4..519ccd40639 100644 --- a/Mage.Sets/src/mage/cards/c/CaptainVargusWrath.java +++ b/Mage.Sets/src/mage/cards/c/CaptainVargusWrath.java @@ -67,7 +67,7 @@ enum CaptainVargusWrathValue implements DynamicValue { } CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class); return watcher == null ? 0 : game - .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER) + .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false) .stream() .mapToInt(watcher::getPlaysCount) .sum(); diff --git a/Mage.Sets/src/mage/cards/c/CommandBeacon.java b/Mage.Sets/src/mage/cards/c/CommandBeacon.java index 63bd737d2b5..29bcf6cc988 100644 --- a/Mage.Sets/src/mage/cards/c/CommandBeacon.java +++ b/Mage.Sets/src/mage/cards/c/CommandBeacon.java @@ -66,13 +66,7 @@ class CommandBeaconEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - List commandersInCommandZone = new ArrayList<>(1); - for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { - Card commander = game.getCard(commanderId); - if (commander != null && game.getState().getZone(commander.getId()) == Zone.COMMAND) { - commandersInCommandZone.add(commander); - } - } + List commandersInCommandZone = new ArrayList<>(game.getCommanderCardsFromCommandZone(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)); if (commandersInCommandZone.size() == 1) { controller.moveCards(commandersInCommandZone.get(0), Zone.HAND, source, game); } else if (commandersInCommandZone.size() == 2) { diff --git a/Mage.Sets/src/mage/cards/c/CommandersInsignia.java b/Mage.Sets/src/mage/cards/c/CommandersInsignia.java index 5d19eb62576..f58dc3240c4 100644 --- a/Mage.Sets/src/mage/cards/c/CommandersInsignia.java +++ b/Mage.Sets/src/mage/cards/c/CommandersInsignia.java @@ -52,7 +52,7 @@ enum CommandersInsigniaValue implements DynamicValue { return 0; } return game - .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER) + .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false) .stream() .mapToInt(watcher::getPlaysCount) .sum(); diff --git a/Mage.Sets/src/mage/cards/c/CommandersPlate.java b/Mage.Sets/src/mage/cards/c/CommandersPlate.java index de2ede55195..ae791be00b4 100644 --- a/Mage.Sets/src/mage/cards/c/CommandersPlate.java +++ b/Mage.Sets/src/mage/cards/c/CommandersPlate.java @@ -115,7 +115,7 @@ class CommandersPlateEffect extends ContinuousEffectImpl { permanent = game.getPermanentOrLKIBattlefield(equipment.getAttachedTo()); } } - Set commanders = game.getCommandersIds(player); + Set commanders = game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false); if (commanders.isEmpty()) { return false; } diff --git a/Mage.Sets/src/mage/cards/g/GeodeGolem.java b/Mage.Sets/src/mage/cards/g/GeodeGolem.java index e591d51a91d..82b5c9e8ba4 100644 --- a/Mage.Sets/src/mage/cards/g/GeodeGolem.java +++ b/Mage.Sets/src/mage/cards/g/GeodeGolem.java @@ -1,16 +1,17 @@ package mage.cards.g; +import mage.ApprovingObject; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.costs.mana.ManaCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.keyword.TrampleAbility; -import mage.cards.*; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.SubType; -import mage.constants.Zone; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.CardsImpl; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.players.Player; @@ -18,10 +19,8 @@ import mage.target.TargetCard; import mage.util.ManaUtil; import mage.watchers.common.CommanderPlaysCountWatcher; -import java.util.HashSet; import java.util.Set; import java.util.UUID; -import mage.ApprovingObject; /** * @author spjspj, JayDi85 @@ -69,36 +68,31 @@ class GeodeGolemEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - UUID selectedCommanderId = null; - Set possibleCommanders = new HashSet<>(); - for (UUID id : game.getCommandersIds(controller)) { - if (game.getState().getZone(id) == Zone.COMMAND) { - possibleCommanders.add(id); - } - } + Card selectedCommander = null; - if (possibleCommanders.isEmpty()) { + Set commandersInCommandZone = game.getCommanderCardsFromCommandZone(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER); + if (commandersInCommandZone.isEmpty()) { return false; } // select from commanders - if (possibleCommanders.size() == 1) { - selectedCommanderId = possibleCommanders.iterator().next(); + if (commandersInCommandZone.size() == 1) { + selectedCommander = commandersInCommandZone.stream().findFirst().get(); } else { - TargetCard target = new TargetCard(Zone.COMMAND, new FilterCard( - "commander to cast without mana cost")); - Cards cards = new CardsImpl(possibleCommanders); + TargetCard target = new TargetCard(Zone.COMMAND, new FilterCard("commander to cast without mana cost")); target.setNotTarget(true); if (controller.canRespond() - && controller.choose(Outcome.PlayForFree, cards, target, game)) { + && controller.choose(Outcome.PlayForFree, new CardsImpl(commandersInCommandZone), target, game)) { if (target.getFirstTarget() != null) { - selectedCommanderId = target.getFirstTarget(); + selectedCommander = commandersInCommandZone.stream() + .filter(c -> c.getId().equals(target.getFirstTarget())) + .findFirst() + .orElse(null); } } } - Card commander = game.getCard(selectedCommanderId); - if (commander == null) { + if (selectedCommander == null) { return false; } @@ -106,7 +100,7 @@ class GeodeGolemEffect extends OneShotEffect { // TODO: this is broken with the commander cost reduction effect ManaCost cost = null; CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class); - int castCount = watcher.getPlaysCount(commander.getId()); + int castCount = watcher.getPlaysCount(selectedCommander.getId()); if (castCount > 0) { cost = ManaUtil.createManaCost(castCount * 2, false); } @@ -114,14 +108,14 @@ class GeodeGolemEffect extends OneShotEffect { // CAST: as spell or as land if (cost == null || cost.pay(source, game, source, controller.getId(), false, null)) { - if (commander.getSpellAbility() != null) { - game.getState().setValue("PlayFromNotOwnHandZone" + commander.getId(), Boolean.TRUE); - Boolean commanderWasCast = controller.cast(controller.chooseAbilityForCast(commander, game, true), + if (selectedCommander.getSpellAbility() != null) { // TODO: can be broken with mdf cards (one side creature, one side land)? + game.getState().setValue("PlayFromNotOwnHandZone" + selectedCommander.getId(), Boolean.TRUE); + Boolean commanderWasCast = controller.cast(controller.chooseAbilityForCast(selectedCommander, game, true), game, true, new ApprovingObject(source, game)); - game.getState().setValue("PlayFromNotOwnHandZone" + commander.getId(), null); + game.getState().setValue("PlayFromNotOwnHandZone" + selectedCommander.getId(), null); return commanderWasCast; } else { - return controller.playLand(commander, game, true); + return controller.playLand(selectedCommander, game, true); } } } diff --git a/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java b/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java index b17e8685706..c97829776c9 100644 --- a/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java +++ b/Mage.Sets/src/mage/cards/j/JeskaThriceReborn.java @@ -88,7 +88,7 @@ enum JeskaThriceRebornValue implements DynamicValue { } CommanderPlaysCountWatcher watcher = game.getState().getWatcher(CommanderPlaysCountWatcher.class); return watcher == null ? 0 : game - .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER) + .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false) .stream() .mapToInt(watcher::getPlaysCount) .sum(); diff --git a/Mage.Sets/src/mage/cards/k/KarnLiberated.java b/Mage.Sets/src/mage/cards/k/KarnLiberated.java index 6e4ee311701..30d9ef48f01 100644 --- a/Mage.Sets/src/mage/cards/k/KarnLiberated.java +++ b/Mage.Sets/src/mage/cards/k/KarnLiberated.java @@ -14,7 +14,6 @@ import mage.game.Game; import mage.game.GameImpl; import mage.game.command.Commander; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentImpl; import mage.players.Player; @@ -109,7 +108,7 @@ class KarnLiberatedEffect extends OneShotEffect { if (card.isOwnedBy(player.getId()) && !card.isCopy() // no copies && !player.getSideboard().contains(card.getId()) && !cards.contains(card)) { // not the exiled cards - if (game.getCommandersIds(player).contains(card.getId())) { + if (game.getCommandersIds(player, CommanderCardType.ANY, false).contains(card.getId())) { game.addCommander(new Commander(card)); // TODO: check restart and init // no needs in initCommander call -- it's used on game startup (init) game.setZone(card.getId(), Zone.COMMAND); diff --git a/Mage.Sets/src/mage/cards/m/MythUnbound.java b/Mage.Sets/src/mage/cards/m/MythUnbound.java index dc80237c67c..ad93e69a47e 100644 --- a/Mage.Sets/src/mage/cards/m/MythUnbound.java +++ b/Mage.Sets/src/mage/cards/m/MythUnbound.java @@ -92,7 +92,8 @@ class MythUnboundCostReductionEffect extends CostModificationEffectImpl { if (abilityToModify instanceof SpellAbility || abilityToModify instanceof PlayLandAbility) { if (abilityToModify.isControlledBy(source.getControllerId())) { - return game.getCommandersIds(player).contains(abilityToModify.getSourceId()); + // must check all card parts (example: mdf commander) + return game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, true).contains(abilityToModify.getSourceId()); } } return false; diff --git a/Mage.Sets/src/mage/cards/n/NetherbornAltar.java b/Mage.Sets/src/mage/cards/n/NetherbornAltar.java index d1fe4997266..fdda88d8e6e 100644 --- a/Mage.Sets/src/mage/cards/n/NetherbornAltar.java +++ b/Mage.Sets/src/mage/cards/n/NetherbornAltar.java @@ -17,10 +17,9 @@ import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; +import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.UUID; -import java.util.stream.Collectors; /** * @author TheElk801 @@ -68,13 +67,8 @@ class NetherbornAltarEffect extends OneShotEffect { if (controller == null) { return false; } - List commandersInCommandZone = game - .getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER) - .stream() - .map(game::getCard) - .filter(Objects::nonNull) - .filter(commander -> game.getState().getZone(commander.getId()) == Zone.COMMAND) - .collect(Collectors.toList()); + + List commandersInCommandZone = new ArrayList<>(game.getCommanderCardsFromCommandZone(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)); if (commandersInCommandZone.size() == 1) { controller.moveCards(commandersInCommandZone.get(0), Zone.HAND, source, game); } else if (commandersInCommandZone.size() == 2) { diff --git a/Mage.Sets/src/mage/cards/o/OpalPalace.java b/Mage.Sets/src/mage/cards/o/OpalPalace.java index 613e6d0314c..5af6e9dd872 100644 --- a/Mage.Sets/src/mage/cards/o/OpalPalace.java +++ b/Mage.Sets/src/mage/cards/o/OpalPalace.java @@ -58,7 +58,7 @@ public final class OpalPalace extends CardImpl { class OpalPalaceWatcher extends Watcher { - private List commanderId = new ArrayList<>(); + private final List commanderPartsId = new ArrayList<>(); private final String originalId; public OpalPalaceWatcher(String originalId) { @@ -66,8 +66,8 @@ class OpalPalaceWatcher extends Watcher { this.originalId = originalId; } - public boolean manaUsedToCastCommander(UUID id){ - return commanderId.contains(id); + public boolean manaUsedToCastCommanderPart(UUID id) { + return commanderPartsId.contains(id); } @Override @@ -81,8 +81,9 @@ class OpalPalaceWatcher extends Watcher { for (UUID playerId : game.getPlayerList()) { Player player = game.getPlayer(playerId); if (player != null) { - if (game.getCommandersIds(player).contains(card.getId())) { - commanderId.add(card.getId()); + // need check all card parts (example: mdf cards) + if (game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, true).contains(card.getId())) { + commanderPartsId.add(card.getId()); break; } } @@ -96,7 +97,7 @@ class OpalPalaceWatcher extends Watcher { @Override public void reset() { super.reset(); - commanderId.clear(); + commanderPartsId.clear(); } } @@ -119,7 +120,7 @@ class OpalPalaceEntersBattlefieldEffect extends ReplacementEffectImpl { @Override public boolean applies(GameEvent event, Ability source, Game game) { OpalPalaceWatcher watcher = game.getState().getWatcher(OpalPalaceWatcher.class, source.getSourceId()); - return watcher != null && watcher.manaUsedToCastCommander(event.getTargetId()); + return watcher != null && watcher.manaUsedToCastCommanderPart(event.getTargetId()); } @Override diff --git a/Mage.Sets/src/mage/cards/p/PathOfAncestry.java b/Mage.Sets/src/mage/cards/p/PathOfAncestry.java index aecf0d3e04e..aea19fa06c9 100644 --- a/Mage.Sets/src/mage/cards/p/PathOfAncestry.java +++ b/Mage.Sets/src/mage/cards/p/PathOfAncestry.java @@ -10,6 +10,7 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CommanderCardType; import mage.constants.Duration; import mage.game.Game; import mage.game.events.GameEvent; @@ -90,14 +91,9 @@ class PathOfAncestryTriggeredAbility extends DelayedTriggeredAbility { if (player == null) { return false; } - for (UUID commanderId : game.getCommandersIds(player)) { - Card commander = game.getPermanent(commanderId); - if (commander == null) { - commander = game.getCard(commanderId); - } - if (commander == null) { - continue; - } + + // share creature type with commander + for (Card commander : game.getCommanderCardsFromAnyZones(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { if (spell.getCard().shareCreatureTypes(game, commander)) { return true; } diff --git a/Mage.Sets/src/mage/cards/r/RoadOfReturn.java b/Mage.Sets/src/mage/cards/r/RoadOfReturn.java index 912ed5ec276..8316ed217e7 100644 --- a/Mage.Sets/src/mage/cards/r/RoadOfReturn.java +++ b/Mage.Sets/src/mage/cards/r/RoadOfReturn.java @@ -76,15 +76,7 @@ class RoadOfReturnEffect extends OneShotEffect { if (controller == null) { return false; } - List commandersInCommandZone = new ArrayList<>(1); - game.getCommandersIds( - controller, CommanderCardType.COMMANDER_OR_OATHBREAKER - ).stream().forEach(commanderId -> { - Card commander = game.getCard(commanderId); - if (commander != null && game.getState().getZone(commander.getId()) == Zone.COMMAND) { - commandersInCommandZone.add(commander); - } - }); + List commandersInCommandZone = new ArrayList<>(game.getCommanderCardsFromCommandZone(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)); if (commandersInCommandZone.size() == 1) { controller.moveCards(commandersInCommandZone.get(0), Zone.HAND, source, game); } else if (commandersInCommandZone.size() == 2) { diff --git a/Mage.Sets/src/mage/cards/s/SkyfirePhoenix.java b/Mage.Sets/src/mage/cards/s/SkyfirePhoenix.java index b19405870cd..e54cec05c64 100644 --- a/Mage.Sets/src/mage/cards/s/SkyfirePhoenix.java +++ b/Mage.Sets/src/mage/cards/s/SkyfirePhoenix.java @@ -14,6 +14,7 @@ import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; +import mage.players.Player; import java.util.UUID; @@ -62,9 +63,17 @@ class SkyfirePhoenixTriggeredAbility extends SpellCastControllerTriggeredAbility @Override public boolean checkTrigger(GameEvent event, Game game) { - return super.checkTrigger(event, game) && game.getCommandersIds( - game.getPlayer(getControllerId()), CommanderCardType.COMMANDER_OR_OATHBREAKER - ).contains(event.getSourceId()); + if (!super.checkTrigger(event, game)) { + return false; + } + + Player controller = game.getPlayer(getControllerId()); + if (controller == null) { + return false; + } + + // must check all parts (example: cast one from from mdf/split card) + return game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER, true).contains(event.getSourceId()); } @Override diff --git a/Mage.Sets/src/mage/cards/t/TeferiMageOfZhalfir.java b/Mage.Sets/src/mage/cards/t/TeferiMageOfZhalfir.java index d5480c2d8fc..8574de0e602 100644 --- a/Mage.Sets/src/mage/cards/t/TeferiMageOfZhalfir.java +++ b/Mage.Sets/src/mage/cards/t/TeferiMageOfZhalfir.java @@ -98,8 +98,8 @@ class TeferiMageOfZhalfirAddFlashEffect extends ContinuousEffectImpl { game.getState().addOtherAbility(card, FlashAbility.getInstance()); } } - // commander in command zone - game.getCommanderCardsFromCommandZone(controller).stream() + // cards in command zone + game.getCommanderCardsFromCommandZone(controller, CommanderCardType.ANY).stream() .filter(MageObject::isCreature) .forEach(card -> { game.getState().addOtherAbility(card, FlashAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/t/TeveshSzatDoomOfFools.java b/Mage.Sets/src/mage/cards/t/TeveshSzatDoomOfFools.java index 57dfa23435d..3869b403f7e 100644 --- a/Mage.Sets/src/mage/cards/t/TeveshSzatDoomOfFools.java +++ b/Mage.Sets/src/mage/cards/t/TeveshSzatDoomOfFools.java @@ -8,6 +8,7 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.continuous.GainControlTargetEffect; import mage.abilities.keyword.PartnerAbility; +import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.CardsImpl; @@ -23,11 +24,10 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.target.targetpointer.FixedTarget; -import java.util.Collection; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; import static mage.constants.Outcome.Benefit; @@ -110,10 +110,12 @@ class TeveshSzatDoomOfFoolsSacrificeEffect extends OneShotEffect { if (permanent == null) { return false; } - boolean isCommander = game.getCommandersIds( - game.getPlayer(permanent.getControllerId()), - CommanderCardType.COMMANDER_OR_OATHBREAKER - ).contains(permanent.getId()); + + // must check all card parts (example: mdf commander) + Player permanentController = game.getPlayer(permanent.getControllerId()); + boolean isCommander = permanentController != null + && game.getCommandersIds(permanentController, CommanderCardType.COMMANDER_OR_OATHBREAKER, true).contains(permanent.getId()); + if (!permanent.sacrifice(source, game)) { return false; } @@ -154,6 +156,8 @@ class TeveshSzatDoomOfFoolsCommanderEffect extends OneShotEffect { if (controller == null) { return false; } + + // gain control of all commanders for (Permanent permanent : game.getBattlefield().getActivePermanents( filter, source.getControllerId(), source.getSourceId(), game )) { @@ -161,16 +165,17 @@ class TeveshSzatDoomOfFoolsCommanderEffect extends OneShotEffect { Duration.Custom, true ).setTargetPointer(new FixedTarget(permanent, game)), source); } - Set commanders = game - .getPlayerList() - .stream() + + // put all commanders to battlefield under control + // TODO: doesn't support range of influence (e.g. take control of all commanders) + Set commandersToPut = new HashSet<>(); + game.getPlayerList().stream() .map(game::getPlayer) .filter(Objects::nonNull) - .map(player -> game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) - .flatMap(Collection::stream) - .filter(uuid -> game.getState().getZone(uuid) == Zone.COMMAND) - .collect(Collectors.toSet()); - controller.moveCards(new CardsImpl(commanders), Zone.BATTLEFIELD, source, game); + .forEach(player -> { + commandersToPut.addAll(game.getCommanderCardsFromCommandZone(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)); + }); + controller.moveCards(new CardsImpl(commandersToPut), Zone.BATTLEFIELD, source, game); return true; } } diff --git a/Mage.Sets/src/mage/cards/w/WarRoom.java b/Mage.Sets/src/mage/cards/w/WarRoom.java index 3d73f81e6c3..a30f1802164 100644 --- a/Mage.Sets/src/mage/cards/w/WarRoom.java +++ b/Mage.Sets/src/mage/cards/w/WarRoom.java @@ -14,6 +14,7 @@ import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.CommanderCardType; import mage.filter.FilterMana; import mage.game.Game; import mage.players.Player; @@ -64,7 +65,7 @@ enum WarRoomValue implements DynamicValue { ObjectColor color = new ObjectColor(); // if no commander then cost can't be paid boolean hasCommander = false; - for (UUID commanderId : game.getCommandersIds(controller)) { + for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER, false)) { Card commander = game.getCard(commanderId); if (commander == null) { continue; 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 2fce6cc1aed..690b387fd2d 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 @@ -3,6 +3,7 @@ package org.mage.test.cards.continuous; import mage.abilities.dynamicvalue.common.CommanderCastCountValue; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.AdventureCard; +import mage.constants.CommanderCardType; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Assert; @@ -448,7 +449,7 @@ public class CommandersCastTest extends CardTestCommander4Players { // can't cast adventure spell for {G} + {2} + {2} // can't cast creature spell for {G}{G} + {2} + {2} runCode("check commander tax 2x", 9, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - AdventureCard card = (AdventureCard) game.getCommanderCardsFromCommandZone(player).stream().findFirst().get(); + AdventureCard card = (AdventureCard) game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY).stream().findFirst().get(); Assert.assertEquals(2, CommanderCastCountValue.instance.calculate(game, card.getSpellAbility(), null)); Assert.assertEquals(2, CommanderCastCountValue.instance.calculate(game, card.getSpellCard().getSpellAbility(), null)); }); @@ -474,7 +475,7 @@ public class CommandersCastTest extends CardTestCommander4Players { checkPermanentCount("after last cast", 13, PhaseStep.PRECOMBAT_MAIN, playerA, "Curious Pair", 1); checkPermanentTapped("after last cast", 13, PhaseStep.PRECOMBAT_MAIN, playerA, "Forest", true, 2 + 2 + 2); runCode("check commander tax 3x", 13, PhaseStep.PRECOMBAT_MAIN, playerA, (info, player, game) -> { - AdventureCard card = (AdventureCard) game.getCard(game.getCommandersIds(player).stream().findFirst().get()); + AdventureCard card = (AdventureCard) game.getCard(game.getCommandersIds(player, CommanderCardType.ANY, false).stream().findFirst().get()); Assert.assertEquals(3, CommanderCastCountValue.instance.calculate(game, card.getSpellAbility(), null)); Assert.assertEquals(3, CommanderCastCountValue.instance.calculate(game, card.getSpellCard().getSpellAbility(), null)); }); @@ -486,7 +487,7 @@ public class CommandersCastTest extends CardTestCommander4Players { } @Test - public void test_ModalDoubleFacesCard() { + public void test_ModalDoubleFacesCard_1() { // Player order: A -> D -> C -> B // use case: @@ -540,4 +541,41 @@ public class CommandersCastTest extends CardTestCommander4Players { execute(); assertAllCommandsUsed(); } + + @Test + public void test_ModalDoubleFacesCard_CanReturnAfterKillAndCommanderControlCondition() { + // Player order: A -> D -> C -> B + + // Cosima, God of the Voyage, {2}{U}, creature, 2/4 + // The Omenkeel, {1}{U}, artifact, vehicle + addCard(Zone.COMMAND, playerA, "Cosima, God of the Voyage", 1); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // + addCard(Zone.HAND, playerA, "Lightning Bolt", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + // If you control a commander, you may cast this spell without paying its mana cost. + // Counter target noncreature spell. + addCard(Zone.HAND, playerA, "Fierce Guardianship", 1); // {2}{U} + + // prepare commander on battlefield + activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 3); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cosima, God of the Voyage"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + checkPermanentCount("prepare", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cosima, God of the Voyage", 1); + + // kill and return to command zone + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Cosima, God of the Voyage"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Cosima, God of the Voyage"); + // check what commander control condition works with mdf parts + checkStackSize("must have 2 bolts on stack", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2); + checkPlayableAbility("must see commander on battle for free cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Fierce Guardianship", true); + // + setChoice(playerA, "Yes"); // return to command zone after kill + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + assertAllCommandsUsed(); + } } 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 fd318f7cb11..5c3e38558dc 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 @@ -943,7 +943,7 @@ public class TestPlayer implements Player { // show command if (params[0].equals(SHOW_COMMAND_COMMAND) && params.length == 1) { printStart(action.getActionName()); - CardsImpl cards = new CardsImpl(game.getCommandersIds(computerPlayer)); + CardsImpl cards = new CardsImpl(game.getCommandersIds(computerPlayer, CommanderCardType.ANY, false)); printCards(cards.getCards(game)); printEnd(); actions.remove(action); @@ -1421,7 +1421,7 @@ public class TestPlayer implements Player { private void assertCommandCardCount(PlayerAction action, Game game, Player player, String cardName, int count) { int realCount = 0; - for (UUID cardId : game.getCommandersIds(player)) { + for (UUID cardId : game.getCommandersIds(player, CommanderCardType.ANY, false)) { Card card = game.getCard(cardId); if (hasObjectTargetNameOrAlias(card, cardName) && Zone.COMMAND.equals(game.getState().getZone(cardId))) { realCount++; @@ -1430,7 +1430,7 @@ public class TestPlayer implements Player { if (realCount != count) { printStart("Cards in command zone from " + player.getName()); - printCards(game.getCommanderCardsFromCommandZone(player)); + printCards(game.getCommanderCardsFromCommandZone(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)); printEnd(); Assert.fail(action.getActionName() + " - must have " + count + " cards with name " + cardName + ", but found " + realCount); } diff --git a/Mage/src/main/java/mage/abilities/condition/common/CommanderInPlayCondition.java b/Mage/src/main/java/mage/abilities/condition/common/CommanderInPlayCondition.java index 28e8f13ca8f..c414bf44f73 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/CommanderInPlayCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/CommanderInPlayCondition.java @@ -2,12 +2,7 @@ package mage.abilities.condition.common; import mage.abilities.Ability; import mage.abilities.condition.Condition; -import mage.constants.CommanderCardType; import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; - -import java.util.UUID; /** * Checks if the player has its commander in play and controls it @@ -20,16 +15,7 @@ public enum CommanderInPlayCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER)) { - Permanent commander = game.getPermanent(commanderId); - if (commander != null && commander.isControlledBy(source.getControllerId())) { - return true; - } - } - } - return false; + return ControlACommanderCondition.instance.apply(game, source); } @Override diff --git a/Mage/src/main/java/mage/abilities/condition/common/ControlACommanderCondition.java b/Mage/src/main/java/mage/abilities/condition/common/ControlACommanderCondition.java index 9cd23bbef9b..aee03164556 100644 --- a/Mage/src/main/java/mage/abilities/condition/common/ControlACommanderCondition.java +++ b/Mage/src/main/java/mage/abilities/condition/common/ControlACommanderCondition.java @@ -21,7 +21,7 @@ public enum ControlACommanderCondition implements Condition { .stream() .map(game::getPlayer) .filter(Objects::nonNull) - .map(player -> game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER)) + .map(player -> game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, true)) // must search all card parts (example: mdf commander on battlefield) .flatMap(Collection::stream) .map(game::getPermanent) .filter(Objects::nonNull) diff --git a/Mage/src/main/java/mage/abilities/effects/GainAbilitySpellsEffect.java b/Mage/src/main/java/mage/abilities/effects/GainAbilitySpellsEffect.java index 776012435f6..ecc9cf6220d 100644 --- a/Mage/src/main/java/mage/abilities/effects/GainAbilitySpellsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/GainAbilitySpellsEffect.java @@ -2,10 +2,7 @@ package mage.abilities.effects; import mage.abilities.Ability; import mage.cards.Card; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.filter.FilterObject; import mage.game.Game; import mage.game.permanent.Permanent; @@ -62,7 +59,7 @@ public class GainAbilitySpellsEffect extends ContinuousEffectImpl { } // workaround to gain cost reduction abilities to commanders before cast (make it playable) - game.getCommanderCardsFromCommandZone(player).stream() + game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY).stream() .filter(card -> filter.match(card, game)) .forEach(card -> { game.getState().addOtherAbility(card, ability); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java index 97dd34233ff..f147c581378 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/GainAbilityControlledSpellsEffect.java @@ -3,10 +3,7 @@ package mage.abilities.effects.common.continuous; import mage.abilities.Ability; import mage.abilities.effects.ContinuousEffectImpl; import mage.cards.Card; -import mage.constants.Duration; -import mage.constants.Layer; -import mage.constants.Outcome; -import mage.constants.SubLayer; +import mage.constants.*; import mage.filter.FilterCard; import mage.game.Game; import mage.game.permanent.Permanent; @@ -68,7 +65,7 @@ public class GainAbilityControlledSpellsEffect extends ContinuousEffectImpl { } // workaround to gain cost reduction abilities to commanders before cast (make it playable) - game.getCommanderCardsFromCommandZone(player).stream() + game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY).stream() .filter(card -> filter.match(card, game)) .forEach(card -> { game.getState().addOtherAbility(card, ability); diff --git a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java index 017fc0b96bb..4c2f566083a 100644 --- a/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/CommanderStormAbility.java @@ -10,7 +10,6 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.stack.Spell; import mage.game.stack.StackObject; import mage.players.Player; @@ -89,7 +88,7 @@ class CommanderStormEffect extends OneShotEffect { return false; } int stormCount = game - .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER) + .getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false) .stream() .mapToInt(watcher::getPlaysCount) .sum(); diff --git a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java index 76862a16c6c..2acc6fc9fa5 100644 --- a/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/NinjutsuAbility.java @@ -1,6 +1,5 @@ package mage.abilities.keyword; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; @@ -10,6 +9,7 @@ import mage.abilities.effects.OneShotEffect; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.constants.CommanderCardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledCreaturePermanent; @@ -20,6 +20,9 @@ import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetControlledCreaturePermanent; import mage.target.common.TargetControlledPermanent; +import mage.util.CardUtil; + +import java.util.UUID; /** * 702.47. Ninjutsu @@ -43,7 +46,7 @@ import mage.target.common.TargetControlledPermanent; public class NinjutsuAbility extends ActivatedAbilityImpl { private final boolean commander; - private static final FilterControlledCreaturePermanent filter = + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("unblocked attacker you control"); static { @@ -150,7 +153,7 @@ class ReturnAttackerToHandTargetCost extends CostImpl { for (UUID targetId : targets.get(0).getTargets()) { Permanent permanent = game.getPermanent(targetId); Player controller = game.getPlayer(controllerId); - if (permanent == null + if (permanent == null || controller == null) { return false; } @@ -194,16 +197,29 @@ class RevealNinjutsuCardCost extends CostImpl { public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { Player player = game.getPlayer(controllerId); + // used from hand Card card = player.getHand().get(ability.getSourceId(), game); - if (card == null && commander - && game.getCommandersIds(player).contains(ability.getSourceId())) { + + // rules: + // Commander ninjutsu is a variant of ninjutsu that can be activated from the command zone as + // well as from your hand. Just as with regular ninjutsu, the Ninja enters attacking the player + // or planeswalker that the returned creature was attacking. + + // used from command zone + // must search all card sides for ability (example: mdf card with Ninjutsu in command zone) + if (card == null + && commander + && game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, true).contains(ability.getSourceId())) { for (CommandObject coj : game.getState().getCommand()) { if (coj != null && coj.getId().equals(ability.getSourceId())) { - card = game.getCard(ability.getSourceId()); - break; + if (CardUtil.getObjectParts(coj).contains(ability.getSourceId())) { + card = game.getCard(CardUtil.getMainCardId(game, ability.getSourceId())); + break; + } } } } + if (card != null) { Cards cards = new CardsImpl(card); player.revealCards("Ninjutsu", cards, game); diff --git a/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java b/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java index 3436bc05e2e..8a82e625b04 100644 --- a/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java +++ b/Mage/src/main/java/mage/abilities/mana/CommanderColorIdentityManaAbility.java @@ -9,6 +9,7 @@ import mage.cards.Card; import mage.choices.Choice; import mage.choices.ChoiceImpl; import mage.constants.ColoredManaSymbol; +import mage.constants.CommanderCardType; import mage.constants.Zone; import mage.filter.FilterMana; import mage.game.Game; @@ -71,7 +72,7 @@ class CommanderIdentityManaEffect extends ManaEffect { } Player controller = game.getPlayer(source.getControllerId()); if (controller != null) { - for (UUID commanderId : game.getCommandersIds(controller)) { + for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER, false)) { Card commander = game.getCard(commanderId); if (commander != null) { FilterMana commanderMana = commander.getColorIdentity(); @@ -106,7 +107,7 @@ class CommanderIdentityManaEffect extends ManaEffect { if (controller != null) { Choice choice = new ChoiceImpl(); choice.setMessage("Pick a mana color"); - for (UUID commanderId : game.getCommandersIds(controller)) { + for (UUID commanderId : game.getCommandersIds(controller, CommanderCardType.COMMANDER_OR_OATHBREAKER, false)) { Card commander = game.getCard(commanderId); if (commander != null) { FilterMana commanderMana = commander.getColorIdentity(); diff --git a/Mage/src/main/java/mage/constants/CommanderCardType.java b/Mage/src/main/java/mage/constants/CommanderCardType.java index a4c3e007d26..2d9ccd5e661 100644 --- a/Mage/src/main/java/mage/constants/CommanderCardType.java +++ b/Mage/src/main/java/mage/constants/CommanderCardType.java @@ -1,6 +1,12 @@ package mage.constants; /** + * rules: + * Cards that reference "your commander" instead reference "your Oathbreaker." + *

+ * So in card rules text contains "commander" then you must use COMMANDER_OR_OATHBREAKER. + * If you card must look to command zone (e.g. target any card) then you must use ANY + * * @author JayDi85 */ public enum CommanderCardType { diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 5e110d020ee..49e804efa2f 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -507,27 +507,44 @@ public interface Game extends MageItem, Serializable { Mulligan getMulligan(); - Set getCommandersIds(Player player, CommanderCardType commanderCardType); - - default Set getCommandersIds(Player player) { - return getCommandersIds(player, CommanderCardType.ANY); - } + Set getCommandersIds(Player player, CommanderCardType commanderCardType, boolean returnAllCardParts); /** * Return not played commander cards from command zone + * Read comments for CommanderCardType for more info on commanderCardType usage * * @param player * @return */ - default Set getCommanderCardsFromCommandZone(Player player) { + default Set getCommanderCardsFromCommandZone(Player player, CommanderCardType commanderCardType) { // commanders in command zone aren't cards so you must call getCard instead getObject - return getCommandersIds(player).stream() + return getCommandersIds(player, commanderCardType, false).stream() .map(this::getCard) .filter(Objects::nonNull) .filter(card -> Zone.COMMAND.equals(this.getState().getZone(card.getId()))) .collect(Collectors.toSet()); } + /** + * Return commander cards from any zones (main card from command and permanent card from battlefield) + * Read comments for CommanderCardType for more info on commanderCardType usage + * + * @param player + * @param commanderCardType commander or signature spell + * @return + */ + default Set getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType) { + // from command zone + Set res = getCommanderCardsFromCommandZone(player, commanderCardType); + + // from battlefield + this.getCommandersIds(player, commanderCardType, true).stream() + .map(this::getPermanent) + .filter(Objects::nonNull) + .forEach(res::add); + return res; + } + /** * Finds is it a commander card/object (use it in conditional and other things) * @@ -546,7 +563,7 @@ public interface Game extends MageItem, Serializable { if (object instanceof Card) { idToCheck = ((Card) object).getMainCard().getId(); } - return idToCheck != null && this.getCommandersIds(player).contains(idToCheck); + return idToCheck != null && this.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false).contains(idToCheck); } void setGameStopped(boolean gameStopped); diff --git a/Mage/src/main/java/mage/game/GameCommanderImpl.java b/Mage/src/main/java/mage/game/GameCommanderImpl.java index c8263fbb12f..cce2beeb371 100644 --- a/Mage/src/main/java/mage/game/GameCommanderImpl.java +++ b/Mage/src/main/java/mage/game/GameCommanderImpl.java @@ -7,10 +7,7 @@ import mage.abilities.effects.common.continuous.CommanderReplacementEffect; import mage.abilities.effects.common.cost.CommanderCostModification; import mage.abilities.keyword.CompanionAbility; import mage.cards.Card; -import mage.constants.MultiplayerAttackOption; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; +import mage.constants.*; import mage.game.mulligan.Mulligan; import mage.game.turn.TurnMod; import mage.players.Player; @@ -67,7 +64,7 @@ public abstract class GameCommanderImpl extends GameImpl { } // init commanders - for (UUID commanderId : this.getCommandersIds(player)) { + for (UUID commanderId : this.getCommandersIds(player, CommanderCardType.ANY, false)) { Card commander = this.getCard(commanderId); if (commander != null) { initCommander(commander, player); @@ -193,7 +190,7 @@ public abstract class GameCommanderImpl extends GameImpl { @Override protected boolean checkStateBasedActions() { for (Player player : getPlayers().values()) { - for (UUID commanderId : this.getCommandersIds(player)) { + for (UUID commanderId : this.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false)) { CommanderInfoWatcher damageWatcher = getState().getWatcher(CommanderInfoWatcher.class, commanderId); if (damageWatcher == null) { continue; diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 33809cc1e4c..58882578519 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1893,8 +1893,9 @@ public abstract class GameImpl implements Game, Serializable { // If a commander is in a graveyard or in exile and that card was put into that zone // since the last time state-based actions were checked, its owner may put it into the command zone. + // signature spells goes to command zone all the time for (Player player : state.getPlayers().values()) { - Set commanderIds = getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER); + Set commanderIds = getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false); if (commanderIds.isEmpty()) { continue; } @@ -3419,8 +3420,28 @@ public abstract class GameImpl implements Game, Serializable { } @Override - public Set getCommandersIds(Player player, CommanderCardType commanderCardType) { - return player.getCommandersIds(); + public Set getCommandersIds(Player player, CommanderCardType commanderCardType, boolean returnAllCardParts) { + //noinspection deprecation - it's ok to use it in inner method + Set mainCards = player.getCommandersIds(); + return filterCommandersBySearchZone(mainCards, returnAllCardParts); + } + + final protected Set filterCommandersBySearchZone(Set commanderMainCards, boolean returnAllCardParts) { + // filter by zone search (example: if you search commanders on battlefield then must see all sides of mdf cards) + Set filteredCards = new HashSet<>(); + if (returnAllCardParts) { + // need all card parts + commanderMainCards.stream() + .map(this::getCard) + .filter(Objects::nonNull) + .forEach(card -> { + filteredCards.addAll(CardUtil.getObjectParts(card)); + }); + } else { + filteredCards.addAll(commanderMainCards); + } + + return filteredCards; } @Override diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index da32f02c3ab..771e1b8bad6 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -43,7 +43,6 @@ import mage.filter.predicate.permanent.PermanentIdPredicate; import mage.game.*; import mage.game.combat.CombatGroup; import mage.game.command.CommandObject; -import mage.game.command.Commander; import mage.game.events.*; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; @@ -314,7 +313,10 @@ public abstract class PlayerImpl implements Player, Serializable { this.sideboard = player.getSideboard().copy(); this.hand = player.getHand().copy(); this.graveyard = player.getGraveyard().copy(); + + //noinspection deprecation - it's ok to use it in inner methods this.commandersIds = new HashSet<>(player.getCommandersIds()); + this.abilities = player.getAbilities().copy(); this.counters = player.getCounters().copy(); @@ -1578,7 +1580,7 @@ public abstract class PlayerImpl implements Player, Serializable { try { // collect and filter playable activated abilities // GUI: user clicks on card, but it must activate ability from ANY card's parts (main, left, right) - Set needIds = getObjectParts(object); + Set needIds = CardUtil.getObjectParts(object); // workaround to find all abilities first and filter it for one object List allPlayable = getPlayable(game, true, zone, false); @@ -1593,40 +1595,6 @@ public abstract class PlayerImpl implements Player, Serializable { return useable; } - protected Set getObjectParts(MageObject object) { - // collect all possible object's parts (example: all sides in mdf/split cards) - Set res = new HashSet<>(); - if (object instanceof SplitCard || object instanceof SplitCardHalf) { - SplitCard mainCard = (SplitCard) ((Card) object).getMainCard(); - res.add(object.getId()); - res.add(mainCard.getId()); - res.add(mainCard.getLeftHalfCard().getId()); - res.add(mainCard.getRightHalfCard().getId()); - } else if (object instanceof ModalDoubleFacesCard || object instanceof ModalDoubleFacesCardHalf) { - ModalDoubleFacesCard mainCard = (ModalDoubleFacesCard) ((Card) object).getMainCard(); - res.add(object.getId()); - res.add(mainCard.getId()); - res.add(mainCard.getLeftHalfCard().getId()); - res.add(mainCard.getRightHalfCard().getId()); - } else if (object instanceof AdventureCard || object instanceof AdventureCardSpell) { - AdventureCard mainCard = (AdventureCard) ((Card) object).getMainCard(); - res.add(object.getId()); - res.add(mainCard.getId()); - res.add(mainCard.getSpellCard().getId()); - } else if (object instanceof Spell) { - // example: activate Lightning Storm's ability from the spell on the stack - res.add(object.getId()); - res.add(((Spell) object).getCard().getId()); // only single side goes to the stack - } else if (object instanceof Commander) { - // commander can contains double sides - res.add(object.getId()); - res.addAll(getObjectParts(((Commander) object).getSourceObject())); - } else { - res.add(object.getId()); - } - return res; - } - protected LinkedHashMap getUseableManaAbilities(MageObject object, Zone zone, Game game) { LinkedHashMap useable = new LinkedHashMap<>(); boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game); diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index 6d4cc56fb81..c74f2b7e74c 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -4,10 +4,10 @@ import mage.MageItem; import mage.abilities.Ability; import mage.cards.Card; import mage.cards.Cards; +import mage.constants.CommanderCardType; import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; -import mage.game.events.GameEvent; import mage.game.events.TargetEvent; import mage.players.Player; @@ -115,6 +115,22 @@ public class TargetCard extends TargetObject { } } break; + case COMMAND: + List possibleCards = game.getCommandersIds(player, CommanderCardType.ANY, false).stream() + .map(game::getCard) + .filter(Objects::nonNull) + .filter(card -> game.getState().getZone(card.getId()).equals(Zone.COMMAND)) + .filter(card -> filter.match(card, sourceId, sourceControllerId, game)) + .collect(Collectors.toList()); + for (Card card : possibleCards) { + if (sourceId == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, sourceId, sourceControllerId))) { + possibleTargets++; + if (possibleTargets >= this.minNumberOfTargets) { + return true; + } + } + } + break; } } } @@ -173,7 +189,7 @@ public class TargetCard extends TargetObject { } break; case COMMAND: - List possibleCards = game.getCommandersIds(player).stream() + List possibleCards = game.getCommandersIds(player, CommanderCardType.ANY, false).stream() .map(game::getCard) .filter(Objects::nonNull) .filter(card -> game.getState().getZone(card.getId()).equals(Zone.COMMAND)) diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 539b2241345..77067d962c0 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1177,4 +1177,50 @@ public final class CardUtil { public static boolean checkCostWords(String text) { return text != null && costWords.stream().anyMatch(text.toLowerCase(Locale.ENGLISH)::startsWith); } + + /** + * Collect all possible object's parts (example: all sides in mdf/split cards) + *

+ * Works with any objects, so commander object can return four ids: commander + main card + left card + right card + * If you pass Card object then it return main card + all parts + * + * @param object + * @return + */ + public static Set getObjectParts(MageObject object) { + Set res = new HashSet<>(); + if (object == null) { + return res; + } + + if (object instanceof SplitCard || object instanceof SplitCardHalf) { + SplitCard mainCard = (SplitCard) ((Card) object).getMainCard(); + res.add(object.getId()); + res.add(mainCard.getId()); + res.add(mainCard.getLeftHalfCard().getId()); + res.add(mainCard.getRightHalfCard().getId()); + } else if (object instanceof ModalDoubleFacesCard || object instanceof ModalDoubleFacesCardHalf) { + ModalDoubleFacesCard mainCard = (ModalDoubleFacesCard) ((Card) object).getMainCard(); + res.add(object.getId()); + res.add(mainCard.getId()); + res.add(mainCard.getLeftHalfCard().getId()); + res.add(mainCard.getRightHalfCard().getId()); + } else if (object instanceof AdventureCard || object instanceof AdventureCardSpell) { + AdventureCard mainCard = (AdventureCard) ((Card) object).getMainCard(); + res.add(object.getId()); + res.add(mainCard.getId()); + res.add(mainCard.getSpellCard().getId()); + } else if (object instanceof Spell) { + // example: activate Lightning Storm's ability from the spell on the stack + res.add(object.getId()); + res.addAll(getObjectParts(((Spell) object).getCard())); + } else if (object instanceof Commander) { + // commander can contains double sides + res.add(object.getId()); + res.addAll(getObjectParts(((Commander) object).getSourceObject())); + } else { + res.add(object.getId()); + } + return res; + } } diff --git a/Mage/src/main/java/mage/watchers/common/CommanderPlaysCountWatcher.java b/Mage/src/main/java/mage/watchers/common/CommanderPlaysCountWatcher.java index a971e338c24..5fe89dc3ff7 100644 --- a/Mage/src/main/java/mage/watchers/common/CommanderPlaysCountWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CommanderPlaysCountWatcher.java @@ -1,5 +1,6 @@ package mage.watchers.common; +import mage.constants.CommanderCardType; import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; @@ -45,11 +46,12 @@ public class CommanderPlaysCountWatcher extends Watcher { objectId = null; } + // must calc all commanders and signature spell cause uses in commander tax boolean isCommanderObject = game .getPlayerList() .stream() .map(game::getPlayer) - .map(game::getCommandersIds) + .map(player -> game.getCommandersIds(player, CommanderCardType.ANY, false)) .flatMap(Collection::stream) .anyMatch(id -> Objects.equals(id, objectId)); if (!isCommanderObject || event.getZone() != Zone.COMMAND) {