mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
- Reworked SourceOnBattlefieldControlUnchangedCondition checking now the LOST_CONTROL event which solves the problem with the old code to not be able to detect all controller changes of layered changeController effects when applied later.
- Simplified and fixed some problems of the handling of the "Until end of your next turn" duration.
- Fixed that some continous effects changed controller but shouldn't dependant from their duration type. Controller chnage will now done duration type dependant.
(that change fixes #6581 in a more general way undoing the effect specific changes of 2e8ece1dbd
).
This commit is contained in:
parent
25802dc105
commit
1e36b39434
30 changed files with 534 additions and 469 deletions
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -18,6 +17,7 @@ import mage.constants.SubType;
|
|||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.predicate.permanent.AnotherPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
* @author Loki
|
||||
|
@ -47,6 +47,7 @@ public final class AegisAngel extends CardImpl {
|
|||
"another target permanent is indestructible for as long as you control Aegis Angel");
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(effect, false);
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.a;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -17,6 +16,7 @@ import mage.constants.Duration;
|
|||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.target.common.TargetArtifactPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -25,7 +25,7 @@ import mage.target.common.TargetArtifactPermanent;
|
|||
public final class Aladdin extends CardImpl {
|
||||
|
||||
public Aladdin(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{R}{R}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{R}");
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.ROGUE);
|
||||
this.power = new MageInt(1);
|
||||
|
@ -39,6 +39,7 @@ public final class Aladdin extends CardImpl {
|
|||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new ManaCostsImpl("{1}{R}{R}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
ability.addTarget(new TargetArtifactPermanent());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.d;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -16,9 +15,9 @@ import mage.abilities.keyword.FlyingAbility;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
@ -26,6 +25,7 @@ import mage.players.Player;
|
|||
import mage.target.common.TargetCreatureOrPlaneswalker;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -50,6 +50,7 @@ public final class DragonlordSilumgar extends CardImpl {
|
|||
// When Dragonlord Silumgar enters the battlefield, gain control of target creature or planeswalker for as long as you control Dragonlord Silumgar.
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(new DragonlordSilumgarEffect(), false);
|
||||
ability.addTarget(new TargetCreatureOrPlaneswalker());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.m;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -11,9 +10,10 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.target.common.TargetArtifactPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
* @author Loki, JayDi85
|
||||
|
@ -33,9 +33,10 @@ public final class MasterThief extends CardImpl {
|
|||
new GainControlTargetEffect(Duration.Custom),
|
||||
new SourceOnBattlefieldControlUnchangedCondition(),
|
||||
"gain control of target artifact for as long as you control {this}"),
|
||||
false);
|
||||
false);
|
||||
|
||||
ability.addTarget(new TargetArtifactPermanent());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.m;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -25,6 +24,7 @@ import mage.game.events.ZoneChangeEvent;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -50,6 +50,7 @@ public final class MeriekeRiBerit extends CardImpl {
|
|||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, MeriekeRiBeritGainControlEffect, new TapSourceCost());
|
||||
ability.addTarget(new TargetPermanent(new FilterCreaturePermanent("target creature")));
|
||||
ability.addEffect(new MeriekeRiBeritCreateDelayedTriggerEffect());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.o;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -9,14 +8,15 @@ import mage.abilities.condition.common.SourceOnBattlefieldControlUnchangedCondit
|
|||
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||
import mage.abilities.effects.common.continuous.AssignNoCombatDamageSourceEffect;
|
||||
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
||||
import mage.constants.SubType;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.common.FilterLandPermanent;
|
||||
import mage.filter.predicate.permanent.DefendingPlayerControlsPredicate;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -45,6 +45,7 @@ public final class OrcishSquatters extends CardImpl {
|
|||
), true);
|
||||
ability.addEffect(new AssignNoCombatDamageSourceEffect(Duration.EndOfTurn, true));
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.q;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -14,12 +13,13 @@ import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledArtifactPermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetAnyTarget;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -44,6 +44,7 @@ public final class QuicksmithRebel extends CardImpl {
|
|||
"target artifact you control gains \"{T}: This artifact deals 2 damage to any target\" for as long as you control {this}");
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(effect, false);
|
||||
ability.addTarget(new TargetPermanent(new FilterControlledArtifactPermanent()));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.q;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -14,11 +13,12 @@ import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterControlledArtifactPermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -42,6 +42,7 @@ public final class QuicksmithSpy extends CardImpl {
|
|||
"target artifact you control gains \"{T}: Draw a card\" for as long as you control {this}");
|
||||
Ability ability = new EntersBattlefieldTriggeredAbility(effect, false);
|
||||
ability.addTarget(new TargetPermanent(new FilterControlledArtifactPermanent()));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.r;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -13,6 +12,7 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -36,6 +36,7 @@ public final class RoilElemental extends CardImpl {
|
|||
ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), new SourceOnBattlefieldControlUnchangedCondition(), rule);
|
||||
Ability ability = new LandfallAbility(Zone.BATTLEFIELD, effect, true);
|
||||
ability.addTarget(new TargetCreaturePermanent());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
package mage.cards.t;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
|
@ -13,44 +13,51 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SubType;
|
||||
import mage.game.Game;
|
||||
import mage.game.combat.CombatGroup;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.common.BlockedAttackerWatcher;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author jeffwadsworth
|
||||
*
|
||||
5/1/2009 The ability grants you control of all creatures that are blocking it as the ability resolves. This will include
|
||||
* any creatures that were put onto the battlefield blocking it.
|
||||
5/1/2009 Any blocking creatures that regenerated during combat will have been removed from combat. Since such creatures
|
||||
* are no longer in combat, they cannot be blocking The Wretched, which means you won't be able to gain control of them.
|
||||
5/1/2009 If The Wretched itself regenerated during combat, then it will have been removed from combat. Since it is no longer
|
||||
* in combat, there cannot be any creatures blocking it, which means you won't be able to gain control of any creatures.
|
||||
10/1/2009 The Wretched's ability triggers only if it's still on the battlefield when the end of combat step begins (after the
|
||||
* combat damage step). For example, if it's blocked by a 7/7 creature and is destroyed, its ability won't trigger at all.
|
||||
10/1/2009 If The Wretched leaves the battlefield, you no longer control it, so the duration of its control-change effect ends.
|
||||
10/1/2009 If you lose control of The Wretched before its ability resolves, you won't gain control of the creatures blocking it at all.
|
||||
10/1/2009 Once the ability resolves, it doesn't care whether the permanents you gained control of remain creatures, only that
|
||||
* they remain on the battlefield.
|
||||
* 5/1/2009 The ability grants you control of all creatures that are blocking it
|
||||
* as the ability resolves. This will include any creatures that were put onto
|
||||
* the battlefield blocking it. 5/1/2009 Any blocking creatures that regenerated
|
||||
* during combat will have been removed from combat. Since such creatures are no
|
||||
* longer in combat, they cannot be blocking The Wretched, which means you won't
|
||||
* be able to gain control of them. 5/1/2009 If The Wretched itself regenerated
|
||||
* during combat, then it will have been removed from combat. Since it is no
|
||||
* longer in combat, there cannot be any creatures blocking it, which means you
|
||||
* won't be able to gain control of any creatures. 10/1/2009 The Wretched's
|
||||
* ability triggers only if it's still on the battlefield when the end of combat
|
||||
* step begins (after the combat damage step). For example, if it's blocked by a
|
||||
* 7/7 creature and is destroyed, its ability won't trigger at all. 10/1/2009 If
|
||||
* The Wretched leaves the battlefield, you no longer control it, so the
|
||||
* duration of its control-change effect ends. 10/1/2009 If you lose control of
|
||||
* The Wretched before its ability resolves, you won't gain control of the
|
||||
* creatures blocking it at all. 10/1/2009 Once the ability resolves, it doesn't
|
||||
* care whether the permanents you gained control of remain creatures, only that
|
||||
* they remain on the battlefield.
|
||||
*/
|
||||
|
||||
public final class TheWretched extends CardImpl {
|
||||
|
||||
public TheWretched(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{B}{B}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}{B}");
|
||||
this.subtype.add(SubType.DEMON);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(5);
|
||||
|
||||
// At end of combat, gain control of all creatures blocking The Wretched for as long as you control The Wretched.
|
||||
this.addAbility(new EndOfCombatTriggeredAbility(new TheWretchedEffect(), false), new BlockedAttackerWatcher());
|
||||
|
||||
Ability ability = new EndOfCombatTriggeredAbility(new TheWretchedEffect(), false);
|
||||
this.addAbility(ability, new BlockedAttackerWatcher());
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
}
|
||||
|
||||
public TheWretched(final TheWretched card) {
|
||||
|
@ -83,16 +90,20 @@ class TheWretchedEffect extends OneShotEffect {
|
|||
if (theWretched.isRemovedFromCombat() || !theWretched.isAttacking()) {
|
||||
return false;
|
||||
}
|
||||
if (!new SourceOnBattlefieldControlUnchangedCondition().apply(game, source)) {
|
||||
// Check if control of source has changed since ability triggered????? (does it work is it neccessary???)
|
||||
Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId());
|
||||
if (permanent != null && !Objects.equals(permanent.getControllerId(), source.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (CombatGroup combatGroup :game.getCombat().getGroups()) {
|
||||
for (CombatGroup combatGroup : game.getCombat().getGroups()) {
|
||||
if (combatGroup.getAttackers().contains(source.getSourceId())) {
|
||||
for(UUID creatureId: combatGroup.getBlockers()) {
|
||||
for (UUID creatureId : combatGroup.getBlockers()) {
|
||||
Permanent blocker = game.getPermanent(creatureId);
|
||||
if (blocker != null && blocker.getBlocking() > 0) {
|
||||
ContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom, source.getControllerId()), new SourceOnBattlefieldControlUnchangedCondition(), "");
|
||||
ContinuousEffect effect = new ConditionalContinuousEffect(
|
||||
new GainControlTargetEffect(Duration.Custom, source.getControllerId()),
|
||||
new SourceOnBattlefieldControlUnchangedCondition(), "");
|
||||
effect.setTargetPointer(new FixedTarget(blocker.getId()));
|
||||
game.addEffect(effect, source);
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.t;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -19,6 +18,7 @@ import mage.constants.SubType;
|
|||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -33,7 +33,7 @@ public final class ThrullChampion extends CardImpl {
|
|||
}
|
||||
|
||||
public ThrullChampion(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{B}");
|
||||
this.subtype.add(SubType.THRULL);
|
||||
this.power = new MageInt(2);
|
||||
this.toughness = new MageInt(2);
|
||||
|
@ -47,6 +47,7 @@ public final class ThrullChampion extends CardImpl {
|
|||
"Gain control of target Thrull for as long as you control {this}");
|
||||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, ThrullChampionGainControlEffect, new TapSourceCost());
|
||||
ability.addTarget(new TargetPermanent(filter));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.w;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -11,8 +10,8 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
@ -20,6 +19,7 @@ import mage.game.events.GameEvent.EventType;
|
|||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.targetpointer.FixedTarget;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -28,7 +28,7 @@ import mage.target.targetpointer.FixedTarget;
|
|||
public final class Willbreaker extends CardImpl {
|
||||
|
||||
public Willbreaker(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}{U}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}");
|
||||
this.subtype.add(SubType.HUMAN);
|
||||
this.subtype.add(SubType.WIZARD);
|
||||
this.power = new MageInt(2);
|
||||
|
@ -37,7 +37,7 @@ public final class Willbreaker extends CardImpl {
|
|||
// Whenever a creature an opponent controls becomes the target of a spell or ability you control, gain control of that creature for as long as you control Willbreaker.
|
||||
ConditionalContinuousEffect effect = new ConditionalContinuousEffect(new GainControlTargetEffect(Duration.Custom), new SourceOnBattlefieldControlUnchangedCondition(), null);
|
||||
effect.setText("gain control of that creature for as long as you control {this}");
|
||||
this.addAbility(new WillbreakerTriggeredAbility(effect));
|
||||
this.addAbility(new WillbreakerTriggeredAbility(effect), new LostControlWatcher());
|
||||
}
|
||||
|
||||
public Willbreaker(final Willbreaker card) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
package mage.cards.w;
|
||||
|
||||
import java.util.UUID;
|
||||
|
@ -15,19 +14,20 @@ import mage.abilities.effects.common.continuous.GainControlTargetEffect;
|
|||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.constants.Zone;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author fireshoes
|
||||
*/
|
||||
public final class WillowSatyr extends CardImpl {
|
||||
|
||||
|
||||
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("legendary creature");
|
||||
|
||||
static {
|
||||
|
@ -35,7 +35,7 @@ public final class WillowSatyr extends CardImpl {
|
|||
}
|
||||
|
||||
public WillowSatyr(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{G}{G}");
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}");
|
||||
this.subtype.add(SubType.SATYR);
|
||||
this.power = new MageInt(1);
|
||||
this.toughness = new MageInt(1);
|
||||
|
@ -44,10 +44,11 @@ public final class WillowSatyr extends CardImpl {
|
|||
this.addAbility(new SkipUntapOptionalAbility());
|
||||
// {tap}: Gain control of target legendary creature for as long as you control Willow Satyr and Willow Satyr remains tapped.
|
||||
ConditionalContinuousEffect effect = new ConditionalContinuousEffect(
|
||||
new GainControlTargetEffect(Duration.Custom), new CompoundCondition(SourceTappedCondition.instance, new SourceOnBattlefieldControlUnchangedCondition()),
|
||||
"Gain control of target legendary creature for as long as you control {this} and {this} remains tapped");
|
||||
new GainControlTargetEffect(Duration.Custom), new CompoundCondition(SourceTappedCondition.instance, new SourceOnBattlefieldControlUnchangedCondition()),
|
||||
"Gain control of target legendary creature for as long as you control {this} and {this} remains tapped");
|
||||
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, effect, new TapSourceCost());
|
||||
ability.addTarget(new TargetCreaturePermanent(filter));
|
||||
ability.addWatcher(new LostControlWatcher());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,81 +1,120 @@
|
|||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.mage.test.cards.abilities.keywords;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.FreeForAll;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class GoadTest extends CardTestMultiPlayerBase {
|
||||
|
||||
@Override
|
||||
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
|
||||
// Player order: A -> D -> C -> B
|
||||
playerA = createPlayer(game, playerA, "PlayerA");
|
||||
playerB = createPlayer(game, playerB, "PlayerB");
|
||||
playerC = createPlayer(game, playerC, "PlayerC");
|
||||
playerD = createPlayer(game, playerD, "PlayerD");
|
||||
return game;
|
||||
}
|
||||
|
||||
/**
|
||||
* In a game of commander, my opponent gained control of Marisi, Breaker of
|
||||
* Coils (until end of turn) and did combat damage to another player. This
|
||||
* caused the creatures damaged by Marisi's controller to be goaded.
|
||||
* However, when the goaded creatures went to attack, they could not attack
|
||||
* me but could attack the (former) controller of Marisi.
|
||||
*/
|
||||
@Test
|
||||
public void goadWithNotOwnedCreatureTest() {
|
||||
// Your opponents can't cast spells during combat.
|
||||
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
|
||||
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Marisi, Breaker of the Coil", 1); // Creature 5/4
|
||||
|
||||
// Untap target creature an opponent controls and gain control of it until end of turn.
|
||||
// That creature gains haste until end of turn.
|
||||
// When you lose control of the creature, tap it.
|
||||
addCard(Zone.HAND, playerD, "Ray of Command"); // Instant {3}{U}
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Island", 4);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 3); // Creature 2/2
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Ray of Command", "Marisi, Breaker of the Coil");
|
||||
|
||||
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
|
||||
|
||||
attack(3, playerC, "Silvercoat Lion", playerA);
|
||||
attack(3, playerC, "Silvercoat Lion", playerB);
|
||||
attack(3, playerC, "Silvercoat Lion", playerD);
|
||||
|
||||
setStopAt(4, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
|
||||
assertGraveyardCount(playerD, "Ray of Command", 1);
|
||||
assertPermanentCount(playerA, "Marisi, Breaker of the Coil", 1);
|
||||
|
||||
assertLife(playerC, 35);
|
||||
|
||||
assertLife(playerB, 38);
|
||||
assertLife(playerA, 38);
|
||||
assertLife(playerD, 38);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package org.mage.test.cards.abilities.keywords;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import mage.constants.MultiplayerAttackOption;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.RangeOfInfluence;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.FreeForAll;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameException;
|
||||
import mage.game.mulligan.MulliganType;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class GoadTest extends CardTestMultiPlayerBase {
|
||||
|
||||
@Override
|
||||
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40);
|
||||
// Player order: A -> D -> C -> B
|
||||
playerA = createPlayer(game, playerA, "PlayerA");
|
||||
playerB = createPlayer(game, playerB, "PlayerB");
|
||||
playerC = createPlayer(game, playerC, "PlayerC");
|
||||
playerD = createPlayer(game, playerD, "PlayerD");
|
||||
return game;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void goadWithOwnedCreatureTest() {
|
||||
// Your opponents can't cast spells during combat.
|
||||
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
|
||||
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Marisi, Breaker of the Coil", 1); // Creature 5/4
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerC, "Abbey Griffin", 3); // Creature 2/2
|
||||
|
||||
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
Permanent griffinPermanent = getPermanent("Abbey Griffin");
|
||||
|
||||
Assert.assertFalse("Griffin can attack playerD but should not be able",
|
||||
griffinPermanent.canAttack(playerD.getId(), currentGame));
|
||||
Assert.assertTrue("Griffin can't attack playerA but should be able",
|
||||
griffinPermanent.canAttack(playerA.getId(), currentGame));
|
||||
Assert.assertTrue("Griffin can't attack playerB but should be able",
|
||||
griffinPermanent.canAttack(playerB.getId(), currentGame));
|
||||
|
||||
assertLife(playerC, 35);
|
||||
assertLife(playerD, 40); // player D can not be attacked from C because the creatures are goaded
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* In a game of commander, my opponent gained control of Marisi, Breaker of
|
||||
* Coils (until end of turn) and did combat damage to another player. This
|
||||
* caused the creatures damaged by Marisi's controller to be goaded.
|
||||
* However, when the goaded creatures went to attack, they could not attack
|
||||
* me but could attack the (former) controller of Marisi.
|
||||
*/
|
||||
@Test
|
||||
public void goadWithNotOwnedCreatureTest() {
|
||||
// Your opponents can't cast spells during combat.
|
||||
// Whenever a creature you control deals combat damage to a player, goad each creature that player controls
|
||||
// (Until your next turn, that creature attacks each combat if able and attacks a player other than you if able.)
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Marisi, Breaker of the Coil", 1); // Creature 5/4
|
||||
|
||||
// Untap target creature an opponent controls and gain control of it until end of turn.
|
||||
// That creature gains haste until end of turn.
|
||||
// When you lose control of the creature, tap it.
|
||||
addCard(Zone.HAND, playerD, "Ray of Command"); // Instant {3}{U}
|
||||
addCard(Zone.BATTLEFIELD, playerD, "Island", 4);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion", 1); // Creature 2/2
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Ray of Command", "Marisi, Breaker of the Coil");
|
||||
|
||||
attack(2, playerD, "Marisi, Breaker of the Coil", playerC);
|
||||
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
setStrictChooseMode(true);
|
||||
execute();
|
||||
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerD, "Ray of Command", 1);
|
||||
assertPermanentCount(playerA, "Marisi, Breaker of the Coil", 1);
|
||||
|
||||
Permanent lion = getPermanent("Silvercoat Lion", playerC);
|
||||
|
||||
Assert.assertFalse("Silvercoat lion shouldn't be able to attack player D but can", lion.canAttack(playerD.getId(), currentGame));
|
||||
Assert.assertTrue("Silvercoat lion should be able to attack player A but can't", lion.canAttack(playerA.getId(), currentGame));
|
||||
Assert.assertTrue("Silvercoat lion should be able to attack player B but can't", lion.canAttack(playerB.getId(), currentGame));
|
||||
|
||||
assertLife(playerD, 40);
|
||||
assertLife(playerC, 35);
|
||||
assertLife(playerA, 40);
|
||||
assertLife(playerB, 40);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ public class TheWretchedTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerA, "The Wretched");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Pine Needles"); // a 3/3 with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Living Wall"); // 0/6 Wall with regeneration
|
||||
|
||||
|
||||
attack(3, playerA, "The Wretched");
|
||||
block(3, playerB, "Wall of Pine Needles", "The Wretched");
|
||||
block(3, playerB, "Living Wall", "The Wretched");
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "The Wretched", 1);
|
||||
|
@ -39,17 +39,17 @@ public class TheWretchedTest extends CardTestPlayerBase {
|
|||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Wretched");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bad Moon"); // +1/+1 for black creatures
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Forest"); // a 3/3 with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Pine Needles"); // a 3/3 with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Living Wall"); // 0/6 Wall with regeneration
|
||||
|
||||
|
||||
// The Wretched
|
||||
// Creature — Demon - Demon 2/5
|
||||
attack(3, playerA, "The Wretched");
|
||||
block(3, playerB, "Wall of Pine Needles", "The Wretched");
|
||||
block(3, playerB, "Living Wall", "The Wretched");
|
||||
|
||||
|
||||
activateAbility(3, PhaseStep.DECLARE_BLOCKERS, playerB, "{G}: Regenerate {this}."); // Wall of Pine Needles
|
||||
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
|
@ -57,53 +57,52 @@ public class TheWretchedTest extends CardTestPlayerBase {
|
|||
|
||||
assertPermanentCount(playerA, "The Wretched", 1);
|
||||
assertPermanentCount(playerA, "Living Wall", 1);
|
||||
|
||||
|
||||
assertPermanentCount(playerB, "Wall of Pine Needles", 1);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLoseControlOfTheWretched() {
|
||||
|
||||
// At end of combat, gain control of all creatures blocking The Wretched for as long as you control The Wretched.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Wretched");
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Pine Needles"); // a 3/3 with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Living Wall"); // 0/6 Wall with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Island", 4);
|
||||
addCard(Zone.HAND, playerB, "Control Magic");
|
||||
|
||||
|
||||
attack(3, playerA, "The Wretched");
|
||||
block(3, playerB, "Wall of Pine Needles", "The Wretched");
|
||||
block(3, playerB, "Living Wall", "The Wretched");
|
||||
|
||||
|
||||
castSpell(4, PhaseStep.POSTCOMBAT_MAIN, playerB, "Control Magic", "The Wretched");
|
||||
|
||||
setStopAt(4, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerB, "The Wretched", 1);
|
||||
assertPermanentCount(playerA, "Wall of Pine Needles", 0);
|
||||
assertPermanentCount(playerB, "Wall of Pine Needles", 1);
|
||||
assertPermanentCount(playerB, "Living Wall", 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRegenTheWretchedThusRemovingFromCombat() {
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "The Wretched");
|
||||
addCard(Zone.HAND, playerA, "Regenerate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Regenerate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Pine Needles"); // a 3/3 with regeneration
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Spears"); // 3/2
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wall of Spears"); // 3/2
|
||||
|
||||
attack(3, playerA, "The Wretched");
|
||||
block(3, playerB, "Wall of Pine Needles", "The Wretched");
|
||||
block(3, playerB, "Wall of Spears", "The Wretched");
|
||||
|
||||
|
||||
castSpell(3, PhaseStep.DECLARE_BLOCKERS, playerA, "Regenerate", "The Wretched");
|
||||
|
||||
|
||||
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.mage.test.cards.copy;
|
||||
|
||||
import mage.abilities.keyword.DeathtouchAbility;
|
||||
import mage.constants.SubType;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.permanent.Permanent;
|
||||
import org.junit.Assert;
|
||||
|
@ -14,12 +14,11 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
|
|||
*
|
||||
* Lazav, Dimir Mastermind
|
||||
*
|
||||
* Legendary Creature — Shapeshifter 3/3, UUBB
|
||||
* Hexproof
|
||||
* Whenever a creature card is put into an opponent's graveyard from anywhere, you may have
|
||||
* Lazav, Dimir Mastermind become a copy of that card except its name is still
|
||||
* Lazav, Dimir Mastermind, it's legendary in addition to its other types, and
|
||||
* it gains hexproof and this ability.
|
||||
* Legendary Creature — Shapeshifter 3/3, UUBB Hexproof Whenever a creature card
|
||||
* is put into an opponent's graveyard from anywhere, you may have Lazav, Dimir
|
||||
* Mastermind become a copy of that card except its name is still Lazav, Dimir
|
||||
* Mastermind, it's legendary in addition to its other types, and it gains
|
||||
* hexproof and this ability.
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
|
@ -32,25 +31,26 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
public void testCopySimpleCreature() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lazav, Dimir Mastermind", 1);
|
||||
// Codex Shredder - Artifact
|
||||
// {T}: Target player mills a card.
|
||||
// {T}: Target player puts the top card of their library into their graveyard.
|
||||
// {5}, {T}, Sacrifice Codex Shredder: Return target card from your graveyard to your hand.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1);
|
||||
|
||||
// Flying 3/2
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin",5);
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin", 5);
|
||||
skipInitShuffling();
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Lazav, Dimir Mastermind", 1);
|
||||
assertPowerToughness(playerA, "Lazav, Dimir Mastermind", 3, 2);
|
||||
|
||||
Permanent lazav = getPermanent("Lazav, Dimir Mastermind", playerA.getId());
|
||||
Assert.assertTrue(lazav.getSubtype(currentGame).contains(SubType.GRIFFIN));
|
||||
Assert.assertTrue("Lazav, Dimir Mastermind must have flying",lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
Assert.assertTrue("Lazav, Dimir Mastermind must have flying", lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,10 +64,10 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
|
||||
// Whenever another nontoken creature dies, you may put a 1/1 black Rat creature token onto the battlefield.
|
||||
// Rats you control have deathtouch.
|
||||
addCard(Zone.LIBRARY, playerB, "Ogre Slumlord",5);
|
||||
addCard(Zone.LIBRARY, playerB, "Ogre Slumlord", 5);
|
||||
skipInitShuffling();
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
@ -86,11 +86,10 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
/**
|
||||
* Tests copy Nightveil Specter
|
||||
*
|
||||
* Nightveil Specter
|
||||
* Creature — Specter 2/3, {U/B}{U/B}{U/B}
|
||||
* Flying
|
||||
* Whenever Nightveil Specter deals combat damage to a player, that player exiles the top card of their library.
|
||||
* You may play cards exiled with Nightveil Specter.
|
||||
* Nightveil Specter Creature — Specter 2/3, {U/B}{U/B}{U/B} Flying Whenever
|
||||
* Nightveil Specter deals combat damage to a player, that player exiles the
|
||||
* top card of their library. You may play cards exiled with Nightveil
|
||||
* Specter.
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
|
@ -99,11 +98,11 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerA, "Lazav, Dimir Mastermind", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1);
|
||||
|
||||
addCard(Zone.LIBRARY, playerB, "Silvercoat Lion",2);
|
||||
addCard(Zone.LIBRARY, playerB, "Nightveil Specter",1);
|
||||
addCard(Zone.LIBRARY, playerB, "Silvercoat Lion", 2);
|
||||
addCard(Zone.LIBRARY, playerB, "Nightveil Specter", 1);
|
||||
skipInitShuffling();
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
attack(3, playerA, "Lazav, Dimir Mastermind");
|
||||
|
||||
|
@ -130,15 +129,15 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerA, "Lazav, Dimir Mastermind", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1);
|
||||
|
||||
addCard(Zone.LIBRARY, playerB, "Silvercoat Lion",2);
|
||||
addCard(Zone.LIBRARY, playerB, "Nightveil Specter",1);
|
||||
addCard(Zone.LIBRARY, playerB, "Silvercoat Lion", 2);
|
||||
addCard(Zone.LIBRARY, playerB, "Nightveil Specter", 1);
|
||||
skipInitShuffling();
|
||||
|
||||
// Lazav becomes a Nightveil Specter
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
// Lazav becomes a Silvercoat Lion
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
setStopAt(3, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
@ -151,52 +150,52 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
Assert.assertTrue(lazav.isLegendary());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests old copy is discarded after reanmiation of Lazav
|
||||
*/
|
||||
@Test
|
||||
public void testCopyAfterReanimation() {
|
||||
addCard(Zone.BATTLEFIELD, playerA ,"Swamp");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
|
||||
// Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its converted mana cost.
|
||||
addCard(Zone.HAND, playerA ,"Reanimate");
|
||||
addCard(Zone.HAND, playerA, "Reanimate");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lazav, Dimir Mastermind", 1);
|
||||
// Codex Shredder - Artifact
|
||||
// {T}: Target player mills a card.
|
||||
// {T}: Target player puts the top card of their library into their graveyard.
|
||||
// {5}, {T}, Sacrifice Codex Shredder: Return target card from your graveyard to your hand.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerB ,"Swamp", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
|
||||
// Flying 3/2
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin",1);
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin", 1);
|
||||
// Target opponent sacrifices a creature. You gain life equal to that creature's toughness.
|
||||
addCard(Zone.HAND, playerB ,"Tribute to Hunger");
|
||||
|
||||
addCard(Zone.HAND, playerB, "Tribute to Hunger");
|
||||
|
||||
skipInitShuffling();
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Tribute to Hunger");
|
||||
|
||||
|
||||
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Reanimate", "Lazav, Dimir Mastermind");
|
||||
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertGraveyardCount(playerB, "Tribute to Hunger", 1);
|
||||
assertGraveyardCount(playerA, "Reanimate", 1);
|
||||
|
||||
|
||||
assertLife(playerA, 16); // -4 from Reanmiate
|
||||
assertLife(playerB, 22); // +3 from Tribute to Hunger because Lazav is 3/2
|
||||
|
||||
assertLife(playerB, 22); // +3 from Tribute to Hunger because Lazav is 3/2
|
||||
|
||||
assertPermanentCount(playerA, "Lazav, Dimir Mastermind", 1);
|
||||
assertPowerToughness(playerA, "Lazav, Dimir Mastermind", 3, 3);
|
||||
Permanent lazav = getPermanent("Lazav, Dimir Mastermind", playerA.getId());
|
||||
Assert.assertFalse(lazav.getSubtype(currentGame).contains(SubType.GRIFFIN)); // no Griffin type
|
||||
Assert.assertFalse("Lazav, Dimir Mastermind must have flying",lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
|
||||
|
||||
Assert.assertFalse("Lazav, Dimir Mastermind must have flying", lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests if Lazav remains a copy of the creature after it is exiled
|
||||
*/
|
||||
|
@ -204,20 +203,20 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
public void testCopyCreatureExiled() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Lazav, Dimir Mastermind", 1);
|
||||
// Codex Shredder - Artifact
|
||||
// {T}: Target player mills a card.
|
||||
// {T}: Target player puts the top card of their library into their graveyard.
|
||||
// {5}, {T}, Sacrifice Codex Shredder: Return target card from your graveyard to your hand.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Codex Shredder", 1);
|
||||
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
|
||||
|
||||
|
||||
addCard(Zone.HAND, playerA, "Rest in Peace", 1);
|
||||
|
||||
// Flying 3/2
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin",5);
|
||||
addCard(Zone.LIBRARY, playerB, "Assault Griffin", 5);
|
||||
skipInitShuffling();
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player mills a card.", playerB);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player puts the top card", playerB);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Rest in Peace");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
|
@ -228,6 +227,6 @@ public class LazavDimirMastermindTest extends CardTestPlayerBase {
|
|||
|
||||
Permanent lazav = getPermanent("Lazav, Dimir Mastermind", playerA.getId());
|
||||
Assert.assertTrue(lazav.getSubtype(currentGame).contains(SubType.GRIFFIN));
|
||||
Assert.assertTrue("Lazav, Dimir Mastermind must have flying",lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
Assert.assertTrue("Lazav, Dimir Mastermind must have flying", lazav.getAbilities().contains(FlyingAbility.getInstance()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package org.mage.test.player;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageItem;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
|
@ -57,13 +62,6 @@ import mage.util.CardUtil;
|
|||
import org.apache.log4j.Logger;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.mage.test.serverside.base.impl.CardTestPlayerAPIImpl.*;
|
||||
|
||||
/**
|
||||
|
@ -193,7 +191,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
/**
|
||||
* @param maxCallsWithoutAction max number of priority passes a player may
|
||||
* have for this test (default = 100)
|
||||
* have for this test (default = 100)
|
||||
*/
|
||||
public void setMaxCallsWithoutAction(int maxCallsWithoutAction) {
|
||||
this.maxCallsWithoutAction = maxCallsWithoutAction;
|
||||
|
@ -1046,13 +1044,13 @@ public class TestPlayer implements Player {
|
|||
|
||||
List<String> data = cards.stream()
|
||||
.map(c -> (((c instanceof PermanentToken) ? "[T] " : "[C] ")
|
||||
+ c.getIdName()
|
||||
+ (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "")
|
||||
+ " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
|
||||
+ (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
|
||||
+ ", " + (c.isTapped() ? "Tapped" : "Untapped")
|
||||
+ getPrintableAliases(", [", c.getId(), "]")
|
||||
+ (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName())))
|
||||
+ c.getIdName()
|
||||
+ (c.isCopy() ? " [copy of " + c.getCopyFrom().getId().toString().substring(0, 3) + "]" : "")
|
||||
+ " - " + c.getPower().getValue() + "/" + c.getToughness().getValue()
|
||||
+ (c.isPlaneswalker() ? " - L" + c.getCounters(game).getCount(CounterType.LOYALTY) : "")
|
||||
+ ", " + (c.isTapped() ? "Tapped" : "Untapped")
|
||||
+ getPrintableAliases(", [", c.getId(), "]")
|
||||
+ (c.getAttachedTo() == null ? "" : ", attached to " + game.getPermanent(c.getAttachedTo()).getIdName())))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
@ -1076,12 +1074,12 @@ public class TestPlayer implements Player {
|
|||
|
||||
List<String> data = abilities.stream()
|
||||
.map(a -> (a.getZone() + " -> "
|
||||
+ a.getSourceObject(game).getIdName() + " -> "
|
||||
+ (a.toString().startsWith("Cast ") ? "[" + a.getManaCostsToPay().getText() + "] -> " : "") // printed cost, not modified
|
||||
+ (a.toString().length() > 0
|
||||
? a.toString().substring(0, Math.min(20, a.toString().length()))
|
||||
: a.getClass().getSimpleName())
|
||||
+ "..."))
|
||||
+ a.getSourceObject(game).getIdName() + " -> "
|
||||
+ (a.toString().startsWith("Cast ") ? "[" + a.getManaCostsToPay().getText() + "] -> " : "") // printed cost, not modified
|
||||
+ (a.toString().length() > 0
|
||||
? a.toString().substring(0, Math.min(20, a.toString().length()))
|
||||
: a.getClass().getSimpleName())
|
||||
+ "..."))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
@ -1446,7 +1444,7 @@ public class TestPlayer implements Player {
|
|||
UUID defenderId = null;
|
||||
boolean mustAttackByAction = false;
|
||||
boolean madeAttackByAction = false;
|
||||
for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<org.mage.test.player.PlayerAction> it = actions.iterator(); it.hasNext();) {
|
||||
PlayerAction action = it.next();
|
||||
|
||||
// aiXXX commands
|
||||
|
@ -2021,7 +2019,7 @@ public class TestPlayer implements Player {
|
|||
// skip targets
|
||||
if (targets.get(0).equals(TARGET_SKIP)) {
|
||||
Assert.assertTrue("found skip target, but it require more targets, needs "
|
||||
+ (target.getMinNumberOfTargets() - target.getTargets().size()) + " more",
|
||||
+ (target.getMinNumberOfTargets() - target.getTargets().size()) + " more",
|
||||
target.getTargets().size() >= target.getMinNumberOfTargets());
|
||||
targets.remove(0);
|
||||
return true;
|
||||
|
@ -2326,7 +2324,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
this.chooseStrictModeFailed("choice", game,
|
||||
"Triggered list (total " + abilities.size() + "):\n"
|
||||
+ abilities.stream().map(a -> getInfo(a, game)).collect(Collectors.joining("\n")));
|
||||
+ abilities.stream().map(a -> getInfo(a, game)).collect(Collectors.joining("\n")));
|
||||
return computerPlayer.chooseTriggeredAbility(abilities, game);
|
||||
}
|
||||
|
||||
|
@ -3496,7 +3494,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Target target,
|
||||
UUID sourceId, Game game
|
||||
UUID sourceId, Game game
|
||||
) {
|
||||
// needed to call here the TestPlayer because it's overwitten
|
||||
return choose(outcome, target, sourceId, game, null);
|
||||
|
@ -3504,7 +3502,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public boolean choose(Outcome outcome, Cards cards,
|
||||
TargetCard target, Game game
|
||||
TargetCard target, Game game
|
||||
) {
|
||||
assertAliasSupportInChoices(false);
|
||||
if (!choices.isEmpty()) {
|
||||
|
@ -3541,7 +3539,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
// chooseTargetAmount calls for EACH target cycle (e.g. one target per click, see TargetAmount)
|
||||
// if use want to stop choosing then chooseTargetAmount must return false (example: up to xxx)
|
||||
|
@ -3554,7 +3552,7 @@ public class TestPlayer implements Player {
|
|||
// skip targets
|
||||
if (targets.get(0).equals(TARGET_SKIP)) {
|
||||
Assert.assertTrue("found skip target, but it require more targets, needs "
|
||||
+ (target.getMinNumberOfTargets() - target.getTargets().size()) + " more",
|
||||
+ (target.getMinNumberOfTargets() - target.getTargets().size()) + " more",
|
||||
target.getTargets().size() >= target.getMinNumberOfTargets());
|
||||
targets.remove(0);
|
||||
return false; // false in chooseTargetAmount = stop to choose
|
||||
|
@ -3607,15 +3605,15 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public boolean choosePile(Outcome outcome, String message,
|
||||
List<? extends Card> pile1, List<? extends Card> pile2,
|
||||
Game game
|
||||
List<? extends Card> pile1, List<? extends Card> pile2,
|
||||
Game game
|
||||
) {
|
||||
return computerPlayer.choosePile(outcome, message, pile1, pile2, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean playMana(Ability ability, ManaCost unpaid,
|
||||
String promptText, Game game
|
||||
String promptText, Game game
|
||||
) {
|
||||
groupsForTargetHandling = null;
|
||||
|
||||
|
@ -3665,15 +3663,15 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public UUID chooseBlockerOrder(List<Permanent> blockers, CombatGroup combatGroup,
|
||||
List<UUID> blockerOrder, Game game
|
||||
List<UUID> blockerOrder, Game game
|
||||
) {
|
||||
return computerPlayer.chooseBlockerOrder(blockers, combatGroup, blockerOrder, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignDamage(int damage, List<UUID> targets,
|
||||
String singleTargetName, UUID sourceId,
|
||||
Game game
|
||||
String singleTargetName, UUID sourceId,
|
||||
Game game
|
||||
) {
|
||||
computerPlayer.assignDamage(damage, targets, singleTargetName, sourceId, game);
|
||||
}
|
||||
|
@ -3692,14 +3690,14 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public void pickCard(List<Card> cards, Deck deck,
|
||||
Draft draft
|
||||
Draft draft
|
||||
) {
|
||||
computerPlayer.pickCard(cards, deck, draft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean scry(int value, Ability source,
|
||||
Game game
|
||||
Game game
|
||||
) {
|
||||
// Don't scry at the start of the game.
|
||||
if (game.getTurnNum() == 1 && game.getStep() == null) {
|
||||
|
@ -3710,44 +3708,44 @@ public class TestPlayer implements Player {
|
|||
|
||||
@Override
|
||||
public boolean surveil(int value, Ability source,
|
||||
Game game
|
||||
Game game
|
||||
) {
|
||||
return computerPlayer.surveil(value, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Card card, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return computerPlayer.moveCards(card, toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Card card, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
return computerPlayer.moveCards(card, toZone, source, game, tapped, faceDown, byOwner, appliedEffects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Cards cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return computerPlayer.moveCards(cards, toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return computerPlayer.moveCards(cards, toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
return computerPlayer.moveCards(cards, toZone, source, game, tapped, faceDown, byOwner, appliedEffects);
|
||||
}
|
||||
|
|
|
@ -1,31 +1,43 @@
|
|||
|
||||
package mage.abilities.condition.common;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.condition.Condition;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.watchers.common.LostControlWatcher;
|
||||
|
||||
/**
|
||||
* This condition remembers controller on the first apply.
|
||||
* As long as this controller keeps unchanged and the source is
|
||||
* on the battlefield, the condition is true.
|
||||
* This condition checks if ever since first call of the apply method the
|
||||
* controller of the source has changed
|
||||
*
|
||||
* Monitoring the LOST_CONTROL event has the advantage that also all layered
|
||||
* effects can correctly check for controller change because comparing old and
|
||||
* new controller during their apply time does not take into account layered
|
||||
* cahnge control effects that will be applied later.
|
||||
*
|
||||
* This condition needs the LostControlWatcher, so be sure to add it to the card
|
||||
* that uses the condition
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class SourceOnBattlefieldControlUnchangedCondition implements Condition {
|
||||
|
||||
private UUID controllerId;
|
||||
|
||||
private Long checkingSince;
|
||||
private int startingZoneChangeCounter;
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
if (controllerId == null) {
|
||||
controllerId = source.getControllerId();
|
||||
if (checkingSince == null) {
|
||||
checkingSince = System.currentTimeMillis() - 1;
|
||||
startingZoneChangeCounter = game.getState().getZoneChangeCounter(source.getSourceId());
|
||||
}
|
||||
Permanent permanent = game.getBattlefield().getPermanent(source.getSourceId());
|
||||
return (permanent != null && Objects.equals(controllerId, source.getControllerId()));
|
||||
if (game.getState().getZoneChangeCounter(source.getSourceId()) > startingZoneChangeCounter) {
|
||||
return false;
|
||||
}
|
||||
LostControlWatcher watcher = game.getState().getWatcher(LostControlWatcher.class);
|
||||
if (watcher != null) {
|
||||
return checkingSince > watcher.getOrderOfLastLostControl(source.getSourceId());
|
||||
}
|
||||
throw new UnsupportedOperationException("LostControlWatcher not found!");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.constants.DependencyType;
|
||||
|
@ -9,11 +13,6 @@ import mage.constants.SubLayer;
|
|||
import mage.game.Game;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
@ -67,8 +66,6 @@ public interface ContinuousEffect extends Effect {
|
|||
|
||||
UUID getStartingController();
|
||||
|
||||
void incYourTurnNumPlayed();
|
||||
|
||||
boolean isYourNextTurn(Game game);
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.CompoundAbility;
|
||||
|
@ -19,8 +20,6 @@ import mage.game.stack.StackObject;
|
|||
import mage.players.Player;
|
||||
import mage.target.targetpointer.TargetPointer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
|
@ -47,9 +46,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
protected boolean characterDefining = false;
|
||||
|
||||
// until your next turn or until end of your next turn
|
||||
private UUID startingControllerId; // player to checkss turns (can't different with real controller ability)
|
||||
private boolean startingTurnWasActive;
|
||||
private int yourTurnNumPlayed = 0; // turnes played after effect was created
|
||||
private UUID startingControllerId; // player to check for turn duration (can't different with real controller ability)
|
||||
private boolean startingTurnWasActive; // effect started during related players turn and related players turn was already active
|
||||
private int effectStartingOnTurn = 0; // turn the effect started
|
||||
|
||||
public ContinuousEffectImpl(Duration duration, Outcome outcome) {
|
||||
super(outcome);
|
||||
|
@ -79,7 +78,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
this.temporary = effect.temporary;
|
||||
this.startingControllerId = effect.startingControllerId;
|
||||
this.startingTurnWasActive = effect.startingTurnWasActive;
|
||||
this.yourTurnNumPlayed = effect.yourTurnNumPlayed;
|
||||
this.effectStartingOnTurn = effect.effectStartingOnTurn;
|
||||
this.dependencyTypes = effect.dependencyTypes;
|
||||
this.dependendToTypes = effect.dependendToTypes;
|
||||
this.characterDefining = effect.characterDefining;
|
||||
|
@ -191,23 +190,13 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
this.startingControllerId = startingController;
|
||||
this.startingTurnWasActive = activePlayerId != null
|
||||
&& activePlayerId.equals(startingController); // you can't use "game" for active player cause it's called from tests/cheat too
|
||||
this.yourTurnNumPlayed = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incYourTurnNumPlayed() {
|
||||
yourTurnNumPlayed++;
|
||||
this.effectStartingOnTurn = game.getTurnNum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isYourNextTurn(Game game) {
|
||||
if (this.startingTurnWasActive) {
|
||||
return yourTurnNumPlayed == 1
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
} else {
|
||||
return yourTurnNumPlayed == 0
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
}
|
||||
return effectStartingOnTurn < game.getTurnNum()
|
||||
&& game.isActivePlayer(startingControllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -367,6 +356,9 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
|||
/**
|
||||
* Auto-generates dependencies on different effects (what's apply first and
|
||||
* what's apply second)
|
||||
*
|
||||
* @param abilityToGain
|
||||
* @param filterToSearch
|
||||
*/
|
||||
public void generateGainAbilityDependencies(Ability abilityToGain, Filter filterToSearch) {
|
||||
this.addDependencyType(DependencyType.AddingAbility);
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
import mage.abilities.Ability;
|
||||
|
@ -26,11 +30,6 @@ import mage.target.common.TargetCardInHand;
|
|||
import mage.util.CardUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
@ -163,28 +162,17 @@ public class ContinuousEffects implements Serializable {
|
|||
spliceCardEffects.removeInactiveEffects(game);
|
||||
}
|
||||
|
||||
public synchronized void incYourTurnNumPlayed(Game game) {
|
||||
layeredEffects.incYourTurnNumPlayed(game);
|
||||
continuousRuleModifyingEffects.incYourTurnNumPlayed(game);
|
||||
replacementEffects.incYourTurnNumPlayed(game);
|
||||
preventionEffects.incYourTurnNumPlayed(game);
|
||||
requirementEffects.incYourTurnNumPlayed(game);
|
||||
restrictionEffects.incYourTurnNumPlayed(game);
|
||||
for (ContinuousEffectsList asThoughtlist : asThoughEffectsMap.values()) {
|
||||
asThoughtlist.incYourTurnNumPlayed(game);
|
||||
}
|
||||
costModificationEffects.incYourTurnNumPlayed(game);
|
||||
spliceCardEffects.incYourTurnNumPlayed(game);
|
||||
}
|
||||
|
||||
public synchronized List<ContinuousEffect> getLayeredEffects(Game game) {
|
||||
return getLayeredEffects(game, "main");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return effects list ordered by timestamps (timestamps are automaticity generates from new/old lists on same layer)
|
||||
* Return effects list ordered by timestamps (timestamps are automaticity
|
||||
* generates from new/old lists on same layer)
|
||||
*
|
||||
* @param timestampGroupName workaround to fix broken timestamps on effect's add/remove between different layers
|
||||
* @param game
|
||||
* @param timestampGroupName workaround to fix broken timestamps on effect's
|
||||
* add/remove between different layers
|
||||
* @return effects list ordered by timestamp
|
||||
*/
|
||||
public synchronized List<ContinuousEffect> getLayeredEffects(Game game, String timestampGroupName) {
|
||||
|
@ -229,8 +217,10 @@ public class ContinuousEffects implements Serializable {
|
|||
* "actual" meaning it becomes turned on that is defined by
|
||||
* Ability.#isInUseableZone(Game, boolean) method in
|
||||
* #getLayeredEffects(Game).
|
||||
* <p>
|
||||
* It must be called with different timestamp group name (otherwise sort order will be changed for add/remove effects, see Urborg and Bloodmoon test)
|
||||
*
|
||||
* It must be called with different timestamp group name (otherwise sort
|
||||
* order will be changed for add/remove effects, see Urborg and Bloodmoon
|
||||
* test)
|
||||
*
|
||||
* @param layerEffects
|
||||
*/
|
||||
|
@ -359,7 +349,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
// boolean checkLKI = event.getType().equals(EventType.ZONE_CHANGE) || event.getType().equals(EventType.DESTROYED_PERMANENT);
|
||||
//get all applicable transient Replacement effects
|
||||
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<ReplacementEffect> iterator = replacementEffects.iterator(); iterator.hasNext();) {
|
||||
ReplacementEffect effect = iterator.next();
|
||||
if (!effect.checksEventType(event, game)) {
|
||||
continue;
|
||||
|
@ -392,7 +382,7 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<PreventionEffect> iterator = preventionEffects.iterator(); iterator.hasNext();) {
|
||||
PreventionEffect effect = iterator.next();
|
||||
if (!effect.checksEventType(event, game)) {
|
||||
continue;
|
||||
|
@ -768,8 +758,8 @@ public class ContinuousEffects implements Serializable {
|
|||
* @param event
|
||||
* @param targetAbility ability the event is attached to. can be null.
|
||||
* @param game
|
||||
* @param silentMode true if the event does not really happen but it's
|
||||
* checked if the event would be replaced
|
||||
* @param silentMode true if the event does not really happen but it's
|
||||
* checked if the event would be replaced
|
||||
* @return
|
||||
*/
|
||||
public boolean preventedByRuleModification(GameEvent event, Ability targetAbility, Game game, boolean silentMode) {
|
||||
|
@ -817,7 +807,7 @@ public class ContinuousEffects implements Serializable {
|
|||
do {
|
||||
Map<ReplacementEffect, Set<Ability>> rEffects = getApplicableReplacementEffects(event, game);
|
||||
// Remove all consumed effects (ability dependant)
|
||||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext(); ) {
|
||||
for (Iterator<ReplacementEffect> it1 = rEffects.keySet().iterator(); it1.hasNext();) {
|
||||
ReplacementEffect entry = it1.next();
|
||||
if (consumed.containsKey(entry.getId()) /*&& !(entry instanceof CommanderReplacementEffect) */) { // 903.9.
|
||||
Set<UUID> consumedAbilitiesIds = consumed.get(entry.getId());
|
||||
|
@ -1002,7 +992,7 @@ public class ContinuousEffects implements Serializable {
|
|||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> dependentTo.contains(entry.getKey().getId())
|
||||
&& entry.getValue().contains(effect.getId()))
|
||||
&& entry.getValue().contains(effect.getId()))
|
||||
.forEach(entry -> {
|
||||
entry.getValue().remove(effect.getId());
|
||||
dependentTo.remove(entry.getKey().getId());
|
||||
|
@ -1036,7 +1026,7 @@ public class ContinuousEffects implements Serializable {
|
|||
continue;
|
||||
}
|
||||
// check if waiting effects can be applied now
|
||||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<Map.Entry<ContinuousEffect, Set<UUID>>> iterator = waitingEffects.entrySet().iterator(); iterator.hasNext();) {
|
||||
Map.Entry<ContinuousEffect, Set<UUID>> entry = iterator.next();
|
||||
if (!appliedEffects.containsAll(entry.getValue())) { // all dependent to effects are applied now so apply the effect itself
|
||||
continue;
|
||||
|
@ -1283,18 +1273,19 @@ public class ContinuousEffects implements Serializable {
|
|||
}
|
||||
|
||||
private void setControllerForEffect(ContinuousEffectsList<?> effects, UUID sourceId, UUID controllerId) {
|
||||
for (Effect effect : effects) {
|
||||
Set<Ability> abilities = effects.getAbility(effect.getId());
|
||||
for (Ability ability : abilities) {
|
||||
if (ability.getSourceId() != null) {
|
||||
if (ability.getSourceId().equals(sourceId)) {
|
||||
ability.setControllerId(controllerId);
|
||||
for (ContinuousEffect effect : effects) {
|
||||
if (!effect.getDuration().isFixedController()) {
|
||||
Set<Ability> abilities = effects.getAbility(effect.getId());
|
||||
for (Ability ability : abilities) {
|
||||
if (ability.getSourceId() != null) {
|
||||
if (ability.getSourceId().equals(sourceId)) {
|
||||
ability.setControllerId(controllerId);
|
||||
}
|
||||
} else if (ability.getZone() != Zone.COMMAND) {
|
||||
logger.fatal("Continuous effect for ability with no sourceId Ability: " + ability);
|
||||
}
|
||||
} else if (ability.getZone() != Zone.COMMAND) {
|
||||
logger.fatal("Continuous effect for ability with no sourceId Ability: " + ability);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package mage.abilities.effects;
|
||||
|
||||
import java.util.*;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.MageSingleton;
|
||||
|
@ -10,8 +11,6 @@ import mage.game.Game;
|
|||
import mage.players.Player;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
|
@ -47,7 +46,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
public void removeEndOfTurnEffects(Game game) {
|
||||
// calls every turn on cleanup step (only end of turn duration)
|
||||
// rules 514.2
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
boolean canRemove = false;
|
||||
switch (entry.getDuration()) {
|
||||
|
@ -67,7 +66,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
|
||||
public void removeEndOfCombatEffects() {
|
||||
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
if (entry.getDuration() == Duration.EndOfCombat) {
|
||||
i.remove();
|
||||
|
@ -77,7 +76,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
}
|
||||
|
||||
public void removeInactiveEffects(Game game) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext();) {
|
||||
T entry = i.next();
|
||||
if (isInactive(entry, game)) {
|
||||
i.remove();
|
||||
|
@ -86,15 +85,6 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
}
|
||||
}
|
||||
|
||||
public void incYourTurnNumPlayed(Game game) {
|
||||
for (Iterator<T> i = this.iterator(); i.hasNext(); ) {
|
||||
T entry = i.next();
|
||||
if (game.isActivePlayer(entry.getStartingController())) {
|
||||
entry.incYourTurnNumPlayed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInactive(T effect, Game game) {
|
||||
// ends all inactive effects -- calls on player leave or apply new effect
|
||||
if (game.getState().isGameOver()) {
|
||||
|
@ -109,9 +99,8 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
those objects are exiled. This is not a state-based action. It happens as soon as the player leaves the game.
|
||||
If the player who left the game had priority at the time they left, priority passes to the next player in turn
|
||||
order who’s still in the game.
|
||||
*/
|
||||
*/
|
||||
// objects removes doing in player.leave() call... effects removes is here
|
||||
|
||||
Set<Ability> set = effectAbilityMap.get(effect.getId());
|
||||
if (set == null) {
|
||||
logger.debug("No abilities for effect found: " + effect.toString());
|
||||
|
@ -224,7 +213,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
|
|||
abilities.removeAll(abilitiesToRemove);
|
||||
}
|
||||
if (abilities == null || abilities.isEmpty()) {
|
||||
for (Iterator<T> iterator = this.iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<T> iterator = this.iterator(); iterator.hasNext();) {
|
||||
ContinuousEffect effect = iterator.next();
|
||||
if (effect.getId().equals(effectIdToRemove)) {
|
||||
iterator.remove();
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
package mage.abilities.effects.common.combat;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.constants.Duration;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
*/
|
||||
public class CantAttackYouEffect extends RestrictionEffect {
|
||||
|
||||
UUID controllerId;
|
||||
|
||||
public CantAttackYouEffect(Duration duration) {
|
||||
super(duration);
|
||||
}
|
||||
|
||||
public CantAttackYouEffect(Duration duration, UUID controllerId) {
|
||||
super(duration);
|
||||
this.controllerId = controllerId;
|
||||
}
|
||||
|
||||
public CantAttackYouEffect(final CantAttackYouEffect effect) {
|
||||
super(effect);
|
||||
this.controllerId = effect.controllerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -44,9 +35,6 @@ public class CantAttackYouEffect extends RestrictionEffect {
|
|||
if (defenderId == null) {
|
||||
return true;
|
||||
}
|
||||
if (controllerId == null) {
|
||||
controllerId = source.getControllerId();
|
||||
}
|
||||
return !defenderId.equals(controllerId);
|
||||
return !defenderId.equals(source.getControllerId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,18 +41,18 @@ public class GoadTargetEffect extends OneShotEffect {
|
|||
Permanent targetCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (targetCreature != null && controller != null) {
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect is not support it
|
||||
// TODO: Allow goad to target controller, current AttacksIfAbleTargetEffect does not support it
|
||||
// https://github.com/magefree/mage/issues/5283
|
||||
/*
|
||||
If the creature doesn’t meet any of the above exceptions and can attack, it must attack a player other than
|
||||
the controller of the spell or ability that goaded it if able. It the creature can’t attack any of those
|
||||
the controller of the spell or ability that goaded it if able. If the creature can’t attack any of those
|
||||
players but could otherwise attack, it must attack an opposing planeswalker (controlled by any opponent)
|
||||
or the player that goaded it. (2016-08-23)
|
||||
*/
|
||||
ContinuousEffect effect = new AttacksIfAbleTargetEffect(Duration.UntilYourNextTurn);
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source)));
|
||||
game.addEffect(effect, source);
|
||||
effect = new CantAttackYouEffect(Duration.UntilYourNextTurn, source.getControllerId()); // remember current controller
|
||||
effect = new CantAttackYouEffect(Duration.UntilYourNextTurn); // remember current controller
|
||||
effect.setTargetPointer(new FixedTarget(getTargetPointer().getFirst(game, source)));
|
||||
game.addEffect(effect, source);
|
||||
game.informPlayers(controller.getLogName() + " is goading " + targetCreature.getLogName());
|
||||
|
|
|
@ -4,25 +4,27 @@ package mage.constants;
|
|||
* @author North
|
||||
*/
|
||||
public enum Duration {
|
||||
OneUse("", true),
|
||||
EndOfGame("for the rest of the game", false),
|
||||
WhileOnBattlefield("", false),
|
||||
WhileOnStack("", false),
|
||||
WhileInGraveyard("", false),
|
||||
EndOfTurn("until end of turn", true),
|
||||
UntilYourNextTurn("until your next turn", true),
|
||||
UntilEndOfYourNextTurn("until the end of your next turn", true),
|
||||
UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true), // supported for continuous layered effects
|
||||
EndOfCombat("until end of combat", true),
|
||||
EndOfStep("until end of phase step", true),
|
||||
Custom("", true);
|
||||
OneUse("", true, true),
|
||||
EndOfGame("for the rest of the game", false, false),
|
||||
WhileOnBattlefield("", false, false),
|
||||
WhileOnStack("", false, true),
|
||||
WhileInGraveyard("", false, false),
|
||||
EndOfTurn("until end of turn", true, true),
|
||||
UntilYourNextTurn("until your next turn", true, true),
|
||||
UntilEndOfYourNextTurn("until the end of your next turn", true, true),
|
||||
UntilSourceLeavesBattlefield("until {source} leaves the battlefield", true, false), // supported for continuous layered effects
|
||||
EndOfCombat("until end of combat", true, true),
|
||||
EndOfStep("until end of phase step", true, true),
|
||||
Custom("", true, true);
|
||||
|
||||
private final String text;
|
||||
private final boolean onlyValidIfNoZoneChange; // defines if an effect lasts only if the source has not chnaged zone since init of the effect
|
||||
private final boolean onlyValidIfNoZoneChange; // defines if an effect lasts only if the source has not changed zone since init of the effect
|
||||
private final boolean fixedController; // has the controller of the effect to change, if the controller of the source changes
|
||||
|
||||
Duration(String text, boolean onlyValidIfNoZoneChange) {
|
||||
Duration(String text, boolean onlyValidIfNoZoneChange, boolean fixedController) {
|
||||
this.text = text;
|
||||
this.onlyValidIfNoZoneChange = onlyValidIfNoZoneChange;
|
||||
this.fixedController = fixedController;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -34,4 +36,7 @@ public enum Duration {
|
|||
return onlyValidIfNoZoneChange;
|
||||
}
|
||||
|
||||
public boolean isFixedController() {
|
||||
return fixedController;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package mage.game;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.MageException;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
|
@ -67,11 +71,6 @@ import mage.util.functions.ApplyToPermanent;
|
|||
import mage.watchers.common.*;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class GameImpl implements Game, Serializable {
|
||||
|
||||
private static final int ROLLBACK_TURNS_MAX = 4;
|
||||
|
@ -1549,7 +1548,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
/**
|
||||
* @param emblem
|
||||
* @param sourceObject
|
||||
* @param toPlayerId controller and owner of the emblem
|
||||
* @param toPlayerId controller and owner of the emblem
|
||||
*/
|
||||
@Override
|
||||
public void addEmblem(Emblem emblem, MageObject sourceObject, UUID toPlayerId) {
|
||||
|
@ -1567,8 +1566,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
/**
|
||||
* @param plane
|
||||
* @param sourceObject
|
||||
* @param toPlayerId controller and owner of the plane (may only be one per
|
||||
* game..)
|
||||
* @param toPlayerId controller and owner of the plane (may only be one per
|
||||
* game..)
|
||||
* @return boolean - whether the plane was added successfully or not
|
||||
*/
|
||||
@Override
|
||||
|
@ -1804,7 +1803,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
break;
|
||||
}
|
||||
// triggered abilities that don't use the stack have to be executed first (e.g. Banisher Priest Return exiled creature
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
|
||||
TriggeredAbility triggeredAbility = it.next();
|
||||
if (!triggeredAbility.isUsesStack()) {
|
||||
state.removeTriggeredAbility(triggeredAbility);
|
||||
|
@ -1917,8 +1916,8 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
*/
|
||||
boolean usePowerInsteadOfToughnessForDamageLethality = usePowerInsteadOfToughnessForDamageLethalityFilters.stream()
|
||||
.anyMatch(filter -> filter.match(perm, this));
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality ?
|
||||
// Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
int lethalDamageThreshold = usePowerInsteadOfToughnessForDamageLethality
|
||||
? // Zilortha, Strength Incarnate, 2020-04-17: A creature with 0 power isn’t destroyed unless it has at least 1 damage marked on it.
|
||||
Math.max(perm.getPower().getValue(), 1) : perm.getToughness().getValue();
|
||||
if (lethalDamageThreshold <= perm.getDamage() || perm.isDeathtouched()) {
|
||||
if (perm.destroy(null, this, false)) {
|
||||
|
@ -2249,7 +2248,6 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
|
||||
//TODO: implement the rest
|
||||
|
||||
return somethingHappened;
|
||||
}
|
||||
|
||||
|
@ -2557,7 +2555,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
//20100423 - 800.4a
|
||||
Set<Card> toOutside = new HashSet<>();
|
||||
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<Permanent> it = getBattlefield().getAllPermanents().iterator(); it.hasNext();) {
|
||||
Permanent perm = it.next();
|
||||
if (perm.isOwnedBy(playerId)) {
|
||||
if (perm.getAttachedTo() != null) {
|
||||
|
@ -2595,14 +2593,14 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
}
|
||||
}
|
||||
}
|
||||
for(Card card : toOutside) {
|
||||
for (Card card : toOutside) {
|
||||
rememberLKI(card.getId(), Zone.BATTLEFIELD, card);
|
||||
}
|
||||
// needed to send event that permanent leaves the battlefield to allow non stack effects to execute
|
||||
player.moveCards(toOutside, Zone.OUTSIDE, null, this);
|
||||
// triggered abilities that don't use the stack have to be executed
|
||||
List<TriggeredAbility> abilities = state.getTriggered(player.getId());
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<TriggeredAbility> it = abilities.iterator(); it.hasNext();) {
|
||||
TriggeredAbility triggeredAbility = it.next();
|
||||
if (!triggeredAbility.isUsesStack()) {
|
||||
state.removeTriggeredAbility(triggeredAbility);
|
||||
|
@ -2622,7 +2620,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
// Remove cards from the player in all exile zones
|
||||
for (ExileZone exile : this.getExile().getExileZones()) {
|
||||
for (Iterator<UUID> it = exile.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<UUID> it = exile.iterator(); it.hasNext();) {
|
||||
Card card = this.getCard(it.next());
|
||||
if (card != null && card.isOwnedBy(playerId)) {
|
||||
it.remove();
|
||||
|
@ -2632,7 +2630,7 @@ public abstract class GameImpl implements Game, Serializable {
|
|||
|
||||
//Remove all commander/emblems/plane the player controls
|
||||
boolean addPlaneAgain = false;
|
||||
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<CommandObject> it = this.getState().getCommand().iterator(); it.hasNext();) {
|
||||
CommandObject obj = it.next();
|
||||
if (obj.isControlledBy(playerId)) {
|
||||
if (obj instanceof Emblem) {
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package mage.game;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import static java.util.Collections.emptyList;
|
||||
import java.util.stream.Collectors;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.*;
|
||||
import mage.abilities.effects.ContinuousEffect;
|
||||
|
@ -35,12 +39,6 @@ import mage.util.ThreadLocalStringBuilder;
|
|||
import mage.watchers.Watcher;
|
||||
import mage.watchers.Watchers;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* <p>
|
||||
|
@ -179,8 +177,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards.putAll(state.copiedCards);
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter)
|
||||
-> this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
public void restoreForRollBack(GameState state) {
|
||||
|
@ -226,8 +224,8 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
this.copiedCards = state.copiedCards;
|
||||
this.permanentOrderNumber = state.permanentOrderNumber;
|
||||
this.applyEffectsCounter = state.applyEffectsCounter;
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter) ->
|
||||
this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
state.usePowerInsteadOfToughnessForDamageLethalityFilters.forEach((uuid, filter)
|
||||
-> this.usePowerInsteadOfToughnessForDamageLethalityFilters.put(uuid, filter.copy()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -605,7 +603,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
delayed.removeEndOfTurnAbilities(game);
|
||||
exile.cleanupEndOfTurnZones(game);
|
||||
game.applyEffects();
|
||||
effects.incYourTurnNumPlayed(game);
|
||||
}
|
||||
|
||||
public void addEffect(ContinuousEffect effect, Ability source) {
|
||||
|
@ -623,7 +620,6 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
// public void addMessage(String message) {
|
||||
// this.messages.add(message);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Returns a list of all players of the game ignoring range or if a player
|
||||
* has lost or left the game.
|
||||
|
@ -797,7 +793,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
for (Map.Entry<ZoneChangeData, List<GameEvent>> entry : eventsByKey.entrySet()) {
|
||||
Set<Card> movedCards = new LinkedHashSet<>();
|
||||
Set<PermanentToken> movedTokens = new LinkedHashSet<>();
|
||||
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<GameEvent> it = entry.getValue().iterator(); it.hasNext();) {
|
||||
GameEvent event = it.next();
|
||||
ZoneChangeEvent castEvent = (ZoneChangeEvent) event;
|
||||
UUID targetId = castEvent.getTargetId();
|
||||
|
@ -1030,7 +1026,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
|||
* @param attachedTo
|
||||
* @param ability
|
||||
* @param copyAbility copies non MageSingleton abilities before adding to
|
||||
* state
|
||||
* state
|
||||
*/
|
||||
public void addOtherAbility(Card attachedTo, Ability ability, boolean copyAbility) {
|
||||
Ability newAbility;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package mage.game.combat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.RequirementEffect;
|
||||
|
@ -33,9 +35,6 @@ import mage.util.Copyable;
|
|||
import mage.util.trace.TraceUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
@ -346,8 +345,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.DECLARING_ATTACKERS, attackingPlayerId, attackingPlayerId))
|
||||
|| (!canBand && !canBandWithOther)
|
||||
|| !player.chooseUse(Outcome.Benefit,
|
||||
"Do you wish to " + (isBanded ? "band " + attacker.getLogName()
|
||||
+ " with another " : "form a band with " + attacker.getLogName() + " and an ")
|
||||
"Do you wish to " + (isBanded ? "band " + attacker.getLogName()
|
||||
+ " with another " : "form a band with " + attacker.getLogName() + " and an ")
|
||||
+ "attacking creature?", null, game)) {
|
||||
break;
|
||||
}
|
||||
|
@ -412,7 +411,8 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
if (!isBanded) {
|
||||
return;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ").append((attacker.getBandedCards().size() + 1) + " creatures: ");
|
||||
StringBuilder sb = new StringBuilder(player.getLogName()).append(" formed a band with ")
|
||||
.append(attacker.getBandedCards().size()).append(1).append(" creatures: ");
|
||||
sb.append(attacker.getLogName());
|
||||
for (UUID id : attacker.getBandedCards()) {
|
||||
sb.append(", ");
|
||||
|
@ -565,7 +565,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* Handle the blocker selection process
|
||||
*
|
||||
* @param blockController player that controls how to block, if null the
|
||||
* defender is the controller
|
||||
* defender is the controller
|
||||
* @param game
|
||||
*/
|
||||
public void selectBlockers(Player blockController, Game game) {
|
||||
|
@ -744,15 +744,15 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
}
|
||||
|
||||
/**
|
||||
* 509.1c The defending player checks each creature they control to
|
||||
* see whether it's affected by any requirements (effects that say a
|
||||
* creature must block, or that it must block if some condition is met). If
|
||||
* the number of requirements that are being obeyed is fewer than the
|
||||
* maximum possible number of requirements that could be obeyed without
|
||||
* disobeying any restrictions, the declaration of blockers is illegal. If a
|
||||
* creature can't block unless a player pays a cost, that player is not
|
||||
* required to pay that cost, even if blocking with that creature would
|
||||
* increase the number of requirements being obeyed.
|
||||
* 509.1c The defending player checks each creature they control to see
|
||||
* whether it's affected by any requirements (effects that say a creature
|
||||
* must block, or that it must block if some condition is met). If the
|
||||
* number of requirements that are being obeyed is fewer than the maximum
|
||||
* possible number of requirements that could be obeyed without disobeying
|
||||
* any restrictions, the declaration of blockers is illegal. If a creature
|
||||
* can't block unless a player pays a cost, that player is not required to
|
||||
* pay that cost, even if blocking with that creature would increase the
|
||||
* number of requirements being obeyed.
|
||||
* <p>
|
||||
* <p>
|
||||
* Example: A player controls one creature that "blocks if able" and another
|
||||
|
@ -1379,7 +1379,7 @@ public class Combat implements Serializable, Copyable<Combat> {
|
|||
* @param playerId
|
||||
* @param game
|
||||
* @param solveBanding check whether also add creatures banded with
|
||||
* attackerId
|
||||
* attackerId
|
||||
*/
|
||||
public void addBlockingGroup(UUID blockerId, UUID attackerId, UUID playerId, Game game, boolean solveBanding) {
|
||||
Permanent blocker = game.getPermanent(blockerId);
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package mage.players;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import mage.ConditionalMana;
|
||||
import mage.MageObject;
|
||||
import mage.MageObjectReference;
|
||||
|
@ -65,10 +68,6 @@ import mage.util.GameLog;
|
|||
import mage.util.RandomUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public abstract class PlayerImpl implements Player, Serializable {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(PlayerImpl.class);
|
||||
|
@ -612,9 +611,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
&& this.hasOpponent(sourceControllerId, game)
|
||||
&& game.getContinuousEffects().asThough(this.getId(), AsThoughEffectType.HEXPROOF, null, sourceControllerId, game) == null
|
||||
&& abilities.stream()
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
.filter(HexproofBaseAbility.class::isInstance)
|
||||
.map(HexproofBaseAbility.class::cast)
|
||||
.anyMatch(ability -> ability.checkObject(source, game))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -654,7 +653,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(getLogName() + " discards down to "
|
||||
+ this.maxHandSize
|
||||
+ (this.maxHandSize == 1
|
||||
? " hand card" : " hand cards"));
|
||||
? " hand card" : " hand cards"));
|
||||
}
|
||||
discard(hand.size() - this.maxHandSize, false, null, game);
|
||||
}
|
||||
|
@ -803,7 +802,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
GameEvent gameEvent = GameEvent.getEvent(GameEvent.EventType.DISCARD_CARD,
|
||||
card.getId(), source == null
|
||||
? null : source.getSourceId(), playerId);
|
||||
? null : source.getSourceId(), playerId);
|
||||
gameEvent.setFlag(source != null); // event from effect or from cost (source == null)
|
||||
if (game.replaceEvent(gameEvent, source)) {
|
||||
return false;
|
||||
|
@ -1345,6 +1344,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean activateAbility(ActivatedAbility ability, Game game) {
|
||||
if (ability == null) {
|
||||
return false;
|
||||
}
|
||||
boolean result;
|
||||
if (ability instanceof PassAbility) {
|
||||
pass(game);
|
||||
|
@ -1502,14 +1504,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
@Override
|
||||
public LinkedHashMap<UUID, ActivatedAbility> getPlayableActivatedAbilities(MageObject object, Zone zone, Game game) {
|
||||
LinkedHashMap<UUID, ActivatedAbility> useable = new LinkedHashMap<>();
|
||||
// It may not be possible to activate abilities of stack abilities
|
||||
if (object instanceof StackAbility || object == null) {
|
||||
return useable;
|
||||
}
|
||||
boolean previousState = game.inCheckPlayableState();
|
||||
game.setCheckPlayableState(true);
|
||||
try {
|
||||
// It may not be possible to activate abilities of stack abilities
|
||||
if (object instanceof StackAbility) {
|
||||
return useable;
|
||||
}
|
||||
|
||||
// collect and filter playable activated abilities
|
||||
// GUI: user clicks on card, but it must activate ability from any card's parts (main, left, right)
|
||||
UUID needId1, needId2, needId3;
|
||||
|
@ -1810,9 +1811,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
private List<Permanent> getPermanentsThatCanBeUntapped(Game game,
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> canBeUntapped,
|
||||
RestrictionUntapNotMoreThanEffect handledEffect,
|
||||
Map<Entry<RestrictionUntapNotMoreThanEffect, Set<Ability>>, Integer> notMoreThanEffectsUsage) {
|
||||
List<Permanent> leftForUntap = new ArrayList<>();
|
||||
// select permanents that can still be untapped
|
||||
for (Permanent permanent : canBeUntapped) {
|
||||
|
@ -2521,7 +2522,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean searchLibrary(TargetCardInLibrary target, Ability source, Game game, UUID targetPlayerId,
|
||||
boolean triggerEvents) {
|
||||
boolean triggerEvents) {
|
||||
//20091005 - 701.14c
|
||||
Library searchedLibrary = null;
|
||||
String searchInfo = null;
|
||||
|
@ -2699,7 +2700,9 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
(event.isWinnable() ? "(You called " + event.getChosenName() + ")" : null),
|
||||
"Heads", "Tails", source, game
|
||||
));
|
||||
} else event.setResult(canChooseHeads);
|
||||
} else {
|
||||
event.setResult(canChooseHeads);
|
||||
}
|
||||
game.informPlayers(getLogName() + " chose to keep " + CardUtil.booleanToFlipName(event.getResult()));
|
||||
}
|
||||
if (event.isWinnable()) {
|
||||
|
@ -2721,7 +2724,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numSides Number of sides the dice has
|
||||
* @param numSides Number of sides the dice has
|
||||
* @return the number that the player rolled
|
||||
*/
|
||||
@Override
|
||||
|
@ -2758,16 +2761,16 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
/**
|
||||
* @param game
|
||||
* @param appliedEffects
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberChaosSides The number of chaos sides the planar die
|
||||
* currently has (normally 1 but can be 5)
|
||||
* @param numberPlanarSides The number of chaos sides the planar die
|
||||
* currently has (normally 1)
|
||||
* currently has (normally 1)
|
||||
* @return the outcome that the player rolled. Either ChaosRoll, PlanarRoll
|
||||
* or NilRoll
|
||||
*/
|
||||
@Override
|
||||
public PlanarDieRoll rollPlanarDie(Game game, List<UUID> appliedEffects, int numberChaosSides,
|
||||
int numberPlanarSides) {
|
||||
int numberPlanarSides) {
|
||||
int result = RandomUtil.nextInt(9) + 1;
|
||||
PlanarDieRoll roll = PlanarDieRoll.NIL_ROLL;
|
||||
if (numberChaosSides + numberPlanarSides > 9) {
|
||||
|
@ -2924,14 +2927,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
/**
|
||||
* @param ability
|
||||
* @param available if null, it won't be checked if enough mana is available
|
||||
* @param available if null, it won't be checked if enough mana is available
|
||||
* @param sourceObject
|
||||
* @param game
|
||||
* @return
|
||||
*/
|
||||
protected boolean canPlay(ActivatedAbility ability, ManaOptions available, MageObject sourceObject, Game game) {
|
||||
if (!(ability instanceof ActivatedManaAbilityImpl)) {
|
||||
ActivatedAbility copy = ability.copy(); // copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
|
||||
ActivatedAbility copy = ability.copy(); // Copy is needed because cost reduction effects modify e.g. the mana to activate/cast the ability
|
||||
if (!copy.canActivate(playerId, game).canActivate()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -3117,7 +3120,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
protected boolean canLandPlayAlternateSourceCostsAbility(Card sourceObject, ManaOptions available, Ability ability, Game game) {
|
||||
if (!(sourceObject instanceof Permanent)) {
|
||||
if (sourceObject != null && !(sourceObject instanceof Permanent)) {
|
||||
Ability sourceAbility = sourceObject.getAbilities().stream()
|
||||
.filter(landAbility -> landAbility.getAbilityType() == AbilityType.PLAY_LAND)
|
||||
.findFirst().orElse(null);
|
||||
|
@ -3158,7 +3161,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
}
|
||||
|
||||
private void getPlayableFromCardAll(Game game, Zone fromZone, Card card, ManaOptions availableMana, List<ActivatedAbility> output) {
|
||||
if (fromZone == null) {
|
||||
if (fromZone == null || card == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -3187,7 +3190,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
boolean isPlayLand = (ability instanceof PlayLandAbility);
|
||||
|
||||
// as original controller
|
||||
|
||||
// play land restrictions
|
||||
if (isPlayLand && game.getContinuousEffects().preventedByRuleModification(
|
||||
GameEvent.getEvent(GameEvent.EventType.PLAY_LAND, ability.getSourceId(),
|
||||
|
@ -3221,7 +3223,6 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|| (fromZone == Zone.GRAVEYARD && canPlayCardsFromGraveyard());
|
||||
|
||||
// as affected controller
|
||||
|
||||
UUID savedControllerId = ability.getControllerId();
|
||||
ability.setControllerId(this.getId());
|
||||
try {
|
||||
|
@ -3620,7 +3621,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean canPaySacrificeCost(Permanent permanent, UUID sourceId,
|
||||
UUID controllerId, Game game
|
||||
UUID controllerId, Game game
|
||||
) {
|
||||
return sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, sourceId, controllerId, game);
|
||||
}
|
||||
|
@ -3773,8 +3774,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Card card, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
Set<Card> cardList = new HashSet<>();
|
||||
if (card != null) {
|
||||
|
@ -3785,22 +3786,22 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCards(Cards cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards.getCards(game), toZone, source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game
|
||||
Ability source, Game game
|
||||
) {
|
||||
return moveCards(cards, toZone, source, game, false, false, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCards(Set<Card> cards, Zone toZone,
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
Ability source, Game game,
|
||||
boolean tapped, boolean faceDown, boolean byOwner, List<UUID> appliedEffects
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
@ -3902,8 +3903,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Card card, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
Set<Card> cards = new HashSet<>();
|
||||
cards.add(card);
|
||||
|
@ -3912,8 +3913,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardsToExile(Set<Card> cards, Ability source,
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
Game game, boolean withName, UUID exileId,
|
||||
String exileZoneName
|
||||
) {
|
||||
if (cards.isEmpty()) {
|
||||
return true;
|
||||
|
@ -3929,14 +3930,14 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game
|
||||
Game game
|
||||
) {
|
||||
return this.moveCardToHandWithInfo(card, sourceId, game, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveCardToHandWithInfo(Card card, UUID sourceId,
|
||||
Game game, boolean withName
|
||||
Game game, boolean withName
|
||||
) {
|
||||
boolean result = false;
|
||||
Zone fromZone = game.getState().getZone(card.getId());
|
||||
|
@ -3961,7 +3962,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public Set<Card> moveCardsToGraveyardWithInfo(Set<Card> allCards, Ability source,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
UUID sourceId = source == null ? null : source.getSourceId();
|
||||
Set<Card> movedCards = new LinkedHashSet<>();
|
||||
|
@ -3969,7 +3970,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
// identify cards from one owner
|
||||
Cards cards = new CardsImpl();
|
||||
UUID ownerId = null;
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<Card> it = allCards.iterator(); it.hasNext();) {
|
||||
Card card = it.next();
|
||||
if (cards.isEmpty()) {
|
||||
ownerId = card.getOwnerId();
|
||||
|
@ -4032,7 +4033,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToGraveyardWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone
|
||||
Game game, Zone fromZone
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
@ -4061,8 +4062,8 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToLibraryWithInfo(Card card, UUID sourceId,
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
Game game, Zone fromZone,
|
||||
boolean toTop, boolean withName
|
||||
) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
|
@ -4127,7 +4128,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
@Override
|
||||
public boolean moveCardToExileWithInfo(Card card, UUID exileId, String exileName, UUID sourceId,
|
||||
Game game, Zone fromZone, boolean withName) {
|
||||
Game game, Zone fromZone, boolean withName) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4150,7 +4151,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
game.informPlayers(this.getLogName() + " moves " + (withName ? card.getLogName()
|
||||
+ (card.isCopy() ? " (Copy)" : "") : "a card face down") + ' '
|
||||
+ (fromZone != null ? "from " + fromZone.toString().toLowerCase(Locale.ENGLISH)
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
+ ' ' : "") + "to the exile zone");
|
||||
|
||||
}
|
||||
result = true;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package mage.watchers.common;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import mage.constants.WatcherScope;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.watchers.Watcher;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class LostControlWatcher extends Watcher {
|
||||
|
||||
private final Map<UUID, Long> lastLostControl = new HashMap<>();
|
||||
|
||||
public LostControlWatcher() {
|
||||
super(WatcherScope.GAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void watch(GameEvent event, Game game) {
|
||||
if (event.getType() == GameEvent.EventType.LOST_CONTROL) {
|
||||
lastLostControl.put(event.getTargetId(), System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
lastLostControl.clear();
|
||||
}
|
||||
|
||||
public long getOrderOfLastLostControl(UUID sourceId) {
|
||||
return lastLostControl.getOrDefault(sourceId, new Long(0));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue