diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index 334fd63fd30..4902eebdcd9 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -1029,7 +1029,7 @@ public class PlayerControllerAi extends PlayerController { */ if (sa.isMayChooseNewTargets() && !sa.setupTargets()) { if (sa.isSpell()) { - sa.getHostCard().ceaseToExist(); + sa.getHostCard().ceaseToExist(false); } continue; } diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index 1a3659d3d2d..0527a1e0063 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -769,12 +769,19 @@ public class Game { p.revealFaceDownCards(); } - for(Card c : cards) { + for (Card c : cards) { + // CR 800.4d if card is controlled by opponent, LTB should trigger + if (c.getOwner().equals(p) && c.getController().equals(p)) { + c.getCurrentState().clearTriggers(); + } + } + + for (Card c : cards) { if (c.getController().equals(p) && (c.isPlane() || c.isPhenomenon())) { planarControllerLost = true; } - if(isMultiplayer) { + if (isMultiplayer) { // unattach all "Enchant Player" c.removeAttachedTo(p); if (c.getOwner().equals(p)) { @@ -783,7 +790,9 @@ public class Game { cc.removeEncodedCard(c); cc.removeRemembered(c); } - c.ceaseToExist(); + c.ceaseToExist(false); + // CR 603.2f owner of trigger source lost game + triggerHandler.clearDelayedTrigger(c); } else { // return stolen permanents if (c.getController().equals(p) && c.isInZone(ZoneType.Battlefield)) { diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java index 1aaf6a1dba9..2e590f74a3c 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -6756,11 +6756,15 @@ public class Card extends GameEntity implements Comparable, IHasSVars { } } - public void ceaseToExist() { - getGame().getTriggerHandler().suppressMode(TriggerType.ChangesZone); - getZone().remove(this); - setZone(null); - getGame().getTriggerHandler().clearSuppression(TriggerType.ChangesZone); + public void ceaseToExist(boolean skipTrig) { + // CR 603.6c other players LTB triggers should work + if (skipTrig) { + getZone().remove(this); + setZone(getOwner().getZone(ZoneType.None)); + } + else { + game.getAction().moveTo(ZoneType.None, this, null); + } } public void forceTurnFaceUp() { diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index b5dd3af1353..bc8f479f3ef 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -133,7 +133,7 @@ import forge.util.collect.FCollectionView; public class Player extends GameEntity implements Comparable { public static final List ALL_ZONES = Collections.unmodifiableList(Arrays.asList(ZoneType.Battlefield, ZoneType.Library, ZoneType.Graveyard, ZoneType.Hand, ZoneType.Exile, ZoneType.Command, ZoneType.Ante, - ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck, ZoneType.Merged, ZoneType.Subgame)); + ZoneType.Sideboard, ZoneType.PlanarDeck, ZoneType.SchemeDeck, ZoneType.Merged, ZoneType.Subgame, ZoneType.None)); private final Map commanderDamage = Maps.newHashMap(); diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 6245c3fe132..145c9fe8413 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -2361,7 +2361,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit public void rollback() { for (Card c : rollbackEffects) { - c.ceaseToExist(); + c.ceaseToExist(true); } rollbackEffects.clear(); } diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java index 406a61fad43..629b54081a2 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -882,6 +882,17 @@ public final class StaticAbilityContinuous { affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost, mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null, mayPlayWithFlash, mayPlayGrantZonePermissions, stAb); + + // If the MayPlay effect only affected itself, check if it is in graveyard and give other player who cast Shaman's Trance MayPlay + if (stAb.getParam("Affected").equals("Card.Self") && affectedCard.isInZone(ZoneType.Graveyard)) { + for (final Player p : game.getPlayers()) { + if (p.hasKeyword("Shaman's Trance") && mayPlayController != p) { + affectedCard.setMayPlay(p, mayPlayWithoutManaCost, + mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null, + mayPlayWithFlash, mayPlayGrantZonePermissions, stAb); + } + } + } } } @@ -979,7 +990,26 @@ public final class StaticAbilityContinuous { affectedCards.addAll(game.getCardsIn(ZoneType.Battlefield)); } if (stAb.hasParam("Affected")) { + // Handle Shaman's Trance + CardCollection affectedCardsOriginal = null; + if (controller.hasKeyword("Shaman's Trance") && stAb.hasParam("MayPlay")) { + affectedCardsOriginal = new CardCollection(affectedCards); + } + affectedCards = CardLists.getValidCards(affectedCards, stAb.getParam("Affected").split(","), controller, hostCard, stAb); + + // Add back all cards that are in other player's graveyard, and meet the restrictions without YouOwn/YouCtrl (treat it as in your graveyard) + if (affectedCardsOriginal != null) { + String affectedParam = stAb.getParam("Affected"); + affectedParam = affectedParam.replaceAll("[\\.\\+]YouOwn", ""); + affectedParam = affectedParam.replaceAll("[\\.\\+]YouCtrl", ""); + String[] restrictions = affectedParam.split(","); + for (final Card card : affectedCardsOriginal) { + if (card.isInZone(ZoneType.Graveyard) && card.getController() != controller && card.isValid(restrictions, controller, hostCard, stAb)) { + affectedCards.add(card); + } + } + } } affectedCards.removeAll(stAb.getIgnoreEffectCards()); diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java index 7ddf5b28cff..656829d12b1 100644 --- a/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java +++ b/forge-game/src/main/java/forge/game/trigger/TriggerHandler.java @@ -631,13 +631,12 @@ public class TriggerHandler { } public void onPlayerLost(Player p) { - List lost = new ArrayList<>(); - for (Trigger t : delayedTriggers) { - // CR 603.2f owner of trigger source lost game || 800.4d trigger controller lost game - if (game.getCardState(t.getHostCard(), null) == null || t.getHostCard().getOwner().equals(p)) { - lost.add(t); + List lost = new ArrayList<>(delayedTriggers); + for (Trigger t : lost) { + // CR 800.4d trigger controller lost game + if (t.getHostCard().getOwner().equals(p)) { + delayedTriggers.remove(t); } } - delayedTriggers.removeAll(lost); } } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index 0c0e5ea7500..434d854065a 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -586,7 +586,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable abilities, final ITriggerEvent triggerEvent) { // make sure another human player can't choose opponents cards just because he might see them - if (triggerEvent != null && !hostCard.isInZone(ZoneType.Battlefield) && !hostCard.getOwner().equals(player) && !hostCard.getController().equals(player) && hostCard.mayPlay(player).size() == 0) { + if (triggerEvent != null && !hostCard.isInZone(ZoneType.Battlefield) && !hostCard.getOwner().equals(player) && + !hostCard.getController().equals(player) && hostCard.mayPlay(player).size() == 0 && + // If player cast Shaman's Trance, they can play spells from any Graveyard (if other effects allow it to be cast) + (!player.hasKeyword("Shaman's Trance") || !hostCard.isInZone(ZoneType.Graveyard))) { return null; } spellViewCache = SpellAbilityView.getMap(abilities); @@ -2838,7 +2841,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont continue; } c.getZone().remove(c); - c.ceaseToExist(); + c.ceaseToExist(true); StringBuilder sb = new StringBuilder(); sb.append(p).append(" removes ").append(c).append(" from game due to Dev Cheats.");