diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index 82504f4cdd5..29557f0ddb5 100644 --- a/forge-ai/src/main/java/forge/ai/GameState.java +++ b/forge-ai/src/main/java/forge/ai/GameState.java @@ -823,6 +823,7 @@ public abstract class GameState { String id = rememberedEnts.getValue(); Card exiledWith = idToCard.get(Integer.parseInt(id)); + exiledWith.addExiledCard(c); c.setExiledWith(exiledWith); c.setExiledBy(exiledWith.getController()); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java index 23cdeaf3f09..828a3139602 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneAllEffect.java @@ -192,6 +192,7 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect { if (host == null) { host = sa.getHostCard(); } + host.addExiledCard(movedCard); movedCard.setExiledWith(host); movedCard.setExiledBy(host.getController()); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index 2edf6e56739..0f4c82e7f47 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -719,6 +719,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (host == null) { host = sa.getHostCard(); } + host.addExiledCard(gameCard); gameCard.setExiledWith(host); gameCard.setExiledBy(host.getController()); } @@ -745,6 +746,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (destination.equals(ZoneType.Exile) && !movedCard.isToken()) { movedCard.setExiledWith(host); if (host != null) { + host.addExiledCard(movedCard); movedCard.setExiledBy(host.getController()); } } @@ -1350,6 +1352,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { if (host == null) { host = sa.getHostCard(); } + host.addExiledCard(movedCard); movedCard.setExiledWith(host); movedCard.setExiledBy(host.getController()); } @@ -1541,6 +1544,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { host = srcSA.getHostCard(); } movedCard = game.getAction().exile(tgtHost, srcSA, params); + host.addExiledCard(movedCard); movedCard.setExiledWith(host); movedCard.setExiledBy(host.getController()); } else if (srcSA.getParam("Destination").equals("TopOfLibrary")) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index cccbe92417c..ab0d9324f4c 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -420,6 +420,7 @@ public class DigEffect extends SpellAbilityEffect { if (sa.hasParam("ExileWithCounter")) { c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable); } + effectHost.addExiledCard(c); c.setExiledWith(effectHost); c.setExiledBy(effectHost.getController()); } @@ -492,6 +493,7 @@ public class DigEffect extends SpellAbilityEffect { if (sa.hasParam("ExileWithCounter")) { c.addCounter(CounterType.getType(sa.getParam("ExileWithCounter")), 1, player, counterTable); } + effectHost.addExiledCard(c); c.setExiledWith(effectHost); c.setExiledBy(effectHost.getController()); if (remZone2) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java index ab41d62def8..2c85562b1ed 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java @@ -68,6 +68,7 @@ public class MillEffect extends SpellAbilityEffect { host = sa.getHostCard(); } for (final Card c : milled) { + host.addExiledCard(c); c.setExiledWith(host); if (facedown) { c.turnFaceDown(true); 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 33e22625611..a4126bf713e 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -109,7 +109,8 @@ public class Card extends GameEntity implements Comparable, IHasSVars { private final Table> hiddenExtrinsicKeywords = TreeBasedTable.create(); // cards attached or otherwise linked to this card - private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, encodedCards; + private CardCollection hauntedBy, devouredCards, exploitedCards, delvedCards, convokedCards, imprintedCards, + exiledCards, encodedCards; private CardCollection gainControlTargets, chosenCards; private CardCollection mergedCards; private Map mustBlockCards = Maps.newHashMap(); @@ -1079,6 +1080,31 @@ public class Card extends GameEntity implements Comparable, IHasSVars { imprintedCards = view.clearCards(imprintedCards, TrackableProperty.ImprintedCards); } + public final CardCollectionView getExiledCards() { + return CardCollection.getView(exiledCards); + } + public final boolean hasExiledCard() { + return FCollection.hasElements(exiledCards); + } + public final boolean hasExiledCard(Card c) { + return FCollection.hasElement(exiledCards, c); + } + public final void addExiledCard(final Card c) { + exiledCards = view.addCard(exiledCards, c, TrackableProperty.ExiledCards); + } + public final void addExiledCards(final Iterable cards) { + exiledCards = view.addCards(exiledCards, cards, TrackableProperty.ExiledCards); + } + public final void removeExiledCard(final Card c) { + exiledCards = view.removeCard(exiledCards, c, TrackableProperty.ExiledCards); + } + public final void removeExiledCards(final Iterable cards) { + exiledCards = view.removeCards(exiledCards, cards, TrackableProperty.ExiledCards); + } + public final void clearExiledCards() { + exiledCards = view.clearCards(exiledCards, TrackableProperty.ExiledCards); + } + public final CardCollectionView getEncodedCards() { return CardCollection.getView(encodedCards); } @@ -1668,6 +1694,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return; } + exiledWith.removeExiledCard(this); exiledWith.removeUntilLeavesBattlefield(this); exiledWith = null; diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 70a2b6e13bf..ce634bb9649 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -647,6 +647,10 @@ public class CardView extends GameEntityView { return get(TrackableProperty.ImprintedCards); } + public FCollectionView getExiledCards() { + return get(TrackableProperty.ExiledCards); + } + public FCollectionView getHauntedBy() { return get(TrackableProperty.HauntedBy); } diff --git a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java index 946cc4f1675..cf3c7a003d0 100644 --- a/forge-game/src/main/java/forge/game/cost/CostAdjustment.java +++ b/forge-game/src/main/java/forge/game/cost/CostAdjustment.java @@ -232,8 +232,10 @@ public class CostAdjustment { } else if (!test) { sa.getHostCard().addDelved(c); final Card d = game.getAction().exile(c, null); - d.setExiledWith(sa.getHostCard()); - d.setExiledBy(sa.getHostCard().getController()); + final Card host = sa.getHostCard(); + host.addExiledCard(d); + d.setExiledWith(host); + d.setExiledBy(host.getController()); table.put(ZoneType.Graveyard, d.getZone().getZoneType(), d); } } diff --git a/forge-game/src/main/java/forge/game/cost/CostExile.java b/forge-game/src/main/java/forge/game/cost/CostExile.java index 4e0b3257a94..c2de23710b3 100644 --- a/forge-game/src/main/java/forge/game/cost/CostExile.java +++ b/forge-game/src/main/java/forge/game/cost/CostExile.java @@ -180,8 +180,10 @@ public class CostExile extends CostPartWithList { @Override protected Card doPayment(SpellAbility ability, Card targetCard, final boolean effect) { final Game game = targetCard.getGame(); + final Card host = ability.getHostCard(); Card newCard = game.getAction().exile(targetCard, null); - newCard.setExiledWith(ability.getHostCard()); + host.addExiledCard(newCard); + newCard.setExiledWith(host); newCard.setExiledBy(ability.getActivatingPlayer()); return newCard; } diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index 5d2dcdc36f8..f1f397ab4a9 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -87,6 +87,7 @@ public enum TrackableProperty { NeedsTapAnimation(TrackableTypes.BooleanType, FreezeMode.IgnoresFreeze), ImprintedCards(TrackableTypes.CardViewCollectionType), + ExiledCards(TrackableTypes.CardViewCollectionType), HauntedBy(TrackableTypes.CardViewCollectionType), Haunting(TrackableTypes.CardViewType), MustBlockCards(TrackableTypes.CardViewCollectionType), diff --git a/forge-gui/res/cardsfolder/upcoming/arcane_bombardment.txt b/forge-gui/res/cardsfolder/upcoming/arcane_bombardment.txt index 772e79f1553..3355631fdfb 100644 --- a/forge-gui/res/cardsfolder/upcoming/arcane_bombardment.txt +++ b/forge-gui/res/cardsfolder/upcoming/arcane_bombardment.txt @@ -1,7 +1,7 @@ Name:Arcane Bombardment ManaCost:4 R R Types:Enchantment -T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigExile | TriggerZones$ Battlefield | TriggerDescription$ Whenever you cast your first instant or sorcery spell each turn, exile an instant or sorcery card at random from your graveyard. Then copy each card exiled with CARDNAME. You may cast any number of the copies without paying their mana costs. +T:Mode$ SpellCast | ValidCard$ Instant,Sorcery | ValidActivatingPlayer$ You | ActivatorThisTurnCast$ EQ1 | NoResolvingCheck$ True | Execute$ TrigExile | TriggerZones$ Battlefield | OrderDuplicates$ True | TriggerDescription$ Whenever you cast your first instant or sorcery spell each turn, exile an instant or sorcery card at random from your graveyard. Then copy each card exiled with CARDNAME. You may cast any number of the copies without paying their mana costs. SVar:TrigExile:DB$ ChangeZone | DefinedPlayer$ You | Destination$ Exile | ChangeNum$ 1 | ChangeType$ Instant.YouOwn,Sorcery.YouOwn | AtRandom$ True | Origin$ Graveyard | Hidden$ True | Mandatory$ True | SubAbility$ DBCast SVar:DBCast:DB$ Play | Valid$ Card.ExiledWithSource | ValidSA$ Spell | ValidZone$ Exile | Amount$ All | CopyCard$ True | Optional$ True | WithoutManaCost$ True DeckNeeds:Type$Instant|Sorcery diff --git a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java index a129b11f179..63b28987d0f 100644 --- a/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java +++ b/forge-gui/src/main/java/forge/gui/card/CardDetailUtil.java @@ -560,6 +560,15 @@ public class CardDetailUtil { area.append(StringUtils.join(card.getImprintedCards(), ", ")); } + // CardsExiledBy + if (card.getExiledCards() != null) { + if (area.length() != 0) { + area.append("\n"); + } + area.append("Exiled: "); + area.append(StringUtils.join(card.getExiledCards(), ", ")); + } + // Haunt if (card.getHauntedBy() != null) { if (area.length() != 0) { diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index c41430b4e4d..14087cd9995 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -577,6 +577,7 @@ public class HumanPlay { hostCard.addDelved(c); final ZoneType o = c.getZone().getZoneType(); final Card d = game.getAction().exile(c, null); + hostCard.addExiledCard(d); d.setExiledWith(hostCard); d.setExiledBy(hostCard.getController()); table.put(o, d.getZone().getZoneType(), d); diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 825e559094a..ee2897a980e 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -1842,7 +1842,7 @@ public class PlayerControllerHuman extends PlayerController implements IGameCont if (!currentSa.isTrigger() && currentSa.usesTargeting()) { needPrompt = true; } - if (!needPrompt && !saStr.equals(firstStr)) { + if (!needPrompt && !saStr.equals(firstStr) && !currentSa.hasParam("OrderDuplicates")) { needPrompt = true; // prompt by default unless all abilities // are the same }