Merge pull request #17 from magefree/master

Merge https://github.com/magefree/mage
This commit is contained in:
Zzooouhh 2017-11-03 19:02:15 +01:00 committed by GitHub
commit 07cee19809
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 1672 additions and 539 deletions

2
.gitignore vendored
View file

@ -44,6 +44,7 @@ Mage.Server.Plugins/Mage.Game.CommanderDuel/target
Mage.Server.Plugins/Mage.Game.CommanderFreeForAll/target/
Mage.Server.Plugins/Mage.Game.FreeForAll/target
Mage.Server.Plugins/Mage.Game.MomirDuel/target
Mage.Server.Plugins/Mage.Game.MomirGame/target/
Mage.Server.Plugins/Mage.Game.PennyDreadfulCommanderFreeForAll/target
Mage.Server.Plugins/Mage.Game.TinyLeadersDuel/target
Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/target
@ -132,3 +133,4 @@ Mage.Client/serverlist.txt
client_secrets.json
dependency-reduced-pom.xml
mage-bundle

View file

@ -1,5 +1,5 @@
#Generated by Maven
#Fri Sep 15 22:14:29 CEST 2017
#Sun Oct 22 00:34:49 EDT 2017
version=1.4.26
groupId=org.mage
artifactId=mage-game-pennydreadfulcommanderfreeforall

View file

