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)
(...)
This commit is contained in:
Neil Gentleman 2015-11-07 02:49:55 -08:00
parent 8af7526acc
commit cad96e1927
2 changed files with 110 additions and 135 deletions

View file

@ -27,32 +27,28 @@
*/ */
package mage.sets.eventide; package mage.sets.eventide;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.condition.Condition; 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.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalActivatedAbility;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.Effects;
import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.constants.AbilityType;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Rarity; import mage.constants.Rarity;
import mage.constants.WatcherScope;
import mage.constants.Zone; import mage.constants.Zone;
import mage.filter.common.FilterCreaturePermanent; import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.AbilityPredicate; import mage.filter.predicate.mageobject.AbilityPredicate;
import mage.game.Game; 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 { 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) { public GroundlingPouncer(UUID ownerId) {
super(ownerId, 154, "Groundling Pouncer", Rarity.UNCOMMON, new CardType[]{CardType.CREATURE}, "{1}{G/U}"); 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); 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. // {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(); Ability ability = new GroundlingPouncerAbility(
Effect effect = new BoostSourceEffect(1, 3, Duration.EndOfTurn); Zone.BATTLEFIELD,
Effect effect2 = new GainAbilitySourceEffect(FlyingAbility.getInstance(), Duration.EndOfTurn, false, true); new BoostSourceEffect(1, 3, Duration.EndOfTurn),
Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{G/U}"), condition, rule); new ManaCostsImpl("{G/U}"),
ability.addEffect(effect2); new OpponentControlsPermanentCondition(filter),
this.addAbility(ability, new ActivatedAbilityUsedThisTurnWatcher()); "{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 { private final Condition condition;
filter.add(new AbilityPredicate(FlyingAbility.class)); 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 @Override
public boolean apply(Game game, Ability source) { public Effects getEffects(Game game, EffectType effectType) {
ActivatedAbilityUsedThisTurnWatcher watcher = (ActivatedAbilityUsedThisTurnWatcher) game.getState().getWatchers().get("ActivatedAbilityUsedThisTurn"); if (!condition.apply(game, this)) {
for (UUID opponentId : game.getOpponents(source.getControllerId())) { return emptyEffects;
if (game.getBattlefield().countAll(filter, opponentId, game) > 0 && !watcher.getActivatedThisTurn().contains(source.getSourceId())) {
return true;
} }
return super.getEffects(game, effectType);
} }
@Override
public boolean canActivate(UUID playerId, Game game) {
if (!condition.apply(game, this)) {
return false; return false;
} }
return super.canActivate(playerId, game);
}
@Override @Override
public String toString() { public GroundlingPouncerAbility copy() {
return "once each turn and only if an opponent controls a flying creature"; return new GroundlingPouncerAbility(this);
} }
}
@Override
class ActivatedAbilityUsedThisTurnWatcher extends Watcher { public String getRule() {
return ruleText;
public Set<UUID> 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<UUID> getActivatedThisTurn() {
return this.activatedThisTurn;
}
@Override
public ActivatedAbilityUsedThisTurnWatcher copy() {
return new ActivatedAbilityUsedThisTurnWatcher(this);
}
@Override
public void reset() {
super.reset();
this.activatedThisTurn.clear();
} }
} }

View file

@ -27,35 +27,29 @@
*/ */
package mage.sets.timespiral; package mage.sets.timespiral;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.LimitedTimesPerTurnActivatedAbility;
import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.Condition; import mage.abilities.condition.Condition;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCostsImpl; 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.BecomesCreatureSourceEffect;
import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.abilities.effects.common.turn.SkipNextTurnSourceEffect; import mage.abilities.effects.common.turn.SkipNextTurnSourceEffect;
import mage.abilities.mana.BlueManaAbility; import mage.abilities.mana.BlueManaAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.constants.AbilityType;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.EffectType;
import mage.constants.Rarity; import mage.constants.Rarity;
import mage.constants.WatcherScope;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.game.permanent.token.Token; 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}"))); 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. // {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, Zone.BATTLEFIELD,
new BoostSourceEffect(3, 3, Duration.EndOfTurn), new BoostSourceEffect(3, 3, Duration.EndOfTurn),
new ManaCostsImpl<>("{0}"), new ManaCostsImpl<>("{0}"),
new ChronatogTotemCondition(), 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");
ability.addEffect(new SkipNextTurnSourceEffect()); ability.addEffect(new SkipNextTurnSourceEffect());
this.addAbility(ability, new ActivatedAbilityUsedThisTurnWatcher()); this.addAbility(ability);
} }
public ChronatogTotem(final ChronatogTotem card) { 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 { class ChronatogTotemToken extends Token {
ChronatogTotemToken() { ChronatogTotemToken() {
@ -111,60 +150,15 @@ class ChronatogTotemCondition implements Condition {
@Override @Override
public boolean apply(Game game, Ability source) { 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()); Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) { if (permanent != null) {
return permanent.getCardType().contains(CardType.CREATURE); return permanent.getCardType().contains(CardType.CREATURE);
} }
}
return false; return false;
} }
@Override @Override
public String toString() { 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<UUID> 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<UUID> getActivatedThisTurn() {
return Collections.unmodifiableSet(this.activatedThisTurn);
}
@Override
public ActivatedAbilityUsedThisTurnWatcher copy() {
return new ActivatedAbilityUsedThisTurnWatcher(this);
}
@Override
public void reset() {
super.reset();
this.activatedThisTurn.clear();
} }
} }