Extend original ID concept to apply to effects as well as abilities#14854
Extend original ID concept to apply to effects as well as abilities#14854matoro wants to merge 2 commits into
Conversation
|
This seems like it could be a nice simple solution to those bugs, but I want to check a few things to confirm it's the right approach. Can you reproduce #11560 before/after this change? There's a test case in #13465, not sure if could be impacted by the other recent Tyvar bugfix. Any opinion on whether deterministic id generating is still worth pursuing? I assume #10802 approach is still needed for Whipgrass Entangler, because that has the additional complexity that the ability can be gained multiple times, but in the becoming a copy case the ability is there exactly once? But what about this example: Make [[Spark Double]] a copy of [[Sivitri, Dragon Master]] with [[Oath of Teferi]] to activate twice, does it work as expected? |
|
Spark Double - (Gatherer) (Scryfall) (EDHREC)
Sivitri, Dragon Master - (Gatherer) (Scryfall) (EDHREC)
Oath of Teferi - (Gatherer) (Scryfall) (EDHREC)
|
| if (rEffect != null) { | ||
| if (consumed.containsKey(rEffect.getId())) { | ||
| Set<UUID> set = consumed.get(rEffect.getId()); | ||
| if (consumed.containsKey(rEffect.getOriginalId())) { |
There was a problem hiding this comment.
Looks like a bad solution. Instead research and fix wrong ApplyEffects usage or effects logic -- it's try to skip it. OriginalId concept for linked abilities, not for game engine.
Whole permanent logic with apply effects uses idempotency -- you must able to call it multiple times and catch same game state result. Not different.
There was a problem hiding this comment.
it's the tapping of lands to pay for the tax that triggers the apply, see:
INFO 2026-05-22 01:41:02,088 [LOG][GAME] T2.DA: PlayerB using choice: Yes (by setChoice, ability: Archangel of Tithes [ed0]) =>[main] PrintGameLogsDataCollector.writeLog
INFO 2026-05-22 01:41:02,091 applying effects =>[main] GameState.applyEffects
java.lang.Exception
at mage.game.GameState.applyEffects(GameState.java:669)
at mage.game.GameImpl.applyEffects(GameImpl.java:1975)
at mage.abilities.AbilityImpl.activate(AbilityImpl.java:276)
at mage.abilities.ActivatedAbilityImpl.activate(ActivatedAbilityImpl.java:227)
at mage.abilities.Ability.activate(Ability.java:297)
at mage.players.PlayerImpl.playManaAbility(PlayerImpl.java:1525)
at mage.players.PlayerImpl.activateAbility(PlayerImpl.java:1669)
at mage.player.ai.ComputerPlayer.playManaHandling(ComputerPlayer.java:578)
at mage.player.ai.ComputerPlayer.playMana(ComputerPlayer.java:393)
at org.mage.test.player.TestPlayer.playMana(TestPlayer.java:4415)
at mage.abilities.costs.mana.ManaCostsImpl.pay(ManaCostsImpl.java:146)
at mage.abilities.costs.mana.ManaCostsImpl.payOrRollback(ManaCostsImpl.java:173)
at mage.abilities.effects.PayCostToAttackBlockEffectImpl.handleManaCosts(PayCostToAttackBlockEffectImpl.java:115)
at mage.abilities.effects.PayCostToAttackBlockEffectImpl.replaceEvent(PayCostToAttackBlockEffectImpl.java:93)
at mage.abilities.effects.ContinuousEffects.replaceEvent(ContinuousEffects.java:936)
at mage.game.GameState.replaceEvent(GameState.java:1054)
at mage.game.GameState.replaceEvent(GameState.java:1047)
at mage.game.GameImpl.replaceEvent(GameImpl.java:3511)
at mage.game.combat.Combat.declareAttacker(Combat.java:1468)
at mage.players.PlayerImpl.declareAttacker(PlayerImpl.java:2863)
at org.mage.test.player.TestPlayer.selectAttackers(TestPlayer.java:1978)
at mage.game.combat.Combat.selectAttackers(Combat.java:271)
at mage.game.turn.DeclareAttackersStep.beginStep(DeclareAttackersStep.java:36)
at mage.game.turn.Phase.prePriority(Phase.java:189)
at mage.game.turn.Phase.playStep(Phase.java:203)
at mage.game.turn.Phase.play(Phase.java:91)
at mage.game.turn.Turn.play(Turn.java:132)
at mage.game.GameImpl.playTurn(GameImpl.java:1180)
at mage.game.GameImpl.play(GameImpl.java:1087)
at mage.game.GameImpl.start(GameImpl.java:1063)
at org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.execute(CardTestPlayerAPIImpl.java:324)
at org.mage.test.cards.copy.CloneTest.testCloneAttackTax(CloneTest.java:309)
so how do we go about this? tapping the lands is an ability being activated, so I do imagine this needs to be here to trigger abilities e.g. Manabarbs.
The first two Tyvar tests pass, but the Enduring Vitality one still does not.
I'll have to look at it, everything in this change should work fine with multiple copies of abilities.
Work correctly, pushing up a test to demonstrate. |
The root cause of the issue here is that when a permanent with a replacement effect is copied, this is applied via a continuous `CopyEffect`. Normally this is not a problem, but for attack taxers, part of choosing whether the replacement effect applies or not is tapping mana sources, which triggers `applyEffects()` to run. The `CopyEffect` is then reapplied, and as part of it, it clears the copied abilities and generates new ones with new IDs from the blueprint. Thus, the loop checking whether all replacement effects have been consumed never terminates, because it sees a new replacement effect with a new ID it needs to apply. This extends the concept of `originalId`, which abilities already have, to also apply to effects. It can be used in any scenarios which need an effect's ID to remain stable even when it is destroyed and recreated via a copy from a continuous effect. Fixes magefree#10372 Fixes magefree#14754
The root cause of the issue here is that when a permanent with a replacement effect is copied, this is applied via a continuous
CopyEffect. Normally this is not a problem, but for attack taxers, part of choosing whether the replacement effect applies or not is tapping mana sources, which triggersapplyEffects()to run. TheCopyEffectis then reapplied, and as part of it, it clears the copied abilities and generates new ones with new IDs from the blueprint. Thus, the loop checking whether all replacement effects have been consumed never terminates, because it sees a new replacement effect with a new ID it needs to apply.This extends the concept of
originalId, which abilities already have, to also apply to effects. It can be used in any scenarios which need an effect's ID to remain stable even when it is destroyed and recreated via a copy from a continuous effect.Fixes #10372
Fixes #14754