Skip to content

Commit

Permalink
Turn under control reworked:
Browse files Browse the repository at this point in the history
 - 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;
  • Loading branch information
JayDi85 committed Jan 7, 2025
1 parent 75d241d commit c076f49
Show file tree
Hide file tree
Showing 17 changed files with 175 additions and 138 deletions.
32 changes: 32 additions & 0 deletions Mage.Client/src/main/java/mage/client/game/GamePanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
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;
Expand All @@ -53,6 +55,7 @@
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.*;
Expand Down Expand Up @@ -1810,6 +1813,7 @@ private void prepareSelectableView() {

// 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);
Expand All @@ -1821,6 +1825,34 @@ private void prepareSelectableView() {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -1159,25 +1160,27 @@ public boolean priority(Game game) {
// 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);
Expand Down Expand Up @@ -1252,7 +1255,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
// it's main step
if (!skippedAtLeastOnce
|| (!playerId.equals(game.getActivePlayerId())
&& !controllingPlayer.getUserData().getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
&& !controllingUserData.getUserSkipPrioritySteps().isStopOnAllMainPhases())) {
skippedAtLeastOnce = true;
if (passWithManaPoolCheck(game)) {
return false;
Expand All @@ -1274,8 +1277,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
// it's end of turn step
if (!skippedAtLeastOnce
|| (playerId.equals(game.getActivePlayerId())
&& !controllingPlayer
.getUserData()
&& !controllingUserData
.getUserSkipPrioritySteps()
.isStopOnAllEndPhases())) {
skippedAtLeastOnce = true;
Expand All @@ -1295,7 +1297,7 @@ && getJustActivatedType().isNonManaActivatedAbility()) {
}

if (!dontCheckPassStep
&& checkPassStep(game, controllingPlayer)) {
&& checkPassStep(game, controllingUserData)) {
if (passWithManaPoolCheck(game)) {
return false;
}
Expand All @@ -1308,8 +1310,7 @@ && checkPassStep(game, controllingPlayer)) {
if (passedUntilStackResolved) {
if (haveNewObjectsOnStack
&& (playerId.equals(game.getActivePlayerId())
&& controllingPlayer
.getUserData()
&& controllingUserData
.getUserSkipPrioritySteps()
.isStopOnStackNewObjects())) {
// new objects on stack -- disable "pass until stack resolved"
Expand Down Expand Up @@ -1433,17 +1434,17 @@ private UUID getFixedResponseUUID(Game game) {
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");
Expand Down Expand Up @@ -2929,19 +2930,8 @@ private void setTriggerAutoOrder(PlayerAction playerAction, Game game, Object da
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -906,14 +906,14 @@ private synchronized void multiAmount(UUID playerId, final List<MultiAmountMessa
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());
}
}
Expand Down Expand Up @@ -1030,7 +1030,7 @@ private void perform(UUID playerId, Command command, boolean informOthers) {
// 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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,14 @@ public static GameView prepareGameView(Game game, UUID playerId, UUID userId) {
// 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();
GameView gameView = new GameView(sourceGame.getState(), sourceGame, playerId, null);

// playable info (if opponent under control then show opponent's playable)
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));
}
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);
Expand Down
23 changes: 11 additions & 12 deletions Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,17 @@ public void onCallback(ClientCallback callback) {
GameClientMessage message = (GameClientMessage) callback.getData();
this.gameView = message.getGameView();
log.info(getLogStartInfo() + " target: " + message.getMessage());
switch (message.getMessage()) {
case "Select a starting player":
session.sendPlayerUUID(gameId, playerId);
return;
case "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:
log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString());
return;
if (message.getMessage().startsWith("Select a starting player")) {
session.sendPlayerUUID(gameId, playerId);
return;
} 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;
} else {
log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString());
return;
}
}

Expand Down

This file was deleted.

6 changes: 6 additions & 0 deletions Mage/src/main/java/mage/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,12 @@ default boolean isOpponent(Player player, UUID playerToCheckId) {
@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);
Expand Down
Loading

0 comments on commit c076f49

Please sign in to comment.