mirror of
https://github.com/correl/mage.git
synced 2024-12-24 03:00:14 +00:00
Commanders improves:
* [KHM] fixed that some effects can't find mdf commanders on battlefield (example: Fierce Guardianship, #7504); * Oathbreaker: fixed that some cards that refer to commander can affects signature spells too;
This commit is contained in:
parent
62cad8e850
commit
dc0a29007c
34 changed files with 293 additions and 199 deletions
|
@ -117,12 +117,12 @@ public class OathbreakerFreeForAll extends GameCommanderImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType) {
|
||||
public Set<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType, boolean returnAllCardParts) {
|
||||
Set<UUID> res = new HashSet<>();
|
||||
if (player != null) {
|
||||
Set<UUID> commanders = this.playerOathbreakers.getOrDefault(player.getId(), new HashSet<>());
|
||||
Set<UUID> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<Card> 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<Card> 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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -115,7 +115,7 @@ class CommandersPlateEffect extends ContinuousEffectImpl {
|
|||
permanent = game.getPermanentOrLKIBattlefield(equipment.getAttachedTo());
|
||||
}
|
||||
}
|
||||
Set<UUID> commanders = game.getCommandersIds(player);
|
||||
Set<UUID> commanders = game.getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER, false);
|
||||
if (commanders.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -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<UUID> 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<Card> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Card> 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<Card> 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) {
|
||||
|
|
|
@ -58,7 +58,7 @@ public final class OpalPalace extends CardImpl {
|
|||
|
||||
class OpalPalaceWatcher extends Watcher {
|
||||
|
||||
private List<UUID> commanderId = new ArrayList<>();
|
||||
private final List<UUID> 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -76,15 +76,7 @@ class RoadOfReturnEffect extends OneShotEffect {
|
|||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
List<Card> 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<Card> 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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<UUID> 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<Card> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
package mage.constants;
|
||||
|
||||
/**
|
||||
* rules:
|
||||
* Cards that reference "your commander" instead reference "your Oathbreaker."
|
||||
* <p>
|
||||
* 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 {
|
||||
|
|
|
@ -507,27 +507,44 @@ public interface Game extends MageItem, Serializable {
|
|||
|
||||
Mulligan getMulligan();
|
||||
|
||||
Set<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType);
|
||||
|
||||
default Set<UUID> getCommandersIds(Player player) {
|
||||
return getCommandersIds(player, CommanderCardType.ANY);
|
||||
}
|
||||
Set<UUID> 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<Card> getCommanderCardsFromCommandZone(Player player) {
|
||||
default Set<Card> 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<Card> getCommanderCardsFromAnyZones(Player player, CommanderCardType commanderCardType) {
|
||||
// from command zone
|
||||
Set<Card> 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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<UUID> commanderIds = getCommandersIds(player, CommanderCardType.COMMANDER_OR_OATHBREAKER);
|
||||
Set<UUID> 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<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType) {
|
||||
return player.getCommandersIds();
|
||||
public Set<UUID> getCommandersIds(Player player, CommanderCardType commanderCardType, boolean returnAllCardParts) {
|
||||
//noinspection deprecation - it's ok to use it in inner method
|
||||
Set<UUID> mainCards = player.getCommandersIds();
|
||||
return filterCommandersBySearchZone(mainCards, returnAllCardParts);
|
||||
}
|
||||
|
||||
final protected Set<UUID> filterCommandersBySearchZone(Set<UUID> commanderMainCards, boolean returnAllCardParts) {
|
||||
// filter by zone search (example: if you search commanders on battlefield then must see all sides of mdf cards)
|
||||
Set<UUID> 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
|
||||
|
|
|
@ -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<UUID> needIds = getObjectParts(object);
|
||||
Set<UUID> needIds = CardUtil.getObjectParts(object);
|
||||
|
||||
// workaround to find all abilities first and filter it for one object
|
||||
List<ActivatedAbility> allPlayable = getPlayable(game, true, zone, false);
|
||||
|
@ -1593,40 +1595,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return useable;
|
||||
}
|
||||
|
||||
protected Set<UUID> getObjectParts(MageObject object) {
|
||||
// collect all possible object's parts (example: all sides in mdf/split cards)
|
||||
Set<UUID> 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<UUID, ActivatedManaAbilityImpl> getUseableManaAbilities(MageObject object, Zone zone, Game game) {
|
||||
LinkedHashMap<UUID, ActivatedManaAbilityImpl> useable = new LinkedHashMap<>();
|
||||
boolean canUse = !(object instanceof Permanent) || ((Permanent) object).canUseActivatedAbilities(game);
|
||||
|
|
|
@ -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<Card> 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<Card> possibleCards = game.getCommandersIds(player).stream()
|
||||
List<Card> possibleCards = game.getCommandersIds(player, CommanderCardType.ANY, false).stream()
|
||||
.map(game::getCard)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(card -> game.getState().getZone(card.getId()).equals(Zone.COMMAND))
|
||||
|
|
|
@ -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)
|
||||
* <p>
|
||||
* 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<UUID> getObjectParts(MageObject object) {
|
||||
Set<UUID> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue