- 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:
LevelX2 2020-06-10 22:28:23 +02:00
parent 25802dc105
commit 1e36b39434
30 changed files with 534 additions and 469 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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()));
}
}

View file

@ -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);
}

View file

@ -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!");
}
}

View file

@ -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

View file

@ -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);

View file

@ -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);
}
}
}
}

View file

@ -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 whos 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();

View file

@ -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());
}
}

View file

@ -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 doesnt 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 cant attack any of those
the controller of the spell or ability that goaded it if able. If the creature cant 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());

View file

@ -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;
}
}

View file

@ -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 isnt 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 isnt 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) {

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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));
}
}