@ -519,7 +519,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
logger.trace("interrupted - " + val);
return val;
}
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) {
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) {
logger.trace("Add actions -- reached end state, node count=" + SimulationNode2.nodeCount + ", depth=" + depth);
val = GameStateEvaluator2.evaluate(playerId, game);
UUID currentPlayerId = node.getGame().getPlayerList().get();
@ -540,7 +540,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
}
}
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game);
} else if (!node.getChildren().isEmpty()) {
//declared attackers or blockers or triggered abilities
@ -588,7 +588,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
logger.debug("Sim Prio [" + depth + "] -- repeated action: " + action.toString());
continue;
}
if (!sim.gameOver(null) && action.isUsesStack()) {
if (!sim.checkIfGameIsOver() && action.isUsesStack()) {
// only pass if the last action uses the stack
UUID nextPlayerId = sim.getPlayerList().get();
do {
@ -864,7 +864,7 @@ public class ComputerPlayer6 extends ComputerPlayer /*implements Player*/ {
break;
case CLEANUP:
game.getPhase().getStep().beginStep(game, activePlayerId);
if (!game.checkStateAndTriggered() && !game.gameOver(null)) {
if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) {
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
game.getTurn().setPhase(new BeginningPhase());
game.getPhase().setStep(new UntapStep());

View file

@ -233,7 +233,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
return GameStateEvaluator2.evaluate(playerId, game);
}
// Condition to stop deeper simulation
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.gameOver(null)) {
if (depth <= 0 || SimulationNode2.nodeCount > maxNodes || game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game);
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder("Add Actions -- reached end state <").append(val).append('>');
@ -267,7 +267,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
}
}
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, game);
} else if (stepFinished) {
logger.debug("Step finished");
@ -481,7 +481,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
Combat simCombat = sim.getCombat().copy();
finishCombat(sim);
if (sim.gameOver(null)) {
if (sim.checkIfGameIsOver()) {
val = GameStateEvaluator2.evaluate(playerId, sim);
} else if (!counter) {
val = simulatePostCombatMain(sim, newNode, depth - 1, alpha, beta);
@ -549,7 +549,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
logger.debug("interrupted");
return;
}
if (!game.gameOver(null)) {
if (!game.checkIfGameIsOver()) {
game.getPhase().setStep(step);
if (!step.skipStep(game, game.getActivePlayerId())) {
step.beginStep(game, game.getActivePlayerId());
@ -598,7 +598,7 @@ public class ComputerPlayer7 extends ComputerPlayer6 {
logger.debug("interrupted");
return;
}
if (!game.gameOver(null)) {
if (!game.checkIfGameIsOver()) {
game.getTurn().getPhase().endPhase(game, game.getActivePlayerId());
game.getTurn().setPhase(new EndPhase());
if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) {

View file

@ -33,7 +33,7 @@ public final class GameStateEvaluator2 {
public static int evaluate(UUID playerId, Game game) {
Player player = game.getPlayer(playerId);
Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next());
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) {
return LOSE_GAME_SCORE;
}

View file

@ -61,7 +61,7 @@ public class ActionSimulator {
public int evaluateState() {
Player opponent = game.getPlayer(game.getOpponents(player.getId()).iterator().next());
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon()) {
return Integer.MIN_VALUE;
}

View file

@ -79,7 +79,7 @@ public class MCTSNode {
this.game = game;
this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null);
this.terminal = game.checkIfGameIsOver();
setPlayer();
nodeCount = 1;
// logger.info(this.stateValue);
@ -90,7 +90,7 @@ public class MCTSNode {
this.game = game;
this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null);
this.terminal = game.checkIfGameIsOver();
this.parent = parent;
this.action = action;
setPlayer();
@ -104,7 +104,7 @@ public class MCTSNode {
this.combat = combat;
this.stateValue = game.getState().getValue(game, targetPlayer);
this.fullStateValue = game.getState().getValue(true, game);
this.terminal = game.gameOver(null);
this.terminal = game.checkIfGameIsOver();
this.parent = parent;
setPlayer();
nodeCount++;

View file

@ -330,7 +330,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
return GameStateEvaluator.evaluate(playerId, game);
}
int val;
if (node.depth > maxDepth || game.gameOver(null)) {
if (node.depth > maxDepth || game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- reached end state");
val = GameStateEvaluator.evaluate(playerId, game);
}
@ -357,7 +357,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
}
}
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, game);
}
else if (!node.getChildren().isEmpty()) {
@ -403,7 +403,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
logger.debug(indent(node.depth) + "found useless action: " + action);
continue;
}
if (!sim.gameOver(null) && action.isUsesStack()) {
if (!sim.checkIfGameIsOver() && action.isUsesStack()) {
// only pass if the last action uses the stack
sim.getPlayer(currentPlayer.getId()).pass(game);
sim.getPlayerList().getNext();
@ -588,7 +588,7 @@ public class ComputerPlayer2 extends ComputerPlayer implements Player {
break;
case CLEANUP:
game.getPhase().getStep().beginStep(game, activePlayerId);
if (!game.checkStateAndTriggered() && !game.gameOver(null)) {
if (!game.checkStateAndTriggered() && !game.checkIfGameIsOver()) {
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
game.getTurn().setPhase(new BeginningPhase());
game.getPhase().setStep(new UntapStep());

View file

@ -184,7 +184,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug(indent(node.depth) + "interrupted");
return GameStateEvaluator.evaluate(playerId, game);
}
if (node.depth > maxDepth || game.gameOver(null)) {
if (node.depth > maxDepth || game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- reached end state");
val = GameStateEvaluator.evaluate(playerId, game);
}
@ -204,7 +204,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
}
}
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, game);
}
else if (stepFinished) {
@ -408,7 +408,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
sim.fireEvent(GameEvent.getEvent(GameEvent.EventType.DECLARE_BLOCKERS_STEP_POST, sim.getActivePlayerId(), sim.getActivePlayerId()));
Combat simCombat = sim.getCombat().copy();
finishCombat(sim);
if (sim.gameOver(null)) {
if (sim.checkIfGameIsOver()) {
val = GameStateEvaluator.evaluate(playerId, sim);
}
else if (!counter) {
@ -450,7 +450,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
return GameStateEvaluator.evaluate(playerId, game);
}
Integer val = null;
if (!game.gameOver(null)) {
if (!game.checkIfGameIsOver()) {
logger.debug(indent(node.depth) + "simulating -- ending turn");
simulateToEnd(game);
game.getState().setActivePlayerId(game.getState().getPlayerList(game.getActivePlayerId()).getNext());
@ -478,7 +478,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug("interrupted");
return;
}
if (!game.gameOver(null)) {
if (!game.checkIfGameIsOver()) {
game.getPhase().setStep(step);
if (!step.skipStep(game, game.getActivePlayerId())) {
step.beginStep(game, game.getActivePlayerId());
@ -526,7 +526,7 @@ public class ComputerPlayer3 extends ComputerPlayer2 implements Player {
logger.debug("interrupted");
return;
}
if (!game.gameOver(null)) {
if (!game.checkIfGameIsOver()) {
game.getTurn().getPhase().endPhase(game, game.getActivePlayerId());
game.getTurn().setPhase(new EndPhase());
if (game.getTurn().getPhase().beginPhase(game, game.getActivePlayerId())) {

View file

@ -70,7 +70,7 @@ public final class GameStateEvaluator {
public static int evaluate(UUID playerId, Game game, boolean ignoreTapped) {
Player player = game.getPlayer(playerId);
Player opponent = game.getPlayer(game.getOpponents(playerId).iterator().next());
if (game.gameOver(null)) {
if (game.checkIfGameIsOver()) {
if (player.hasLost() || opponent.hasWon())
return LOSE_SCORE;
if (opponent.hasLost() || player.hasWon())

View file

@ -53,6 +53,7 @@ import mage.filter.common.FilterCreatureForCombat;
import mage.filter.common.FilterCreatureForCombatBlock;
import mage.filter.predicate.permanent.ControllerIdPredicate;
import mage.game.Game;
import mage.game.GameImpl;
import mage.game.combat.CombatGroup;
import mage.game.draft.Draft;
import mage.game.events.GameEvent;
@ -186,13 +187,25 @@ public class HumanPlayer extends PlayerImpl {
response.clear();
logger.debug("Waiting response from player: " + getId());
game.resumeTimer(getTurnControlledBy());
synchronized (response) {
try {
response.wait();
} catch (InterruptedException ex) {
logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex);
} finally {
game.pauseTimer(getTurnControlledBy());
boolean loop = true;
while (loop) {
loop = false;
synchronized (response) {
try {
response.wait();
} catch (InterruptedException ex) {
logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex);
} finally {
game.pauseTimer(getTurnControlledBy());
}
}
if (response.getResponseConcedeCheck()) {
((GameImpl) game).checkConcede();
if (game.hasEnded()) {
return;
}
response.clear();
loop = true;
}
}
if (recordingMacro && !macroTriggeredSelectionFlag) {
@ -1706,6 +1719,15 @@ public class HumanPlayer extends PlayerImpl {
}
}
@Override
public void signalPlayerConcede() {
synchronized (response) {
response.setResponseConcedeCheck();
response.notifyAll();
logger.debug("Set check concede for waiting player: " + getId());
}
}
@Override
public void skip() {
synchronized (response) {

View file

@ -43,6 +43,7 @@ public class PlayerResponse implements Serializable {
private Integer responseInteger;
private ManaType responseManaType;
private UUID responseManaTypePlayerId;
private Boolean responseConcedeCheck;
public PlayerResponse() {
clear();
@ -55,7 +56,8 @@ public class PlayerResponse implements Serializable {
+ ',' + responseBoolean
+ ',' + responseInteger
+ ',' + responseManaType
+ ',' + responseManaTypePlayerId;
+ ',' + responseManaTypePlayerId
+ ',' + responseConcedeCheck;
}
public PlayerResponse(PlayerResponse other) {
@ -69,6 +71,7 @@ public class PlayerResponse implements Serializable {
responseInteger = other.responseInteger;
responseManaType = other.responseManaType;
responseManaTypePlayerId = other.responseManaTypePlayerId;
responseConcedeCheck = other.responseConcedeCheck;
}
public void clear() {
@ -78,6 +81,7 @@ public class PlayerResponse implements Serializable {
responseInteger = null;
responseManaType = null;
responseManaTypePlayerId = null;
responseConcedeCheck = null;
}
public String getString() {
@ -104,6 +108,17 @@ public class PlayerResponse implements Serializable {
this.responseBoolean = responseBoolean;
}
public Boolean getResponseConcedeCheck() {
if (responseConcedeCheck == null) {
return false;
}
return responseConcedeCheck;
}
public void setResponseConcedeCheck() {
this.responseConcedeCheck = true;
}
public Integer getInteger() {
return responseInteger;
}

View file

@ -0,0 +1,176 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.d;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.EquipAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.combat.CombatGroup;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.target.targetpointer.FixedTargets;
/**
*
* @author jerekwilson
*/
public class DeadIronSledge extends CardImpl {
public DeadIronSledge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}");
this.subtype.add(SubType.EQUIPMENT);
// Whenever equipped creature blocks or becomes blocked by a creature, destroy both creatures.
this.addAbility(new DeadIronSledgeTriggeredAbility());
// Equip {2}
this.addAbility(new EquipAbility(Outcome.AddAbility, new GenericManaCost(2)));
}
public DeadIronSledge(final DeadIronSledge card) {
super(card);
}
@Override
public DeadIronSledge copy() {
return new DeadIronSledge(this);
}
}
class DeadIronSledgeTriggeredAbility extends TriggeredAbilityImpl {
private Set<UUID> possibleTargets = new HashSet<>();
DeadIronSledgeTriggeredAbility() {
super(Zone.BATTLEFIELD, new DeadIronSledgeDestroyEffect(), false);
}
DeadIronSledgeTriggeredAbility(final DeadIronSledgeTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARED_BLOCKERS;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
List<Permanent> targetPermanents = new ArrayList<>();
Permanent equipment = game.getPermanentOrLKIBattlefield((this.getSourceId()));
if (equipment != null && equipment.getAttachedTo() != null) {
Permanent equippedPermanent = game.getPermanentOrLKIBattlefield((equipment.getAttachedTo()));
if (equippedPermanent != null) {
possibleTargets.clear();
if (equippedPermanent.isBlocked(game)) {
possibleTargets.add(equippedPermanent.getId()); //add equipped creature to target list
}
if (equippedPermanent.isAttacking()) {
for (CombatGroup group : game.getCombat().getGroups()) {
if (group.getAttackers().contains(equippedPermanent.getId())) {
possibleTargets.addAll(group.getBlockers());
}
}
} else if (equippedPermanent.getBlocking() > 0) {
for (CombatGroup group : game.getCombat().getGroups()) {
if (group.getBlockers().contains(equippedPermanent.getId())) {
possibleTargets.addAll(group.getAttackers());
}
}
}
if (!possibleTargets.isEmpty()) {
this.getTargets().clear();
for (UUID creatureId : possibleTargets) {
Permanent target = game.getPermanentOrLKIBattlefield(creatureId);
targetPermanents.add(target);
}
this.getEffects().get(0).setTargetPointer(new FixedTargets(targetPermanents, game));
return true;
}
}
}
return false;
}
@Override
public TriggeredAbility copy() {
return new DeadIronSledgeTriggeredAbility(this);
}
@Override
public String getRule() {
return "Whenever equipped creature blocks or becomes blocked by a creature, destroy both creatures.";
}
}
class DeadIronSledgeDestroyEffect extends OneShotEffect {
public DeadIronSledgeDestroyEffect() {
super(Outcome.DestroyPermanent);
this.staticText = "destroy both creatures";
}
public DeadIronSledgeDestroyEffect(final DeadIronSledgeDestroyEffect effect) {
super(effect);
}
@Override
public DeadIronSledgeDestroyEffect copy() {
return new DeadIronSledgeDestroyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
for (UUID targetId : this.getTargetPointer().getTargets(game, source)) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
permanent.destroy(targetId, game, false);
}
}
return true;
}
}

View file

@ -30,9 +30,8 @@ package mage.cards.d;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.CycleTriggeredAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.OneShotEffect;
@ -55,15 +54,14 @@ import mage.players.Player;
public class DecreeOfJustice extends CardImpl {
public DecreeOfJustice(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{X}{X}{2}{W}{W}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{X}{2}{W}{W}");
// create X 4/4 white Angel creature tokens with flying.
// Create X 4/4 white Angel creature tokens with flying.
this.getSpellAbility().addEffect(new CreateTokenEffect(new AngelToken(), new ManacostVariableValue()));
// Cycling {2}{W}
this.addAbility(new CyclingAbility(new ManaCostsImpl<>("{2}{W}")));
// When you cycle Decree of Justice, you may pay {X}. If you do, create X 1/1 white Soldier creature tokens.
Ability ability = new CycleTriggeredAbility(new DecreeOfJusticeCycleEffect(), true);
this.addAbility(ability);
@ -80,31 +78,31 @@ public class DecreeOfJustice extends CardImpl {
}
class DecreeOfJusticeCycleEffect extends OneShotEffect {
DecreeOfJusticeCycleEffect() {
super(Outcome.Benefit);
this.staticText = "you may pay {X}. If you do, create X 1/1 white Soldier creature tokens";
}
DecreeOfJusticeCycleEffect(final DecreeOfJusticeCycleEffect effect) {
super(effect);
}
@Override
public DecreeOfJusticeCycleEffect copy() {
return new DecreeOfJusticeCycleEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
ManaCosts<ManaCost> cost = new ManaCostsImpl<>("{X}");
if (player != null) {
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source);
cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false, null)) {
int X = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source);
Cost cost = new GenericManaCost(X);
if (cost.pay(source, game, source.getSourceId(), source.getControllerId(), false)) {
Token token = new SoldierToken();
token.putOntoBattlefield(costX, game, source.getSourceId(), source.getControllerId());
token.putOntoBattlefield(X, game, source.getSourceId(), source.getControllerId());
return true;
}
}
return false;

View file

@ -60,7 +60,7 @@ import mage.target.TargetPermanent;
public class DereviEmpyrialTactician extends CardImpl {
public DereviEmpyrialTactician(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{G}{W}{U}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{G}{W}{U}");
addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.BIRD);
this.subtype.add(SubType.WIZARD);
@ -74,7 +74,7 @@ public class DereviEmpyrialTactician extends CardImpl {
Ability ability = new DereviEmpyrialTacticianTriggeredAbility(new MayTapOrUntapTargetEffect());
ability.addTarget(new TargetPermanent());
this.addAbility(ability);
// {1}{G}{W}{U}: Put Derevi onto the battlefield from the command zone.
this.addAbility(new DereviEmpyrialTacticianAbility());
}
@ -132,7 +132,7 @@ class DereviEmpyrialTacticianTriggeredAbility extends TriggeredAbilityImpl {
}
}
class DereviEmpyrialTacticianAbility extends ActivatedAbilityImpl {
class DereviEmpyrialTacticianAbility extends ActivatedAbilityImpl {
public DereviEmpyrialTacticianAbility() {
super(Zone.COMMAND, new PutCommanderOnBattlefieldEffect(), new ManaCostsImpl("{1}{G}{W}{U}"));
@ -182,7 +182,7 @@ class PutCommanderOnBattlefieldEffect extends OneShotEffect {
}
Card card = game.getCard(source.getSourceId());
if (card != null) {
card.putOntoBattlefield(game, Zone.COMMAND, source.getSourceId(), source.getControllerId());
player.moveCards(card, Zone.BATTLEFIELD, source, game);
return true;
}
return false;

View file

@ -27,11 +27,13 @@
*/
package mage.cards.e;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PhaseOutAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -55,8 +57,7 @@ import mage.target.TargetPlayer;
public class Equipoise extends CardImpl {
public Equipoise(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{W}");
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
// At the beginning of your upkeep, for each land target player controls in excess of the number you control, choose a land he or she controls, then the chosen permanents phase out. Repeat this process for artifacts and creatures.
Ability ability = new BeginningOfUpkeepTriggeredAbility(new EquipoiseEffect(), TargetController.YOU, false);
@ -112,17 +113,12 @@ class EquipoiseEffect extends OneShotEffect {
int numberTargetPlayer = game.getBattlefield().count(filter, source.getSourceId(), targetPlayer.getId(), game);
int excess = numberTargetPlayer - numberController;
if (excess > 0) {
FilterPermanent filterChoose = new FilterPermanent(cardType.toString().toLowerCase() + (excess > 1 ? "s":"") +" of target player");
FilterPermanent filterChoose = new FilterPermanent(cardType.toString().toLowerCase() + (excess > 1 ? "s" : "") + " of target player");
filterChoose.add(new ControllerIdPredicate(targetPlayer.getId()));
filterChoose.add(new CardTypePredicate(cardType));
Target target = new TargetPermanent(excess, excess, filterChoose, true);
controller.chooseTarget(outcome, target, source, game);
for (UUID permanentId:target.getTargets()) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
permanent.phaseOut(game);
}
}
new PhaseOutAllEffect(target.getTargets()).apply(game, source);
}
}
}

View file

@ -32,7 +32,6 @@ import mage.abilities.Ability;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.common.SkipUntapOptionalAbility;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.DontUntapAsLongAsSourceTappedEffect;
import mage.abilities.effects.common.TapTargetEffect;
import mage.abilities.keyword.FlyingAbility;
@ -46,6 +45,7 @@ import mage.target.common.FilterCreatureAttackingYou;
import mage.target.common.TargetCreaturePermanent;
/**
*
*
* @author TheElk801
*/
@ -63,9 +63,8 @@ public class IceFloe extends CardImpl {
// You may choose not to untap Ice Floe during your untap step.
this.addAbility(new SkipUntapOptionalAbility());
// {tap}: Tap target creature without flying that's attacking you. It doesn't untap during its controller's untap step for as long as Ice Floe remains tapped.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TapTargetEffect(), new GenericManaCost(1));
ability.addCost(new TapSourceCost());
// {T}: Tap target creature without flying that's attacking you. It doesn't untap during its controller's untap step for as long as Ice Floe remains tapped.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new TapTargetEffect(), new TapSourceCost());
ability.addTarget(new TargetCreaturePermanent(filter));
ability.addEffect(new DontUntapAsLongAsSourceTappedEffect());
this.addAbility(ability);

View file

@ -0,0 +1,153 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.j;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.DealtDamageToSourceTriggeredAbility;
import mage.abilities.condition.common.HellbentCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.AbilityWord;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author jerekwilson
*/
public class JaggedPoppet extends CardImpl {
public JaggedPoppet(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{R}");
this.subtype.add(SubType.OGRE);
this.subtype.add(SubType.WARRIOR);
this.power = new MageInt(3);
this.toughness = new MageInt(4);
// Whenever Jagged Poppet is dealt damage, discard that many cards.
this.addAbility(new DealtDamageToSourceTriggeredAbility(Zone.BATTLEFIELD, new JaggedPoppetDealtDamageEffect(), false, false, true));
// Hellbent - Whenever Jagged Poppet deals combat damage to a player, if you have no cards in hand, that player discards cards equal to the damage.
Ability hellbentAbility = new ConditionalTriggeredAbility(
new DealsCombatDamageToAPlayerTriggeredAbility(new JaggedPoppetDealsDamageEffect(), false, true),
HellbentCondition.instance,
"<i>Hellbent</i> - Whenever {this} deals combat damage to a player, if you have no cards in hand, that player discards cards equal to the damage.");
hellbentAbility.setAbilityWord(AbilityWord.HELLBENT);
this.addAbility(hellbentAbility);
}
public JaggedPoppet(final JaggedPoppet card) {
super(card);
}
@Override
public JaggedPoppet copy() {
return new JaggedPoppet(this);
}
}
class JaggedPoppetDealsDamageEffect extends OneShotEffect {
public JaggedPoppetDealsDamageEffect() {
super(Outcome.Discard);
//staticText = "it deals that much damage to each creature that player controls";
}
public JaggedPoppetDealsDamageEffect(final JaggedPoppetDealsDamageEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
//According to the Balefire Dragon code, This statement gets the player that was dealt the combat damage
Player player = game.getPlayer(targetPointer.getFirst(game, source));
if (player != null) {
//Call the getValue method of the Effect class to retrieve the amount of damage
int amount = (Integer) getValue("damage");
if (amount > 0) {
//Call the player discard function discarding cards equal to damage
player.discard(amount, false, source, game);
}
return true;
}
return false;
}
@Override
public JaggedPoppetDealsDamageEffect copy() {
return new JaggedPoppetDealsDamageEffect(this);
}
}
class JaggedPoppetDealtDamageEffect extends OneShotEffect {
public JaggedPoppetDealtDamageEffect() {
super(Outcome.Discard);
staticText = "discard that many cards";
}
public JaggedPoppetDealtDamageEffect(final JaggedPoppetDealtDamageEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
//According to the Firedrinker Satyr code, This statement gets the player that controls Jagged Poppet
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
//Call the getValue method of the Effect class to retrieve the amount of damage
int amount = (Integer) getValue("damage");
if (amount > 0) {
//Call the player discard function discarding cards equal to damage
player.discard(amount, false, source, game);
}
return true;
}
return false;
}
@Override
public JaggedPoppetDealtDamageEffect copy() {
return new JaggedPoppetDealtDamageEffect(this);
}
}

View file

@ -30,13 +30,14 @@ package mage.cards.n;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.InvertCondition;
import mage.abilities.condition.common.TargetAttackedThisTurnCondition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.decorator.ConditionalActivatedAbility;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.common.DestroyTargetAtBeginningOfNextEndStepEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DestroyTargetEffect;
import mage.abilities.effects.common.combat.AttacksIfAbleTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -49,6 +50,7 @@ import mage.filter.predicate.permanent.ControllerPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import mage.watchers.common.AttackedThisTurnWatcher;
/**
@ -56,30 +58,33 @@ import mage.watchers.common.AttackedThisTurnWatcher;
* @author MTGfan
*/
public class NettlingImp extends CardImpl {
final static FilterCreaturePermanent filter = new FilterCreaturePermanent("non-Wall");
static {
filter.add(Predicates.not(new SubtypePredicate(SubType.WALL)));
filter.add(new ControlledFromStartOfControllerTurnPredicate());
filter.add(new ControlledFromStartOfControllerTurnPredicate());
filter.add(new ControllerPredicate(TargetController.ACTIVE));
filter.setMessage("non-Wall creature the active player has controlled continuously since the beginning of the turn.");
}
public NettlingImp(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}");
this.subtype.add(SubType.IMP);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// {tap}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. Activate this ability only during an opponent's turn, before attackers are declared.
Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new AttacksIfAbleTargetEffect(Duration.EndOfTurn), new TapSourceCost(), new NettlingImpTurnCondition(), "{T}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. Activate this ability only during an opponent's turn, before attackers are declared.");
ability.addEffect(new ConditionalOneShotEffect(new DestroyTargetAtBeginningOfNextEndStepEffect(), new InvertCondition(TargetAttackedThisTurnCondition.instance)));
// {T}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. Activate this ability only during an opponent's turn, before attackers are declared.
Ability ability = new ConditionalActivatedAbility(Zone.BATTLEFIELD, new AttacksIfAbleTargetEffect(Duration.EndOfTurn),
new TapSourceCost(), new NettlingImpTurnCondition(),
"{T}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. "
+ "That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. "
+ "Activate this ability only during an opponent's turn, before attackers are declared.");
ability.addEffect(new NettlingImpDelayedDestroyEffect());
ability.addTarget(new TargetCreaturePermanent(filter));
this.addAbility(ability, new AttackedThisTurnWatcher());
}
public NettlingImp(final NettlingImp card) {
@ -96,7 +101,7 @@ class NettlingImpTurnCondition implements Condition {
@Override
public boolean apply(Game game, Ability source) {
Player activePlayer = game.getPlayer(game.getActivePlayerId());
Player activePlayer = game.getPlayer(game.getActivePlayerId());
return activePlayer != null && activePlayer.hasOpponent(source.getControllerId(), game) && game.getPhase().getStep().getType().getIndex() < 5;
}
@ -105,3 +110,32 @@ class NettlingImpTurnCondition implements Condition {
return "";
}
}
class NettlingImpDelayedDestroyEffect extends OneShotEffect {
public NettlingImpDelayedDestroyEffect() {
super(Outcome.Detriment);
this.staticText = "If it doesn't, destroy it at the beginning of the next end step";
}
public NettlingImpDelayedDestroyEffect(final NettlingImpDelayedDestroyEffect effect) {
super(effect);
}
@Override
public NettlingImpDelayedDestroyEffect copy() {
return new NettlingImpDelayedDestroyEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
DestroyTargetEffect effect = new DestroyTargetEffect();
effect.setTargetPointer(new FixedTarget(source.getFirstTarget()));
AtTheBeginOfNextEndStepDelayedTriggeredAbility delayedAbility
= new AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone.ALL, effect, TargetController.ANY, new InvertCondition(TargetAttackedThisTurnCondition.instance));
delayedAbility.getDuration();
delayedAbility.getTargets().addAll(source.getTargets());
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;
}
}

View file

@ -0,0 +1,172 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.r;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.BasicManaEffect;
import mage.abilities.effects.common.DoUnlessAnyPlayerPaysManaEffect;
import mage.abilities.effects.common.ManaEffect;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.ChoiceColor;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.game.Game;
import mage.players.Player;
/**
*
* @author jerekwilson
*/
public class RhysticCave extends CardImpl {
public RhysticCave(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.LAND}, "");
// {T}: Choose a color. Add one mana of that color to your mana pool unless any player pays {1}. Activate this ability only any time you could cast an instant.
this.addAbility(new RhysticCaveManaAbility());
}
public RhysticCave(final RhysticCave card) {
super(card);
}
@Override
public RhysticCave copy() {
return new RhysticCave(this);
}
}
class RhysticCaveManaAbility extends ActivatedManaAbilityImpl {
public RhysticCaveManaAbility() {
super(Zone.BATTLEFIELD, new DoUnlessAnyPlayerPaysManaEffect(new RhysticCaveManaEffect(), new GenericManaCost(1), "Pay {1} to prevent mana adding from {this}."), new TapSourceCost());
this.netMana.add(new Mana(0, 0, 0, 0, 0, 0, 1, 0));
this.setUndoPossible(false);
}
public RhysticCaveManaAbility(Zone zone, Mana mana, Cost cost) {
super(zone, new BasicManaEffect(mana), cost);
}
public RhysticCaveManaAbility(final RhysticCaveManaAbility ability) {
super(ability);
}
@Override
public boolean canActivate(UUID playerId, Game game) {
Player player = game.getPlayer(playerId);
if (player != null && !player.isInPayManaMode()) {
return super.canActivate(playerId, game);
}
return false;
}
@Override
public RhysticCaveManaAbility copy() {
return new RhysticCaveManaAbility(this);
}
@Override
public String getRule() {
return super.getRule() + " Activate this ability only any time you could cast an instant.";
}
}
class RhysticCaveManaEffect extends ManaEffect {
private final Mana chosenMana;
public RhysticCaveManaEffect() {
super();
chosenMana = new Mana();
this.staticText = "Choose a color. Add one mana of that color to your mana pool ";
}
public RhysticCaveManaEffect(final RhysticCaveManaEffect effect) {
super(effect);
this.chosenMana = effect.chosenMana.copy();
}
@Override
public Mana getMana(Game game, Ability source) {
return null;
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject mageObject = game.getPermanentOrLKIBattlefield(source.getSourceId()); //get obj reference to Rhystic Cave
if (controller != null) {
if (mageObject != null) {
ChoiceColor choice = new ChoiceColor(true);
controller.choose(outcome, choice, game);
if (choice.getColor() != null) {
String color = choice.getColor().toString();
switch (color) {
case "R":
chosenMana.setRed(1);
break;
case "U":
chosenMana.setBlue(1);
break;
case "W":
chosenMana.setWhite(1);
break;
case "B":
chosenMana.setBlack(1);
break;
case "G":
chosenMana.setGreen(1);
break;
}
}
checkToFirePossibleEvents(chosenMana, game, source);
controller.getManaPool().addMana(chosenMana, game, source);
return true;
}
}
return false;
}
@Override
public Effect copy() {
return new RhysticCaveManaEffect(this);
}
}

View file

@ -0,0 +1,199 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.VariableCostImpl;
import mage.abilities.costs.common.RemoveCountersSourceCost;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.ManacostVariableValue;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreatureOrPlayer;
/**
*
* @author jerekwilson
*/
public class TalonOfPain extends CardImpl {
public TalonOfPain(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
/*
* Whenever a source you control other than Talon of Pain deals damage to an opponent,
* put a charge counter on Talon of Pain.
*/
this.addAbility(new TalonOfPainTriggeredAbility());
// {X}, {T}, Remove X charge counters from Talon of Pain: Talon of Pain deals X damage to target creature or player.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DamageTargetEffect(new ManacostVariableValue()), new ManaCostsImpl("{X}"));
ability.addCost(new TapSourceCost());
ability.addCost(new TalonOfPainRemoveVariableCountersSourceCost(CounterType.CHARGE.createInstance()));
ability.addTarget(new TargetCreatureOrPlayer());
this.addAbility(ability);
}
public TalonOfPain(final TalonOfPain card) {
super(card);
}
@Override
public TalonOfPain copy() {
return new TalonOfPain(this);
}
private class TalonOfPainTriggeredAbility extends TriggeredAbilityImpl {
public TalonOfPainTriggeredAbility() {
super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.CHARGE.createInstance()));
}
public TalonOfPainTriggeredAbility(final TalonOfPainTriggeredAbility ability) {
super(ability);
}
@Override
public TalonOfPainTriggeredAbility copy() {
return new TalonOfPainTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DAMAGED_PLAYER;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
// to another player
Player controller = game.getPlayer(this.getControllerId());
if (controller == null) {
return false;
}
if (controller.hasOpponent(event.getTargetId(), game)) {
// a source you control other than Talon of Pain
UUID sourceControllerId = game.getControllerId(event.getSourceId());
if (sourceControllerId != null
&& sourceControllerId.equals(this.getControllerId())
&& this.getSourceId() != event.getSourceId()) {
// return true so the effect will fire and a charge counter will be added
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever a source you control other than {this} deals damage to an opponent, " + super.getRule();
}
}
}
class TalonOfPainRemoveVariableCountersSourceCost extends VariableCostImpl {
protected int minimalCountersToPay = 0;
private String counterName;
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter) {
this(counter, 0);
}
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter, String text) {
this(counter, 0, text);
}
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter, int minimalCountersToPay) {
this(counter, minimalCountersToPay, "");
}
public TalonOfPainRemoveVariableCountersSourceCost(Counter counter, int minimalCountersToPay, String text) {
super(counter.getName() + " counters to remove");
this.minimalCountersToPay = minimalCountersToPay;
this.counterName = counter.getName();
if (text == null || text.isEmpty()) {
this.text = "Remove X " + counterName + " counters from {this}";
} else {
this.text = text;
}
}
public TalonOfPainRemoveVariableCountersSourceCost(final TalonOfPainRemoveVariableCountersSourceCost cost) {
super(cost);
this.minimalCountersToPay = cost.minimalCountersToPay;
this.counterName = cost.counterName;
}
@Override
public TalonOfPainRemoveVariableCountersSourceCost copy() {
return new TalonOfPainRemoveVariableCountersSourceCost(this);
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
return new RemoveCountersSourceCost(new Counter(counterName, xValue));
}
@Override
public int getMinValue(Ability source, Game game) {
return minimalCountersToPay;
}
@Override
public int getMaxValue(Ability source, Game game) {
int maxValue = 0;
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent != null) {
maxValue = permanent.getCounters(game).getCount(counterName);
}
return maxValue;
}
@Override
public int announceXValue(Ability source, Game game) {
return source.getManaCostsToPay().getX();
}
}

View file

@ -27,11 +27,14 @@
*/
package mage.cards.t;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PhaseOutAllEffect;
import mage.abilities.keyword.PhasingAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
@ -41,6 +44,7 @@ import mage.constants.SubType;
import mage.constants.Outcome;
import mage.constants.SuperType;
import mage.constants.TargetController;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledLandPermanent;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -53,7 +57,7 @@ import mage.players.Player;
public class Taniwha extends CardImpl {
public Taniwha(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{3}{U}{U}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}");
addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SERPENT);
this.power = new MageInt(7);
@ -61,10 +65,10 @@ public class Taniwha extends CardImpl {
// Trample
this.addAbility(TrampleAbility.getInstance());
// Phasing
this.addAbility(PhasingAbility.getInstance());
// At the beginning of your upkeep, all lands you control phase out.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(new TaniwhaEffect(), TargetController.YOU, false));
}
@ -99,10 +103,11 @@ class TaniwhaEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
List<UUID> permIds = new ArrayList<>();
for (Permanent permanent : game.getBattlefield().getActivePermanents(new FilterControlledLandPermanent(), controller.getId(), game)) {
permanent.phaseOut(game);
permIds.add(permanent.getId());
}
return true;
return new PhaseOutAllEffect(permIds).apply(game, source);
}
return false;
}

View file

@ -27,11 +27,14 @@
*/
package mage.cards.t;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.PhaseOutAllEffect;
import mage.abilities.effects.common.continuous.GainAbilityControllerEffect;
import mage.abilities.effects.common.continuous.LifeTotalCantChangeControllerEffect;
import mage.abilities.keyword.ProtectionAbility;
@ -170,10 +173,11 @@ class TeferisProtectionPhaseOutEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
List<UUID> permIds = new ArrayList<>();
for (Permanent permanent : game.getBattlefield().getActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT, controller.getId(), game)) {
permanent.phaseOut(game);
permIds.add(permanent.getId());
}
return true;
return new PhaseOutAllEffect(permIds).apply(game, source);
}
return false;
}

View file

@ -27,6 +27,7 @@
*/
package mage.cards.t;
import java.util.ArrayList;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
@ -45,8 +46,11 @@ import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.abilities.effects.common.PhaseOutAllEffect;
import mage.filter.common.FilterControlledLandPermanent;
/**
*
@ -55,7 +59,7 @@ import java.util.UUID;
public class TeferisRealm extends CardImpl {
public TeferisRealm(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{1}{U}{U}");
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{U}{U}");
addSuperType(SuperType.WORLD);
// At the beginning of each player's upkeep, that player chooses artifact, creature, land, or non-Aura enchantment. All nontoken permanents of that type phase out.
@ -135,10 +139,11 @@ class TeferisRealmEffect extends OneShotEffect {
return false;
}
game.informPlayers(player.getLogName() + " chooses " + choosenType + "s to phase out");
List<UUID> permIds = new ArrayList<>();
for (Permanent permanent : game.getBattlefield().getActivePermanents(filter, controller.getId(), game)) {
permanent.phaseOut(game);
permIds.add(permanent.getId());
}
return true;
return new PhaseOutAllEffect(permIds).apply(game, source);
}
return false;
}

View file

@ -61,7 +61,7 @@ public class WatertrapWeaver extends CardImpl {
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.
// When Watertrap Weaver enters the battlefield, tap target creature an opponent controls. That creature doesn't untap during its controller's next untap step.
EntersBattlefieldTriggeredAbility ability = new EntersBattlefieldTriggeredAbility(new TapTargetEffect());
ability.addEffect(new DontUntapInControllersNextUntapStepTargetEffect("that creature"));
ability.addTarget(new TargetCreaturePermanent(filter));

View file

@ -39,11 +39,11 @@ import mage.abilities.keyword.SwampwalkAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -56,15 +56,15 @@ import mage.target.common.TargetOpponent;
public class WitchEngine extends CardImpl {
public WitchEngine(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{B}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}");
this.subtype.add(SubType.HORROR);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Swampwalk
this.addAbility(new SwampwalkAbility());
// {tap}: Add {B}{B}{B}{B} to your mana pool. Target opponent gains control of Witch Engine.
// {T}: Add {B}{B}{B}{B} to your mana pool. Target opponent gains control of Witch Engine. (Activate this ability only any time you could cast an instant.)
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new BasicManaEffect(Mana.BlackMana(4)), new TapSourceCost());
ability.addEffect(new WitchEngineEffect());
ability.addTarget(new TargetOpponent());

View file

@ -157,6 +157,7 @@ public class Darksteel extends ExpansionSet {
cards.add(new SetCardInfo("Sword of Fire and Ice", 148, Rarity.RARE, mage.cards.s.SwordOfFireAndIce.class));
cards.add(new SetCardInfo("Sword of Light and Shadow", 149, Rarity.RARE, mage.cards.s.SwordOfLightAndShadow.class));
cards.add(new SetCardInfo("Synod Artificer", 34, Rarity.RARE, mage.cards.s.SynodArtificer.class));
cards.add(new SetCardInfo("Talon of Pain", 150, Rarity.UNCOMMON, mage.cards.t.TalonOfPain.class));
cards.add(new SetCardInfo("Tangle Golem", 151, Rarity.COMMON, mage.cards.t.TangleGolem.class));
cards.add(new SetCardInfo("Tangle Spider", 85, Rarity.COMMON, mage.cards.t.TangleSpider.class));
cards.add(new SetCardInfo("Tanglewalker", 86, Rarity.UNCOMMON, mage.cards.t.Tanglewalker.class));

View file

@ -71,7 +71,7 @@ public class Dissension extends ExpansionSet {
cards.add(new SetCardInfo("Blood Crypt", 171, Rarity.RARE, mage.cards.b.BloodCrypt.class));
cards.add(new SetCardInfo("Bond of Agony", 38, Rarity.UNCOMMON, mage.cards.b.BondOfAgony.class));
cards.add(new SetCardInfo("Bound // Determined", 149, Rarity.RARE, mage.cards.b.BoundDetermined.class));
cards.add(new SetCardInfo("Brace for Impact", 5, Rarity.UNCOMMON, mage.cards.b.BraceForImpact.class));
cards.add(new SetCardInfo("Brace for Impact", 5, Rarity.UNCOMMON, mage.cards.b.BraceForImpact.class));
cards.add(new SetCardInfo("Brain Pry", 39, Rarity.UNCOMMON, mage.cards.b.BrainPry.class));
cards.add(new SetCardInfo("Breeding Pool", 172, Rarity.RARE, mage.cards.b.BreedingPool.class));
cards.add(new SetCardInfo("Bronze Bombshell", 160, Rarity.RARE, mage.cards.b.BronzeBombshell.class));
@ -118,6 +118,7 @@ public class Dissension extends ExpansionSet {
cards.add(new SetCardInfo("Indrik Stomphowler", 86, Rarity.UNCOMMON, mage.cards.i.IndrikStomphowler.class));
cards.add(new SetCardInfo("Infernal Tutor", 46, Rarity.RARE, mage.cards.i.InfernalTutor.class));
cards.add(new SetCardInfo("Isperia the Inscrutable", 114, Rarity.RARE, mage.cards.i.IsperiaTheInscrutable.class));
cards.add(new SetCardInfo("Jagged Poppet", 115, Rarity.UNCOMMON, mage.cards.j.JaggedPoppet.class));
cards.add(new SetCardInfo("Kill-Suit Cultist", 65, Rarity.COMMON, mage.cards.k.KillSuitCultist.class));
cards.add(new SetCardInfo("Kindle the Carnage", 66, Rarity.UNCOMMON, mage.cards.k.KindleTheCarnage.class));
cards.add(new SetCardInfo("Leafdrake Roost", 116, Rarity.UNCOMMON, mage.cards.l.LeafdrakeRoost.class));
@ -199,7 +200,7 @@ public class Dissension extends ExpansionSet {
cards.add(new SetCardInfo("Trial // Error", 158, Rarity.UNCOMMON, mage.cards.t.TrialError.class));
cards.add(new SetCardInfo("Trygon Predator", 133, Rarity.UNCOMMON, mage.cards.t.TrygonPredator.class));
cards.add(new SetCardInfo("Twinstrike", 134, Rarity.UNCOMMON, mage.cards.t.Twinstrike.class));
cards.add(new SetCardInfo("Unliving Psychopath", 56, Rarity.RARE, mage.cards.u.UnlivingPsychopath.class));
cards.add(new SetCardInfo("Unliving Psychopath", 56, Rarity.RARE, mage.cards.u.UnlivingPsychopath.class));
cards.add(new SetCardInfo("Utopia Sprawl", 99, Rarity.COMMON, mage.cards.u.UtopiaSprawl.class));
cards.add(new SetCardInfo("Utvara Scalper", 76, Rarity.COMMON, mage.cards.u.UtvaraScalper.class));
cards.add(new SetCardInfo("Valor Made Real", 20, Rarity.COMMON, mage.cards.v.ValorMadeReal.class));

View file

@ -68,6 +68,7 @@ public class Mirrodin extends ExpansionSet {
cards.add(new SetCardInfo("Crystal Shard", 159, Rarity.UNCOMMON, mage.cards.c.CrystalShard.class));
cards.add(new SetCardInfo("Culling Scales", 160, Rarity.RARE, mage.cards.c.CullingScales.class));
cards.add(new SetCardInfo("Damping Matrix", 161, Rarity.RARE, mage.cards.d.DampingMatrix.class));
cards.add(new SetCardInfo("Dead-Iron Sledge", 162, Rarity.UNCOMMON, mage.cards.d.DeadIronSledge.class));
cards.add(new SetCardInfo("Deconstruct", 118, Rarity.COMMON, mage.cards.d.Deconstruct.class));
cards.add(new SetCardInfo("Detonate", 88, Rarity.UNCOMMON, mage.cards.d.Detonate.class));
cards.add(new SetCardInfo("Disarm", 32, Rarity.COMMON, mage.cards.d.Disarm.class));

View file

@ -130,6 +130,7 @@ public class Prophecy extends ExpansionSet {
cards.add(new SetCardInfo("Rebel Informer", 75, Rarity.RARE, mage.cards.r.RebelInformer.class));
cards.add(new SetCardInfo("Rethink", 42, Rarity.COMMON, mage.cards.r.Rethink.class));
cards.add(new SetCardInfo("Reveille Squad", 18, Rarity.UNCOMMON, mage.cards.r.ReveilleSquad.class));
cards.add(new SetCardInfo("Rhystic Cave", 142, Rarity.UNCOMMON, mage.cards.r.RhysticCave.class));
cards.add(new SetCardInfo("Rhystic Circle", 19, Rarity.COMMON, mage.cards.r.RhysticCircle.class));
cards.add(new SetCardInfo("Rhystic Study", 45, Rarity.COMMON, mage.cards.r.RhysticStudy.class));
cards.add(new SetCardInfo("Rhystic Tutor", 77, Rarity.RARE, mage.cards.r.RhysticTutor.class));

View file

@ -0,0 +1,63 @@
/*
* 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.triggers.delayed;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
*
* @author LevelX2
*/
public class NettlingImplTest extends CardTestPlayerBase {
@Test
public void testForcedDidAttack() {
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
// {T}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. Activate this ability only during an opponent's turn, before attackers are declared.
addCard(Zone.BATTLEFIELD, playerA, "Nettling Imp", 1);
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Choose", "Silvercoat Lion");
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Nettling Imp", 1);
assertPermanentCount(playerB, "Silvercoat Lion", 1);
assertLife(playerA, 18);
assertLife(playerB, 20);
}
@Test
public void testForcedDidNotAttack() {
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// {T}: Choose target non-Wall creature the active player has controlled continuously since the beginning of the turn. That creature attacks this turn if able. If it doesn't, destroy it at the beginning of the next end step. Activate this ability only during an opponent's turn, before attackers are declared.
addCard(Zone.BATTLEFIELD, playerA, "Nettling Imp", 1);
// Tap target creature.
// Draw a card.
addCard(Zone.HAND, playerA, "Pressure Point", 1); // Instant {1}{W}
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Choose", "Silvercoat Lion");
castSpell(2, PhaseStep.BEGIN_COMBAT, playerA, "Pressure Point", "Silvercoat Lion");
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Nettling Imp", 1);
assertGraveyardCount(playerA, "Pressure Point", 1);
assertPermanentCount(playerB, "Silvercoat Lion", 0);
assertHandCount(playerA, 2);
assertLife(playerA, 20);
assertLife(playerB, 20);
}
}

View file

@ -1,373 +1,373 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package org.mage.test.multiplayer;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.FreeForAll;
import mage.game.Game;
import mage.game.GameException;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
/**
*
* @author LevelX2
*/
public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
@Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2);
// 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;
}
/**
* Tests Enchantment to control other permanent
*/
@Test
public void TestControlledByEnchantment() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Enchant creature
// You control enchanted creature.
addCard(Zone.HAND, playerA, "Control Magic");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0);
assertGraveyardCount(playerA, "Control Magic", 1);
}
/**
* Tests Sorcery to control other players permanent
*/
@Test
public void TestControlledBySorcery() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Exchange control of target artifact or creature and another target permanent that shares one of those types with it.
// (This effect lasts indefinitely.)
addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery
addCard(Zone.BATTLEFIELD, playerA, "Wall of Air");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertGraveyardCount(playerA, "Legerdemain", 1);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left
assertPermanentCount(playerB, "Wall of Air", 0);
assertGraveyardCount(playerA, "Wall of Air", 0);
assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A
}
/**
* Tests Instant to control other permanent
*/
@Test
public void TestOtherPlayerControllsCreature() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn.
addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertGraveyardCount(playerA, "Blind with Anger", 1);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left
assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A
}
/**
* Xmage throws an error involving an emblem unable to find the initial
* source if it has a proc. To reproduce, a Planeswalker was taken from an
* original player's control, such as using Scrambleverse to shuffle Jace,
* Unraveler of Secrets, to a second player and then the second player uses
* Jace's ability to create an emblem ("Whenever an opponent casts his or
* her first spell each turn, counter that spell."). Then the original
* player concedes the game and removes the Planeswalker. Once it becomes an
* opponent of the original player's turn and that opponent plays a spell,
* Xmage throws an error and rollsback the turn.
*
* I don't have the actual error report on my due to negligence, but what I
* can recollect is that the error message was along the lines of "The
* emblem cannot find the original source. This turn will be rolled back".
* This error message will always appear when an opponent tries to play a
* spell. Player order: A -> D -> C -> B
*/
@Test
public void TestOtherPlayerPlaneswalkerCreatedEmblem() {
// +1: Scry 1, then draw a card.
// -2: Return target creature to its owner's hand.
// -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell."
addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets");
addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8);
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
// Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.)
// You control enchanted permanent.
addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura
addCard(Zone.BATTLEFIELD, playerC, "Plains", 2);
addCard(Zone.HAND, playerC, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion");
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
setStopAt(5, PhaseStep.END_TURN);
execute();
assertLife(playerB, 0);
assertPermanentCount(playerB, 0);
assertGraveyardCount(playerA, "Confiscate", 1);
assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game
assertEmblemCount(playerA, 1);
assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1
assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell
}
/**
* Situation: I attacked an opponent with some creatures with True
* Conviction in play. There were multiple "deals combat damage to a
* player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al),
* then the opponent lost the game during the first strike combat
* damage-step . In the second combat damage step the triggers went on the
* stack again, although there was no player being dealt damage (multiplayer
* game, so the game wasn't over yet). I don't think these abilities should
* trigger again here.
*/
@Test
public void TestPlayerDiesDuringFirstStrikeDamageStep() {
// Creatures you control have double strike and lifelink.
addCard(Zone.BATTLEFIELD, playerD, "True Conviction");
// Whenever a creature deals combat damage to one of your opponents, its controller may draw a card.
addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest");
addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1
attack(2, playerD, "Dross Crocodile", playerC);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertLife(playerC, -3);
assertLife(playerD, 7);
assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition
assertPermanentCount(playerC, 0);
}
/**
* I've encountered a case today where someone conceded on their turn. The
* remaining phases were went through as normal, but my Luminarch Ascension
* did not trigger during the end step.
*/
// Player order: A -> D -> C -> B
@Test
public void TestTurnEndTrigger() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerC, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1);
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertGraveyardCount(playerC, "Lightning Bolt", 1);
assertLife(playerD, -1);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
}
@Test
public void TestTurnEndTriggerAfterConcede() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
concede(2, PhaseStep.BEGIN_COMBAT, playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertLife(playerD, 2);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
}
/**
* Pithing Needle keeps the named card's abilities disabled even after the
* player controlling the Needle loses the game.
*
* I saw it happen during a Commander game. A player cast Pithing Needle
* targeting my Proteus Staff. After I killed him, I still couldn't activate
* the Staff.
*/
@Test
public void TestPithingNeedle() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
// As Pithing Needle enters the battlefield, name a card.
// Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.
addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1}
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1);
addCard(Zone.BATTLEFIELD, playerD, "Island", 3);
// {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the
// top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the
// rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery.
addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1);
addCard(Zone.LIBRARY, playerD, "Storm Crow", 2);
addCard(Zone.BATTLEFIELD, playerC, "Island", 3);
addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1);
addCard(Zone.LIBRARY, playerC, "Wind Drake", 2);
addCard(Zone.BATTLEFIELD, playerB, "Island", 3);
addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1);
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle");
setChoice(playerA, "Proteus Staff");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game
setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, 0);
assertLife(playerA, 2);
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame());
Permanent staffPlayerD = getPermanent("Proteus Staff", playerD);
Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped());
assertPermanentCount(playerD, "Eager Cadet", 0);
assertPermanentCount(playerD, "Storm Crow", 1);
Permanent staffPlayerC = getPermanent("Proteus Staff", playerC);
Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped());
assertPermanentCount(playerC, "Wall of Air", 0);
assertPermanentCount(playerC, "Wind Drake", 1);
Permanent staffPlayerB = getPermanent("Proteus Staff", playerB);
Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped());
}
}
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package org.mage.test.multiplayer;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.FreeForAll;
import mage.game.Game;
import mage.game.GameException;
import mage.game.permanent.Permanent;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
/**
*
* @author LevelX2
*/
public class PlayerLeftGameRange1Test extends CardTestMultiPlayerBase {
@Override
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
// Start Life = 2
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ONE, 0, 2);
// 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;
}
/**
* Tests Enchantment to control other permanent
*/
@Test
public void TestControlledByEnchantment() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Enchant creature
// You control enchanted creature.
addCard(Zone.HAND, playerA, "Control Magic");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Control Magic", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0);
assertGraveyardCount(playerA, "Control Magic", 1);
}
/**
* Tests Sorcery to control other players permanent
*/
@Test
public void TestControlledBySorcery() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
// Exchange control of target artifact or creature and another target permanent that shares one of those types with it.
// (This effect lasts indefinitely.)
addCard(Zone.HAND, playerA, "Legerdemain"); // Sorcery
addCard(Zone.BATTLEFIELD, playerA, "Wall of Air");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Legerdemain", "Rootwater Commando^Wall of Air");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertGraveyardCount(playerA, "Legerdemain", 1);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // removed from game because player B left
assertPermanentCount(playerB, "Wall of Air", 0);
assertGraveyardCount(playerA, "Wall of Air", 0);
assertPermanentCount(playerA, "Wall of Air", 1); // Returned back to player A
}
/**
* Tests Instant to control other permanent
*/
@Test
public void TestOtherPlayerControllsCreature() {
addCard(Zone.BATTLEFIELD, playerB, "Rootwater Commando");
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4);
// Untap target nonlegendary creature and gain control of it until end of turn. That creature gains haste until end of turn.
addCard(Zone.HAND, playerA, "Blind with Anger"); // Instant
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerB, 0);
assertGraveyardCount(playerA, "Blind with Anger", 1);
assertPermanentCount(playerB, 0);
assertPermanentCount(playerA, "Rootwater Commando", 0); // Removed from game because player C left
assertPermanentCount(playerA, "Rootwater Commando", 0); // Returned back to player A
}
/**
* Xmage throws an error involving an emblem unable to find the initial
* source if it has a proc. To reproduce, a Planeswalker was taken from an
* original player's control, such as using Scrambleverse to shuffle Jace,
* Unraveler of Secrets, to a second player and then the second player uses
* Jace's ability to create an emblem ("Whenever an opponent casts his or
* her first spell each turn, counter that spell."). Then the original
* player concedes the game and removes the Planeswalker. Once it becomes an
* opponent of the original player's turn and that opponent plays a spell,
* Xmage throws an error and rollsback the turn.
*
* I don't have the actual error report on my due to negligence, but what I
* can recollect is that the error message was along the lines of "The
* emblem cannot find the original source. This turn will be rolled back".
* This error message will always appear when an opponent tries to play a
* spell. Player order: A -> D -> C -> B
*/
@Test
public void TestOtherPlayerPlaneswalkerCreatedEmblem() {
// +1: Scry 1, then draw a card.
// -2: Return target creature to its owner's hand.
// -8: You get an emblem with "Whenever an opponent casts his or her first spell each turn, counter that spell."
addCard(Zone.BATTLEFIELD, playerB, "Jace, Unraveler of Secrets");
addCounters(1, PhaseStep.DRAW, playerB, "Jace, Unraveler of Secrets", CounterType.LOYALTY, 8);
addCard(Zone.BATTLEFIELD, playerA, "Island", 6);
// Enchant permanent (Target a permanent as you cast this. This card enters the battlefield attached to that permanent.)
// You control enchanted permanent.
addCard(Zone.HAND, playerA, "Confiscate"); // Enchantment Aura
addCard(Zone.BATTLEFIELD, playerC, "Plains", 2);
addCard(Zone.HAND, playerC, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerC, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Confiscate", "Jace, Unraveler of Secrets");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-8: You get an emblem with");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Blind with Anger", "Rootwater Commando");
attack(3, playerC, "Silvercoat Lion", playerB);
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Silvercoat Lion");
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
setStopAt(5, PhaseStep.END_TURN);
execute();
assertLife(playerB, 0);
assertPermanentCount(playerB, 0);
assertGraveyardCount(playerA, "Confiscate", 1);
assertPermanentCount(playerA, "Jace, Unraveler of Secrets", 0); // Removed from game because player C left the game
assertEmblemCount(playerA, 1);
assertPermanentCount(playerC, "Silvercoat Lion", 2); // Emblem does not work yet on player C, because range 1
assertGraveyardCount(playerD, "Silvercoat Lion", 1); // Emblem should counter the spell
}
/**
* Situation: I attacked an opponent with some creatures with True
* Conviction in play. There were multiple "deals combat damage to a
* player"-triggers (Edric, Spymaster of Trest, Daxos of Meletis et al),
* then the opponent lost the game during the first strike combat
* damage-step . In the second combat damage step the triggers went on the
* stack again, although there was no player being dealt damage (multiplayer
* game, so the game wasn't over yet). I don't think these abilities should
* trigger again here.
*/
@Test
public void TestPlayerDiesDuringFirstStrikeDamageStep() {
// Creatures you control have double strike and lifelink.
addCard(Zone.BATTLEFIELD, playerD, "True Conviction");
// Whenever a creature deals combat damage to one of your opponents, its controller may draw a card.
addCard(Zone.BATTLEFIELD, playerD, "Edric, Spymaster of Trest");
addCard(Zone.BATTLEFIELD, playerD, "Dross Crocodile", 8); // Creature 5/1
attack(2, playerD, "Dross Crocodile", playerC);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertLife(playerC, -3);
assertLife(playerD, 7);
assertHandCount(playerD, 2); // 1 (normal draw) + 1 from True Convition
assertPermanentCount(playerC, 0);
}
/**
* I've encountered a case today where someone conceded on their turn. The
* remaining phases were went through as normal, but my Luminarch Ascension
* did not trigger during the end step.
*/
// Player order: A -> D -> C -> B
@Test
public void TestTurnEndTrigger() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerC, "Lightning Bolt");
addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1);
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
castSpell(2, PhaseStep.BEGIN_COMBAT, playerC, "Lightning Bolt", playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertGraveyardCount(playerC, "Lightning Bolt", 1);
assertLife(playerD, -1);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
}
@Test
public void TestTurnEndTriggerAfterConcede() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
// At the beginning of each opponent's end step, if you didn't lose life this turn, you may put a quest counter on Luminarch Ascension.
// {1}{W}: Create a 4/4 white Angel creature token with flying. Activate this ability only if Luminarch Ascension has four or more quest counters on it..
addCard(Zone.HAND, playerA, "Luminarch Ascension"); // Enchantment {1}{W}
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Luminarch Ascension");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
concede(2, PhaseStep.BEGIN_COMBAT, playerD);
setStopAt(3, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Luminarch Ascension", 1);
assertLife(playerD, 2);
Assert.assertFalse("Player D is no longer in the game", playerD.isInGame());
assertCounterCount(playerA, "Luminarch Ascension", CounterType.QUEST, 1); // 1 from turn 2
}
/**
* Pithing Needle keeps the named card's abilities disabled even after the
* player controlling the Needle loses the game.
*
* I saw it happen during a Commander game. A player cast Pithing Needle
* targeting my Proteus Staff. After I killed him, I still couldn't activate
* the Staff.
*/
@Test
public void TestPithingNeedle() {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
// As Pithing Needle enters the battlefield, name a card.
// Activated abilities of sources with the chosen name can't be activated unless they're mana abilities.
addCard(Zone.HAND, playerA, "Pithing Needle"); // Artifact {1}
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion", 1);
addCard(Zone.LIBRARY, playerA, "Pillarfield Ox", 1);
addCard(Zone.BATTLEFIELD, playerD, "Island", 3);
// {2}{U}, {T}: Put target creature on the bottom of its owner's library. That creature's controller reveals cards from the
// top of his or her library until he or she reveals a creature card. The player puts that card onto the battlefield and the
// rest on the bottom of his or her library in any order. Activate this ability only any time you could cast a sorcery.
addCard(Zone.BATTLEFIELD, playerD, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerD, "Eager Cadet", 1);
addCard(Zone.LIBRARY, playerD, "Storm Crow", 2);
addCard(Zone.BATTLEFIELD, playerC, "Island", 3);
addCard(Zone.BATTLEFIELD, playerC, "Proteus Staff", 1);
addCard(Zone.BATTLEFIELD, playerC, "Wall of Air", 1);
addCard(Zone.LIBRARY, playerC, "Wind Drake", 2);
addCard(Zone.BATTLEFIELD, playerB, "Island", 3);
addCard(Zone.BATTLEFIELD, playerB, "Proteus Staff", 1);
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Pithing Needle");
setChoice(playerA, "Proteus Staff");
activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerD, "{2}{U}", "Silvercoat Lion"); // not allowed
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerC, "{2}{U}", "Eager Cadet"); // allowed because Needle out of range
concede(3, PhaseStep.POSTCOMBAT_MAIN, playerA);
activateAbility(4, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}{U}", "Wall of Air"); // allowed because Needle lost game
setStopAt(4, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertLife(playerA, 2);
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame());
assertPermanentCount(playerA, 0);
Permanent staffPlayerD = getPermanent("Proteus Staff", playerD);
Assert.assertFalse("Staff of player D could not be used", staffPlayerD.isTapped());
assertPermanentCount(playerD, "Eager Cadet", 0);
assertPermanentCount(playerD, "Storm Crow", 1);
Permanent staffPlayerC = getPermanent("Proteus Staff", playerC);
Assert.assertTrue("Staff of player C could be used", staffPlayerC.isTapped());
assertPermanentCount(playerC, "Wall of Air", 0);
assertPermanentCount(playerC, "Wind Drake", 1);
Permanent staffPlayerB = getPermanent("Proteus Staff", playerB);
Assert.assertTrue("Staff of player B could be used", staffPlayerB.isTapped());
}
}

