* Until your next turn effects - fixed that continuous effects of lost/leaved players can be discarded by combat or some cards before next turn starts;

This commit is contained in:
Oleg Agafonov 2019-12-26 07:28:37 +04:00
parent a2e4e55811
commit 2460408da8
14 changed files with 147 additions and 93 deletions

View file

@ -1,4 +1,3 @@
package mage.cards.a;
import mage.abilities.Ability;
@ -21,7 +20,6 @@ import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class Aetherspouts extends CardImpl {
@ -73,7 +71,7 @@ class AetherspoutsEffect extends OneShotEffect {
game.getPlayerList();
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
PlayerList playerList = game.getPlayerList();
PlayerList playerList = game.getPlayerList().copy();
playerList.setCurrent(game.getActivePlayerId());
Player player = game.getPlayer(game.getActivePlayerId());
Player activePlayer = player;
@ -169,7 +167,7 @@ class AetherspoutsEffect extends OneShotEffect {
for (Permanent permanent : toLibrary) {
player.moveCardToLibraryWithInfo(permanent, source.getSourceId(), game, Zone.BATTLEFIELD, false, false);
}
player = playerList.getNext(game);
player = playerList.getNext(game, false);
} while (player != null && !player.getId().equals(game.getActivePlayerId()) && activePlayer.canRespond());
return true;
}

View file

@ -1,9 +1,5 @@
package mage.cards.e;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
@ -27,16 +23,21 @@ import mage.players.PlayerList;
import mage.target.Target;
import mage.target.common.TargetNonlandPermanent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class EyeOfDoom extends CardImpl {
private static final FilterPermanent filter = new FilterPermanent("permanent with a doom counter on it");
static {
filter.add(new CounterPredicate(CounterType.DOOM));
}
public EyeOfDoom(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
@ -93,7 +94,7 @@ class EyeOfDoomEffect extends OneShotEffect {
game.informPlayers(player.getLogName() + " chooses " + permanent.getName());
}
}
player = playerList.getNext(game);
player = playerList.getNext(game, false);
} while (!player.getId().equals(game.getActivePlayerId()));
for (Permanent permanent : permanents) {

View file

@ -1,10 +1,6 @@
package mage.cards.i;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.abilities.keyword.DoubleStrikeAbility;
@ -21,8 +17,12 @@ import mage.players.Player;
import mage.players.PlayerList;
import mage.target.common.TargetCreaturePermanent;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
*
* @author Quercitron
*/
public final class IllicitAuction extends CardImpl {
@ -66,18 +66,17 @@ class IllicitAuctionEffect extends GainControlTargetEffect {
public void init(Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
Permanent targetCreature = game.getPermanent(source.getFirstTarget());
if (controller != null
&& targetCreature != null) {
PlayerList playerList = game.getPlayerList().copy();
playerList.setCurrent(game.getActivePlayerId());
Player winner = game.getPlayer(game.getActivePlayerId());
if (controller != null && targetCreature != null) {
PlayerList playerList = game.getState().getPlayersInRange(controller.getId(), game);
Player winner = game.getPlayer(controller.getId());
int highBid = 0;
game.informPlayers(winner.getLogName() + " has bet 0 lifes");
Player currentPlayer = playerList.getNextInRange(controller, game);
while (!Objects.equals(currentPlayer, winner)) {
Player currentPlayer = playerList.getNext(game, false);
while (currentPlayer != null && !Objects.equals(currentPlayer, winner)) {
String text = winner.getLogName() + " has bet " + highBid + " life" + (highBid > 1 ? "s" : "") + ". Top the bid?";
if (currentPlayer.chooseUse(Outcome.GainControl, text, source, game)) {
if (currentPlayer.canRespond()
&& currentPlayer.chooseUse(Outcome.GainControl, text, source, game)) {
int newBid = 0;
if (!currentPlayer.isHuman()) {//AI will evaluate the creature and bid
CreatureEvaluator eval = new CreatureEvaluator();
@ -85,15 +84,22 @@ class IllicitAuctionEffect extends GainControlTargetEffect {
int creatureValue = eval.evaluate(targetCreature, game);
newBid = Math.max(creatureValue % 2, computerLife - 100);
} else {
if (currentPlayer.canRespond()) {
newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", game);
}
}
if (newBid > highBid) {
highBid = newBid;
winner = currentPlayer;
game.informPlayers(currentPlayer.getLogName() + " bet " + newBid + " life" + (newBid > 1 ? "s" : ""));
}
}
currentPlayer = playerList.getNextInRange(controller, game);
currentPlayer = playerList.getNext(game, false);
// stops loop on all players quite
if (game.getState().getPlayersInRange(controller.getId(), game).isEmpty()) {
break;
}
}
game.informPlayers(winner.getLogName() + " won the auction with a bid of " + highBid + " life" + (highBid > 1 ? "s" : ""));

View file

@ -1,7 +1,5 @@
package mage.cards.k;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
@ -16,8 +14,9 @@ import mage.players.Player;
import mage.players.PlayerList;
import mage.target.common.TargetAnyTarget;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class Kindle extends CardImpl {
@ -64,7 +63,7 @@ class KindleCardsInAllGraveyardsCount implements DynamicValue {
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
int amount = 0;
PlayerList playerList = game.getPlayerList();
PlayerList playerList = game.getPlayerList().copy();
for (UUID playerUUID : playerList) {
Player player = game.getPlayer(playerUUID);
if (player != null) {

View file

@ -1,9 +1,5 @@
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.costs.mana.ManaCosts;
@ -24,8 +20,11 @@ import mage.players.Player;
import mage.players.PlayerList;
import mage.target.TargetCard;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
*
* @author Quercitron
*/
public final class Tariff extends CardImpl {
@ -69,10 +68,9 @@ class TariffEffect extends OneShotEffect {
PlayerList playerList = game.getPlayerList().copy();
playerList.setCurrent(game.getActivePlayerId());
Player player = game.getPlayer(game.getActivePlayerId());
do {
processPlayer(game, source, player);
player = playerList.getNext(game);
player = playerList.getNext(game, false);
} while (!player.getId().equals(game.getActivePlayerId()));
return true;

View file

@ -1,9 +1,5 @@
package mage.cards.t;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
@ -18,8 +14,11 @@ import mage.players.Player;
import mage.players.PlayerList;
import mage.target.common.TargetControlledCreaturePermanent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
*
* @author LevelX2
*/
public final class TemptWithReflections extends CardImpl {
@ -81,7 +80,7 @@ class TemptWithReflectionsEffect extends OneShotEffect {
}
game.informPlayers((player.getLogName() + decision + permanent.getName()));
}
player = playerList.getNext(game);
player = playerList.getNext(game, false);
} while (!player.getId().equals(game.getActivePlayerId()));
for (UUID playerId : playersSaidYes) {

View file

@ -1,14 +1,8 @@
package mage.cards.t;
import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
@ -24,8 +18,9 @@ import mage.target.TargetCard;
import mage.target.common.TargetCardInExile;
import mage.util.CardUtil;
import java.util.UUID;
/**
*
* @author emerald000
*/
public final class ThievesAuction extends CardImpl {
@ -98,7 +93,7 @@ class ThievesAuctionEffect extends OneShotEffect {
}
}
// Repeat this process until all cards exiled this way have been chosen.
player = playerList.getNext(game);
player = playerList.getNext(game, false);
}
return true;
}

View file

@ -153,4 +153,58 @@ public class EndOfTurnMultiOpponentsTest extends CardTestMultiPlayerBaseWithRang
assertAllCommandsUsed();
}
// leaved players
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.
@Test
public void test_UntilYourNextTurnMulti_Leaved() {
// Player order: A -> D -> C -> B
addCustomCardWithAbility("boost1", playerA, new SimpleStaticAbility(Zone.ALL, new BoostAllEffect(1, 1, Duration.UntilYourNextTurn)));
EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 1, playerA, true, PhaseStep.END_TURN);
EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 2, playerD, true, PhaseStep.END_TURN);
EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 3, playerC, true, PhaseStep.END_TURN);
EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 4, playerB, true, PhaseStep.END_TURN);
EndOfTurnOneOpponentTest.prepareStepChecks(this, "Duration.UntilYourNextTurn effect", 5, playerD, true, null);
addCard(Zone.BATTLEFIELD, playerA, cardBear2, 1);
addCard(Zone.BATTLEFIELD, playerB, cardBear2, 1);
addCard(Zone.BATTLEFIELD, playerC, cardBear2, 1);
addCard(Zone.BATTLEFIELD, playerD, cardBear2, 1);
//
// When Eye of Doom enters the battlefield, each player chooses a nonland permanent and puts a doom counter on it.
addCard(Zone.HAND, playerC, "Eye of Doom", 1);
addCard(Zone.BATTLEFIELD, playerC, "Forest", 4);
checkPlayerInGame("A must plays in 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, playerA, true);
attack(1, playerA, cardBear2);
checkPlayerInGame("A must plays in 2", 2, PhaseStep.PRECOMBAT_MAIN, playerD, playerA, true);
attack(2, playerD, cardBear2);
checkPlayerInGame("A must plays in 3 before", 3, PhaseStep.PRECOMBAT_MAIN, playerC, playerA, true);
attack(3, playerC, cardBear2);
concede(3, PhaseStep.PRECOMBAT_MAIN, playerA);
checkPlayerInGame("A must leaved in 3 after", 3, PhaseStep.POSTCOMBAT_MAIN, playerC, playerA, false);
// test PlayerList.getNext processing
// play Eye of Doom, ask all players to put doom counter
castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerC, "Eye of Doom");
addTarget(playerC, cardBear2);
addTarget(playerB, cardBear2);
//addTarget(playerA, cardBear2); // leaved
addTarget(playerD, cardBear2);
checkPlayerInGame("A must leaved in 4", 4, PhaseStep.POSTCOMBAT_MAIN, playerB, playerA, false);
attack(4, playerB, cardBear2);
checkPlayerInGame("A must leaved in 5", 5, PhaseStep.POSTCOMBAT_MAIN, playerD, playerA, false);
attack(5, playerD, cardBear2);
setStopAt(5, PhaseStep.CLEANUP);
setStrictChooseMode(true);
execute();
assertAllCommandsUsed();
}
}

View file

@ -217,7 +217,7 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
boolean canDelete = false;
Player player = game.getPlayer(startingControllerId);
// discard on start of turn for leave player
// discard on start of turn for leaved player
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
// or until a specific point in that turn will last until that turn would have begun.
// They neither expire immediately nor last indefinitely.

View file

@ -1,7 +1,5 @@
package mage.abilities.effects.common;
import java.io.ObjectStreamException;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -20,6 +18,8 @@ import mage.players.PlayerList;
import mage.target.Target;
import mage.target.common.TargetOpponent;
import java.io.ObjectStreamException;
/**
* 1. The controller of the spell or ability chooses an opponent. (This doesn't
* target the opponent.) 2. Each player involved in the clash reveals the top
@ -36,7 +36,7 @@ import mage.target.common.TargetOpponent;
* 7. The clash spell or ability finishes resolving. That usually involves a
* bonus gained by the controller of the clash spell or ability if they won
* the clash. 8. Abilities that triggered during the clash are put on the stack.
*
* <p>
* There are no draws or losses in a clash. Either you win it or you don't. Each
* spell or ability with clash says what happens if you (the controller of that
* spell or ability) win the clash. Typically, if you don't win the clash,
@ -148,7 +148,7 @@ public class ClashEffect extends OneShotEffect implements MageSingleton {
if (cardOpponent != null && current.getId().equals(opponent.getId())) {
topOpponent = current.chooseUse(Outcome.Detriment, "Put " + cardOpponent.getLogName() + " back on top of your library? (otherwise it goes to bottom)", source, game);
}
nextPlayer = playerList.getNext(game);
nextPlayer = playerList.getNext(game, false);
} while (nextPlayer != null && !nextPlayer.getId().equals(game.getActivePlayerId()));
// put the cards back to library
if (cardController != null) {

View file

@ -766,7 +766,7 @@ public abstract class GameImpl implements Game, Serializable {
state.getTurn().resumePlay(this, wasPaused);
if (!isPaused() && !checkIfGameIsOver()) {
endOfTurn();
player = playerList.getNext(this);
player = playerList.getNext(this, true);
state.setTurnNum(state.getTurnNum() + 1);
}
}
@ -791,7 +791,7 @@ public abstract class GameImpl implements Game, Serializable {
if (!playExtraTurns()) {
break;
}
playerByOrder = playerList.getNext(this);
playerByOrder = playerList.getNext(this, true);
state.setPlayerByOrderId(playerByOrder.getId());
}
}
@ -2494,7 +2494,6 @@ public abstract class GameImpl implements Game, Serializable {
perm.removeFromCombat(this, true);
}
toOutside.add(perm);
// it.remove();
} else if (perm.isControlledBy(player.getId())) {
// and any effects which give that player control of any objects or players end
Effects:
@ -2591,7 +2590,7 @@ public abstract class GameImpl implements Game, Serializable {
if (!isActivePlayer(playerId)) {
setMonarchId(null, getActivePlayerId());
} else {
Player nextPlayer = getPlayerList().getNext(this);
Player nextPlayer = getPlayerList().getNext(this, true);
if (nextPlayer != null) {
setMonarchId(null, nextPlayer.getId());
}

View file

@ -1236,7 +1236,7 @@ public class Combat implements Serializable, Copyable<Combat> {
case LEFT:
players = game.getState().getPlayerList(attackingPlayerId);
while (attackingPlayer.isInGame()) {
Player opponent = players.getNext(game);
Player opponent = players.getNext(game, false);
if (attackingPlayer.hasOpponent(opponent.getId(), game)) {
attackablePlayers.add(opponent.getId());
break;

View file

@ -487,10 +487,10 @@ public abstract class PlayerImpl implements Player, Serializable {
inRange.add(playerId);
PlayerList players = game.getState().getPlayerList(playerId);
for (int i = 0; i < range.getRange(); i++) {
Player player = players.getNext(game);
Player player = players.getNext(game, false);
if (player != null) {
while (player.hasLeft()) {
player = players.getNext(game);
player = players.getNext(game, false);
}
inRange.add(player.getId());
}

View file

@ -1,12 +1,11 @@
package mage.players;
import java.util.UUID;
import mage.game.Game;
import mage.util.CircularList;
import java.util.UUID;
/**
*
* @author BetaSteward_at_googlemail.com
*/
public class PlayerList extends CircularList<UUID> {
@ -23,7 +22,7 @@ public class PlayerList extends CircularList<UUID> {
}
public Player getNextInRange(Player basePlayer, Game game) {
UUID currentPlayerBefore = get();
UUID currentPlayerBefore = this.get();
UUID nextPlayerId = super.getNext();
do {
if (basePlayer.getInRange().contains(nextPlayerId)) {
@ -34,7 +33,10 @@ public class PlayerList extends CircularList<UUID> {
return null;
}
public Player getNext(Game game) {
/**
* checkNextTurnReached - use it turns/priority code only to mark leaved player as "reached next turn end" (need for some continous effects)
*/
public Player getNext(Game game, boolean checkNextTurnReached) {
UUID start = this.get();
if (start == null) {
return null;
@ -42,12 +44,15 @@ public class PlayerList extends CircularList<UUID> {
Player player;
while (true) {
player = game.getPlayer(super.getNext());
if (!player.hasLeft() && !player.hasLost()) {
if (player.isInGame()) {
break;
}
if (checkNextTurnReached) {
if (!player.hasReachedNextTurnAfterLeaving()) {
player.setReachedNextTurnAfterLeaving(true);
}
}
if (player.getId().equals(start)) {
return null;
}
@ -60,7 +65,7 @@ public class PlayerList extends CircularList<UUID> {
UUID start = this.get();
while (true) {
player = game.getPlayer(super.getPrevious());
if (!player.hasLeft() && !player.hasLost()) {
if (player.isInGame()) {
break;
}
if (player.getId().equals(start)) {