From cad96e192791e419ac846be1f67c852f9df06760 Mon Sep 17 00:00:00 2001 From: Neil Gentleman Date: Sat, 7 Nov 2015 02:49:55 -0800 Subject: [PATCH] fix ActivatedAbilityUsedThisTurnWatcher ClassCastException Chronatog Totem and Groundling Pouncer were breaking when trying to play spells with convoke: java.lang.ClassCastException: mage.game.stack.Spell cannot be cast to mage.game.stack.StackAbility mage.sets.timespiral.ActivatedAbilityUsedThisTurnWatcher.watch(ChronatogTotem.java:148) mage.watchers.Watchers.watch(Watchers.java:63) mage.game.GameState.handleEvent(GameState.java:665) mage.game.GameImpl.fireEvent(GameImpl.java:2286) mage.players.PlayerImpl.specialAction(PlayerImpl.java:1094) mage.players.PlayerImpl.activateAbility(PlayerImpl.java:1133) mage.player.human.HumanPlayer.activateAbility(HumanPlayer.java:1219) mage.player.human.HumanPlayer.specialManaAction(HumanPlayer.java:1209) mage.player.human.HumanPlayer.playManaHandling(HumanPlayer.java:791) mage.player.human.HumanPlayer.playMana(HumanPlayer.java:769) mage.abilities.costs.mana.ManaCostsImpl.pay(ManaCostsImpl.java:135) mage.abilities.AbilityImpl.activate(AbilityImpl.java:394) mage.game.stack.Spell.activate(Spell.java:128) mage.players.PlayerImpl.cast(PlayerImpl.java:969) (...) --- .../mage/sets/eventide/GroundlingPouncer.java | 123 ++++++++---------- .../mage/sets/timespiral/ChronatogTotem.java | 122 +++++++++-------- 2 files changed, 110 insertions(+), 135 deletions(-) diff --git a/Mage.Sets/src/mage/sets/eventide/GroundlingPouncer.java b/Mage.Sets/src/mage/sets/eventide/GroundlingPouncer.java index 79141ba5cc..9d6f8aa0df 100644 --- a/Mage.Sets/src/mage/sets/eventide/GroundlingPouncer.java +++ b/Mage.Sets/src/mage/sets/eventide/GroundlingPouncer.java @@ -27,32 +27,28 @@ */ package mage.sets.eventide; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.condition.Condition; +import mage.abilities.condition.common.OpponentControlsPermanentCondition; +import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalActivatedAbility; import mage.abilities.effects.Effect; +import mage.abilities.effects.Effects; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; -import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.EffectType; import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.constants.Zone; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.stack.StackAbility; -import mage.game.stack.StackObject; -import mage.watchers.Watcher; /** * @@ -61,7 +57,11 @@ import mage.watchers.Watcher; */ public class GroundlingPouncer extends CardImpl { - private String rule = "{this} gets +1/+3 and gains flying until end of turn. Activate this ability only once each turn and only if an opponent controls a creature with flying."; + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + + static { + filter.add(new AbilityPredicate(FlyingAbility.class)); + } public GroundlingPouncer(UUID ownerId) { super(ownerId, 154, "Groundling Pouncer", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{G/U}"); @@ -72,12 +72,14 @@ public class GroundlingPouncer extends CardImpl { this.toughness = new MageInt(1); // {GU}: Groundling Pouncer gets +1/+3 and gains flying until end of turn. Activate this ability only once each turn and only if an opponent controls a creature with flying. - Condition condition = new GroundingPouncerCondition(); - Effect effect = new BoostSourceEffect(1, 3, Duration.EndOfTurn); - Effect effect2 = new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, false, true); - Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{G/U}"), condition, rule); - ability.addEffect(effect2); - this.addAbility(ability, new ActivatedAbilityUsedThisTurnWatcher()); + Ability ability = new GroundlingPouncerAbility( + Zone.BATTLEFIELD, + new BoostSourceEffect(1, 3, Duration.EndOfTurn), + new ManaCostsImpl("{G/U}"), + new OpponentControlsPermanentCondition(filter), + "{G/U}: {this} gets +1/+3 and gains flying until end of turn. Activate this ability only once each turn and only if an opponent controls a creature with flying."); + ability.addEffect(new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, false, true)); + this.addAbility(ability); } @@ -91,69 +93,48 @@ public class GroundlingPouncer extends CardImpl { } } -class GroundingPouncerCondition implements Condition { +class GroundlingPouncerAbility extends LimitedTimesPerTurnActivatedAbility { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(); + private static final Effects emptyEffects = new Effects(); - static { - filter.add(new AbilityPredicate(FlyingAbility.class)); + private final Condition condition; + private final String ruleText; + + public GroundlingPouncerAbility(Zone zone, Effect effect, Cost cost, Condition condition, String rule) { + super(zone, effect, cost); + this.condition = condition; + this.ruleText = rule; + } + + public GroundlingPouncerAbility(GroundlingPouncerAbility ability) { + super(ability); + this.condition = ability.condition; + this.ruleText = ability.ruleText; } @Override - public boolean apply(Game game, Ability source) { - ActivatedAbilityUsedThisTurnWatcher watcher = (ActivatedAbilityUsedThisTurnWatcher) game.getState().getWatchers().get("ActivatedAbilityUsedThisTurn"); - for (UUID opponentId : game.getOpponents(source.getControllerId())) { - if (game.getBattlefield().countAll(filter, opponentId, game) > 0 && !watcher.getActivatedThisTurn().contains(source.getSourceId())) { - return true; - } + public Effects getEffects(Game game, EffectType effectType) { + if (!condition.apply(game, this)) { + return emptyEffects; } - return false; + return super.getEffects(game, effectType); } @Override - public String toString() { - return "once each turn and only if an opponent controls a flying creature"; - } -} - -class ActivatedAbilityUsedThisTurnWatcher extends Watcher { - - public Set activatedThisTurn = new HashSet<>(); - - public ActivatedAbilityUsedThisTurnWatcher() { - super("ActivatedAbilityUsedThisTurn", WatcherScope.GAME); - } - - public ActivatedAbilityUsedThisTurnWatcher(final ActivatedAbilityUsedThisTurnWatcher watcher) { - super(watcher); - this.activatedThisTurn.addAll(watcher.activatedThisTurn); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ACTIVATED_ABILITY) { - StackObject stackObject = game.getStack().getStackObject(event.getTargetId()); - if (stackObject != null) { - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getTargetId()); - if (stackAbility != null && stackAbility.getAbilityType() == AbilityType.ACTIVATED) { - this.activatedThisTurn.add(stackAbility.getOriginalId()); - } - } - } - } - - public Set getActivatedThisTurn() { - return this.activatedThisTurn; - } - - @Override - public ActivatedAbilityUsedThisTurnWatcher copy() { - return new ActivatedAbilityUsedThisTurnWatcher(this); - } - - @Override - public void reset() { - super.reset(); - this.activatedThisTurn.clear(); + public boolean canActivate(UUID playerId, Game game) { + if (!condition.apply(game, this)) { + return false; + } + return super.canActivate(playerId, game); + } + + @Override + public GroundlingPouncerAbility copy() { + return new GroundlingPouncerAbility(this); + } + + @Override + public String getRule() { + return ruleText; } } diff --git a/Mage.Sets/src/mage/sets/timespiral/ChronatogTotem.java b/Mage.Sets/src/mage/sets/timespiral/ChronatogTotem.java index 35c67c7ff6..492a19ee6c 100644 --- a/Mage.Sets/src/mage/sets/timespiral/ChronatogTotem.java +++ b/Mage.Sets/src/mage/sets/timespiral/ChronatogTotem.java @@ -27,35 +27,29 @@ */ package mage.sets.timespiral; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; +import mage.abilities.common.LimitedTimesPerTurnActivatedAbility; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.condition.Condition; +import mage.abilities.costs.Cost; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.decorator.ConditionalActivatedAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.Effects; import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.turn.SkipNextTurnSourceEffect; import mage.abilities.mana.BlueManaAbility; import mage.cards.CardImpl; -import mage.constants.AbilityType; import mage.constants.CardType; import mage.constants.Duration; +import mage.constants.EffectType; import mage.constants.Rarity; -import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; import mage.game.permanent.token.Token; -import mage.game.stack.StackAbility; -import mage.game.stack.StackObject; -import mage.watchers.Watcher; /** * @@ -74,14 +68,13 @@ public class ChronatogTotem extends CardImpl { this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new BecomesCreatureSourceEffect(new ChronatogTotemToken(), "", Duration.EndOfTurn), new ManaCostsImpl<>("{1}{U}"))); // {0}: Chronatog Totem gets +3/+3 until end of turn. You skip your next turn. Activate this ability only once each turn and only if Chronatog Totem is a creature. - Ability ability = new ConditionalActivatedAbility( + Ability ability = new ChronatogTotemAbility( Zone.BATTLEFIELD, new BoostSourceEffect(3, 3, Duration.EndOfTurn), new ManaCostsImpl<>("{0}"), - new ChronatogTotemCondition(), - "{0}: {this} gets +3/+3 until end of turn. You skip your next turn. Activate this ability only once each turn and only if {this} is a creature"); + new ChronatogTotemCondition()); ability.addEffect(new SkipNextTurnSourceEffect()); - this.addAbility(ability, new ActivatedAbilityUsedThisTurnWatcher()); + this.addAbility(ability); } public ChronatogTotem(final ChronatogTotem card) { @@ -94,6 +87,52 @@ public class ChronatogTotem extends CardImpl { } } +class ChronatogTotemAbility extends LimitedTimesPerTurnActivatedAbility { + + private static final Effects emptyEffects = new Effects(); + + private final Condition condition; + + public ChronatogTotemAbility(Zone zone, Effect effect, Cost cost, Condition condition) { + super(zone, effect, cost); + this.condition = condition; + } + + public ChronatogTotemAbility(ChronatogTotemAbility ability) { + super(ability); + this.condition = ability.condition; + } + + @Override + public Effects getEffects(Game game, EffectType effectType) { + if (!condition.apply(game, this)) { + return emptyEffects; + } + return super.getEffects(game, effectType); + } + + @Override + public boolean canActivate(UUID playerId, Game game) { + if (!condition.apply(game, this)) { + return false; + } + return super.canActivate(playerId, game); + } + + @Override + public ChronatogTotemAbility copy() { + return new ChronatogTotemAbility(this); + } + + @Override + public String getRule() { + StringBuilder sb = new StringBuilder(super.getRule()); + sb.deleteCharAt(sb.length() - 1); // remove last '.' + sb.append(" and only if ").append(condition.toString()).append("."); + return sb.toString(); + } +} + class ChronatogTotemToken extends Token { ChronatogTotemToken() { @@ -111,60 +150,15 @@ class ChronatogTotemCondition implements Condition { @Override public boolean apply(Game game, Ability source) { - ActivatedAbilityUsedThisTurnWatcher watcher = (ActivatedAbilityUsedThisTurnWatcher) game.getState().getWatchers().get("ActivatedAbilityUsedThisTurn"); - if (!watcher.getActivatedThisTurn().contains(source.getOriginalId())) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent != null) { - return permanent.getCardType().contains(CardType.CREATURE); - } + Permanent permanent = game.getPermanent(source.getSourceId()); + if (permanent != null) { + return permanent.getCardType().contains(CardType.CREATURE); } return false; } @Override public String toString() { - return "once each turn and only if an opponent controls a flying creature"; + return "{this} is a creature"; } } - -class ActivatedAbilityUsedThisTurnWatcher extends Watcher { - - public Set activatedThisTurn = new HashSet<>(0); - - ActivatedAbilityUsedThisTurnWatcher() { - super("ActivatedAbilityUsedThisTurn", WatcherScope.GAME); - } - - ActivatedAbilityUsedThisTurnWatcher(final ActivatedAbilityUsedThisTurnWatcher watcher) { - super(watcher); - this.activatedThisTurn.addAll(watcher.activatedThisTurn); - } - - @Override - public void watch(GameEvent event, Game game) { - if (event.getType() == EventType.ACTIVATED_ABILITY) { - StackObject stackObject = game.getStack().getStackObject(event.getTargetId()); - if (stackObject != null) { - StackAbility stackAbility = (StackAbility) game.getStack().getStackObject(event.getTargetId()); - if (stackAbility != null && stackAbility.getAbilityType() == AbilityType.ACTIVATED) { - this.activatedThisTurn.add(stackAbility.getOriginalId()); - } - } - } - } - - public Set getActivatedThisTurn() { - return Collections.unmodifiableSet(this.activatedThisTurn); - } - - @Override - public ActivatedAbilityUsedThisTurnWatcher copy() { - return new ActivatedAbilityUsedThisTurnWatcher(this); - } - - @Override - public void reset() { - super.reset(); - this.activatedThisTurn.clear(); - } -} \ No newline at end of file