View file

@ -57,6 +57,7 @@ import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.NamePredicate;
import mage.filter.predicate.permanent.SummoningSicknessPredicate;
import mage.game.Game;
import mage.game.GameImpl;
import mage.game.Graveyard;
import mage.game.Table;
import mage.game.combat.CombatGroup;
@ -519,6 +520,7 @@ public class TestPlayer implements Player {
}
if (groups[0].equals("Concede")) {
game.concede(getId());
((GameImpl) game).checkConcede();
actions.remove(action);
}
}
@ -1182,6 +1184,11 @@ public class TestPlayer implements Player {
computerPlayer.abort();
}
@Override
public void signalPlayerConcede() {
computerPlayer.signalPlayerConcede();
}
@Override
public void abortReset() {
computerPlayer.abortReset();

View file

@ -27,6 +27,8 @@
*/
package org.mage.test.stub;
import java.io.Serializable;
import java.util.*;
import mage.MageObject;
import mage.abilities.*;
import mage.abilities.costs.AlternativeSourceCosts;
@ -62,9 +64,6 @@ import mage.target.TargetAmount;
import mage.target.TargetCard;
import mage.target.common.TargetCardInLibrary;
import java.io.Serializable;
import java.util.*;
/**
*
* @author Quercitron
@ -702,6 +701,11 @@ public class PlayerStub implements Player {
}
@Override
public void signalPlayerConcede() {
}
@Override
public void abortReset() {

View file

@ -28,7 +28,9 @@
package mage.abilities.common.delayed;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.effects.Effect;
import mage.constants.Duration;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.game.Game;
@ -42,6 +44,7 @@ import mage.game.permanent.Permanent;
public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTriggeredAbility {
private TargetController targetController;
private Condition condition;
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Effect effect) {
this(effect, TargetController.ANY);
@ -52,14 +55,20 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone zone, Effect effect, TargetController targetController) {
super(effect);
this(zone, effect, targetController, null);
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(Zone zone, Effect effect, TargetController targetController, Condition condition) {
super(effect, Duration.EndOfTurn);
this.zone = zone;
this.targetController = targetController;
this.condition = condition;
}
public AtTheBeginOfNextEndStepDelayedTriggeredAbility(final AtTheBeginOfNextEndStepDelayedTriggeredAbility ability) {
super(ability);
this.targetController = ability.targetController;
this.condition = ability.condition;
}
@Override
@ -69,27 +78,34 @@ public class AtTheBeginOfNextEndStepDelayedTriggeredAbility extends DelayedTrigg
@Override
public boolean checkTrigger(GameEvent event, Game game) {
boolean correctEndPhase = false;
switch (targetController) {
case ANY:
return true;
correctEndPhase = true;
break;
case YOU:
return event.getPlayerId().equals(this.controllerId);
correctEndPhase = event.getPlayerId().equals(this.controllerId);
break;
case OPPONENT:
if (game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) {
return true;
correctEndPhase = true;
}
break;
case CONTROLLER_ATTACHED_TO:
Permanent attachment = game.getPermanent(sourceId);
if (attachment != null && attachment.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(attachment.getAttachedTo());
if (attachedTo != null && attachedTo.getControllerId().equals(event.getPlayerId())) {
return true;
correctEndPhase = true;
}
}
}
if (correctEndPhase) {
if (condition != null && !condition.apply(game, this)) {
return false;
}
return true;
}
return false;
}

View file

@ -0,0 +1,94 @@
/*
* 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 mage.abilities.effects.common;
import java.util.UUID;
import mage.MageObject;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.costs.Cost;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
/**
*
* @author LevelX2
*/
public class DoUnlessAnyPlayerPaysManaEffect extends ManaEffect {
private final ManaEffect manaEffect;
private final Cost cost;
private final String chooseUseText;
public DoUnlessAnyPlayerPaysManaEffect(ManaEffect effect, Cost cost, String chooseUseText) {
this.manaEffect = effect;
this.cost = cost;
this.chooseUseText = chooseUseText;
}
public DoUnlessAnyPlayerPaysManaEffect(final DoUnlessAnyPlayerPaysManaEffect effect) {
super(effect);
this.manaEffect = (ManaEffect) effect.manaEffect.copy();
this.cost = effect.cost.copy();
this.chooseUseText = effect.chooseUseText;
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = game.getObject(source.getSourceId());
if (controller != null && sourceObject != null) {
String message = CardUtil.replaceSourceName(chooseUseText, sourceObject.getName());
boolean result = true;
boolean doEffect = true;
// check if any player is willing to pay
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null && cost.canPay(source, source.getSourceId(), player.getId(), game) && player.chooseUse(Outcome.Detriment, message, source, game)) {
cost.clearPaid();
if (cost.pay(source, game, source.getSourceId(), player.getId(), false, null)) {
if (!game.isSimulation()) {
game.informPlayers(player.getLogName() + " pays the cost to prevent the effect");
}
doEffect = false;
}
}
}
// do the effects if nobody paid
if (doEffect) {
return manaEffect.apply(game, source);
}
return result;
}
return false;
}
protected Player getPayingPlayer(Game game, Ability source) {
return game.getPlayer(source.getControllerId());
}
@Override
public String getText(Mode mode) {
if (!staticText.isEmpty()) {
return staticText;
}
return manaEffect.getText(mode) + " unless any player pays " + cost.getText();
}
@Override
public Mana getMana(Game game, Ability source) {
return manaEffect.getMana(game, source);
}
@Override
public ManaEffect copy() {
return new DoUnlessAnyPlayerPaysManaEffect(this);
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of BetaSteward_at_googlemail.com.
*/
package mage.abilities.effects.common;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.permanent.Permanent;
/**
* This class should only be used within the application of another effect
*
* @author TheElk801
*/
public class PhaseOutAllEffect extends OneShotEffect {
private final List<UUID> idList;
public PhaseOutAllEffect(List<UUID> idList) {
super(Outcome.Neutral);
this.idList = idList;
}
public PhaseOutAllEffect(final PhaseOutAllEffect effect) {
super(effect);
this.idList = effect.idList;
}
@Override
public PhaseOutAllEffect copy() {
return new PhaseOutAllEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
// First we phase out everything that isn't attached to anything
// Anything attached to these permanents will phase out indirectly
for (UUID permanentId : idList) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null) {
Permanent attachedTo = game.getPermanent(permanent.getAttachedTo());
if (attachedTo == null) {
permanent.phaseOut(game);
}
}
}
// Once this is done, we'll have permanents which are attached to something but haven't phased out
// These will be phased out directly
for (UUID permanentId : idList) {
Permanent permanent = game.getPermanent(permanentId);
if (permanent != null && permanent.isPhasedIn()) {
permanent.phaseOut(game);
}
}
return true;
}
}

