From 6f7dc38f2a3d2f1b08721694731de0d28788dc8e Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:10:56 -0400 Subject: [PATCH 01/10] TrackableProperty.java add ExiledCards --- forge-game/src/main/java/forge/trackable/TrackableProperty.java | 1 + 1 file changed, 1 insertion(+) 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), From 69377622cce6bda1eaf7bc3a10e7ceaf37060f80 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:12:16 -0400 Subject: [PATCH 02/10] include addExiledCard command in Effects that exile cards --- .../java/forge/game/ability/effects/ChangeZoneAllEffect.java | 1 + .../java/forge/game/ability/effects/ChangeZoneEffect.java | 4 ++++ .../src/main/java/forge/game/ability/effects/DigEffect.java | 2 ++ .../src/main/java/forge/game/ability/effects/MillEffect.java | 1 + 4 files changed, 8 insertions(+) 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); From fa3f7715a3eeb07d08c65b365539b686049b45f0 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:13:19 -0400 Subject: [PATCH 03/10] Card.java: add exiledCards CardCollection --- .../src/main/java/forge/game/card/Card.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) 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 e14b0ede6b8..390fa9ca5b5 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(); @@ -1082,6 +1083,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); } @@ -1686,6 +1712,7 @@ public class Card extends GameEntity implements Comparable, IHasSVars { return; } + exiledWith.removeExiledCard(this); exiledWith.removeUntilLeavesBattlefield(this); exiledWith = null; From b895e283a8de096fe0056b4d9a8cf8776023526f Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:19:14 -0400 Subject: [PATCH 04/10] include addExiledCard command in Costs that exile cards --- .../src/main/java/forge/game/cost/CostAdjustment.java | 6 ++++-- forge-game/src/main/java/forge/game/cost/CostExile.java | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) 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; } From df67f8c2c630ff5a819eeca60fbb626fef41b400 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:19:51 -0400 Subject: [PATCH 05/10] CardView add getExiledCards() --- forge-game/src/main/java/forge/game/card/CardView.java | 4 ++++ 1 file changed, 4 insertions(+) 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); } From 8d42c07608232cf7cfeafd780ebbfb1b8c8cd59e Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:20:20 -0400 Subject: [PATCH 06/10] add Exiled list to Card Detail --- .../src/main/java/forge/gui/card/CardDetailUtil.java | 9 +++++++++ 1 file changed, 9 insertions(+) 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) { From 05df77cdbef02951d6750908f0cee11cd4f88bbf Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:21:54 -0400 Subject: [PATCH 07/10] GameState addExiledCard for Exiled with X --- forge-ai/src/main/java/forge/ai/GameState.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-ai/src/main/java/forge/ai/GameState.java b/forge-ai/src/main/java/forge/ai/GameState.java index c6ed30c0134..2fc8c2a1f5c 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()); } From 7eb8a437b5594a148c85b00674416488a92fd9a6 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:22:14 -0400 Subject: [PATCH 08/10] HumanPlay addExiledCard for Delve --- forge-gui/src/main/java/forge/player/HumanPlay.java | 1 + 1 file changed, 1 insertion(+) 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); From b76061f6d8287b391e8fe5d163c7f9ec04115335 Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:22:59 -0400 Subject: [PATCH 09/10] arcane_bombardment.txt add "OrderDuplicates" to Trigger --- forge-gui/res/cardsfolder/upcoming/arcane_bombardment.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 6b6636deb399173eba9aa8af99a58018dc20eb1f Mon Sep 17 00:00:00 2001 From: Northmoc Date: Mon, 25 Apr 2022 11:23:29 -0400 Subject: [PATCH 10/10] PlayerControllerHuman: support "OrderDuplicates" on sa --- forge-gui/src/main/java/forge/player/PlayerControllerHuman.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 52b164a653f..001f49d88ae 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 }