From ffe75f77edc86a5b6d8864ba34e8ea5dda8027c0 Mon Sep 17 00:00:00 2001 From: jeffwadsworth Date: Wed, 14 Jul 2021 16:44:28 -0500 Subject: [PATCH] - Fixed #7572 --- .../src/mage/cards/c/CurseOfTheSwine.java | 33 ++++----------- .../mage/cards/r/RanarTheEverWatchful.java | 30 +++++++------ Mage/src/main/java/mage/game/GameState.java | 42 ++++++++++++++----- .../src/main/java/mage/game/ZonesHandler.java | 9 +++- .../game/events/ZoneChangeGroupEvent.java | 10 ++++- 5 files changed, 69 insertions(+), 55 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java b/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java index 47a4b15b415..a2e0db13c77 100644 --- a/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java +++ b/Mage.Sets/src/mage/cards/c/CurseOfTheSwine.java @@ -20,6 +20,7 @@ import java.util.UUID; import mage.cards.Card; import mage.cards.Cards; import mage.cards.CardsImpl; +import mage.game.permanent.PermanentToken; /** * @author LevelX2 @@ -82,6 +83,12 @@ class CurseOfTheSwineEffect extends OneShotEffect { Map playersWithTargets = new HashMap<>(); for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { Permanent creature = game.getPermanent(targetId); + // handle creature tokens here due to LKI being non-existent for them + // TODO implement a way to verify that tokens are indeed exiled from the battlefield + if (creature instanceof PermanentToken) { + playersWithTargets.put(creature.getControllerId(), + playersWithTargets.getOrDefault(creature.getControllerId(), 0) + 1); + } if (creature != null) { creaturesToExile.add(creature); } @@ -93,6 +100,7 @@ class CurseOfTheSwineEffect extends OneShotEffect { // Count all creatures actually exiled and add them to the player's count for (Card card : creaturesToExile.getCards(game)) { Permanent lkiP = game.getPermanentOrLKIBattlefield(card.getId()); + // note that tokens have no LKI once they are moved from the battlefield so they are handled earlier if (lkiP != null && game.getState().getZone(lkiP.getId()) == Zone.EXILED) { playersWithTargets.put(lkiP.getControllerId(), @@ -108,28 +116,3 @@ class CurseOfTheSwineEffect extends OneShotEffect { return false; } } - - -/* -if (controller != null) { - Map playersWithTargets = new HashMap<>(); - for (UUID targetId : this.getTargetPointer().getTargets(game, source)) { - Permanent creature = game.getPermanent(targetId); - if (creature != null) { - if (controller.moveCards(creature, Zone.EXILED, source, game)) { - playersWithTargets.put(creature.getControllerId(), - playersWithTargets.getOrDefault(creature.getControllerId(), 0) + 1); - } - } - } - Boar2Token swineToken = new Boar2Token(); - for (Map.Entry exiledByController : playersWithTargets.entrySet()) { - swineToken.putOntoBattlefield(exiledByController.getValue(), game, source, exiledByController.getKey()); - } - return true; - } - - - - -*/ diff --git a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java index 738a16b0ada..7eee4209c6a 100644 --- a/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java +++ b/Mage.Sets/src/mage/cards/r/RanarTheEverWatchful.java @@ -5,11 +5,9 @@ import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.Effect; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.keyword.FlyingAbility; -import mage.abilities.keyword.ForetellAbility; import mage.abilities.keyword.VigilanceAbility; import mage.cards.Card; import mage.cards.CardImpl; @@ -19,10 +17,11 @@ import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeGroupEvent; import mage.game.permanent.token.SpiritWhiteToken; -import mage.watchers.common.ForetoldWatcher; import java.util.Objects; import java.util.UUID; +import mage.abilities.keyword.ForetellAbility; +import mage.watchers.common.ForetoldWatcher; /** * @author jeffwadsworth @@ -49,7 +48,7 @@ public final class RanarTheEverWatchful extends CardImpl { this.addAbility(ability); // Whenever you exile one or more cards from your hand and/or permanents from the battlefield, create a 1/1 white Spirit creature token with flying. - this.addAbility(new RanarTheEverWatchfulTriggeredAbility(new CreateTokenEffect(new SpiritWhiteToken()))); + this.addAbility(new RanarTheEverWatchfulTriggeredAbility()); } @@ -88,7 +87,7 @@ class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl @Override public boolean applies(Ability abilityToModify, Ability source, Game game) { - ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class); + ForetoldWatcher watcher = game.getState().getWatcher(ForetoldWatcher.class, source.getControllerId()); return (watcher != null && watcher.countNumberForetellThisTurn() == 0 && abilityToModify.isControlledBy(source.getControllerId()) @@ -98,8 +97,8 @@ class RanarTheEverWatchfulCostReductionEffect extends CostModificationEffectImpl class RanarTheEverWatchfulTriggeredAbility extends TriggeredAbilityImpl { - RanarTheEverWatchfulTriggeredAbility(Effect effect) { - super(Zone.BATTLEFIELD, effect, false); + RanarTheEverWatchfulTriggeredAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new SpiritWhiteToken()), false); } RanarTheEverWatchfulTriggeredAbility(final RanarTheEverWatchfulTriggeredAbility ability) { @@ -119,16 +118,15 @@ class RanarTheEverWatchfulTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkTrigger(GameEvent event, Game game) { ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event; - if (zEvent.getToZone() != Zone.EXILED || zEvent.getCards() == null) { + final int numberExiled = zEvent.getCards().size() + zEvent.getTokens().size(); + if (zEvent.getToZone() != Zone.EXILED + || numberExiled == 0) { return false; } switch (zEvent.getFromZone()) { case BATTLEFIELD: - return isControlledBy(game.getControllerId(zEvent.getSourceId())) - && zEvent - .getCards() - .stream() - .anyMatch(Objects::nonNull); + return controllerId.equals(zEvent.getSource().getControllerId()) + && numberExiled > 0; // must include both card permanents and tokens on the battlefield case HAND: return zEvent .getCards() @@ -142,8 +140,8 @@ class RanarTheEverWatchfulTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever one or more cards are put into exile from your hand " + - "or a spell or ability you control exiles one or more permanents from the battlefield, " + - "create a 1/1 white Spirit creature token with flying."; + return "Whenever one or more cards are put into exile from your hand " + + "or a spell or ability you control exiles one or more permanents from the battlefield, " + + "create a 1/1 white Spirit creature token with flying."; } } diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index cdd0f975ab3..f7fb4500fb6 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -816,12 +816,14 @@ public class GameState implements Serializable, Copyable { private final Zone toZone; private final UUID sourceId; private final UUID playerId; + Ability source; - public ZoneChangeData(UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { + public ZoneChangeData(Ability source, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { this.sourceId = sourceId; this.playerId = playerId; this.fromZone = fromZone; this.toZone = toZone; + this.source = source; } @Override @@ -829,7 +831,7 @@ public class GameState implements Serializable, Copyable { return (this.fromZone.ordinal() + 1) * 1 + (this.toZone.ordinal() + 1) * 10 + (this.sourceId != null ? this.sourceId.hashCode() : 0) - + (this.playerId != null ? this.playerId.hashCode() : 0); + + (this.source != null ? this.source.hashCode() : 0); } @Override @@ -839,7 +841,7 @@ public class GameState implements Serializable, Copyable { return this.fromZone == data.fromZone && this.toZone == data.toZone && Objects.equals(this.sourceId, data.sourceId) - && Objects.equals(this.playerId, data.playerId); + && Objects.equals(this.source, data.source); } return false; } @@ -850,7 +852,12 @@ public class GameState implements Serializable, Copyable { for (GameEvent event : events) { if (event instanceof ZoneChangeEvent) { ZoneChangeEvent castEvent = (ZoneChangeEvent) event; - ZoneChangeData key = new ZoneChangeData(castEvent.getSourceId(), castEvent.getPlayerId(), castEvent.getFromZone(), castEvent.getToZone()); + ZoneChangeData key = new ZoneChangeData( + castEvent.getSource(), + castEvent.getSourceId(), + castEvent.getPlayerId(), + castEvent.getFromZone(), + castEvent.getToZone()); if (eventsByKey.containsKey(key)) { eventsByKey.get(key).add(event); } else { @@ -863,20 +870,30 @@ public class GameState implements Serializable, Copyable { for (Map.Entry> entry : eventsByKey.entrySet()) { Set movedCards = new LinkedHashSet<>(); Set movedTokens = new LinkedHashSet<>(); - for (Iterator it = entry.getValue().iterator(); it.hasNext(); ) { + for (Iterator it = entry.getValue().iterator(); it.hasNext();) { GameEvent event = it.next(); ZoneChangeEvent castEvent = (ZoneChangeEvent) event; UUID targetId = castEvent.getTargetId(); Card card = ZonesHandler.getTargetCard(game, targetId); if (card instanceof PermanentToken) { movedTokens.add((PermanentToken) card); + } else if (game.getObject(targetId) instanceof PermanentToken) { + movedTokens.add((PermanentToken) game.getObject(targetId)); } else if (card != null) { movedCards.add(card); } } ZoneChangeData eventData = entry.getKey(); if (!movedCards.isEmpty() || !movedTokens.isEmpty()) { - ZoneChangeGroupEvent event = new ZoneChangeGroupEvent(movedCards, movedTokens, eventData.sourceId, eventData.playerId, eventData.fromZone, eventData.toZone); + System.out.println("The movedCards and movedTokens : " + movedCards + " " + movedTokens); + ZoneChangeGroupEvent event = new ZoneChangeGroupEvent( + movedCards, + movedTokens, + eventData.sourceId, + eventData.source, + eventData.playerId, + eventData.fromZone, + eventData.toZone); groupEvents.add(event); } } @@ -928,7 +945,8 @@ public class GameState implements Serializable, Copyable { * span * * @param ability - * @param sourceId - if source object can be moved between zones then you must set it here (each game cycle clear all source related triggers) + * @param sourceId - if source object can be moved between zones then you + * must set it here (each game cycle clear all source related triggers) * @param attachedTo */ public void addAbility(Ability ability, UUID sourceId, MageObject attachedTo) { @@ -1046,7 +1064,8 @@ public class GameState implements Serializable, Copyable { /** * Return values list starting with searching key. *

