Turn under control reworked:

- game: added support for human games (cards like Emrakul, the Promised End, #12878);
 - game: added support of 720.1. to reset control in the turn beginning instead cleanup step (related to #12115);
 - game: added game logs for priorities in cleanup step;
 - game: fixed game freezes and wrong skip settings usages (related to #12878);
 - gui: added playable and choose-able marks for controlling player's cards and permanents, including switched hands;
 - gui: added controlling player name in all choice dialogs;
 - info: control of computer players is it not yet supported;
This commit is contained in:
Oleg Agafonov 2025-01-07 12:26:30 +04:00
parent 75d241d541
commit c076f4925f
17 changed files with 177 additions and 140 deletions

View file

@ -30,8 +30,10 @@ import mage.constants.*;
import mage.game.events.PlayerQueryEvent;
import mage.players.PlayableObjectStats;
import mage.players.PlayableObjectsList;
import mage.util.CardUtil;
import mage.util.DebugUtil;
import mage.util.MultiAmountMessage;
import mage.util.StreamUtils;
import mage.view.*;
import org.apache.log4j.Logger;
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
@ -53,6 +55,7 @@ import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static mage.client.dialog.PreferencesDialog.*;
import static mage.constants.PlayerAction.*;
@ -1810,6 +1813,7 @@ public final class GamePanel extends javax.swing.JPanel {
// hand
if (needZone == Zone.HAND || needZone == Zone.ALL) {
// my hand
for (CardView card : lastGameData.game.getMyHand().values()) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
@ -1821,6 +1825,34 @@ public final class GamePanel extends javax.swing.JPanel {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
// opponent hands (switching by GUI's button with my hand)
List<SimpleCardView> list = lastGameData.game.getOpponentHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
for (SimpleCardView card : list) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
// watched hands (switching by GUI's button with my hand)
list = lastGameData.game.getWatchedHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
for (SimpleCardView card : list) {
if (needSelectable.contains(card.getId())) {
card.setChoosable(true);
}
if (needChosen.contains(card.getId())) {
card.setSelected(true);
}
if (needPlayable.containsObject(card.getId())) {
card.setPlayableStats(needPlayable.getStats(card.getId()));
}
}
}
// stack

View file

@ -37,6 +37,7 @@ import mage.game.tournament.Tournament;
import mage.players.Player;
import mage.players.PlayerImpl;
import mage.players.PlayerList;
import mage.players.net.UserData;
import mage.target.Target;
import mage.target.TargetAmount;
import mage.target.TargetCard;
@ -93,7 +94,7 @@ public class HumanPlayer extends PlayerImpl {
// * - GAME thread: open response for income command and wait (go to sleep by response.wait)
// * - CALL thread: on closed response - waiting open status of player's response object (if it's too long then cancel the answer)
// * - CALL thread: on opened response - save answer to player's response object and notify GAME thread about it by response.notifyAll
// * - GAME thread: on nofify from response - check new answer value and process it (if it bad then repeat and wait the next one);
// * - GAME thread: on notify from response - check new answer value and process it (if it bad then repeat and wait the next one);
private transient Boolean responseOpenedForAnswer = false; // GAME thread waiting new answer
private transient long responseLastWaitingThreadId = 0;
private final transient PlayerResponse response = new PlayerResponse();
@ -1159,25 +1160,27 @@ public class HumanPlayer extends PlayerImpl {
// TODO: change pass and other states like passedUntilStackResolved for controlling player, not for "this"
// TODO: check and change all "this" to controling player calls, many bugs with hand, mana, skips - https://github.com/magefree/mage/issues/2088
// TODO: use controlling player in all choose dialogs (and canRespond too, what's with take control of player AI?!)
UserData controllingUserData = this.userData;
if (canRespond()) {
HumanPlayer controllingPlayer = this;
if (isGameUnderControl()) { // TODO: must be ! to get real controlling player
if (!isGameUnderControl()) {
Player player = game.getPlayer(getTurnControlledBy());
if (player instanceof HumanPlayer) {
controllingPlayer = (HumanPlayer) player;
controllingUserData = player.getUserData();
} else {
// TODO: add computer opponent here?!
}
}
// TODO: check that all skips and stops used from real controlling player
// like holdingPriority (is it a bug here?)
if (getJustActivatedType() != null && !holdingPriority) {
if (controllingPlayer.getUserData().isPassPriorityCast()
if (controllingUserData.isPassPriorityCast()
&& getJustActivatedType() == AbilityType.SPELL) {
setJustActivatedType(null);
pass(game);
return false;
}
if (controllingPlayer.getUserData().isPassPriorityActivation()
if (controllingUserData.isPassPriorityActivation()
&& getJustActivatedType().isNonManaActivatedAbility()) {
setJustActivatedType(null);
pass(game);
@ -1252,7 +1255,7 @@ public class HumanPlayer extends PlayerImpl {
// it's main step
if (!skippedAtLeastOnce
|| (!playerId.equals(game.getActivePlayerId())
&& !controllingPlayer.getUserData().getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
&& !controllingUserData.getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
skippedAtLeastOnce = true;
if (passWithManaPoolCheck(game)) {
return false;
@ -1274,8 +1277,7 @@ public class HumanPlayer extends PlayerImpl {
// it's end of turn step
if (!skippedAtLeastOnce
|| (playerId.equals(game.getActivePlayerId())
&& !controllingPlayer
.getUserData()
&& !controllingUserData
.getUserSkipPrioritySteps()
.isStopOnAllEndPhases())) {
skippedAtLeastOnce = true;
@ -1295,7 +1297,7 @@ public class HumanPlayer extends PlayerImpl {
}
if (!dontCheckPassStep
&& checkPassStep(game, controllingPlayer)) {
&& checkPassStep(game, controllingUserData)) {
if (passWithManaPoolCheck(game)) {
return false;
}
@ -1308,8 +1310,7 @@ public class HumanPlayer extends PlayerImpl {
if (passedUntilStackResolved) {
if (haveNewObjectsOnStack
&& (playerId.equals(game.getActivePlayerId())
&& controllingPlayer
.getUserData()
&& controllingUserData
.getUserSkipPrioritySteps()
.isStopOnStackNewObjects())) {
// new objects on stack -- disable "pass until stack resolved"
@ -1433,17 +1434,17 @@ public class HumanPlayer extends PlayerImpl {
return response.getUUID();
}
private boolean checkPassStep(Game game, HumanPlayer controllingPlayer) {
private boolean checkPassStep(Game game, UserData controllingUserData) {
try {
if (playerId.equals(game.getActivePlayerId())) {
return !controllingPlayer.getUserData().getUserSkipPrioritySteps().getYourTurn().isPhaseStepSet(game.getTurnStepType());
return !controllingUserData.getUserSkipPrioritySteps().getYourTurn().isPhaseStepSet(game.getTurnStepType());
} else {
return !controllingPlayer.getUserData().getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getTurnStepType());
return !controllingUserData.getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getTurnStepType());
}
} catch (NullPointerException ex) {
if (controllingPlayer.getUserData() != null) {
if (controllingPlayer.getUserData().getUserSkipPrioritySteps() != null) {
if (controllingUserData != null) {
if (controllingUserData.getUserSkipPrioritySteps() != null) {
if (game.getStep() != null) {
if (game.getTurnStepType() == null) {
logger.error("game.getTurnStepType() == null");
@ -2929,19 +2930,8 @@ public class HumanPlayer extends PlayerImpl {
protected boolean passWithManaPoolCheck(Game game) {
if (userData.confirmEmptyManaPool()
&& game.getStack().isEmpty() && getManaPool().count() > 0 && getManaPool().canLostManaOnEmpty()) {
String activePlayerText;
if (game.isActivePlayer(playerId)) {
activePlayerText = "Your turn";
} else {
activePlayerText = game.getPlayer(game.getActivePlayerId()).getName() + "'s turn";
}
String priorityPlayerText = "";
if (!isGameUnderControl()) {
priorityPlayerText = " / priority " + game.getPlayer(game.getPriorityPlayerId()).getName();
}
// TODO: chooseUse and other dialogs must be under controlling player
if (!chooseUse(Outcome.Detriment, GameLog.getPlayerConfirmColoredText("You still have mana in your mana pool and it will be lose. Pass anyway?")
+ GameLog.getSmallSecondLineText(activePlayerText + " / " + game.getTurnStepType().toString() + priorityPlayerText), null, game)) {
String message = GameLog.getPlayerConfirmColoredText("You still have mana in your mana pool and it will be lose. Pass anyway?");
if (!chooseUse(Outcome.Detriment, message, null, game)) {
sendPlayerAction(PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS, game, null);
return false;
}

View file

@ -906,14 +906,14 @@ public class GameController implements GameCallback {
perform(playerId, playerId1 -> getGameSession(playerId1).getMultiAmount(messages, min, max, options));
}
private void informOthers(UUID playerId) {
private void informOthers(UUID waitingPlayerId) {
StringBuilder message = new StringBuilder();
if (game.getStep() != null) {
message.append(game.getTurnStepType().toString()).append(" - ");
}
message.append("Waiting for ").append(game.getPlayer(playerId).getLogName());
message.append("Waiting for ").append(game.getPlayer(waitingPlayerId).getLogName());
for (final Entry<UUID, GameSessionPlayer> entry : getGameSessionsMap().entrySet()) {
if (!entry.getKey().equals(playerId)) {
if (!entry.getKey().equals(waitingPlayerId)) {
entry.getValue().inform(message.toString());
}
}
@ -1030,7 +1030,7 @@ public class GameController implements GameCallback {
// TODO: if watcher disconnects then game freezes with active timer, must be fix for such use case
// same for another player (can be fixed by super-duper connection)
if (informOthers) {
informOthers(playerId);
informOthers(realPlayerController.getId());
}
}

View file

@ -212,13 +212,14 @@ public class GameSessionPlayer extends GameSessionWatcher {
// game view calculation can take some time and can be called from non-game thread,
// so use copy for thread save (protection from ConcurrentModificationException)
Game sourceGame = game.copy();
Player player = sourceGame.getPlayer(playerId); // null for watcher
GameView gameView = new GameView(sourceGame.getState(), sourceGame, playerId, null);
if (player != null) {
if (gameView.getPriorityPlayerName().equals(player.getName())) {
gameView.setCanPlayObjects(player.getPlayableObjects(sourceGame, Zone.ALL));
}
// playable info (if opponent under control then show opponent's playable)
Player player = sourceGame.getPlayer(playerId); // null for watcher
Player priorityPlayer = sourceGame.getPlayer(sourceGame.getPriorityPlayerId());
Player controllingPlayer = priorityPlayer == null ? null : sourceGame.getPlayer(priorityPlayer.getTurnControlledBy());
if (controllingPlayer != null && player == controllingPlayer) {
gameView.setCanPlayObjects(priorityPlayer.getPlayableObjects(sourceGame, Zone.ALL));
}
processControlledPlayers(sourceGame, player, gameView);

View file

@ -104,16 +104,15 @@ public class LoadCallbackClient implements CallbackClient {
GameClientMessage message = (GameClientMessage) callback.getData();
this.gameView = message.getGameView();
log.info(getLogStartInfo() + " target: " + message.getMessage());
switch (message.getMessage()) {
case "Select a starting player":
if (message.getMessage().startsWith("Select a starting player")) {
session.sendPlayerUUID(gameId, playerId);
return;
case "Select a card to discard":
} else if (message.getMessage().startsWith("Select a card to discard")) {
log.info(getLogStartInfo() + "hand size: " + gameView.getMyHand().size());
SimpleCardView card = gameView.getMyHand().values().iterator().next();
session.sendPlayerUUID(gameId, card.getId());
return;
default:
} else {
log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString());
return;
}

View file

@ -1,42 +0,0 @@
package mage.abilities.common.delayed;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.game.events.GameEvent;
/**
* @author nantuko
*/
public class AtTheEndOfTurnStepPostDelayedTriggeredAbility extends DelayedTriggeredAbility {
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(Effect effect) {
this(effect, false);
}
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(Effect effect, boolean usesStack) {
super(effect);
this.usesStack = usesStack;
setTriggerPhrase("At end of turn ");
}
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(AtTheEndOfTurnStepPostDelayedTriggeredAbility ability) {
super(ability);
}
@Override
public AtTheEndOfTurnStepPostDelayedTriggeredAbility copy() {
return new AtTheEndOfTurnStepPostDelayedTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.END_TURN_STEP_POST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return true;
}
}

View file

@ -550,6 +550,12 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
@Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
boolean checkStateAndTriggered();
/**
* Play priority by all players
*
* @param activePlayerId starting priority player
* @param resuming false to reset passed priority and ask it again
*/
void playPriority(UUID activePlayerId, boolean resuming);
void resetControlAfterSpellResolve(UUID topId);

View file

@ -2987,21 +2987,35 @@ public abstract class GameImpl implements Game {
}
String message;
if (this.canPlaySorcery(playerId)) {
message = "Play spells and abilities.";
message = "Play spells and abilities";
} else {
message = "Play instants and activated abilities.";
message = "Play instants and activated abilities";
}
playerQueryEventSource.select(playerId, message);
message += getControllingPlayerHint(playerId);
Player player = this.getPlayer(playerId);
playerQueryEventSource.select(player.getTurnControlledBy(), message);
getState().clearLookedAt();
getState().clearRevealed();
}
private String getControllingPlayerHint(UUID playerId) {
Player player = this.getPlayer(playerId);
Player controllingPlayer = this.getPlayer(player.getTurnControlledBy());
if (player != controllingPlayer) {
return " (as " + player.getLogName() + ")";
} else {
return "";
}
}
@Override
public synchronized void fireSelectEvent(UUID playerId, String message) {
if (simulation) {
return;
}
playerQueryEventSource.select(playerId, message);
playerQueryEventSource.select(playerId, message + getControllingPlayerHint(playerId));
}
@Override
@ -3009,7 +3023,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.select(playerId, message, options);
playerQueryEventSource.select(playerId, message + getControllingPlayerHint(playerId), options);
}
@Override
@ -3017,7 +3031,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.playMana(playerId, message, options);
playerQueryEventSource.playMana(playerId, message + getControllingPlayerHint(playerId), options);
}
@Override
@ -3025,7 +3039,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.playXMana(playerId, message);
playerQueryEventSource.playXMana(playerId, message + getControllingPlayerHint(playerId));
}
@Override
@ -3038,7 +3052,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.ask(playerId, message.getMessage(), source, addMessageToOptions(message, options));
playerQueryEventSource.ask(playerId, message.getMessage() + getControllingPlayerHint(playerId), source, addMessageToOptions(message, options));
}
@Override
@ -3050,7 +3064,7 @@ public abstract class GameImpl implements Game {
if (object != null) {
objectName = object.getName();
}
playerQueryEventSource.chooseAbility(playerId, message, objectName, choices);
playerQueryEventSource.chooseAbility(playerId, message + getControllingPlayerHint(playerId), objectName, choices);
}
@Override
@ -3058,7 +3072,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.chooseMode(playerId, message, modes);
playerQueryEventSource.chooseMode(playerId, message + getControllingPlayerHint(playerId), modes);
}
@Override
@ -3066,7 +3080,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.target(playerId, message.getMessage(), targets, required, addMessageToOptions(message, options));
playerQueryEventSource.target(playerId, message.getMessage() + getControllingPlayerHint(playerId), targets, required, addMessageToOptions(message, options));
}
@Override
@ -3074,7 +3088,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.target(playerId, message.getMessage(), cards, required, addMessageToOptions(message, options));
playerQueryEventSource.target(playerId, message.getMessage() + getControllingPlayerHint(playerId), cards, required, addMessageToOptions(message, options));
}
/**
@ -3087,7 +3101,7 @@ public abstract class GameImpl implements Game {
*/
@Override
public void fireSelectTargetTriggeredAbilityEvent(UUID playerId, String message, List<TriggeredAbility> abilities) {
playerQueryEventSource.target(playerId, message, abilities);
playerQueryEventSource.target(playerId, message + getControllingPlayerHint(playerId), abilities);
}
@Override
@ -3095,7 +3109,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.target(playerId, message, perms, required);
playerQueryEventSource.target(playerId, message + getControllingPlayerHint(playerId), perms, required);
}
@Override
@ -3103,7 +3117,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.amount(playerId, message, min, max);
playerQueryEventSource.amount(playerId, message + getControllingPlayerHint(playerId), min, max);
}
@Override
@ -3128,7 +3142,7 @@ public abstract class GameImpl implements Game {
if (simulation) {
return;
}
playerQueryEventSource.choosePile(playerId, message, pile1, pile2);
playerQueryEventSource.choosePile(playerId, message + getControllingPlayerHint(playerId), pile1, pile2);
}
@Override

View file

@ -1,5 +1,3 @@
package mage.game.turn;
import mage.constants.TurnPhase;

View file

@ -1,14 +1,12 @@
package mage.game.turn;
import java.util.UUID;
import mage.constants.PhaseStep;
import mage.game.Game;
import mage.game.events.GameEvent.EventType;
import mage.players.Player;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -30,19 +28,31 @@ public class CleanupStep extends Step {
super.beginStep(game, activePlayerId);
Player activePlayer = game.getPlayer(activePlayerId);
game.getState().setPriorityPlayerId(activePlayer.getId());
//20091005 - 514.1
// 514.1
// First, if the active players hand contains more cards than his or her maximum hand size
// (normally seven), he or she discards enough cards to reduce his or her hand size to that number.
// This turn-based action doesnt use the stack.
if (activePlayer.isInGame()) {
activePlayer.discardToMax(game);
}
//20100423 - 514.2
// 514.2
// Second, the following actions happen simultaneously: all damage marked on permanents
// (including phased-out permanents) is removed and all "until end of turn" and "this turn"
// effects end. This turn-based action doesnt use the stack.
game.getBattlefield().endOfTurn(game);
game.getState().removeEotEffects(game);
// 514.3
// Normally, no player receives priority during the cleanup step, so no spells can be cast
// and no abilities can be activated. However, this rule is subject to the following exception: 514.3a
//
// Look at EndPhase code to process 514.3
}
@Override
public void endStep(Game game, UUID activePlayerId) {
Player activePlayer = game.getPlayer(activePlayerId);
activePlayer.setGameUnderYourControl(true);
super.endStep(game, activePlayerId);
}

View file

@ -31,16 +31,21 @@ public class EndPhase extends Phase {
game.getState().increaseStepNum();
game.getTurn().setEndTurnRequested(false); // so triggers trigger again
prePriority(game, activePlayerId);
// 514.3a At this point, the game checks to see if any state-based actions would be performed
// 514.3.
// Normally, no player receives priority during the cleanup step, so no spells can be cast and
// no abilities can be activated. However, this rule is subject to the following exception:
// 514.3a
// At this point, the game checks to see if any state-based actions would be performed
// and/or any triggered abilities are waiting to be put onto the stack (including those that
// trigger "at the beginning of the next cleanup step"). If so, those state-based actions are
// performed, then those triggered abilities are put on the stack, then the active player gets
// priority. Players may cast spells and activate abilities. Once the stack is empty and all players
// pass in succession, another cleanup step begins
if (game.checkStateAndTriggered()) {
// Queues a new cleanup step
game.informPlayers("State-based actions or triggers happened on cleanup step, so players get priority due 514.3a");
// queues a new cleanup step and request new priorities
game.getState().getTurnMods().add(new TurnMod(activePlayerId).withExtraStep(new CleanupStep()));
// resume priority
if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
currentStep.priority(game, activePlayerId, false);
if (game.executingRollback()) {

View file

@ -60,6 +60,12 @@ public abstract class Step implements Serializable, Copyable<Step> {
stepPart = StepPart.PRE;
}
/**
* Play priority by all players
*
* @param activePlayerId starting priority player
* @param resuming false to reset passed priority and ask it again
*/
public void priority(Game game, UUID activePlayerId, boolean resuming) {
if (hasPriority) {
stepPart = StepPart.PRIORITY;

View file

@ -107,12 +107,16 @@ public class Turn implements Serializable {
));
return true;
}
logStartOfTurn(game, activePlayer);
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
logStartOfTurn(game, activePlayer);
resetCounts();
this.activePlayerId = activePlayer.getId();
resetCounts();
this.currentPhase = null;
// turn control must be called after potential turn skip due 720.1.
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
game.getPlayer(activePlayer.getId()).beginTurn(game);
for (Phase phase : phases) {
if (game.isPaused() || game.checkIfGameIsOver()) {
@ -220,8 +224,31 @@ public class Turn implements Serializable {
}
private void checkTurnIsControlledByOtherPlayer(Game game, UUID activePlayerId) {
// 720.1.
// Some cards allow a player to control another player during that players next turn.
// This effect applies to the next turn that the affected player actually takes.
// The affected player is controlled during the entire turn; the effect doesnt end until
// the beginning of the next turn.
//
// 720.1b
// If a turn is skipped, any pending player-controlling effects wait until the player who would be
// affected actually takes a turn.
// remove old under control
game.getPlayers().values().forEach(player -> {
if (player.isInGame() && !player.isGameUnderControl()) {
Player controllingPlayer = game.getPlayer(player.getTurnControlledBy());
if (player != controllingPlayer && controllingPlayer != null) {
game.informPlayers(controllingPlayer.getLogName() + " lost control over " + player.getLogName());
}
player.setGameUnderYourControl(true);
}
});
// add new under control
TurnMod newControllerMod = game.getState().getTurnMods().useNextNewController(activePlayerId);
if (newControllerMod != null && !newControllerMod.getNewControllerId().equals(activePlayerId)) {
// set player under new control
// game logs added in child's call (controlPlayersTurn)
game.getPlayer(newControllerMod.getNewControllerId()).controlPlayersTurn(game, activePlayerId, newControllerMod.getInfo());
}

View file

@ -366,7 +366,7 @@ public interface Player extends MageItem, Copyable<Player> {
boolean isGameUnderControl();
/**
* Returns false in case you don't control the game.
* False in case you don't control the game.
* <p>
* Note: For effects like "You control target player during that player's
* next turn".

View file

@ -7,7 +7,6 @@ import mage.abilities.ActivatedAbility.ActivationStatus;
import mage.abilities.common.PassAbility;
import mage.abilities.common.PlayLandAsCommanderAbility;
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
import mage.abilities.costs.*;
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
import mage.abilities.costs.mana.ManaCost;
@ -15,7 +14,6 @@ import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.RestrictionUntapNotMoreThanEffect;
import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect;
import mage.abilities.keyword.*;
import mage.abilities.mana.ActivatedManaAbilityImpl;
import mage.abilities.mana.ManaOptions;
@ -609,11 +607,7 @@ public abstract class PlayerImpl implements Player, Serializable {
if (!playerUnderControl.hasLeft() && !playerUnderControl.hasLost()) {
playerUnderControl.setGameUnderYourControl(false);
}
DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility(
new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), playerUnderControl.getLogName()));
ability.setSourceId(getId());
ability.setControllerId(getId());
game.addDelayedTriggeredAbility(ability, null);
// control will reset on start of the turn
}
}

View file

@ -2127,9 +2127,10 @@ public final class CardUtil {
return null;
}
// not started game
// T0 - for not started game
// T2 - for starting of the turn
if (gameState.getTurn().getStep() == null) {
return "T0";
return "T" + gameState.getTurnNum();
}
// normal game

View file

@ -160,10 +160,6 @@ public final class GameLog {
return "<font color='" + LOG_COLOR_PLAYER_CONFIRM + "'>" + name + "</font>";
}
public static String getSmallSecondLineText(String text) {
return "<div style='font-size:11pt'>" + text + "</div>";
}
private static String getColorName(ObjectColor objectColor) {
if (objectColor.isMulticolored()) {
return LOG_COLOR_MULTI;