* Fix to handle returning effects correct if multiple objects return at the same time (e.g. two creatures with evolve return from exile because two Banisher Priests die by damage to all effect). (not complete finished yet, because Undying test does not run without error).

This commit is contained in:
LevelX2 2014-05-06 17:51:37 +02:00
parent 3caacdfec8
commit 9364616517
9 changed files with 142 additions and 29 deletions

View file

@ -178,8 +178,6 @@ public abstract class AbilityImpl<T extends AbilityImpl<T>> implements Ability {
if (effect.applyEffectsAfter()) {
game.applyEffects();
}
// effects like entersBattlefield have to trigger simultanously so objects see each other
game.getState().handleSimultaneousEvent(game);
}
}
return result;

View file

@ -28,17 +28,17 @@
package mage.abilities.keyword;
import java.util.UUID;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.OneShotEffect;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTarget;
/**
* FAQ 2013/01/11
@ -52,6 +52,34 @@ import mage.game.permanent.Permanent;
*
* 702.98b If a creature has multiple instances of evolve, each triggers separately
*
* Rulings
*
* When comparing the stats of the two creatures, you always compare power to power and toughness to toughness.
* Whenever a creature enters the battlefield under your control, check its power and toughness against
* the power and toughness of the creature with evolve. If neither stat of the new creature is greater,
* evolve won't trigger at all. For example, if you control a 2/3 creature with evolve and a 2/2 creature
* enters the battlefield under your control, you won't have the opportunity to cast a spell like Giant Growth
* to make the 2/2 creature large enough to cause evolve to trigger.
* If evolve triggers, the stat comparison will happen again when the ability tries to resolve. If
* neither stat of the new creature is greater, the ability will do nothing. If the creature that
* entered the battlefield leaves the battlefield before evolve tries to resolve, use its last known
* power and toughness to compare the stats.
* If a creature enters the battlefield with +1/+1 counters on it, consider those counters when determining
* if evolve will trigger. For example, a 1/1 creature that enters the battlefield with two +1/+1 counters
* on it will cause the evolve ability of a 2/2 creature to trigger.
* If multiple creatures enter the battlefield at the same time, evolve may trigger multiple times, although the stat
* comparison will take place each time one of those abilities tries to resolve. For example, if you control a 2/2
* creature with evolve and two 3/3 creatures enter the battlefield, evolve will trigger twice. The first ability
* will resolve and put a +1/+1 counter on the creature with evolve. When the second ability tries to resolve,
* neither the power nor the toughness of the new creature is greater than that of the creature with evolve,
* so that ability does nothing.
* When comparing the stats as the evolve ability resolves, it's possible that the stat that's greater changes
* from power to toughness or vice versa. If this happens, the ability will still resolve and you'll put a +1/+1
* counter on the creature with evolve. For example, if you control a 2/2 creature with evolve and a 1/3 creature
* enters the battlefield under your control, it toughness is greater so evolve will trigger. In response, the 1/3
* creature gets +2/-2. When the evolve trigger tries to resolve, its power is greater. You'll put a +1/+1
* counter on the creature with evolve.
*
* @author LevelX2
*/
@ -68,15 +96,17 @@ public class EvolveAbility extends TriggeredAbilityImpl<EvolveAbility> {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD && !event.getTargetId().equals(this.getSourceId())) {
Permanent triggeringCreature = game.getPermanent(event.getTargetId());
if (triggeringCreature != null
&& triggeringCreature.getCardType().contains(CardType.CREATURE)
&& triggeringCreature.getControllerId().equals(this.controllerId)) {
Permanent sourceCreature = game.getPermanent(sourceId);
if (sourceCreature != null && isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) {
this.getEffects().get(0).setValue("triggeringCreature", event.getTargetId());
return true;
if (event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD) {
if (!event.getTargetId().equals(this.getSourceId())) {
Permanent triggeringCreature = game.getPermanent(event.getTargetId());
if (triggeringCreature != null
&& triggeringCreature.getCardType().contains(CardType.CREATURE)
&& triggeringCreature.getControllerId().equals(this.controllerId)) {
Permanent sourceCreature = game.getPermanent(sourceId);
if (sourceCreature != null && isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) {
this.getEffects().get(0).setTargetPointer(new FixedTarget(event.getTargetId()));
return true;
}
}
}
}
@ -93,10 +123,7 @@ public class EvolveAbility extends TriggeredAbilityImpl<EvolveAbility> {
if (newCreature.getPower().getValue() > sourceCreature.getPower().getValue()) {
return true;
}
if (newCreature.getToughness().getValue() > sourceCreature.getToughness().getValue()) {
return true;
}
return false;
return newCreature.getToughness().getValue() > sourceCreature.getToughness().getValue();
}
@Override
@ -127,11 +154,7 @@ class EvolveEffect extends OneShotEffect<EvolveEffect> {
@Override
public boolean apply(Game game, Ability source) {
UUID triggeringCreatureId = (UUID) getValue("triggeringCreature");
Permanent triggeringCreature = game.getPermanent(triggeringCreatureId);
if (triggeringCreature == null) {
triggeringCreature = (Permanent) game.getLastKnownInformation(triggeringCreatureId, Zone.BATTLEFIELD);
}
Permanent triggeringCreature = game.getPermanentOrLKIBattlefield(getTargetPointer().getFirst(game, source));
if (triggeringCreature != null) {
Permanent sourceCreature = game.getPermanent(source.getSourceId());
if (sourceCreature != null && EvolveAbility.isPowerOrThoughnessGreater(sourceCreature, triggeringCreature)) {

View file

@ -376,7 +376,7 @@ public abstract class CardImpl<T extends CardImpl<T>> extends MageObjectImpl<T>
}
setControllerId(ownerId);
game.setZone(objectId, event.getToZone());
game.fireEvent(event);
game.addSimultaneousEvent(event);
return game.getState().getZone(objectId) == toZone;
}
return false;

View file

@ -986,7 +986,10 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
while (!player.isPassed() && player.isInGame() && !isPaused() && !gameOver(null)) {
if (!resuming) {
if (checkStateAndTriggered()) {
applyEffects();
do {
state.handleSimultaneousEvent(this);
applyEffects();
} while (state.hasSimultaneousEvents());
}
//resetLKI();
applyEffects();
@ -1057,6 +1060,7 @@ public abstract class GameImpl<T extends GameImpl<T>> implements Game, Serializa
} finally {
if (top != null) {
state.getStack().remove(top);
state.handleSimultaneousEvent(this);
}
}
}

View file

@ -494,12 +494,18 @@ public class GameState implements Serializable, Copyable<GameState> {
public void handleSimultaneousEvent(Game game) {
if (!simultaneousEvents.isEmpty()) {
for (GameEvent event:simultaneousEvents) {
// it can happen, that the events add new simultaneous events, so copy the list before
List<GameEvent> eventsToHandle = new ArrayList<>();
eventsToHandle.addAll(simultaneousEvents);
simultaneousEvents.clear();
for (GameEvent event:eventsToHandle) {
this.handleEvent(event, game);
}
simultaneousEvents.clear();
}
}
public boolean hasSimultaneousEvents() {
return !simultaneousEvents.isEmpty();
}
public void handleEvent(GameEvent event, Game game) {
watchers.watch(event, game);

View file

@ -161,7 +161,7 @@ public class PermanentCard extends PermanentImpl<PermanentCard> {
break;
}
game.setZone(objectId, event.getToZone());
game.fireEvent(event);
game.addSimultaneousEvent(event);
if (event.getFromZone().equals(Zone.BATTLEFIELD)) {
game.resetForSourceId(getId());
game.applyEffects(); // LevelX2: needed to execute isInactive for of custom duration copy effect if source returns directly (e.g. cloudshifted clone)