- * Usage example: if you want to find all saved values from related ability/effect + * Usage example: if you want to find all saved values from related + * ability/effect * * @param startWithValue * @return @@ -1133,8 +1152,8 @@ public class GameState implements Serializable, Copyable { * @param attachedTo * @param ability * @param copyAbility copies non MageSingleton abilities before adding to - * state (allows to have multiple instances in one object, - * e.g. false param will simulate keyword/singletone) + * state (allows to have multiple instances in one object, e.g. false param + * will simulate keyword/singleton) */ public void addOtherAbility(Card attachedTo, Ability ability, boolean copyAbility) { checkWrongDynamicAbilityUsage(attachedTo, ability); @@ -1271,7 +1290,8 @@ public class GameState implements Serializable, Copyable { } /** - * Make full copy of the card and all of the card's parts and put to the game. + * Make full copy of the card and all of the card's parts and put to the + * game. * * @param mainCardToCopy * @param newController diff --git a/Mage/src/main/java/mage/game/ZonesHandler.java b/Mage/src/main/java/mage/game/ZonesHandler.java index 8bc54fb6cb9..9f10fcdbcbc 100644 --- a/Mage/src/main/java/mage/game/ZonesHandler.java +++ b/Mage/src/main/java/mage/game/ZonesHandler.java @@ -36,7 +36,14 @@ public final class ZonesHandler { } else { cards.add(targetCard); } - game.fireEvent(new ZoneChangeGroupEvent(cards, tokens, info.event.getSourceId(), info.event.getPlayerId(), info.event.getFromZone(), info.event.getToZone())); + game.fireEvent(new ZoneChangeGroupEvent( + cards, + tokens, + info.event.getSourceId(), + info.event.getSource(), + info.event.getPlayerId(), + info.event.getFromZone(), + info.event.getToZone())); // normal movement game.fireEvent(info.event); return true; diff --git a/Mage/src/main/java/mage/game/events/ZoneChangeGroupEvent.java b/Mage/src/main/java/mage/game/events/ZoneChangeGroupEvent.java index a2cd7884f1a..129b4167cf0 100644 --- a/Mage/src/main/java/mage/game/events/ZoneChangeGroupEvent.java +++ b/Mage/src/main/java/mage/game/events/ZoneChangeGroupEvent.java @@ -3,9 +3,9 @@ package mage.game.events; import mage.cards.Card; import mage.constants.Zone; import mage.game.permanent.PermanentToken; - import java.util.Set; import java.util.UUID; +import mage.abilities.Ability; /** * @author LevelX2 @@ -16,13 +16,15 @@ public class ZoneChangeGroupEvent extends GameEvent { private final Zone toZone; private final Set cards; private final Set tokens; + /* added this */ Ability source; - public ZoneChangeGroupEvent(Set cards, Set tokens, UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { + public ZoneChangeGroupEvent(Set cards, Set tokens, UUID sourceId, Ability source, UUID playerId, Zone fromZone, Zone toZone) { super(GameEvent.EventType.ZONE_CHANGE_GROUP, null, null, playerId); this.fromZone = fromZone; this.toZone = toZone; this.cards = cards; this.tokens = tokens; + this.source = source; } public Zone getFromZone() { @@ -40,5 +42,9 @@ public class ZoneChangeGroupEvent extends GameEvent { public Set getTokens() { return tokens; } + + public Ability getSource() { + return source; + } }