Skip to content

Commit c076f49

Browse files
committed
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;
1 parent 75d241d commit c076f49

File tree

17 files changed

+175
-138
lines changed

17 files changed

+175
-138
lines changed

Mage.Client/src/main/java/mage/client/game/GamePanel.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
import mage.game.events.PlayerQueryEvent;
3131
import mage.players.PlayableObjectStats;
3232
import mage.players.PlayableObjectsList;
33+
import mage.util.CardUtil;
3334
import mage.util.DebugUtil;
3435
import mage.util.MultiAmountMessage;
36+
import mage.util.StreamUtils;
3537
import mage.view.*;
3638
import org.apache.log4j.Logger;
3739
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
@@ -53,6 +55,7 @@
5355
import java.util.concurrent.CancellationException;
5456
import java.util.concurrent.ExecutionException;
5557
import java.util.concurrent.TimeUnit;
58+
import java.util.stream.Collectors;
5659

5760
import static mage.client.dialog.PreferencesDialog.*;
5861
import static mage.constants.PlayerAction.*;
@@ -1810,6 +1813,7 @@ private void prepareSelectableView() {
18101813

18111814
// hand
18121815
if (needZone == Zone.HAND || needZone == Zone.ALL) {
1816+
// my hand
18131817
for (CardView card : lastGameData.game.getMyHand().values()) {
18141818
if (needSelectable.contains(card.getId())) {
18151819
card.setChoosable(true);
@@ -1821,6 +1825,34 @@ private void prepareSelectableView() {
18211825
card.setPlayableStats(needPlayable.getStats(card.getId()));
18221826
}
18231827
}
1828+
1829+
// opponent hands (switching by GUI's button with my hand)
1830+
List<SimpleCardView> list = lastGameData.game.getOpponentHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
1831+
for (SimpleCardView card : list) {
1832+
if (needSelectable.contains(card.getId())) {
1833+
card.setChoosable(true);
1834+
}
1835+
if (needChosen.contains(card.getId())) {
1836+
card.setSelected(true);
1837+
}
1838+
if (needPlayable.containsObject(card.getId())) {
1839+
card.setPlayableStats(needPlayable.getStats(card.getId()));
1840+
}
1841+
}
1842+
1843+
// watched hands (switching by GUI's button with my hand)
1844+
list = lastGameData.game.getWatchedHands().values().stream().flatMap(s -> s.values().stream()).collect(Collectors.toList());
1845+
for (SimpleCardView card : list) {
1846+
if (needSelectable.contains(card.getId())) {
1847+
card.setChoosable(true);
1848+
}
1849+
if (needChosen.contains(card.getId())) {
1850+
card.setSelected(true);
1851+
}
1852+
if (needPlayable.containsObject(card.getId())) {
1853+
card.setPlayableStats(needPlayable.getStats(card.getId()));
1854+
}
1855+
}
18241856
}
18251857

18261858
// stack

Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import mage.players.Player;
3838
import mage.players.PlayerImpl;
3939
import mage.players.PlayerList;
40+
import mage.players.net.UserData;
4041
import mage.target.Target;
4142
import mage.target.TargetAmount;
4243
import mage.target.TargetCard;
@@ -93,7 +94,7 @@ public class HumanPlayer extends PlayerImpl {
9394
// * - GAME thread: open response for income command and wait (go to sleep by response.wait)
9495
// * - CALL thread: on closed response - waiting open status of player's response object (if it's too long then cancel the answer)
9596
// * - CALL thread: on opened response - save answer to player's response object and notify GAME thread about it by response.notifyAll
96-
// * - GAME thread: on nofify from response - check new answer value and process it (if it bad then repeat and wait the next one);
97+
// * - GAME thread: on notify from response - check new answer value and process it (if it bad then repeat and wait the next one);
9798
private transient Boolean responseOpenedForAnswer = false; // GAME thread waiting new answer
9899
private transient long responseLastWaitingThreadId = 0;
99100
private final transient PlayerResponse response = new PlayerResponse();
@@ -1159,25 +1160,27 @@ public boolean priority(Game game) {
11591160
// TODO: change pass and other states like passedUntilStackResolved for controlling player, not for "this"
11601161
// TODO: check and change all "this" to controling player calls, many bugs with hand, mana, skips - https://github.com/magefree/mage/issues/2088
11611162
// TODO: use controlling player in all choose dialogs (and canRespond too, what's with take control of player AI?!)
1163+
UserData controllingUserData = this.userData;
11621164
if (canRespond()) {
1163-
HumanPlayer controllingPlayer = this;
1164-
if (isGameUnderControl()) { // TODO: must be ! to get real controlling player
1165+
if (!isGameUnderControl()) {
11651166
Player player = game.getPlayer(getTurnControlledBy());
11661167
if (player instanceof HumanPlayer) {
1167-
controllingPlayer = (HumanPlayer) player;
1168+
controllingUserData = player.getUserData();
1169+
} else {
1170+
// TODO: add computer opponent here?!
11681171
}
11691172
}
11701173

11711174
// TODO: check that all skips and stops used from real controlling player
11721175
// like holdingPriority (is it a bug here?)
11731176
if (getJustActivatedType() != null && !holdingPriority) {
1174-
if (controllingPlayer.getUserData().isPassPriorityCast()
1177+
if (controllingUserData.isPassPriorityCast()
11751178
&& getJustActivatedType() == AbilityType.SPELL) {
11761179
setJustActivatedType(null);
11771180
pass(game);
11781181
return false;
11791182
}
1180-
if (controllingPlayer.getUserData().isPassPriorityActivation()
1183+
if (controllingUserData.isPassPriorityActivation()
11811184
&& getJustActivatedType().isNonManaActivatedAbility()) {
11821185
setJustActivatedType(null);
11831186
pass(game);
@@ -1252,7 +1255,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
12521255
// it's main step
12531256
if (!skippedAtLeastOnce
12541257
|| (!playerId.equals(game.getActivePlayerId())
1255-
&& !controllingPlayer.getUserData().getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
1258+
&& !controllingUserData.getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
12561259
skippedAtLeastOnce = true;
12571260
if (passWithManaPoolCheck(game)) {
12581261
return false;
@@ -1274,8 +1277,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
12741277
// it's end of turn step
12751278
if (!skippedAtLeastOnce
12761279
|| (playerId.equals(game.getActivePlayerId())
1277-
&& !controllingPlayer
1278-
.getUserData()
1280+
&& !controllingUserData
12791281
.getUserSkipPrioritySteps()
12801282
.isStopOnAllEndPhases())) {
12811283
skippedAtLeastOnce = true;
@@ -1295,7 +1297,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
12951297
}
12961298

12971299
if (!dontCheckPassStep
1298-
&& checkPassStep(game, controllingPlayer)) {
1300+
&& checkPassStep(game, controllingUserData)) {
12991301
if (passWithManaPoolCheck(game)) {
13001302
return false;
13011303
}
@@ -1308,8 +1310,7 @@ && checkPassStep(game, controllingPlayer)) {
13081310
if (passedUntilStackResolved) {
13091311
if (haveNewObjectsOnStack
13101312
&& (playerId.equals(game.getActivePlayerId())
1311-
&& controllingPlayer
1312-
.getUserData()
1313+
&& controllingUserData
13131314
.getUserSkipPrioritySteps()
13141315
.isStopOnStackNewObjects())) {
13151316
// new objects on stack -- disable "pass until stack resolved"
@@ -1433,17 +1434,17 @@ private UUID getFixedResponseUUID(Game game) {
14331434
return response.getUUID();
14341435
}
14351436

1436-
private boolean checkPassStep(Game game, HumanPlayer controllingPlayer) {
1437+
private boolean checkPassStep(Game game, UserData controllingUserData) {
14371438
try {
14381439

14391440
if (playerId.equals(game.getActivePlayerId())) {
1440-
return !controllingPlayer.getUserData().getUserSkipPrioritySteps().getYourTurn().isPhaseStepSet(game.getTurnStepType());
1441+
return !controllingUserData.getUserSkipPrioritySteps().getYourTurn().isPhaseStepSet(game.getTurnStepType());
14411442
} else {
1442-
return !controllingPlayer.getUserData().getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getTurnStepType());
1443+
return !controllingUserData.getUserSkipPrioritySteps().getOpponentTurn().isPhaseStepSet(game.getTurnStepType());
14431444
}
14441445
} catch (NullPointerException ex) {
1445-
if (controllingPlayer.getUserData() != null) {
1446-
if (controllingPlayer.getUserData().getUserSkipPrioritySteps() != null) {
1446+
if (controllingUserData != null) {
1447+
if (controllingUserData.getUserSkipPrioritySteps() != null) {
14471448
if (game.getStep() != null) {
14481449
if (game.getTurnStepType() == null) {
14491450
logger.error("game.getTurnStepType() == null");
@@ -2929,19 +2930,8 @@ private void setTriggerAutoOrder(PlayerAction playerAction, Game game, Object da
29292930
protected boolean passWithManaPoolCheck(Game game) {
29302931
if (userData.confirmEmptyManaPool()
29312932
&& game.getStack().isEmpty() && getManaPool().count() > 0 && getManaPool().canLostManaOnEmpty()) {
2932-
String activePlayerText;
2933-
if (game.isActivePlayer(playerId)) {
2934-
activePlayerText = "Your turn";
2935-
} else {
2936-
activePlayerText = game.getPlayer(game.getActivePlayerId()).getName() + "'s turn";
2937-
}
2938-
String priorityPlayerText = "";
2939-
if (!isGameUnderControl()) {
2940-
priorityPlayerText = " / priority " + game.getPlayer(game.getPriorityPlayerId()).getName();
2941-
}
2942-
// TODO: chooseUse and other dialogs must be under controlling player
2943-
if (!chooseUse(Outcome.Detriment, GameLog.getPlayerConfirmColoredText("You still have mana in your mana pool and it will be lose. Pass anyway?")
2944-
+ GameLog.getSmallSecondLineText(activePlayerText + " / " + game.getTurnStepType().toString() + priorityPlayerText), null, game)) {
2933+
String message = GameLog.getPlayerConfirmColoredText("You still have mana in your mana pool and it will be lose. Pass anyway?");
2934+
if (!chooseUse(Outcome.Detriment, message, null, game)) {
29452935
sendPlayerAction(PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS, game, null);
29462936
return false;
29472937
}

Mage.Server/src/main/java/mage/server/game/GameController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -906,14 +906,14 @@ private synchronized void multiAmount(UUID playerId, final List<MultiAmountMessa
906906
perform(playerId, playerId1 -> getGameSession(playerId1).getMultiAmount(messages, min, max, options));
907907
}
908908

909-
private void informOthers(UUID playerId) {
909+
private void informOthers(UUID waitingPlayerId) {
910910
StringBuilder message = new StringBuilder();
911911
if (game.getStep() != null) {
912912
message.append(game.getTurnStepType().toString()).append(" - ");
913913
}
914-
message.append("Waiting for ").append(game.getPlayer(playerId).getLogName());
914+
message.append("Waiting for ").append(game.getPlayer(waitingPlayerId).getLogName());
915915
for (final Entry<UUID, GameSessionPlayer> entry : getGameSessionsMap().entrySet()) {
916-
if (!entry.getKey().equals(playerId)) {
916+
if (!entry.getKey().equals(waitingPlayerId)) {
917917
entry.getValue().inform(message.toString());
918918
}
919919
}
@@ -1030,7 +1030,7 @@ private void perform(UUID playerId, Command command, boolean informOthers) {
10301030
// TODO: if watcher disconnects then game freezes with active timer, must be fix for such use case
10311031
// same for another player (can be fixed by super-duper connection)
10321032
if (informOthers) {
1033-
informOthers(playerId);
1033+
informOthers(realPlayerController.getId());
10341034
}
10351035
}
10361036

Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,14 @@ public static GameView prepareGameView(Game game, UUID playerId, UUID userId) {
212212
// game view calculation can take some time and can be called from non-game thread,
213213
// so use copy for thread save (protection from ConcurrentModificationException)
214214
Game sourceGame = game.copy();
215+
GameView gameView = new GameView(sourceGame.getState(), sourceGame, playerId, null);
215216

217+
// playable info (if opponent under control then show opponent's playable)
216218
Player player = sourceGame.getPlayer(playerId); // null for watcher
217-
GameView gameView = new GameView(sourceGame.getState(), sourceGame, playerId, null);
218-
if (player != null) {
219-
if (gameView.getPriorityPlayerName().equals(player.getName())) {
220-
gameView.setCanPlayObjects(player.getPlayableObjects(sourceGame, Zone.ALL));
221-
}
219+
Player priorityPlayer = sourceGame.getPlayer(sourceGame.getPriorityPlayerId());
220+
Player controllingPlayer = priorityPlayer == null ? null : sourceGame.getPlayer(priorityPlayer.getTurnControlledBy());
221+
if (controllingPlayer != null && player == controllingPlayer) {
222+
gameView.setCanPlayObjects(priorityPlayer.getPlayableObjects(sourceGame, Zone.ALL));
222223
}
223224

224225
processControlledPlayers(sourceGame, player, gameView);

Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,17 @@ public void onCallback(ClientCallback callback) {
104104
GameClientMessage message = (GameClientMessage) callback.getData();
105105
this.gameView = message.getGameView();
106106
log.info(getLogStartInfo() + " target: " + message.getMessage());
107-
switch (message.getMessage()) {
108-
case "Select a starting player":
109-
session.sendPlayerUUID(gameId, playerId);
110-
return;
111-
case "Select a card to discard":
112-
log.info(getLogStartInfo() + "hand size: " + gameView.getMyHand().size());
113-
SimpleCardView card = gameView.getMyHand().values().iterator().next();
114-
session.sendPlayerUUID(gameId, card.getId());
115-
return;
116-
default:
117-
log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString());
118-
return;
107+
if (message.getMessage().startsWith("Select a starting player")) {
108+
session.sendPlayerUUID(gameId, playerId);
109+
return;
110+
} else if (message.getMessage().startsWith("Select a card to discard")) {
111+
log.info(getLogStartInfo() + "hand size: " + gameView.getMyHand().size());
112+
SimpleCardView card = gameView.getMyHand().values().iterator().next();
113+
session.sendPlayerUUID(gameId, card.getId());
114+
return;
115+
} else {
116+
log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString());
117+
return;
119118
}
120119
}
121120

Mage/src/main/java/mage/abilities/common/delayed/AtTheEndOfTurnStepPostDelayedTriggeredAbility.java

Lines changed: 0 additions & 42 deletions
This file was deleted.

Mage/src/main/java/mage/game/Game.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,12 @@ default boolean isOpponent(Player player, UUID playerToCheckId) {
550550
@Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
551551
boolean checkStateAndTriggered();
552552

553+
/**
554+
* Play priority by all players
555+
*
556+
* @param activePlayerId starting priority player
557+
* @param resuming false to reset passed priority and ask it again
558+
*/
553559
void playPriority(UUID activePlayerId, boolean resuming);
554560

555561
void resetControlAfterSpellResolve(UUID topId);

0 commit comments

Comments
 (0)