View file

@ -173,7 +173,7 @@ public interface Game extends MageItem, Serializable {
UUID getPriorityPlayerId();
boolean gameOver(UUID playerId);
boolean checkIfGameIsOver();
boolean hasEnded();
@ -347,6 +347,8 @@ public interface Game extends MageItem, Serializable {
void concede(UUID playerId);
void setConcedingPlayer(UUID playerId);
void setManaPaymentMode(UUID playerId, boolean autoPayment);
void setManaPaymentModeRestricted(UUID playerId, boolean autoPaymentRestricted);

View file

@ -161,6 +161,8 @@ public abstract class GameImpl implements Game, Serializable {
private final LinkedList<UUID> stackObjectsCheck = new LinkedList<>(); // used to check if different sources used the stack
// used to set the counters a permanent adds the battlefield (if no replacement effect is used e.g. Persist)
protected Map<UUID, Counters> enterWithCounters = new HashMap<>();
// used to proceed player conceding requests
private final LinkedList<UUID> concedingPlayers = new LinkedList<>(); // used to handle asynchronous request of a player to leave the game
public GameImpl(MultiplayerAttackOption attackOption, RangeOfInfluence range, int freeMulligans, int startLife) {
this.id = UUID.randomUUID();
@ -535,26 +537,59 @@ public abstract class GameImpl implements Game, Serializable {
}
}
/**
* Starts check if game is over or if playerId is given let the player
* concede.
*
* @param playerId
* @return
*/
// /**
// * Starts check if game is over or if playerId is given let the player
// * concede.
// *
// * @param playerId
// * @return
// */
// @Override
// public synchronized boolean gameOver(UUID playerId) {
// if (playerId == null) {
// boolean result = checkIfGameIsOver();
// return result;
// } else {
// logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
// concedingPlayers.add(playerId);
// Player player = getPlayer(state.getPriorityPlayerId());
// if (player != null && player.isHuman()) {
// player.signalPlayerConcede();
// } else {
// checkConcede();
// }
// return true;
// }
// }
@Override
public synchronized boolean gameOver(UUID playerId) {
if (playerId == null) {
boolean result = checkIfGameIsOver();
return result;
public void setConcedingPlayer(UUID playerId) {
Player player = getPlayer(state.getPriorityPlayerId());
if (player != null) {
if (!player.hasLeft() && player.isHuman()) {
if (!concedingPlayers.contains(playerId)) {
logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
concedingPlayers.add(playerId);
player.signalPlayerConcede();
}
} else {
// no asynchronous action so check directly
concedingPlayers.add(playerId);
checkConcede();
}
} else {
logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
leave(playerId);
return true;
checkConcede();
checkIfGameIsOver();
}
}
private boolean checkIfGameIsOver() {
public void checkConcede() {
while (!concedingPlayers.isEmpty()) {
leave(concedingPlayers.removeFirst());
}
}
@Override
public boolean checkIfGameIsOver() {
if (state.isGameOver()) {
return true;
}
@ -578,7 +613,7 @@ public abstract class GameImpl implements Game, Serializable {
}
for (Player player : state.getPlayers().values()) {
if (!player.hasLeft() && !player.hasLost()) {
logger.debug(new StringBuilder("Player ").append(player.getName()).append(" has won gameId: ").append(this.getId()));
logger.debug("Player " + player.getName() + " has won gameId: " + this.getId());
player.won(this);
}
}
@ -696,13 +731,13 @@ public abstract class GameImpl implements Game, Serializable {
Player player = getPlayer(playerList.get());
boolean wasPaused = state.isPaused();
state.resume();
if (!gameOver(null)) {
if (!checkIfGameIsOver()) {
fireInformEvent("Turn " + state.getTurnNum());
if (checkStopOnTurnOption()) {
return;
}
state.getTurn().resumePlay(this, wasPaused);
if (!isPaused() && !gameOver(null)) {
if (!isPaused() && !checkIfGameIsOver()) {
endOfTurn();
player = playerList.getNext(this);
state.setTurnNum(state.getTurnNum() + 1);
@ -712,11 +747,11 @@ public abstract class GameImpl implements Game, Serializable {
}
protected void play(UUID nextPlayerId) {
if (!isPaused() && !gameOver(null)) {
if (!isPaused() && !checkIfGameIsOver()) {
playerList = state.getPlayerList(nextPlayerId);
Player playerByOrder = getPlayer(playerList.get());
state.setPlayerByOrderId(playerByOrder.getId());
while (!isPaused() && !gameOver(null)) {
while (!isPaused() && !checkIfGameIsOver()) {
if (!playExtraTurns()) {
break;
}
@ -733,7 +768,7 @@ public abstract class GameImpl implements Game, Serializable {
state.setPlayerByOrderId(playerByOrder.getId());
}
}
if (gameOver(null) && !isSimulation()) {
if (checkIfGameIsOver() && !isSimulation()) {
winnerId = findWinnersAndLosers();
StringBuilder sb = new StringBuilder("GAME END gameId: ").append(this.getId()).append(' ');
int count = 0;
@ -816,7 +851,7 @@ public abstract class GameImpl implements Game, Serializable {
skipTurn = state.getTurn().play(this, player);
} while (executingRollback);
if (isPaused() || gameOver(null)) {
if (isPaused() || checkIfGameIsOver()) {
return false;
}
if (!skipTurn) {
@ -854,7 +889,7 @@ public abstract class GameImpl implements Game, Serializable {
saveState(false);
if (gameOver(null)) {
if (checkIfGameIsOver()) {
return;
}
@ -1245,7 +1280,7 @@ public abstract class GameImpl implements Game, Serializable {
clearAllBookmarks();
try {
applyEffects();
while (!isPaused() && !gameOver(null) && !this.getTurn().isEndTurnRequested()) {
while (!isPaused() && !checkIfGameIsOver() && !this.getTurn().isEndTurnRequested()) {
if (!resuming) {
state.getPlayers().resetPassed();
state.getPlayerList().setCurrent(activePlayerId);
@ -1254,14 +1289,14 @@ public abstract class GameImpl implements Game, Serializable {
}
fireUpdatePlayersEvent();
Player player;
while (!isPaused() && !gameOver(null)) {
while (!isPaused() && !checkIfGameIsOver()) {
try {
if (bookmark == 0) {
bookmark = bookmarkState();
}
player = getPlayer(state.getPlayerList().get());
state.setPriorityPlayerId(player.getId());
while (!player.isPassed() && player.canRespond() && !isPaused() && !gameOver(null)) {
while (!player.isPassed() && player.canRespond() && !isPaused() && !checkIfGameIsOver()) {
if (!resuming) {
// 603.3. Once an ability has triggered, its controller puts it on the stack as an object that's not a card the next time a player would receive priority
checkStateAndTriggered();
@ -1270,7 +1305,7 @@ public abstract class GameImpl implements Game, Serializable {
resetLKI();
}
saveState(false);
if (isPaused() || gameOver(null)) {
if (isPaused() || checkIfGameIsOver()) {
return;
}
// resetPassed should be called if player performs any action
@ -1289,13 +1324,14 @@ public abstract class GameImpl implements Game, Serializable {
}
resetShortLivingLKI();
resuming = false;
if (isPaused() || gameOver(null)) {
if (isPaused() || checkIfGameIsOver()) {
return;
}
if (allPassed()) {
if (!state.getStack().isEmpty()) {
//20091005 - 115.4
resolve();
checkConcede();
applyEffects();
state.getPlayers().resetPassed();
fireUpdatePlayersEvent();
@ -1609,11 +1645,11 @@ public abstract class GameImpl implements Game, Serializable {
public boolean checkStateAndTriggered() {
boolean somethingHappened = false;
//20091005 - 115.5
while (!isPaused() && !gameOver(null)) {
while (!isPaused() && !checkIfGameIsOver()) {
if (!checkStateBasedActions()) {
// nothing happened so check triggers
state.handleSimultaneousEvent(this);
if (isPaused() || gameOver(null) || getTurn().isEndTurnRequested() || !checkTriggered()) {
if (isPaused() || checkIfGameIsOver() || getTurn().isEndTurnRequested() || !checkTriggered()) {
break;
}
}
@ -1621,6 +1657,7 @@ public abstract class GameImpl implements Game, Serializable {
applyEffects(); // needed e.g if boost effects end and cause creatures to die
somethingHappened = true;
}
checkConcede();
return somethingHappened;
}
@ -1734,7 +1771,6 @@ public abstract class GameImpl implements Game, Serializable {
}
}
List<Permanent> planeswalkers = new ArrayList<>();
List<Permanent> legendary = new ArrayList<>();
List<Permanent> worldEnchantment = new ArrayList<>();
for (Permanent perm : getBattlefield().getAllActivePermanents()) {
@ -1781,7 +1817,6 @@ public abstract class GameImpl implements Game, Serializable {
continue;
}
}
planeswalkers.add(perm);
}
if (perm.isWorld()) {
worldEnchantment.add(perm);
@ -2288,7 +2323,6 @@ public abstract class GameImpl implements Game, Serializable {
* @param playerId
*/
protected void leave(UUID playerId) { // needs to be executed from the game thread, not from the concede thread of conceding player!
Player player = getPlayer(playerId);
if (player == null || player.hasLeft()) {
logger.debug("Player already left " + (player != null ? player.getName() : playerId));

View file

@ -265,7 +265,7 @@ public class Combat implements Serializable, Copyable<Combat> {
player.selectAttackers(game, attackingPlayerId);
}
firstTime = false;
if (game.isPaused() || game.gameOver(null) || game.executingRollback()) {
if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
return;
}
// because of possible undo during declare attackers it's neccassary to call here the methods with "game.getCombat()." to get the current combat object!!!
@ -462,7 +462,7 @@ public class Combat implements Serializable, Copyable<Combat> {
}
while (choose) {
controller.selectBlockers(game, defenderId);
if (game.isPaused() || game.gameOver(null) || game.executingRollback()) {
if (game.isPaused() || game.checkIfGameIsOver() || game.executingRollback()) {
return;
}
if (!game.getCombat().checkBlockRestrictions(defender, game)) {

View file

@ -78,10 +78,16 @@ public interface Permanent extends Card, Controllable {
boolean isPhasedIn();
boolean isPhasedOutIndirectly();
boolean phaseIn(Game game);
boolean phaseIn(Game game, boolean onlyDirect);
boolean phaseOut(Game game);
boolean phaseOut(Game game, boolean indirectPhase);
boolean isMonstrous();
void setMonstrous(boolean value);

View file

@ -89,6 +89,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
protected boolean controlledFromStartOfControllerTurn;
protected int turnsOnBattlefield;
protected boolean phasedIn = true;
protected boolean indirectPhase = false;
protected boolean faceDown;
protected boolean attacking;
protected int blocking;
@ -138,6 +139,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
this.controlledFromStartOfControllerTurn = permanent.controlledFromStartOfControllerTurn;
this.turnsOnBattlefield = permanent.turnsOnBattlefield;
this.phasedIn = permanent.phasedIn;
this.indirectPhase = permanent.indirectPhase;
this.faceDown = permanent.faceDown;
this.attacking = permanent.attacking;
this.blocking = permanent.blocking;
@ -461,14 +463,32 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
return phasedIn;
}
@Override
public boolean isPhasedOutIndirectly() {
return !phasedIn && indirectPhase;
}
@Override
public boolean phaseIn(Game game) {
return phaseIn(game, true);
}
@Override
public boolean phaseIn(Game game, boolean onlyDirect) {
if (!phasedIn) {
if (!replaceEvent(EventType.PHASE_IN, game)) {
if (!replaceEvent(EventType.PHASE_IN, game)
&& ((onlyDirect && !indirectPhase) || (!onlyDirect))) {
this.phasedIn = true;
this.indirectPhase = false;
if (!game.isSimulation()) {
game.informPlayers(getLogName() + " phased in");
}
for (UUID attachedId : this.getAttachments()) {
Permanent attachedPerm = game.getPermanent(attachedId);
if (attachedPerm != null) {
attachedPerm.phaseIn(game, false);
}
}
fireEvent(EventType.PHASED_IN, game);
return true;
}
@ -478,9 +498,21 @@ public abstract class PermanentImpl extends CardImpl implements Permanent {
@Override
public boolean phaseOut(Game game) {
return phaseOut(game, false);
}
@Override
public boolean phaseOut(Game game, boolean indirectPhase) {
if (phasedIn) {
if (!replaceEvent(EventType.PHASE_OUT, game)) {
for (UUID attachedId : this.getAttachments()) {
Permanent attachedPerm = game.getPermanent(attachedId);
if (attachedPerm != null) {
attachedPerm.phaseOut(game, true);
}
}
this.phasedIn = false;
this.indirectPhase = indirectPhase;
if (!game.isSimulation()) {
game.informPlayers(getLogName() + " phased out");
}

View file

@ -95,7 +95,7 @@ public abstract class Phase implements Serializable {
}
public boolean play(Game game, UUID activePlayerId) {
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
@ -104,7 +104,7 @@ public abstract class Phase implements Serializable {
if (beginPhase(game, activePlayerId)) {
for (Step step : steps) {
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
if (game.getTurn().isEndTurnRequested() && step.getType()!=PhaseStep.CLEANUP) {
@ -122,7 +122,7 @@ public abstract class Phase implements Serializable {
}
}
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
count++;
@ -143,7 +143,7 @@ public abstract class Phase implements Serializable {
}
public boolean resumePlay(Game game, PhaseStep stepType, boolean wasPaused) {
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
@ -157,7 +157,7 @@ public abstract class Phase implements Serializable {
resumeStep(game, wasPaused);
while (it.hasNext()) {
step = it.next();
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
currentStep = step;
@ -169,7 +169,7 @@ public abstract class Phase implements Serializable {
}
}
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
count++;
@ -206,13 +206,13 @@ public abstract class Phase implements Serializable {
if (!currentStep.skipStep(game, activePlayerId)) {
game.getState().increaseStepNum();
prePriority(game, activePlayerId);
if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) {
if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
currentStep.priority(game, activePlayerId, false);
if (game.executingRollback()) {
return;
}
}
if (!game.isPaused() && !game.gameOver(null) && !game.executingRollback()) {
if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
postPriority(game, activePlayerId);
}
}
@ -233,11 +233,11 @@ public abstract class Phase implements Serializable {
prePriority(game, activePlayerId);
}
case PRIORITY:
if (!game.isPaused() && !game.gameOver(null)) {
if (!game.isPaused() && !game.checkIfGameIsOver()) {
currentStep.priority(game, activePlayerId, resuming);
}
case POST:
if (!game.isPaused() && !game.gameOver(null)) {
if (!game.isPaused() && !game.checkIfGameIsOver()) {
postPriority(game, activePlayerId);
}
}

View file

@ -127,7 +127,7 @@ public class Turn implements Serializable {
public boolean play(Game game, Player activePlayer) {
activePlayer.becomesActivePlayer();
this.setDeclareAttackersStepStarted(false);
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
@ -143,7 +143,7 @@ public class Turn implements Serializable {
resetCounts();
game.getPlayer(activePlayer.getId()).beginTurn(game);
for (Phase phase : phases) {
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return false;
}
if (!isEndTurnRequested() || phase.getType() == TurnPhase.END) {
@ -189,7 +189,7 @@ public class Turn implements Serializable {
}
while (it.hasNext()) {
phase = it.next();
if (game.isPaused() || game.gameOver(null)) {
if (game.isPaused() || game.checkIfGameIsOver()) {
return;
}
currentPhase = phase;

View file

@ -444,6 +444,8 @@ public interface Player extends MageItem, Copyable<Player> {
void abortReset();
void signalPlayerConcede();
void skip();
// priority, undo, ...

View file

@ -1484,16 +1484,15 @@ public abstract class PlayerImpl implements Player, Serializable {
// phasing out is known as phasing out "indirectly." An enchantment or Equipment
// that phased out indirectly won't phase in by itself, but instead phases in
// along with the card it's attached to.
for (UUID attachmentId : permanent.getAttachments()) {
Permanent attachment = game.getPermanent(attachmentId);
if (attachment != null) {
attachment.phaseOut(game);
}
Permanent attachedTo = game.getPermanent(permanent.getAttachedTo());
if (!(attachedTo != null && attachedTo.getControllerId().equals(this.getId()))) {
permanent.phaseOut(game, false);
}
permanent.phaseOut(game);
}
for (Permanent permanent : phasedOut) {
permanent.phaseIn(game);
if (!permanent.isPhasedOutIndirectly()) {
permanent.phaseIn(game);
}
}
}
@ -2039,9 +2038,9 @@ public abstract class PlayerImpl implements Player, Serializable {
@Override
public void concede(Game game) {
game.gameOver(playerId);
game.setConcedingPlayer(playerId);
lost(game);
this.left = true;
// this.left = true;
}
@Override
@ -2136,7 +2135,7 @@ public abstract class PlayerImpl implements Player, Serializable {
// for draw - first all players that have lost have to be set to lost
if (!hasLeft()) {
logger.debug("Game over playerId: " + playerId);
game.gameOver(playerId);
game.setConcedingPlayer(playerId);
}
}
@ -2197,7 +2196,7 @@ public abstract class PlayerImpl implements Player, Serializable {
this.draws = true;
game.fireEvent(GameEvent.getEvent(GameEvent.EventType.DRAW_PLAYER, null, null, playerId));
game.informPlayers("For " + this.getLogName() + " the game is a draw.");
game.gameOver(playerId);
game.setConcedingPlayer(playerId);
}
}
@ -3579,6 +3578,11 @@ public abstract class PlayerImpl implements Player, Serializable {
abort = false;
}
@Override
public void signalPlayerConcede() {
}
@Override
public boolean scry(int value, Ability source,
Game game