Implement miracle-granting cards - #15591
Conversation
Engine support for a watcher that dynamically grants miracle to cards as they are drawn. This watcher is unique in that it adds a `MiracleAbility` inside of its `watch()` function, which is a class which adds a watcher to the global list when instantiated. Since `watch()` is called while iterating over each watcher, dynamically adding a new `Watcher` triggers a `ConcurrentModificationException`. To work around this, I've introduced a new `isolated` flag to watchers. When this flag is set, a copy of the watcher is made, `watch()` called on that, and the resulting changes merged back to the `Watchers` map. This flag defaults to unset, in which case the old behavior is retained. It should only be necessary to grant this flag for watchers which may possible add additonal watchers to the global map inside their `watch()` code. The second major change is that when control of a permanent is changed, it also updates the `controllerId` (and `sourceId`, when that control change is due to the card becoming a new permanent e.g. blinking) of all associated watchers in the global map. This is necessary because the watchers associated with an ability (via `Ability.getWatchers()`) are only used for instantiating new watchers, and it doesn't apply to watchers which were created by old instances of the permanent. This should help improve some possible bugs with control-changing effects on watchers, especially CARD-scoped watchers (see magefree#13774) The third change is that object-copying code now supports copying arbitrary serializable objects by serializing them to a byte array and back. It is possible to serialize lambdas, which is why this was necessary since we need to instantiate a new instance of an effect on the fly.
|
[[Molecule Man]] [[Lorehold, the Historian]] [[Aminatou, Veil Piercer]] |
|
Molecule Man - (Gatherer) (Scryfall) (EDHREC)
Lorehold, the Historian - (Gatherer) (Scryfall) (EDHREC)
Aminatou, Veil Piercer - (Gatherer) (Scryfall) (EDHREC)
|
This implements engine support for, and the individual cards of, the subset of 3 cards that dynamically grant miracle to other cards in hand.
My first draft of this was relatively straightforward, but the hardest problem was specifically the interaction between Aminatou and X-cost spells. We can't apply our cost modifications prior to calling
controller.cast(), as we don't yet know the value of X. So they must be applied in the form of a continuous effect. But that cost reduction effect must returntrueforapplies()only for the specific card that has been drawn. Thus we have to dynamically create the effect, insert it into the effects list, cast the spell, and then remove the effect.This watcher is unique in that it adds a
MiracleAbilityinside of itswatch()function, which is a class which adds a watcher to the global list when instantiated. Sincewatch()is called while iterating over each watcher, dynamically adding a newWatchertriggers aConcurrentModificationException.To work around this, I've introduced a new
isolatedflag to watchers. When this flag is set, a copy of the watcher is made,watch()called on that, and the resulting changes merged back to theWatchersmap. This flag defaults to unset, in which case the old behavior is retained. It should only be necessary to grant this flag for watchers which may possible add additonal watchers to the global map inside theirwatch()code.The second major change is that when control of a permanent is changed, it also updates the
controllerId(andsourceId, when that control change is due to the card becoming a new permanent e.g. blinking) of all associated watchers in the global map. This is necessary because the watchers associated with an ability (viaAbility.getWatchers()) are only used for instantiating new watchers, and it doesn't apply to watchers which were created by old instances of the permanent.This should help improve some possible bugs with control-changing effects on watchers, especially CARD-scoped watchers (see #13774)
The third change is that object-copying code now supports copying arbitrary serializable objects by serializing them to a byte array and back. It is possible to serialize lambdas, which is why this was necessary since we need to instantiate a new instance of an effect on the fly.