From baab31d2e2d876b5ebf2b5fba72a22fdad64b849 Mon Sep 17 00:00:00 2001 From: tool4ever Date: Sat, 26 Jul 2025 12:04:17 +0200 Subject: [PATCH] Rework Shared Fate (#8215) --- .../java/forge/game/ability/AbilityUtils.java | 2 ++ .../java/forge/game/card/CardFactoryUtil.java | 3 --- .../staticability/StaticAbilityContinuous.java | 18 ++++++++++++------ .../res/cardsfolder/g/gonti_lord_of_luxury.txt | 6 +++--- .../cardsfolder/h/heartless_conscription.txt | 2 +- .../res/cardsfolder/h/hoarding_broodlord.txt | 2 +- .../res/cardsfolder/r/release_to_the_wind.txt | 5 ++--- forge-gui/res/cardsfolder/s/shared_fate.txt | 13 ++++--------- .../java/forge/player/HumanCostDecision.java | 1 + 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java index 48e2646c629..24cb587ce14 100644 --- a/forge-game/src/main/java/forge/game/ability/AbilityUtils.java +++ b/forge-game/src/main/java/forge/game/ability/AbilityUtils.java @@ -1191,6 +1191,8 @@ public class AbilityUtils { if (sa.getHostCard().wasCast()) { players.add((sa.getHostCard().getCastSA().getActivatingPlayer())); } + } else if (defined.equals("Exiler")) { + players.add(card.getExiledBy()); } else if (defined.equals("ActivePlayer")) { players.add(game.getPhaseHandler().getPlayerTurn()); } else if (defined.equals("You")) { diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java index eaf2913ae88..cc068e1171e 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -1355,9 +1355,6 @@ public class CardFactoryUtil { final String[] k = keyword.split(":"); String n = k[1]; - // The exiled card gains ‘Any player who has controlled the permanent that exiled this card may look at this card in the exile zone.’ - // this is currently not possible because the StaticAbility currently has no information about the OriginalHost - List triggers = Lists.newArrayList(); StringBuilder sb = new StringBuilder(); sb.append("Mode$ ChangesZone | Destination$ Battlefield | ValidCard$ Card.Self | Secondary$ True | "); 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 5e11e003501..64c755f6810 100644 --- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java +++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java @@ -466,9 +466,11 @@ public final class StaticAbilityContinuous { if (params.containsKey("MayLookAt")) { String look = params.get("MayLookAt"); if ("True".equals(look)) { - look = "You"; + // shortcut when combined with MayPlay + mayLookAt = new PlayerCollection(); + } else { + mayLookAt = AbilityUtils.getDefinedPlayers(hostCard, look, stAb); } - mayLookAt = AbilityUtils.getDefinedPlayers(hostCard, look, stAb); } if (params.containsKey("MayPlay")) { controllerMayPlay = true; @@ -870,10 +872,6 @@ public final class StaticAbilityContinuous { } } - if (mayLookAt != null && (!affectedCard.getOwner().getTopXCardsFromLibrary(1).contains(affectedCard) || game.getTopLibForPlayer(affectedCard.getOwner()) == null || game.getTopLibForPlayer(affectedCard.getOwner()) == affectedCard)) { - affectedCard.addMayLookAt(se.getTimestamp(), mayLookAt); - } - if (controllerMayPlay && (mayPlayLimit == null || stAb.getMayPlayTurn() < mayPlayLimit)) { String mayPlayAltCost = mayPlayAltManaCost; @@ -891,6 +889,10 @@ public final class StaticAbilityContinuous { mayPlayAltCost != null ? new Cost(mayPlayAltCost, false, affectedCard.equals(hostCard)) : null, mayPlayWithFlash, mayPlayGrantZonePermissions, stAb); + if (mayLookAt != null && mayLookAt.isEmpty()) { + mayLookAt.add(mayPlayController); + } + // 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.hasParam("Affected") && stAb.getParam("Affected").equals("Card.Self") && affectedCard.isInZone(ZoneType.Graveyard)) { for (final Player p : game.getPlayers()) { @@ -902,6 +904,10 @@ public final class StaticAbilityContinuous { } } } + + if (mayLookAt != null && (!affectedCard.getOwner().getTopXCardsFromLibrary(1).contains(affectedCard) || game.getTopLibForPlayer(affectedCard.getOwner()) == null || game.getTopLibForPlayer(affectedCard.getOwner()) == affectedCard)) { + affectedCard.addMayLookAt(se.getTimestamp(), mayLookAt); + } } return affectedCards; diff --git a/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt b/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt index b13db4fb75d..37c2bd5a35f 100644 --- a/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt +++ b/forge-gui/res/cardsfolder/g/gonti_lord_of_luxury.txt @@ -3,10 +3,10 @@ ManaCost:2 B B Types:Legendary Creature Aetherborn Rogue PT:2/3 K:Deathtouch -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. For as long as that card remains exiled, you may look at it, you may cast it, and you may spend mana as though it were mana of any type to cast that spell. +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDig | TriggerDescription$ When CARDNAME enters, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may cast that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. SVar:TrigDig:DB$ Dig | ValidTgts$ Opponent | DigNum$ 4 | ChangeNum$ 1 | DestinationZone$ Exile | DestinationZone2$ Library | LibraryPosition$ -1 | RestRandomOrder$ True | ExileFaceDown$ True | WithMayLook$ True | ChangeValid$ Card | RememberChanged$ True | SubAbility$ DBEffect SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup -SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreType$ True | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may look at the card, you may cast it, and you may spend mana as though it were mana of any type to cast that spell. +SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreType$ True | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ You may cast that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True SVar:PlayMain1:TRUE -Oracle:Deathtouch\nWhen Gonti, Lord of Luxury enters, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may look at and cast that card for as long as it remains exiled, and you may spend mana as though it were mana of any type to cast that spell. +Oracle:Deathtouch\nWhen Gonti, Lord of Luxury enters, look at the top four cards of target opponent's library, exile one of them face down, then put the rest on the bottom of that library in a random order. You may cast that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. diff --git a/forge-gui/res/cardsfolder/h/heartless_conscription.txt b/forge-gui/res/cardsfolder/h/heartless_conscription.txt index 630c970fcad..3132c7f01dc 100644 --- a/forge-gui/res/cardsfolder/h/heartless_conscription.txt +++ b/forge-gui/res/cardsfolder/h/heartless_conscription.txt @@ -3,7 +3,7 @@ ManaCost:6 B B Types:Sorcery A:SP$ ChangeZoneAll | Origin$ Battlefield | Destination$ Exile | ChangeType$ Creature | RememberChanged$ True | SubAbility$ DBEffect | SpellDescription$ Exile all creatures. For each card exiled this way, you may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. Exile CARDNAME. SVar:DBEffect:DB$ Effect | RememberObjects$ RememberedCard | StaticAbilities$ STPlay | SubAbility$ DBChange | ForgetOnMoved$ Exile | Duration$ Permanent -SVar:STPlay:Mode$ Continuous | MayLookAt$ You | MayPlay$ True | MayPlayIgnoreType$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the cards exiled with EFFECTSOURCE for as long as they remain exiled, and mana of any type can be spent to cast those spells. +SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayIgnoreType$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may play the cards exiled with EFFECTSOURCE for as long as they remain exiled, and mana of any type can be spent to cast those spells. SVar:DBChange:DB$ ChangeZone | Origin$ Stack | Destination$ Exile | SubAbility$ DBCleanup SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile all creatures. For each card exiled this way, you may play that card for as long as it remains exiled, and mana of any type can be spent to cast that spell. Exile CARDNAME. diff --git a/forge-gui/res/cardsfolder/h/hoarding_broodlord.txt b/forge-gui/res/cardsfolder/h/hoarding_broodlord.txt index 5762ea5afe4..2a397a7e0d7 100644 --- a/forge-gui/res/cardsfolder/h/hoarding_broodlord.txt +++ b/forge-gui/res/cardsfolder/h/hoarding_broodlord.txt @@ -8,6 +8,6 @@ T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.S SVar:TrigSearch:DB$ ChangeZone | ChangeNum$ 1 | ChangeType$ Card | Mandatory$ True | SubAbility$ DBEffect | Origin$ Library | Destination$ Exile | ExileFaceDown$ True | RememberChanged$ True SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayLookAt$ You | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ For as long as that card remains exiled, you may play it. -S:Mode$ Continuous | Affected$ Card.YouCtrl+wasCastFromExile | AffectedZone$ Stack | AddKeyword$ Convoke | Description$ Spells you cast from exile have convoke. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True +S:Mode$ Continuous | Affected$ Card.YouCtrl+wasCastFromExile | AffectedZone$ Stack | AddKeyword$ Convoke | Description$ Spells you cast from exile have convoke. Oracle:Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creature's color.)\nFlying\nWhen Hoarding Broodlord enters, search your library for a card, exile it face down, then shuffle. For as long as that card remains exiled, you may play it.\nSpells you cast from exile have convoke. diff --git a/forge-gui/res/cardsfolder/r/release_to_the_wind.txt b/forge-gui/res/cardsfolder/r/release_to_the_wind.txt index 6b5dec94579..eb072aa9165 100644 --- a/forge-gui/res/cardsfolder/r/release_to_the_wind.txt +++ b/forge-gui/res/cardsfolder/r/release_to_the_wind.txt @@ -2,8 +2,7 @@ Name:Release to the Wind ManaCost:2 U Types:Instant A:SP$ ChangeZone | ValidTgts$ Permanent.nonLand | TgtPrompt$ Select target nonland permanent | Origin$ Battlefield | SubAbility$ DBEffect | Destination$ Exile | SpellDescription$ Exile target nonland permanent. For as long as that card remains exiled, its owner may cast it without paying its mana cost. -SVar:DBEffect:DB$ Effect | RememberObjects$ ParentTarget | EffectOwner$ TargetedOwner | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup -SVar:STPlay1:Mode$ Continuous | MayLookAt$ You | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ For as long as that card remains exiled, its owner may cast it without paying its mana cost. -SVar:STPlay2:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Secondary$ True +SVar:DBEffect:DB$ Effect | RememberObjects$ ParentTarget | EffectOwner$ TargetedOwner | StaticAbilities$ STPlay | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup +SVar:STPlay:Mode$ Continuous | MayPlay$ True | MayPlayWithoutManaCost$ True | Affected$ Card.IsRemembered+nonLand | AffectedZone$ Exile | Description$ For as long as that card remains exiled, its owner may cast it without paying its mana cost. SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True Oracle:Exile target nonland permanent. For as long as that card remains exiled, its owner may cast it without paying its mana cost. diff --git a/forge-gui/res/cardsfolder/s/shared_fate.txt b/forge-gui/res/cardsfolder/s/shared_fate.txt index 0437e0c9196..3deada8314f 100644 --- a/forge-gui/res/cardsfolder/s/shared_fate.txt +++ b/forge-gui/res/cardsfolder/s/shared_fate.txt @@ -1,15 +1,10 @@ Name:Shared Fate ManaCost:4 U Types:Enchantment -Text:If a player would draw a card, that player exiles the top card of one of their opponents' libraries face down instead. Each player may look at cards they exiled with CARDNAME, and they may play lands and cast spells from among those cards. -T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigEffects | Static$ True -# Create an effect for each player. The effect contains both Shared Fate's abilities. -SVar:TrigEffects:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBEffect -SVar:DBEffect:DB$ Effect | EffectOwner$ Remembered | StaticAbilities$ STPlay | ReplacementEffects$ RDraw | Duration$ UntilHostLeavesPlay | ForgetOnMoved$ Exile -SVar:RDraw:Event$ Draw | ActiveZones$ Command | ValidPlayer$ You | ReplaceWith$ DBChooseOpp | Description$ If you would draw a card, exile the top card of an opponent's library face down instead. -SVar:DBChooseOpp:DB$ ChoosePlayer | ChoiceTitle$ Choose an opponent whose top library card to exile | Choices$ Player.Opponent | AILogic$ Curse | SubAbility$ DBExile | DontNotify$ True -SVar:DBExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | ExileFaceDown$ True | Defined$ Player.Chosen | RememberChanged$ True -SVar:STPlay:Mode$ Continuous | MayLookAt$ You | MayPlay$ True | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at and play cards exiled with EFFECTSOURCE. +R:Event$ Draw | ReplaceWith$ DBChooseOpp | Description$ If a player would draw a card, that player exiles the top card of one of their opponents' libraries face down instead. +SVar:DBChooseOpp:DB$ ChoosePlayer | Defined$ ReplacedPlayer | ChoiceTitle$ Choose an opponent whose top library card to exile | Choices$ Player.OpponentOf ReplacedPlayer | AILogic$ Curse | SubAbility$ DBExile | DontNotify$ True +SVar:DBExile:DB$ Dig | DigNum$ 1 | ChangeNum$ All | DestinationZone$ Exile | ExileFaceDown$ True | Defined$ Player.Chosen | DefinedExiler$ ReplacedPlayer +S:Mode$ Continuous | MayPlayPlayer$ Exiler | MayLookAt$ True | MayPlay$ True | Affected$ Card.ExiledWithSource | AffectedZone$ Exile | Description$ Each player may look at cards they exiled with CARDNAME, and they may play lands and cast spells from among those cards. AI:RemoveDeck:All AI:RemoveDeck:Random Oracle:If a player would draw a card, that player exiles the top card of one of their opponents' libraries face down instead.\nEach player may look at cards they exiled with Shared Fate, and they may play lands and cast spells from among those cards. diff --git a/forge-gui/src/main/java/forge/player/HumanCostDecision.java b/forge-gui/src/main/java/forge/player/HumanCostDecision.java index 75696abf7a2..71ed024c605 100644 --- a/forge-gui/src/main/java/forge/player/HumanCostDecision.java +++ b/forge-gui/src/main/java/forge/player/HumanCostDecision.java @@ -491,6 +491,7 @@ public class HumanCostDecision extends CostDecisionMakerBase { min = 0; } GameEntityViewMap gameCacheExile = GameEntityView.getMap(list); + // CR 406.4 technically if you can't see their face some randomness should be involved List views = controller.getGui().many( Localizer.getInstance().getMessage("lblChooseAnExiledCardPutIntoGraveyard"), Localizer.getInstance().getMessage("lblToGraveyard"), min, c, CardView.getCollection(list), CardView.get(source));