From 111114e338613dbfa05a18adf33ff51eb9e93bf0 Mon Sep 17 00:00:00 2001 From: emerald000 Date: Sun, 3 May 2020 09:42:16 -0400 Subject: [PATCH 1/2] Refactor and add hint for "Ability resolved X times" Affects Ashling the Pilgrim, Inner-Flame Igniter and Soulbright Flamekin. --- .../src/mage/cards/a/AshlingThePilgrim.java | 30 +++++------ .../src/mage/cards/i/InnerFlameIgniter.java | 46 +++------------- .../src/mage/cards/s/SoulbrightFlamekin.java | 20 ++++--- .../common/AbilityResolutionCount.java | 39 ++++++++++++++ .../IfAbilityHasResolvedXTimesEffect.java | 52 +++++++++++++++++++ .../common/AbilityResolutionCountHint.java | 26 ++++++++++ .../main/java/mage/game/events/GameEvent.java | 3 +- .../java/mage/game/stack/StackAbility.java | 1 + .../common/AbilityResolvedWatcher.java | 15 +++--- 9 files changed, 156 insertions(+), 76 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/dynamicvalue/common/AbilityResolutionCount.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java create mode 100644 Mage/src/main/java/mage/abilities/hint/common/AbilityResolutionCountHint.java diff --git a/Mage.Sets/src/mage/cards/a/AshlingThePilgrim.java b/Mage.Sets/src/mage/cards/a/AshlingThePilgrim.java index edf24e4da5..9188c0d57e 100644 --- a/Mage.Sets/src/mage/cards/a/AshlingThePilgrim.java +++ b/Mage.Sets/src/mage/cards/a/AshlingThePilgrim.java @@ -6,7 +6,9 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.DamageEverythingEffect; +import mage.abilities.effects.common.IfAbilityHasResolvedXTimesEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.hint.common.AbilityResolutionCountHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -17,7 +19,6 @@ import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.permanent.Permanent; -import mage.players.Player; import mage.watchers.common.AbilityResolvedWatcher; import java.util.UUID; @@ -40,7 +41,8 @@ public final class AshlingThePilgrim extends CardImpl { Ability ability = new SimpleActivatedAbility( new AddCountersSourceEffect(CounterType.P1P1.createInstance()), new ManaCostsImpl("{1}{R}") ); - ability.addEffect(new AshlingThePilgrimEffect()); + ability.addEffect(new IfAbilityHasResolvedXTimesEffect(Outcome.Damage, 3, new AshlingThePilgrimEffect())); + ability.addHint(AbilityResolutionCountHint.instance); this.addAbility(ability, new AbilityResolvedWatcher()); } @@ -58,8 +60,7 @@ class AshlingThePilgrimEffect extends OneShotEffect { AshlingThePilgrimEffect() { super(Outcome.Damage); - this.staticText = "If this is the third time this ability has resolved this turn, " + - "remove all +1/+1 counters from {this}, and it deals that much damage to each creature and each player"; + this.staticText = "remove all +1/+1 counters from {this}, and it deals that much damage to each creature and each player"; } private AshlingThePilgrimEffect(final AshlingThePilgrimEffect effect) { @@ -73,20 +74,15 @@ class AshlingThePilgrimEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId()); - AbilityResolvedWatcher watcher = game.getState().getWatcher(AbilityResolvedWatcher.class); - if (controller == null - || sourcePermanent == null - || watcher == null - || !watcher.checkActivations(source, game)) { - return false; + if (sourcePermanent != null) { + int counters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); + if (counters < 1) { + return false; + } + sourcePermanent.removeCounters(CounterType.P1P1.createInstance(counters), game); + return new DamageEverythingEffect(counters, StaticFilters.FILTER_PERMANENT_CREATURE).apply(game, source); } - int counters = sourcePermanent.getCounters(game).getCount(CounterType.P1P1); - if (counters < 1) { - return false; - } - sourcePermanent.removeCounters(CounterType.P1P1.createInstance(counters), game); - return new DamageEverythingEffect(counters, StaticFilters.FILTER_PERMANENT_CREATURE).apply(game, source); + return true; } } diff --git a/Mage.Sets/src/mage/cards/i/InnerFlameIgniter.java b/Mage.Sets/src/mage/cards/i/InnerFlameIgniter.java index 86ff0ed941..19b6a685f4 100644 --- a/Mage.Sets/src/mage/cards/i/InnerFlameIgniter.java +++ b/Mage.Sets/src/mage/cards/i/InnerFlameIgniter.java @@ -4,9 +4,11 @@ import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.common.IfAbilityHasResolvedXTimesEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.hint.common.AbilityResolutionCountHint; import mage.abilities.keyword.FirstStrikeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -15,8 +17,6 @@ import mage.constants.Duration; import mage.constants.Outcome; import mage.constants.SubType; import mage.filter.StaticFilters; -import mage.game.Game; -import mage.players.Player; import mage.watchers.common.AbilityResolvedWatcher; import java.util.UUID; @@ -37,7 +37,9 @@ public final class InnerFlameIgniter extends CardImpl { Ability ability = new SimpleActivatedAbility( new BoostControlledEffect(1, 0, Duration.EndOfTurn), new ManaCostsImpl("{2}{R}") ); - ability.addEffect(new InnerFlameIgniterEffect()); + ContinuousEffect effectIf3rdResolution = new GainAbilityControlledEffect(FirstStrikeAbility.getInstance(), Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES); + ability.addEffect(new IfAbilityHasResolvedXTimesEffect(Outcome.AddAbility, 3, effectIf3rdResolution)); + ability.addHint(AbilityResolutionCountHint.instance); this.addAbility(ability, new AbilityResolvedWatcher()); } @@ -49,38 +51,4 @@ public final class InnerFlameIgniter extends CardImpl { public InnerFlameIgniter copy() { return new InnerFlameIgniter(this); } -} - -class InnerFlameIgniterEffect extends OneShotEffect { - - InnerFlameIgniterEffect() { - super(Outcome.AddAbility); - this.staticText = "If this is the third time this ability has resolved this turn, " + - "creatures you control gain first strike until end of turn"; - } - - private InnerFlameIgniterEffect(final InnerFlameIgniterEffect effect) { - super(effect); - } - - @Override - public InnerFlameIgniterEffect copy() { - return new InnerFlameIgniterEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player controller = game.getPlayer(source.getControllerId()); - AbilityResolvedWatcher watcher = game.getState().getWatcher(AbilityResolvedWatcher.class); - if (controller == null - || watcher == null - || !watcher.checkActivations(source, game)) { - return false; - } - game.addEffect(new GainAbilityControlledEffect( - FirstStrikeAbility.getInstance(), Duration.EndOfTurn, - StaticFilters.FILTER_PERMANENT_CREATURE - ), source); - return true; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/SoulbrightFlamekin.java b/Mage.Sets/src/mage/cards/s/SoulbrightFlamekin.java index c4fa17cb3a..95f3198e03 100644 --- a/Mage.Sets/src/mage/cards/s/SoulbrightFlamekin.java +++ b/Mage.Sets/src/mage/cards/s/SoulbrightFlamekin.java @@ -6,7 +6,9 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.IfAbilityHasResolvedXTimesEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.hint.common.AbilityResolutionCountHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -37,8 +39,9 @@ public final class SoulbrightFlamekin extends CardImpl { Ability ability = new SimpleActivatedAbility(new GainAbilityTargetEffect( TrampleAbility.getInstance(), Duration.EndOfTurn ), new ManaCostsImpl("{2}")); - ability.addEffect(new SoulbrightFlamekinEffect()); + ability.addEffect(new IfAbilityHasResolvedXTimesEffect(Outcome.PutManaInPool, 3, new SoulbrightFlamekinEffect())); ability.addTarget(new TargetCreaturePermanent()); + ability.addHint(AbilityResolutionCountHint.instance); this.addAbility(ability, new AbilityResolvedWatcher()); } @@ -56,8 +59,7 @@ class SoulbrightFlamekinEffect extends OneShotEffect { SoulbrightFlamekinEffect() { super(Outcome.Damage); - this.staticText = "If this is the third time this ability has resolved this turn, " + - "you may add {R}{R}{R}{R}{R}{R}{R}{R}"; + this.staticText = "you may add {R}{R}{R}{R}{R}{R}{R}{R}"; } private SoulbrightFlamekinEffect(final SoulbrightFlamekinEffect effect) { @@ -72,14 +74,10 @@ class SoulbrightFlamekinEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - AbilityResolvedWatcher watcher = game.getState().getWatcher(AbilityResolvedWatcher.class); - if (controller == null - || watcher == null - || !watcher.checkActivations(source, game) - || !controller.chooseUse(Outcome.PutManaInPool, "Add {R}{R}{R}{R}{R}{R}{R}{R}}?", source, game)) { - return false; + if (controller != null && controller.chooseUse(Outcome.PutManaInPool, "Add {R}{R}{R}{R}{R}{R}{R}{R}?", source, game)) { + controller.getManaPool().addMana(Mana.RedMana(8), game, source); + return true; } - controller.getManaPool().addMana(Mana.RedMana(8), game, source); - return true; + return false; } } diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/AbilityResolutionCount.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AbilityResolutionCount.java new file mode 100644 index 0000000000..b2fcb80e22 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/AbilityResolutionCount.java @@ -0,0 +1,39 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; +import mage.watchers.common.AbilityResolvedWatcher; + +/** + * @author emerald000 + */ +public enum AbilityResolutionCount implements DynamicValue { + + instance; + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + AbilityResolvedWatcher watcher = game.getState().getWatcher(AbilityResolvedWatcher.class); + if (watcher != null) { + return watcher.getResolutionCount(game, sourceAbility); + } + return 0; + } + + @Override + public AbilityResolutionCount copy() { + return instance; + } + + @Override + public String toString() { + return "X"; + } + + @Override + public String getMessage() { + return "permanents you control"; + } +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java b/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java new file mode 100644 index 0000000000..21814f17ec --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/IfAbilityHasResolvedXTimesEffect.java @@ -0,0 +1,52 @@ +package mage.abilities.effects.common; + +import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.Effect; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.game.Game; +import mage.util.CardUtil; +import mage.watchers.common.AbilityResolvedWatcher; + +/** + * @author emerald000 + */ +public class IfAbilityHasResolvedXTimesEffect extends OneShotEffect { + + private final int resolutionNumber; + private final Effect effect; + + public IfAbilityHasResolvedXTimesEffect(Outcome outcome, int resolutionNumber, Effect effect) { + super(outcome); + this.resolutionNumber = resolutionNumber; + this.effect = effect; + this.staticText = "If this is the " + CardUtil.numberToOrdinalText(resolutionNumber) + " time this ability has resolved this turn, " + + effect.getText(null); + } + + private IfAbilityHasResolvedXTimesEffect(final IfAbilityHasResolvedXTimesEffect effect) { + super(effect); + this.resolutionNumber = effect.resolutionNumber; + this.effect = effect.effect; + } + + @Override + public IfAbilityHasResolvedXTimesEffect copy() { + return new IfAbilityHasResolvedXTimesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + AbilityResolvedWatcher watcher = game.getState().getWatcher(AbilityResolvedWatcher.class); + if (watcher != null && watcher.getResolutionCount(game, source) == resolutionNumber) { + if (effect instanceof OneShotEffect) { + return effect.apply(game, source); + } else { + game.addEffect((ContinuousEffect) effect, source); + return true; + } + } + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/hint/common/AbilityResolutionCountHint.java b/Mage/src/main/java/mage/abilities/hint/common/AbilityResolutionCountHint.java new file mode 100644 index 0000000000..5c44331381 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/AbilityResolutionCountHint.java @@ -0,0 +1,26 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.common.AbilityResolutionCount; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.game.Game; + +/** + * @author emerald000 + */ +public enum AbilityResolutionCountHint implements Hint { + + instance; + private static final Hint hint = new ValueHint("Resolution count:", AbilityResolutionCount.instance); + + @Override + public String getText(Game game, Ability ability) { + return hint.getText(game, ability); + } + + @Override + public Hint copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index cad89d63a9..171da25007 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -150,7 +150,8 @@ public class GameEvent implements Serializable { SPELL_CAST, ACTIVATE_ABILITY, ACTIVATED_ABILITY, TRIGGERED_ABILITY, - COPY_STACKOBJECT,COPIED_STACKOBJECT, + RESOLVED_ABILITY, + COPY_STACKOBJECT, COPIED_STACKOBJECT, /* ADD_MANA targetId id of the ability that added the mana sourceId sourceId of the ability that added the mana diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index cb4149bcfe..b35a664bb2 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -82,6 +82,7 @@ public class StackAbility extends StackObjImpl implements Ability { @Override public boolean resolve(Game game) { if (ability.getTargets().stillLegal(ability, game) || !canFizzle()) { + game.fireEvent(new GameEvent(GameEvent.EventType.RESOLVED_ABILITY, ability.getOriginalId(), ability.getSourceId(), ability.getControllerId())); boolean result = ability.resolve(game); game.getStack().remove(this, game); return result; diff --git a/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java b/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java index ccb1f75c81..dfa3d5daab 100644 --- a/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java @@ -1,6 +1,5 @@ package mage.watchers.common; -import mage.MageObjectReference; import mage.abilities.Ability; import mage.constants.WatcherScope; import mage.game.Game; @@ -9,14 +8,13 @@ import mage.watchers.Watcher; import java.util.HashMap; import java.util.Map; -import java.util.UUID; /** * @author TheElk801 */ public class AbilityResolvedWatcher extends Watcher { - private final Map> activationMap = new HashMap<>(); + private final Map resolutionMap = new HashMap<>(); public AbilityResolvedWatcher() { super(WatcherScope.GAME); @@ -24,17 +22,18 @@ public class AbilityResolvedWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { + if (event.getType() == GameEvent.EventType.RESOLVED_ABILITY) { + resolutionMap.merge(event.getTargetId().toString() + game.getState().getZoneChangeCounter(event.getSourceId()), 1, Integer::sum); + } } @Override public void reset() { super.reset(); - activationMap.clear(); + resolutionMap.clear(); } - public boolean checkActivations(Ability source, Game game) { - return activationMap.computeIfAbsent(new MageObjectReference( - source.getSourceId(), source.getSourceObjectZoneChangeCounter(), game - ), x -> new HashMap<>()).compute(source.getOriginalId(), (u, i) -> i == null ? 1 : i + 1).intValue() == 3; + public int getResolutionCount(Game game, Ability source) { + return resolutionMap.getOrDefault(source.getOriginalId().toString() + game.getState().getZoneChangeCounter(source.getSourceId()), 0); } } From 3465493ccf0df6c7a7fa7abb8ac943259391d14e Mon Sep 17 00:00:00 2001 From: emerald000 Date: Sun, 3 May 2020 18:56:35 -0400 Subject: [PATCH 2/2] Rename, move and comment resolving ability event creation. Should be clearer now. --- Mage/src/main/java/mage/abilities/AbilityImpl.java | 3 +++ Mage/src/main/java/mage/game/events/GameEvent.java | 2 +- Mage/src/main/java/mage/game/stack/StackAbility.java | 1 - .../main/java/mage/watchers/common/AbilityResolvedWatcher.java | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Mage/src/main/java/mage/abilities/AbilityImpl.java b/Mage/src/main/java/mage/abilities/AbilityImpl.java index 361a6c4ec4..6edcbfc98a 100644 --- a/Mage/src/main/java/mage/abilities/AbilityImpl.java +++ b/Mage/src/main/java/mage/abilities/AbilityImpl.java @@ -152,6 +152,9 @@ public abstract class AbilityImpl implements Ability { boolean result = true; //20100716 - 117.12 if (checkIfClause(game)) { + // Ability has started resolving. Fire event. + // Used for abilities counting the number of resolutions like Ashling the Pilgrim. + game.fireEvent(new GameEvent(GameEvent.EventType.RESOLVING_ABILITY, this.getOriginalId(), this.getSourceId(), this.getControllerId())); if (this instanceof TriggeredAbility) { for (UUID modeId : this.getModes().getSelectedModes()) { this.getModes().setActiveMode(modeId); diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index 171da25007..7c467e3b40 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -150,7 +150,7 @@ public class GameEvent implements Serializable { SPELL_CAST, ACTIVATE_ABILITY, ACTIVATED_ABILITY, TRIGGERED_ABILITY, - RESOLVED_ABILITY, + RESOLVING_ABILITY, COPY_STACKOBJECT, COPIED_STACKOBJECT, /* ADD_MANA targetId id of the ability that added the mana diff --git a/Mage/src/main/java/mage/game/stack/StackAbility.java b/Mage/src/main/java/mage/game/stack/StackAbility.java index b35a664bb2..cb4149bcfe 100644 --- a/Mage/src/main/java/mage/game/stack/StackAbility.java +++ b/Mage/src/main/java/mage/game/stack/StackAbility.java @@ -82,7 +82,6 @@ public class StackAbility extends StackObjImpl implements Ability { @Override public boolean resolve(Game game) { if (ability.getTargets().stillLegal(ability, game) || !canFizzle()) { - game.fireEvent(new GameEvent(GameEvent.EventType.RESOLVED_ABILITY, ability.getOriginalId(), ability.getSourceId(), ability.getControllerId())); boolean result = ability.resolve(game); game.getStack().remove(this, game); return result; diff --git a/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java b/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java index dfa3d5daab..953e4d64df 100644 --- a/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/AbilityResolvedWatcher.java @@ -22,7 +22,7 @@ public class AbilityResolvedWatcher extends Watcher { @Override public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.RESOLVED_ABILITY) { + if (event.getType() == GameEvent.EventType.RESOLVING_ABILITY) { resolutionMap.merge(event.getTargetId().toString() + game.getState().getZoneChangeCounter(event.getSourceId()), 1, Integer::sum); } }