mirror of
https://github.com/magefree/mage.git
synced 2026-01-26 21:29:17 -08:00
fix [OTJ] Fortune, Loyal Steed — DelayedAbility's zcc was wrong when started from another trigger (#12154)
This commit is contained in:
parent
fa728eafb1
commit
d8959f1588
11 changed files with 312 additions and 73 deletions
|
|
@ -1,9 +1,10 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.SaddledMountWatcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ public class AttacksWhileSaddledTriggeredAbility extends AttacksTriggeredAbility
|
|||
public AttacksWhileSaddledTriggeredAbility(Effect effect) {
|
||||
super(effect);
|
||||
this.setTriggerPhrase("Whenever {this} attacks while saddled, ");
|
||||
this.addWatcher(new SaddledMountWatcher());
|
||||
}
|
||||
|
||||
private AttacksWhileSaddledTriggeredAbility(final AttacksWhileSaddledTriggeredAbility ability) {
|
||||
|
|
@ -31,7 +33,7 @@ public class AttacksWhileSaddledTriggeredAbility extends AttacksTriggeredAbility
|
|||
return super.checkTrigger(event, game)
|
||||
&& Optional
|
||||
.ofNullable(getSourcePermanentIfItStillExists(game))
|
||||
.map(Permanent::isSaddled)
|
||||
.map(p -> SaddledMountWatcher.hasBeenSaddledThisTurn(new MageObjectReference(p, game), game))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package mage.abilities.condition.common;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.SaddledMountWatcher;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ public enum SaddledCondition implements Condition {
|
|||
public boolean apply(Game game, Ability source) {
|
||||
return Optional
|
||||
.ofNullable(source.getSourcePermanentIfItStillExists(game))
|
||||
.map(Permanent::isSaddled)
|
||||
.map(p -> SaddledMountWatcher.hasBeenSaddledThisTurn(new MageObjectReference(p, game), game))
|
||||
.orElse(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ import mage.abilities.common.SimpleActivatedAbility;
|
|||
import mage.abilities.condition.common.SaddledCondition;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.CostImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.hint.ConditionHint;
|
||||
import mage.abilities.hint.Hint;
|
||||
import mage.abilities.hint.HintUtils;
|
||||
import mage.constants.*;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.TimingRule;
|
||||
import mage.filter.common.FilterControlledCreaturePermanent;
|
||||
import mage.filter.predicate.mageobject.AnotherPredicate;
|
||||
import mage.filter.predicate.permanent.TappedPredicate;
|
||||
|
|
@ -24,11 +25,10 @@ import mage.watchers.common.SaddledMountWatcher;
|
|||
|
||||
import java.awt.*;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
* @author TheElk801, Susucr
|
||||
*/
|
||||
public class SaddleAbility extends SimpleActivatedAbility {
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ public class SaddleAbility extends SimpleActivatedAbility {
|
|||
private static final Hint hint = new ConditionHint(SaddledCondition.instance, "This permanent is saddled");
|
||||
|
||||
public SaddleAbility(int value) {
|
||||
super(new SaddleEffect(), new SaddleCost(value));
|
||||
super(new SaddleEventEffect(), new SaddleCost(value));
|
||||
this.value = value;
|
||||
this.addHint(hint);
|
||||
this.setTiming(TimingRule.SORCERY);
|
||||
|
|
@ -60,42 +60,36 @@ public class SaddleAbility extends SimpleActivatedAbility {
|
|||
}
|
||||
}
|
||||
|
||||
class SaddleEffect extends ContinuousEffectImpl {
|
||||
class SaddleEventEffect extends OneShotEffect {
|
||||
|
||||
SaddleEffect() {
|
||||
super(Duration.EndOfTurn, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
|
||||
SaddleEventEffect() {
|
||||
super(Outcome.Benefit);
|
||||
}
|
||||
|
||||
private SaddleEffect(final SaddleEffect effect) {
|
||||
private SaddleEventEffect(final SaddleEventEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SaddleEffect copy() {
|
||||
return new SaddleEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Ability source, Game game) {
|
||||
super.init(source, game);
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.MOUNT_SADDLED,
|
||||
source.getSourceId(),
|
||||
source, source.getControllerId()
|
||||
));
|
||||
public SaddleEventEffect copy() {
|
||||
return new SaddleEventEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Optional.ofNullable(source.getSourcePermanentIfItStillExists(game))
|
||||
.ifPresent(permanent -> permanent.setSaddled(true));
|
||||
if (source.getSourcePermanentIfItStillExists(game) != null) {
|
||||
game.fireEvent(GameEvent.getEvent(
|
||||
GameEvent.EventType.MOUNT_SADDLED,
|
||||
source.getSourceId(),
|
||||
source, source.getControllerId()
|
||||
));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class SaddleCost extends CostImpl {
|
||||
|
||||
|
||||
private static final FilterControlledCreaturePermanent filter
|
||||
= new FilterControlledCreaturePermanent("another untapped creature you control");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package mage.abilities.keyword;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.ActivatedAbilityImpl;
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.effects.ReplacementEffectImpl;
|
||||
import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
|
||||
import mage.abilities.effects.common.ExileSourceEffect;
|
||||
import mage.abilities.effects.common.ExileTargetEffect;
|
||||
import mage.abilities.effects.common.ReturnSourceFromGraveyardToBattlefieldEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
|
|
@ -15,6 +16,8 @@ import mage.constants.Zone;
|
|||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.events.ZoneChangeEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
|
@ -60,7 +63,7 @@ public class UnearthAbility extends ActivatedAbilityImpl {
|
|||
class UnearthDelayedTriggeredAbility extends DelayedTriggeredAbility {
|
||||
|
||||
public UnearthDelayedTriggeredAbility() {
|
||||
super(new ExileSourceEffect());
|
||||
super(new ExileTargetEffect());
|
||||
}
|
||||
|
||||
protected UnearthDelayedTriggeredAbility(final UnearthDelayedTriggeredAbility ability) {
|
||||
|
|
@ -79,7 +82,19 @@ class UnearthDelayedTriggeredAbility extends DelayedTriggeredAbility {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return event.getPlayerId().equals(this.controllerId);
|
||||
if (!event.getPlayerId().equals(this.controllerId)) {
|
||||
return false;
|
||||
}
|
||||
// The delayed trigger source is the card in the graveyard.
|
||||
// So we need to exile the zcc + 1 permanent
|
||||
MageObjectReference object = new MageObjectReference(getSourceId(), getSourceObjectZoneChangeCounter() + 1, game);
|
||||
Permanent permanent = object.getPermanent(game);
|
||||
if (permanent == null || !permanent.isPhasedIn()) {
|
||||
// Triggers, but do nothing.
|
||||
return true;
|
||||
}
|
||||
getEffects().setTargetPointer(new FixedTarget(permanent, game));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mage.filter.predicate.permanent;
|
||||
|
||||
import mage.MageObjectReference;
|
||||
import mage.filter.predicate.ObjectSourcePlayer;
|
||||
import mage.filter.predicate.ObjectSourcePlayerPredicate;
|
||||
import mage.game.Game;
|
||||
|
|
@ -17,7 +18,7 @@ public enum SaddledSourceThisTurnPredicate implements ObjectSourcePlayerPredicat
|
|||
@Override
|
||||
public boolean apply(ObjectSourcePlayer<Permanent> input, Game game) {
|
||||
return SaddledMountWatcher.checkIfSaddledThisTurn(
|
||||
input.getObject(), input.getSource().getSourcePermanentOrLKI(game), game
|
||||
input.getObject(), new MageObjectReference(input.getSourceId(), input.getSource().getSourceObjectZoneChangeCounter(), game), game
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2162,11 +2162,27 @@ public abstract class GameImpl implements Game {
|
|||
delayedAbility.setSourceId(source.getSourceId());
|
||||
delayedAbility.setControllerId(source.getControllerId());
|
||||
}
|
||||
// return addDelayedTriggeredAbility(delayedAbility);
|
||||
DelayedTriggeredAbility newAbility = delayedAbility.copy();
|
||||
newAbility.newId();
|
||||
if (source != null) {
|
||||
newAbility.setSourceObjectZoneChangeCounter(getState().getZoneChangeCounter(source.getSourceId()));
|
||||
// Relevant ruling:
|
||||
// 603.7e If an activated or triggered ability creates a delayed triggered ability,
|
||||
// the source of that delayed triggered ability is the same as the source of that other ability.
|
||||
// The controller of that delayed triggered ability is the player who controlled that other ability as it resolved.
|
||||
// 603.7f If a static ability generates a replacement effect which causes a delayed triggered ability to be created,
|
||||
// the source of that delayed triggered ability is the object with that static ability.
|
||||
// The controller of that delayed triggered ability is the same as the controller of that object at the time
|
||||
// the replacement effect was applied.
|
||||
//
|
||||
// There are two possibility for the zcc:
|
||||
// 1/ the source is an Ability with a valid (not 0) zcc, and we must use the same.
|
||||
int zcc = source.getSourceObjectZoneChangeCounter();
|
||||
if (zcc == 0) {
|
||||
// 2/ the source has not a valid zcc (it is most likely a StaticAbility instantiated at beginning of game)
|
||||
// we use the source objects's zcc
|
||||
zcc = getState().getZoneChangeCounter(source.getSourceId());
|
||||
}
|
||||
newAbility.setSourceObjectZoneChangeCounter(zcc);
|
||||
newAbility.setSourcePermanentTransformCount(this);
|
||||
}
|
||||
newAbility.init(this);
|
||||
|
|
|
|||
|
|
@ -78,10 +78,6 @@ public interface Permanent extends Card, Controllable {
|
|||
|
||||
void setSuspected(boolean value, Game game, Ability source);
|
||||
|
||||
boolean isSaddled();
|
||||
|
||||
void setSaddled(boolean value);
|
||||
|
||||
boolean isPrototyped();
|
||||
|
||||
void setPrototyped(boolean value);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
protected boolean monstrous;
|
||||
protected boolean renowned;
|
||||
protected boolean suspected;
|
||||
protected boolean saddled;
|
||||
protected boolean manifested = false;
|
||||
protected boolean morphed = false;
|
||||
protected boolean disguised = false;
|
||||
|
|
@ -176,7 +175,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.monstrous = permanent.monstrous;
|
||||
this.renowned = permanent.renowned;
|
||||
this.suspected = permanent.suspected;
|
||||
this.saddled = permanent.saddled;
|
||||
this.ringBearerFlag = permanent.ringBearerFlag;
|
||||
this.classLevel = permanent.classLevel;
|
||||
this.goadingPlayers.addAll(permanent.goadingPlayers);
|
||||
|
|
@ -239,7 +237,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
this.maxBlockedBy = 0;
|
||||
this.copy = false;
|
||||
this.goadingPlayers.clear();
|
||||
this.saddled = false;
|
||||
this.loyaltyActivationsAvailable = 1;
|
||||
this.legendRuleApplies = true;
|
||||
this.canBeSacrificed = true;
|
||||
|
|
@ -1730,16 +1727,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaddled() {
|
||||
return saddled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSaddled(boolean saddled) {
|
||||
this.saddled = saddled;
|
||||
}
|
||||
|
||||
// Used as key for the ring bearer info.
|
||||
private static final String ringbearerInfoKey = "IS_RINGBEARER";
|
||||
|
||||
|
|
|
|||
|
|
@ -10,22 +10,30 @@ import mage.watchers.Watcher;
|
|||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
* @author TheElk801, Susucr
|
||||
*/
|
||||
public class SaddledMountWatcher extends Watcher {
|
||||
|
||||
// key: the mount, value: set of creatures which saddled
|
||||
// key: the mount mor, value: set of creatures which saddled (on Saddle Cost payment)
|
||||
private final Map<MageObjectReference, Set<MageObjectReference>> saddleMap = new HashMap<>();
|
||||
|
||||
// set of mount mor actually saddled (on Saddle Ability resolution)
|
||||
private final Set<MageObjectReference> saddledSet = new HashSet<>();
|
||||
|
||||
public SaddledMountWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.SADDLED_MOUNT) {
|
||||
saddleMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), x -> new HashSet<>())
|
||||
.add(new MageObjectReference(event.getTargetId(), game));
|
||||
switch (event.getType()) {
|
||||
case SADDLED_MOUNT:
|
||||
saddleMap.computeIfAbsent(new MageObjectReference(event.getSourceId(), game), x -> new HashSet<>())
|
||||
.add(new MageObjectReference(event.getTargetId(), game));
|
||||
break;
|
||||
case MOUNT_SADDLED:
|
||||
saddledSet.add(new MageObjectReference(event.getSourceId(), game));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,24 +41,21 @@ public class SaddledMountWatcher extends Watcher {
|
|||
public void reset() {
|
||||
super.reset();
|
||||
saddleMap.clear();
|
||||
saddledSet.clear();
|
||||
}
|
||||
|
||||
public static boolean checkIfSaddledThisTurn(Permanent saddler, Permanent mount, Game game) {
|
||||
return game
|
||||
public static boolean hasBeenSaddledThisTurn(MageObjectReference mountMOR, Game game) {
|
||||
SaddledMountWatcher watcher = game.getState().getWatcher(SaddledMountWatcher.class);
|
||||
return watcher != null && watcher.saddledSet.contains(mountMOR);
|
||||
}
|
||||
|
||||
public static boolean checkIfSaddledThisTurn(Permanent saddler, MageObjectReference mountMOR, Game game) {
|
||||
return hasBeenSaddledThisTurn(mountMOR, game) && game
|
||||
.getState()
|
||||
.getWatcher(SaddledMountWatcher.class)
|
||||
.saddleMap
|
||||
.getOrDefault(new MageObjectReference(mount, game), Collections.emptySet())
|
||||
.getOrDefault(mountMOR, Collections.emptySet())
|
||||
.stream()
|
||||
.anyMatch(mor -> mor.refersTo(saddler, game));
|
||||
}
|
||||
|
||||
public static int getSaddleCount(Permanent vehicle, Game game) {
|
||||
return game
|
||||
.getState()
|
||||
.getWatcher(SaddledMountWatcher.class)
|
||||
.saddleMap
|
||||
.getOrDefault(new MageObjectReference(vehicle, game), Collections.emptySet())
|
||||
.size();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue