mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
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:
parent
75d241d541
commit
c076f4925f
17 changed files with 177 additions and 140 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.game.turn;
|
||||
|
||||
import mage.constants.TurnPhase;
|
||||
|
|
|
|||
|
|
@ -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 player’s 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 doesn’t 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 doesn’t 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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 player’s 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 doesn’t 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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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".
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue