mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
Improved network stability and other related fixes:
* server: fixed that a critical errors ignored in user commands threads (now it will be added to the logs); * network: fixed frozen user responses in some use cases; * network: fixed accidental and incorrect user responses (only latest response will be used now); * network: improved freeze logs, added problem method name and code's line number; * cheats: removed outdated deck and card load logic (only init.txt commands supports now); * cheats: fixed wrong priority after add card dialog (closes #11437); * cheats: improved stability and random errors on cheat executes (related to #11437); * docs: added details on network and thread logic, human feedback life cycle, etc (see HumanPlayer, ThreadExecutorImpl);
This commit is contained in:
parent
4ba3e1fec5
commit
53add71826
33 changed files with 476 additions and 273 deletions
|
|
@ -225,8 +225,8 @@ public final class SessionHandler {
|
||||||
return session.isTestMode();
|
return session.isTestMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void cheat(UUID gameId, UUID playerId, DeckCardLists deckCardLists) {
|
public static void cheatShow(UUID gameId, UUID playerId) {
|
||||||
session.cheat(gameId, playerId, deckCardLists);
|
session.cheatShow(gameId, playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSessionId() {
|
public static String getSessionId() {
|
||||||
|
|
|
||||||
|
|
@ -588,7 +588,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnCheatActionPerformed(java.awt.event.ActionEvent evt) {
|
private void btnCheatActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
SessionHandler.cheat(gameId, playerId, DeckImporter.importDeckFromFile("cheat.dck", false));
|
SessionHandler.cheatShow(gameId, playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSmallMode() {
|
public boolean isSmallMode() {
|
||||||
|
|
|
||||||
|
|
@ -970,8 +970,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnCheatActionPerformed(java.awt.event.ActionEvent evt) {
|
private void btnCheatActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
DckDeckImporter deckImporter = new DckDeckImporter();
|
SessionHandler.cheatShow(gameId, playerId);
|
||||||
SessionHandler.cheat(gameId, playerId, deckImporter.importDeck("cheat.dck", false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnToolHintsHelperActionPerformed(java.awt.event.ActionEvent evt) {
|
private void btnToolHintsHelperActionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,4 @@ public interface CardImageSource {
|
||||||
default boolean isTokenImageProvided(String setCode, String cardName, Integer tokenNumber) {
|
default boolean isTokenImageProvided(String setCode, String cardName, Integer tokenNumber) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default int getDownloadTimeoutMs() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,9 +155,7 @@ public interface MageServer {
|
||||||
|
|
||||||
void replaySkipForward(UUID gameId, String sessionId, int moves) throws MageException;
|
void replaySkipForward(UUID gameId, String sessionId, int moves) throws MageException;
|
||||||
|
|
||||||
void cheatMultiple(UUID gameId, String sessionId, UUID playerId, DeckCardLists deckList) throws MageException;
|
void cheatShow(UUID gameId, String sessionId, UUID playerId) throws MageException;
|
||||||
|
|
||||||
boolean cheatOne(UUID gameId, String sessionId, UUID playerId, String cardName) throws MageException;
|
|
||||||
|
|
||||||
List<UserView> adminGetUsers(String sessionId) throws MageException;
|
List<UserView> adminGetUsers(String sessionId) throws MageException;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1499,10 +1499,10 @@ public class SessionImpl implements Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean cheat(UUID gameId, UUID playerId, DeckCardLists deckList) {
|
public boolean cheatShow(UUID gameId, UUID playerId) {
|
||||||
try {
|
try {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
server.cheatMultiple(gameId, sessionId, playerId, deckList);
|
server.cheatShow(gameId, sessionId, playerId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (MageException ex) {
|
} catch (MageException ex) {
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,5 @@ public interface Testable {
|
||||||
|
|
||||||
boolean isTestMode();
|
boolean isTestMode();
|
||||||
|
|
||||||
boolean cheat(UUID gameId, UUID playerId, DeckCardLists deckList);
|
boolean cheatShow(UUID gameId, UUID playerId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package mage.server.util;
|
package mage.utils;
|
||||||
|
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
|
@ -248,23 +248,15 @@ public final class SystemUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces cards in player's hands by specified in config/init.txt.<br/>
|
* Execute cheat commands from an init.txt file, for more details see
|
||||||
* <br/>
|
* <a href="https://github.com/magefree/mage/wiki/Development-Testing-Tools">wiki page</a>
|
||||||
* <b>Implementation note:</b><br/>
|
* about testing tools
|
||||||
* 1. Read init.txt line by line<br/>
|
|
||||||
* 2. Parse line using for searching groups like: [group 1] 3. Parse line
|
|
||||||
* using the following format: line ::=
|
|
||||||
* <zone>:<nickname>:<card name>:<amount><br/>
|
|
||||||
* 4. If zone equals to 'hand', add card to player's library<br/>
|
|
||||||
* 5a. Then swap added card with any card in player's hand<br/>
|
|
||||||
* 5b. Parse next line (go to 2.), If EOF go to 4.<br/>
|
|
||||||
* 6. Log message to all players that cards were added (to prevent unfair
|
|
||||||
* play).<br/>
|
|
||||||
* 7. Exit<br/>
|
|
||||||
*
|
*
|
||||||
* @param game
|
* @param game
|
||||||
|
* @param commandsFilePath file path with commands in init.txt format
|
||||||
|
* @param feedbackPlayer player to execute that cheats (will see choose dialogs)
|
||||||
*/
|
*/
|
||||||
public static void addCardsForTesting(Game game, String fileSource, Player feedbackPlayer) {
|
public static void executeCheatCommands(Game game, String commandsFilePath, Player feedbackPlayer) {
|
||||||
|
|
||||||
// fake test ability for triggers and events
|
// fake test ability for triggers and events
|
||||||
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
|
Ability fakeSourceAbilityTemplate = new SimpleStaticAbility(Zone.OUTSIDE, new InfoEffect("adding testing cards"));
|
||||||
|
|
@ -272,7 +264,7 @@ public final class SystemUtil {
|
||||||
|
|
||||||
List<String> errorsList = new ArrayList<>();
|
List<String> errorsList = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
String fileName = fileSource;
|
String fileName = commandsFilePath;
|
||||||
if (fileName == null) {
|
if (fileName == null) {
|
||||||
fileName = INIT_FILE_PATH;
|
fileName = INIT_FILE_PATH;
|
||||||
}
|
}
|
||||||
|
|
@ -755,12 +747,13 @@ public final class SystemUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String mes = String.format("Catch critical error: %s", e.getMessage());
|
String mes = String.format("Catch critical error on cheating: %s", e.getMessage());
|
||||||
errorsList.add(mes);
|
errorsList.add(mes);
|
||||||
logger.error(mes, e);
|
logger.error(mes, e);
|
||||||
}
|
} finally {
|
||||||
sendCheatCommandsFeedback(game, feedbackPlayer, errorsList);
|
sendCheatCommandsFeedback(game, feedbackPlayer, errorsList);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void sendCheatCommandsFeedback(Game game, Player feedbackPlayer, List<String> errorsList) {
|
private static void sendCheatCommandsFeedback(Game game, Player feedbackPlayer, List<String> errorsList) {
|
||||||
// inform all players about wrong commands or other errors
|
// inform all players about wrong commands or other errors
|
||||||
|
|
@ -907,4 +900,21 @@ public final class SystemUtil {
|
||||||
}
|
}
|
||||||
return Arrays.asList(cardSet, cardName);
|
return Arrays.asList(cardSet, cardName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ensureRunInGameThread() {
|
||||||
|
String name = Thread.currentThread().getName();
|
||||||
|
if (!name.startsWith("GAME")) {
|
||||||
|
// how-to fix: use signal logic to inform a game about new command to execute instead direct execute (see example with WantConcede)
|
||||||
|
// reason: user responses/commands are received by network/call thread, but must be processed by game thread
|
||||||
|
throw new IllegalArgumentException("Wrong code usage: game related code must run in GAME thread, but it used in " + name, new Throwable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ensureRunInCallThread() {
|
||||||
|
String name = Thread.currentThread().getName();
|
||||||
|
if (!name.startsWith("CALL")) {
|
||||||
|
// how-to fix: something wrong in your code logic
|
||||||
|
throw new IllegalArgumentException("Wrong code usage: client commands code must run in CALL threads, but used in " + name, new Throwable());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ import mage.target.common.TargetAttackingCreature;
|
||||||
import mage.target.common.TargetDefender;
|
import mage.target.common.TargetDefender;
|
||||||
import mage.target.targetpointer.TargetPointer;
|
import mage.target.targetpointer.TargetPointer;
|
||||||
import mage.util.*;
|
import mage.util.*;
|
||||||
|
import mage.utils.SystemUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
@ -58,7 +59,7 @@ import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
||||||
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class HumanPlayer extends PlayerImpl {
|
public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
|
|
@ -66,8 +67,36 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
// TODO: all user feedback actions executed and waited in diff threads and can't catch exeptions, e.g. on wrong code usage
|
// TODO: all user feedback actions executed and waited in diff threads and can't catch exeptions, e.g. on wrong code usage
|
||||||
// must catch and log such errors
|
// must catch and log such errors
|
||||||
private transient Boolean responseOpenedForAnswer = false; // can't get response until prepared target (e.g. until send all fire events to all players)
|
|
||||||
|
// Network and threads logic:
|
||||||
|
// * starting point: ThreadExecutorImpl.java
|
||||||
|
// * server executing a game's code by single game thread (named: "GAME xxx", one thread per game)
|
||||||
|
// * data transfering goes by inner jboss threads (named: "WorkerThread xxx", one thread per client connection)
|
||||||
|
// - from server to client: sync mode, a single game thread uses jboss networking to send data to each player and viewer/watcher one by one
|
||||||
|
// - from client to server: async mode, each income command executes by shared call thread
|
||||||
|
//
|
||||||
|
// Client commands logic:
|
||||||
|
// * commands can do anything in server, user, table and game contexts
|
||||||
|
// * it's income in async mode at any time and in any order by "CALL xxx" threads
|
||||||
|
// * there are two types of client commands:
|
||||||
|
// - feedback commands - must be executed and synced by GAME thread (example: answer for choose dialog, priority, etc)
|
||||||
|
// - other commands - can be executed at any time and don't require sync with a game and a GAME thread (example: user/skip settings, concede/cheat, chat messages)
|
||||||
|
//
|
||||||
|
// Feedback commands logic:
|
||||||
|
// * income by CALL thread
|
||||||
|
// * must be synced and executed by GAME thread
|
||||||
|
// * keep only latest income feedback (if user sends multiple clicks/choices)
|
||||||
|
// * HumanPlayer contains "response" object for threads sync and data exchange
|
||||||
|
// * so sync logic:
|
||||||
|
// * - 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);
|
||||||
|
private transient Boolean responseOpenedForAnswer = false; // GAME thread waiting new answer
|
||||||
|
private transient long responseLastWaitingThreadId = 0;
|
||||||
private final transient PlayerResponse response = new PlayerResponse();
|
private final transient PlayerResponse response = new PlayerResponse();
|
||||||
|
private final int RESPONSE_WAITING_TIME_SECS = 30; // waiting time before cancel current response
|
||||||
|
private final int RESPONSE_WAITING_CHECK_MS = 100; // timeout for open status check
|
||||||
|
|
||||||
protected static FilterCreatureForCombatBlock filterCreatureForCombatBlock = new FilterCreatureForCombatBlock();
|
protected static FilterCreatureForCombatBlock filterCreatureForCombatBlock = new FilterCreatureForCombatBlock();
|
||||||
protected static FilterCreatureForCombat filterCreatureForCombat = new FilterCreatureForCombat();
|
protected static FilterCreatureForCombat filterCreatureForCombat = new FilterCreatureForCombat();
|
||||||
|
|
@ -141,24 +170,68 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|| (actionIterations > 0 && !actionQueueSaved.isEmpty()));
|
|| (actionIterations > 0 && !actionQueueSaved.isEmpty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void waitResponseOpen() {
|
/**
|
||||||
// wait response open for answer process
|
* Waiting for opened response and save new value in it
|
||||||
int numTimesWaiting = 0;
|
* Use it in CALL threads only, e.g. for client commands
|
||||||
|
*
|
||||||
|
* @return on true result game can use response value, on false result - it's outdated response
|
||||||
|
*/
|
||||||
|
protected boolean waitResponseOpen() {
|
||||||
|
// client send commands in async mode and can come too early
|
||||||
|
// so if next command come too fast then wait here until game ready
|
||||||
|
//
|
||||||
|
// example with too early response:
|
||||||
|
// * game must send new update to 3 users and ask user 2 for feedback answer, but user 3 is too slow
|
||||||
|
// +0 secs: start sending data to 3 players
|
||||||
|
// +0 secs: user 1 getting data
|
||||||
|
// +1 secs: user 1 done
|
||||||
|
// +1 secs: user 2 getting data
|
||||||
|
// +3 secs: user 2 done
|
||||||
|
// +3 secs: user 3 getting data (it's slow)
|
||||||
|
// +4 secs: user 2 sent answer 1 (but game closed for it, so waiting)
|
||||||
|
// +5 secs: user 2 sent answer 2 (he can't see changes after answer 1, so trying again - server must keep only latest answer)
|
||||||
|
// +8 secs: user 3 done
|
||||||
|
// +8 secs: game start wating a new answer from user 2
|
||||||
|
// +8 secs: game find answer
|
||||||
|
|
||||||
|
int currentTimesWaiting = 0;
|
||||||
|
int maxTimesWaiting = RESPONSE_WAITING_TIME_SECS * 1000 / RESPONSE_WAITING_CHECK_MS;
|
||||||
|
long currentThreadId = Thread.currentThread().getId();
|
||||||
|
// it's a latest response
|
||||||
|
responseLastWaitingThreadId = currentThreadId;
|
||||||
while (!responseOpenedForAnswer && canRespond()) {
|
while (!responseOpenedForAnswer && canRespond()) {
|
||||||
numTimesWaiting++;
|
if (responseLastWaitingThreadId != currentThreadId) {
|
||||||
if (numTimesWaiting >= 300) {
|
// there is another latest response, so cancel current
|
||||||
// game frozen -- need to report about error and continue to execute
|
return false;
|
||||||
String s = "Game frozen in waitResponseOpen for user " + getName() + " (connection problem)";
|
}
|
||||||
logger.warn(s);
|
|
||||||
break;
|
// keep waiting
|
||||||
|
currentTimesWaiting++;
|
||||||
|
if (currentTimesWaiting > maxTimesWaiting) {
|
||||||
|
// game frozen, possible reasons:
|
||||||
|
// * ANOTHER player lost connection and GAME thread trying to send data to him
|
||||||
|
// * current player send answer, but lost connect after it
|
||||||
|
String possibleReason;
|
||||||
|
if (response.getActiveAction() == null) {
|
||||||
|
possibleReason = "maybe connection problem with another player/watcher";
|
||||||
|
} else {
|
||||||
|
possibleReason = "something wrong with your priority on " + response.getActiveAction();
|
||||||
|
}
|
||||||
|
logger.warn(String.format("Game frozen in waitResponseOpen for %d secs: current user %s, %s",
|
||||||
|
RESPONSE_WAITING_CHECK_MS * currentTimesWaiting / 1000,
|
||||||
|
this.getName(),
|
||||||
|
possibleReason
|
||||||
|
));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(100);
|
Thread.sleep(RESPONSE_WAITING_CHECK_MS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException ignore) {
|
||||||
logger.warn("Response waiting interrupted for " + getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true; // can use new value
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean pullResponseFromQueue(Game game) {
|
protected boolean pullResponseFromQueue(Game game) {
|
||||||
|
|
@ -179,7 +252,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
//waitResponseOpen(); // it's a macro action, no need it here?
|
//waitResponseOpen(); // it's a macro action, no need it here?
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.copy(action);
|
response.copyFrom(action);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
macroTriggeredSelectionFlag = false;
|
macroTriggeredSelectionFlag = false;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -188,12 +261,39 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare priority player for new feedback, call it for every choose cycle before waitForResponse
|
||||||
|
*/
|
||||||
protected void prepareForResponse(Game game) {
|
protected void prepareForResponse(Game game) {
|
||||||
//logger.info("Prepare waiting " + getId());
|
SystemUtil.ensureRunInGameThread();
|
||||||
|
|
||||||
|
// prepare priority player
|
||||||
|
// on null - it's a discard in cleanaup and other non-user code, so don't change it here at that moment
|
||||||
|
// TODO: must research null use case
|
||||||
|
if (game.getState().getPriorityPlayerId() != null) {
|
||||||
|
if (getId() == null) {
|
||||||
|
logger.fatal("Player with no ID: " + name);
|
||||||
|
this.quit(game);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.debug("Setting game priority for " + getId() + " [" + DebugUtil.getMethodNameWithSource(2) + ']');
|
||||||
|
}
|
||||||
|
game.getState().setPriorityPlayerId(getId());
|
||||||
|
}
|
||||||
|
|
||||||
responseOpenedForAnswer = false;
|
responseOpenedForAnswer = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waiting feedback from priority player
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
*/
|
||||||
protected void waitForResponse(Game game) {
|
protected void waitForResponse(Game game) {
|
||||||
|
SystemUtil.ensureRunInGameThread();
|
||||||
|
;
|
||||||
|
|
||||||
if (isExecutingMacro()) {
|
if (isExecutingMacro()) {
|
||||||
pullResponseFromQueue(game);
|
pullResponseFromQueue(game);
|
||||||
// logger.info("MACRO pull from queue: " + response.toString());
|
// logger.info("MACRO pull from queue: " + response.toString());
|
||||||
|
|
@ -204,36 +304,46 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait player's answer loop
|
|
||||||
boolean loop = true;
|
boolean loop = true;
|
||||||
while (loop) {
|
while (loop) {
|
||||||
// start waiting for next answer
|
// start waiting for next answer
|
||||||
response.clear();
|
response.clear();
|
||||||
|
response.setActiveAction(DebugUtil.getMethodNameWithSource(2));
|
||||||
game.resumeTimer(getTurnControlledBy());
|
game.resumeTimer(getTurnControlledBy());
|
||||||
responseOpenedForAnswer = true;
|
responseOpenedForAnswer = true;
|
||||||
|
|
||||||
loop = false;
|
loop = false;
|
||||||
|
synchronized (response) { // TODO: synchronized response smells bad here, possible deadlocks? Need research
|
||||||
synchronized (response) {
|
|
||||||
try {
|
try {
|
||||||
response.wait();
|
response.wait(); // start waiting a response.notifyAll command from CALL thread (client answer)
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ignore) {
|
||||||
logger.error("Response error for player " + getName() + " gameId: " + game.getId(), ex);
|
|
||||||
} finally {
|
} finally {
|
||||||
responseOpenedForAnswer = false;
|
responseOpenedForAnswer = false;
|
||||||
game.pauseTimer(getTurnControlledBy());
|
game.pauseTimer(getTurnControlledBy());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async command: concede by any player
|
||||||
// game recived immediately response on OTHER player concede -- need to process end game and continue to wait
|
// game recived immediately response on OTHER player concede -- need to process end game and continue to wait
|
||||||
if (response.getResponseConcedeCheck()) {
|
// TODO: is it possible to break choose dialog of current player (check it in multiplayer)?
|
||||||
|
if (response.getAsyncWantConcede()) {
|
||||||
((GameImpl) game).checkConcede();
|
((GameImpl) game).checkConcede();
|
||||||
if (game.hasEnded()) {
|
if (game.hasEnded()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canRespond()) {
|
|
||||||
// wait another answer
|
// wait another answer
|
||||||
|
if (canRespond()) {
|
||||||
|
loop = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// async command: cheat by current player
|
||||||
|
if (response.getAsyncWantCheat()) {
|
||||||
|
// execute cheats and continue
|
||||||
|
SystemUtil.executeCheatCommands(game, null, this);
|
||||||
|
game.fireUpdatePlayersEvent(); // need force to game update for new possible data
|
||||||
|
// wait another answer
|
||||||
|
if (canRespond()) {
|
||||||
loop = true;
|
loop = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +375,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("UI.left.btn.text", "Mulligan");
|
options.put("UI.left.btn.text", "Mulligan");
|
||||||
options.put("UI.right.btn.text", "Keep");
|
options.put("UI.right.btn.text", "Keep");
|
||||||
|
|
||||||
updateGameStatePriority("chooseMulligan", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireAskPlayerEvent(playerId, new MessageToClient(message), null, options);
|
game.fireAskPlayerEvent(playerId, new MessageToClient(message), null, options);
|
||||||
|
|
@ -323,7 +432,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
messageToClient.setSecondMessage(getRelatedObjectName(source, game));
|
messageToClient.setSecondMessage(getRelatedObjectName(source, game));
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("chooseUse", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireAskPlayerEvent(playerId, messageToClient, source, options);
|
game.fireAskPlayerEvent(playerId, messageToClient, source, options);
|
||||||
|
|
@ -415,7 +523,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("chooseEffect", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireChooseChoiceEvent(playerId, replacementEffectChoice);
|
game.fireChooseChoiceEvent(playerId, replacementEffectChoice);
|
||||||
|
|
@ -493,7 +600,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("choose(3)", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireChooseChoiceEvent(playerId, choice);
|
game.fireChooseChoiceEvent(playerId, choice);
|
||||||
|
|
@ -556,7 +662,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
List<UUID> chosenTargets = target.getTargets();
|
List<UUID> chosenTargets = target.getTargets();
|
||||||
options.put("chosenTargets", (Serializable) chosenTargets);
|
options.put("chosenTargets", (Serializable) chosenTargets);
|
||||||
|
|
||||||
updateGameStatePriority("choose(5)", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargetIds, required, getOptions(target, options));
|
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargetIds, required, getOptions(target, options));
|
||||||
|
|
@ -658,7 +763,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
List<UUID> chosenTargets = target.getTargets();
|
List<UUID> chosenTargets = target.getTargets();
|
||||||
options.put("chosenTargets", (Serializable) chosenTargets);
|
options.put("chosenTargets", (Serializable) chosenTargets);
|
||||||
|
|
||||||
updateGameStatePriority("chooseTarget", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)),
|
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)),
|
||||||
|
|
@ -757,7 +861,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("choose(4)", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options);
|
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options);
|
||||||
|
|
@ -841,7 +944,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("chooseTarget(5)", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options);
|
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options);
|
||||||
|
|
@ -936,7 +1038,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("possibleTargets", (Serializable) possibleTargets);
|
options.put("possibleTargets", (Serializable) possibleTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("chooseTargetAmount", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters";
|
String multiType = multiAmountType == MultiAmountType.DAMAGE ? " to divide %d damage" : " to distribute %d counters";
|
||||||
|
|
@ -1204,7 +1305,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
holdingPriority = false;
|
holdingPriority = false;
|
||||||
|
|
||||||
updateGameStatePriority("priority", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.firePriorityEvent(playerId);
|
game.firePriorityEvent(playerId);
|
||||||
|
|
@ -1428,7 +1528,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
macroTriggeredSelectionFlag = true;
|
macroTriggeredSelectionFlag = true;
|
||||||
updateGameStatePriority("chooseTriggeredAbility", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetTriggeredAbilityEvent(playerId, "Pick triggered ability (goes to the stack first)", abilitiesWithNoOrderSet);
|
game.fireSelectTargetTriggeredAbilityEvent(playerId, "Pick triggered ability (goes to the stack first)", abilitiesWithNoOrderSet);
|
||||||
|
|
@ -1477,7 +1576,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
// TODO: make canRespond cycle?
|
// TODO: make canRespond cycle?
|
||||||
if (canRespond()) {
|
if (canRespond()) {
|
||||||
Map<String, Serializable> options = new HashMap<>();
|
Map<String, Serializable> options = new HashMap<>();
|
||||||
updateGameStatePriority("playMana", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.firePlayManaEvent(playerId, "Pay " + promptText, options);
|
game.firePlayManaEvent(playerId, "Pay " + promptText, options);
|
||||||
|
|
@ -1486,17 +1584,19 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
UUID responseId = getFixedResponseUUID(game);
|
UUID responseId = getFixedResponseUUID(game);
|
||||||
if (response.getBoolean() != null) {
|
if (response.getBoolean() != null) {
|
||||||
|
// cancel
|
||||||
return false;
|
return false;
|
||||||
} else if (responseId != null) {
|
} else if (responseId != null) {
|
||||||
|
// pay from mana object
|
||||||
playManaAbilities(responseId, abilityToCast, unpaid, game);
|
playManaAbilities(responseId, abilityToCast, unpaid, game);
|
||||||
} else if (response.getString() != null
|
} else if (response.getString() != null && response.getString().equals("special")) {
|
||||||
&& response.getString().equals("special")) {
|
// pay from special action
|
||||||
if (unpaid instanceof ManaCostsImpl) {
|
if (unpaid instanceof ManaCostsImpl) {
|
||||||
activateSpecialAction(game, unpaid);
|
activateSpecialAction(game, unpaid);
|
||||||
}
|
}
|
||||||
} else if (response.getManaType() != null) {
|
} else if (response.getManaType() != null) {
|
||||||
// this mana type can be paid once from pool
|
// pay from own mana pool
|
||||||
if (response.getResponseManaTypePlayerId().equals(this.getId())) {
|
if (response.getManaPlayerId().equals(this.getId())) {
|
||||||
this.getManaPool().unlockManaType(response.getManaType());
|
this.getManaPool().unlockManaType(response.getManaType());
|
||||||
}
|
}
|
||||||
// TODO: Handle if mana pool
|
// TODO: Handle if mana pool
|
||||||
|
|
@ -1521,7 +1621,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
int xValue = 0;
|
int xValue = 0;
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("announceRepetitions", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
game.fireGetAmountEvent(playerId, "How many times do you want to repeat your shortcut?", 0, 999);
|
game.fireGetAmountEvent(playerId, "How many times do you want to repeat your shortcut?", 0, 999);
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
|
|
@ -1557,7 +1656,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
int xValue = 0;
|
int xValue = 0;
|
||||||
String extraMessage = (multiplier == 1 ? "" : ", X will be increased by " + multiplier + " times");
|
String extraMessage = (multiplier == 1 ? "" : ", X will be increased by " + multiplier + " times");
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("announceXMana", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetAmountEvent(playerId, message + extraMessage, min, max);
|
game.fireGetAmountEvent(playerId, message + extraMessage, min, max);
|
||||||
|
|
@ -1583,7 +1681,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
int xValue = 0;
|
int xValue = 0;
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("announceXCost", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetAmountEvent(playerId, message, min, max);
|
game.fireGetAmountEvent(playerId, message, min, max);
|
||||||
|
|
@ -1602,7 +1699,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void playManaAbilities(UUID objectId, Ability abilityToCast, ManaCost unpaid, Game game) {
|
protected void playManaAbilities(UUID objectId, Ability abilityToCast, ManaCost unpaid, Game game) {
|
||||||
updateGameStatePriority("playManaAbilities", game);
|
|
||||||
MageObject object = game.getObject(objectId);
|
MageObject object = game.getObject(objectId);
|
||||||
if (object == null) {
|
if (object == null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1703,7 +1799,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put(Constants.Option.SPECIAL_BUTTON, "All attack");
|
options.put(Constants.Option.SPECIAL_BUTTON, "All attack");
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("selectAttackers", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectEvent(playerId, "Select attackers", options);
|
game.fireSelectEvent(playerId, "Select attackers", options);
|
||||||
|
|
@ -1930,7 +2025,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("selectBlockers", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
Map<String, Serializable> options = new HashMap<>();
|
Map<String, Serializable> options = new HashMap<>();
|
||||||
|
|
@ -1974,7 +2068,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("chooseAttackerOrder", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true);
|
game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true);
|
||||||
|
|
@ -2000,7 +2093,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("chooseBlockerOrder", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireSelectTargetEvent(playerId, "Pick blocker", blockers, true);
|
game.fireSelectTargetEvent(playerId, "Pick blocker", blockers, true);
|
||||||
|
|
@ -2032,7 +2124,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
UUID responseId = null;
|
UUID responseId = null;
|
||||||
|
|
||||||
updateGameStatePriority("selectCombatGroup", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
// possible attackers to block
|
// possible attackers to block
|
||||||
|
|
@ -2075,7 +2166,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void assignDamage(int damage, java.util.List<UUID> targets, String singleTargetName, UUID attackerId, Ability source, Game game) {
|
public void assignDamage(int damage, java.util.List<UUID> targets, String singleTargetName, UUID attackerId, Ability source, Game game) {
|
||||||
updateGameStatePriority("assignDamage", game);
|
|
||||||
int remainingDamage = damage;
|
int remainingDamage = damage;
|
||||||
while (remainingDamage > 0 && canRespond()) {
|
while (remainingDamage > 0 && canRespond()) {
|
||||||
Target target = new TargetAnyTarget();
|
Target target = new TargetAnyTarget();
|
||||||
|
|
@ -2083,9 +2173,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
if (singleTargetName != null) {
|
if (singleTargetName != null) {
|
||||||
target.setTargetName(singleTargetName);
|
target.setTargetName(singleTargetName);
|
||||||
}
|
}
|
||||||
choose(Outcome.Damage, target, source, game);
|
this.choose(Outcome.Damage, target, source, game);
|
||||||
if (targets.isEmpty() || targets.contains(target.getFirstTarget())) {
|
if (targets.isEmpty() || targets.contains(target.getFirstTarget())) {
|
||||||
int damageAmount = getAmount(0, remainingDamage, "Select amount", game);
|
int damageAmount = this.getAmount(0, remainingDamage, "Select amount", game);
|
||||||
Permanent permanent = game.getPermanent(target.getFirstTarget());
|
Permanent permanent = game.getPermanent(target.getFirstTarget());
|
||||||
if (permanent != null) {
|
if (permanent != null) {
|
||||||
permanent.damage(damageAmount, attackerId, source, game, false, true);
|
permanent.damage(damageAmount, attackerId, source, game, false, true);
|
||||||
|
|
@ -2108,7 +2198,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("getAmount", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetAmountEvent(playerId, message, min, max);
|
game.fireGetAmountEvent(playerId, message, min, max);
|
||||||
|
|
@ -2149,7 +2238,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
List<Integer> answer = null;
|
List<Integer> answer = null;
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("getMultiAmount", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
Map<String, Serializable> options = new HashMap<>(2);
|
Map<String, Serializable> options = new HashMap<>(2);
|
||||||
|
|
@ -2222,8 +2310,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(playerId, unpaidForManaAction != null);
|
Map<UUID, SpecialAction> specialActions = game.getState().getSpecialActions().getControlledBy(playerId, unpaidForManaAction != null);
|
||||||
if (!specialActions.isEmpty()) {
|
if (!specialActions.isEmpty()) {
|
||||||
|
|
||||||
updateGameStatePriority("specialAction", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values()));
|
game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values()));
|
||||||
|
|
@ -2253,8 +2339,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("activateAbility", game);
|
|
||||||
|
|
||||||
if (abilities.size() == 1
|
if (abilities.size() == 1
|
||||||
&& suppressAbilityPicker(abilities.values().iterator().next(), game)) {
|
&& suppressAbilityPicker(abilities.values().iterator().next(), game)) {
|
||||||
ActivatedAbility ability = abilities.values().iterator().next();
|
ActivatedAbility ability = abilities.values().iterator().next();
|
||||||
|
|
@ -2281,7 +2365,8 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
message = message + "<br>" + object.getLogName();
|
message = message + "<br>" + object.getLogName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add canRespond cycle?
|
// it's inner method, parent code already uses while and canRespond cycle,
|
||||||
|
// so can request one time here
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetChoiceEvent(playerId, message, object, new ArrayList<>(abilities.values()));
|
game.fireGetChoiceEvent(playerId, message, object, new ArrayList<>(abilities.values()));
|
||||||
|
|
@ -2305,6 +2390,8 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
*/
|
*/
|
||||||
private boolean suppressAbilityPicker(ActivatedAbility ability, Game game) {
|
private boolean suppressAbilityPicker(ActivatedAbility ability, Game game) {
|
||||||
if (getControllingPlayersUserData(game).isShowAbilityPickerForced()) {
|
if (getControllingPlayersUserData(game).isShowAbilityPickerForced()) {
|
||||||
|
// TODO: is it bugged on mana payment + under control?
|
||||||
|
// (if player under control then priority player must use own settings, not controlling)
|
||||||
// user activated an ability picker in preferences
|
// user activated an ability picker in preferences
|
||||||
|
|
||||||
// force to show ability picker for double faces cards in hand/commander/exile and other zones
|
// force to show ability picker for double faces cards in hand/commander/exile and other zones
|
||||||
|
|
@ -2355,7 +2442,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
} else if (useableAbilities != null
|
} else if (useableAbilities != null
|
||||||
&& !useableAbilities.isEmpty()) {
|
&& !useableAbilities.isEmpty()) {
|
||||||
|
|
||||||
updateGameStatePriority("chooseAbilityForCast", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetChoiceEvent(playerId, message, object, new ArrayList<>(useableAbilities.values()));
|
game.fireGetChoiceEvent(playerId, message, object, new ArrayList<>(useableAbilities.values()));
|
||||||
|
|
@ -2404,7 +2490,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
case 1:
|
case 1:
|
||||||
return useableAbilities.values().iterator().next();
|
return useableAbilities.values().iterator().next();
|
||||||
default:
|
default:
|
||||||
updateGameStatePriority("chooseLandOrSpellAbility", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
String message = "Choose spell or ability to play" + (noMana ? " for FREE" : "") + "<br>" + object.getLogName();
|
String message = "Choose spell or ability to play" + (noMana ? " for FREE" : "") + "<br>" + object.getLogName();
|
||||||
|
|
@ -2491,7 +2576,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
message = message + "<br>" + obj.getLogName();
|
message = message + "<br>" + obj.getLogName();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("chooseMode", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireGetModeEvent(playerId, message, modeMap);
|
game.fireGetModeEvent(playerId, message, modeMap);
|
||||||
|
|
@ -2541,7 +2625,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (canRespond()) {
|
while (canRespond()) {
|
||||||
updateGameStatePriority("choosePile", game);
|
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
game.fireChoosePileEvent(playerId, message, pile1, pile2);
|
game.fireChoosePileEvent(playerId, message, pile1, pile2);
|
||||||
|
|
@ -2562,7 +2645,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResponseString(String responseString) {
|
public void setResponseString(String responseString) {
|
||||||
waitResponseOpen();
|
if (!waitResponseOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setString(responseString);
|
response.setString(responseString);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
|
|
@ -2572,10 +2657,12 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResponseManaType(UUID manaTypePlayerId, ManaType manaType) {
|
public void setResponseManaType(UUID manaTypePlayerId, ManaType manaType) {
|
||||||
waitResponseOpen();
|
if (!waitResponseOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setManaType(manaType);
|
response.setManaType(manaType);
|
||||||
response.setResponseManaTypePlayerId(manaTypePlayerId);
|
response.setResponseManaPlayerId(manaTypePlayerId);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
logger.debug("Got response mana type from player: " + getId());
|
logger.debug("Got response mana type from player: " + getId());
|
||||||
}
|
}
|
||||||
|
|
@ -2583,7 +2670,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResponseUUID(UUID responseUUID) {
|
public void setResponseUUID(UUID responseUUID) {
|
||||||
waitResponseOpen();
|
if (!waitResponseOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setUUID(responseUUID);
|
response.setUUID(responseUUID);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
|
|
@ -2593,7 +2682,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResponseBoolean(Boolean responseBoolean) {
|
public void setResponseBoolean(Boolean responseBoolean) {
|
||||||
waitResponseOpen();
|
if (!waitResponseOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setBoolean(responseBoolean);
|
response.setBoolean(responseBoolean);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
|
|
@ -2603,7 +2694,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResponseInteger(Integer responseInteger) {
|
public void setResponseInteger(Integer responseInteger) {
|
||||||
waitResponseOpen();
|
if (!waitResponseOpen()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setInteger(responseInteger);
|
response.setInteger(responseInteger);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
|
|
@ -2613,8 +2706,8 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abort() {
|
public void abort() {
|
||||||
|
// abort must cancel any response and stop waiting immediately
|
||||||
abort = true;
|
abort = true;
|
||||||
waitResponseOpen();
|
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
logger.debug("Got cancel action from player: " + getId());
|
logger.debug("Got cancel action from player: " + getId());
|
||||||
|
|
@ -2623,17 +2716,28 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void signalPlayerConcede() {
|
public void signalPlayerConcede() {
|
||||||
//waitResponseOpen(); //concede is direct event, no need to wait it
|
// waitResponseOpen(); // concede is async event, will be processed on first priority
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setResponseConcedeCheck();
|
response.setAsyncWantConcede();
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
logger.debug("Set check concede for waiting player: " + getId());
|
logger.debug("Set check concede for waiting player: " + getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalPlayerCheat() {
|
||||||
|
// waitResponseOpen(); // cheat is async event, will be processed on first player's priority
|
||||||
|
synchronized (response) {
|
||||||
|
response.setAsyncWantCheat();
|
||||||
|
response.notifyAll();
|
||||||
|
logger.debug("Set cheat for waiting player: " + getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void skip() {
|
public void skip() {
|
||||||
// waitResponseOpen(); //skip is direct event, no need to wait it
|
// waitResponseOpen(); //skip is direct event, no need to wait it
|
||||||
|
// TODO: can be bugged and must be reworked, see wantConcede as example?!
|
||||||
synchronized (response) {
|
synchronized (response) {
|
||||||
response.setInteger(0);
|
response.setInteger(0);
|
||||||
response.notifyAll();
|
response.notifyAll();
|
||||||
|
|
@ -2646,20 +2750,6 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return new HumanPlayer(this);
|
return new HumanPlayer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateGameStatePriority(String methodName, Game game) {
|
|
||||||
// call that for every choose cycle before prepareForResponse
|
|
||||||
// (some choose logic can asks another question with different game state priority)
|
|
||||||
if (game.getState().getPriorityPlayerId() != null) { // don't do it if priority was set to null before (e.g. discard in cleanaup)
|
|
||||||
if (getId() == null) {
|
|
||||||
logger.fatal("Player with no ID: " + name);
|
|
||||||
this.quit(game);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug("Setting game priority to " + getId() + " [" + methodName + ']');
|
|
||||||
game.getState().setPriorityPlayerId(getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendPlayerAction(PlayerAction playerAction, Game game, Object data) {
|
public void sendPlayerAction(PlayerAction playerAction, Game game, Object data) {
|
||||||
switch (playerAction) {
|
switch (playerAction) {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,40 @@
|
||||||
|
|
||||||
package mage.player.human;
|
package mage.player.human;
|
||||||
|
|
||||||
|
import mage.constants.ManaType;
|
||||||
|
import mage.util.Copyable;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import mage.constants.ManaType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Network: server side data for waiting a user's response like new choice
|
||||||
|
* <p>
|
||||||
|
* Details:
|
||||||
|
* - one response object per user;
|
||||||
|
* - support multiple data types;
|
||||||
|
* - waiting and writing response on diff threads;
|
||||||
|
* - start by response.wait (game thread) and end by response.notifyAll (network/call thread)
|
||||||
|
* - user's request can income in diff order, so only one latest response allowed (except async commands like concede and cheat)
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class PlayerResponse implements Serializable {
|
public class PlayerResponse implements Serializable, Copyable<PlayerResponse> {
|
||||||
|
|
||||||
|
private String activeAction; // for debug information
|
||||||
|
|
||||||
private String responseString;
|
private String responseString;
|
||||||
private UUID responseUUID;
|
private UUID responseUUID;
|
||||||
private Boolean responseBoolean;
|
private Boolean responseBoolean;
|
||||||
private Integer responseInteger;
|
private Integer responseInteger;
|
||||||
private ManaType responseManaType;
|
private ManaType responseManaType;
|
||||||
private UUID responseManaTypePlayerId;
|
private UUID responseManaPlayerId;
|
||||||
private Boolean responseConcedeCheck;
|
|
||||||
|
// async commands can income any time from network thread as signal,
|
||||||
|
// must process it in game thread on any priority
|
||||||
|
// TODO: is concede/hand view confirmation can broken waiting cycle for other choosing/priority player
|
||||||
|
// (with same response type, with diff response type)???
|
||||||
|
private Boolean asyncWantConcede;
|
||||||
|
private Boolean asyncWantCheat;
|
||||||
|
|
||||||
public PlayerResponse() {
|
public PlayerResponse() {
|
||||||
clear();
|
clear();
|
||||||
|
|
@ -25,96 +42,122 @@ public class PlayerResponse implements Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return ((responseString == null) ? "null" : responseString)
|
return ((this.responseString == null) ? "null" : this.responseString)
|
||||||
+ ',' + responseUUID
|
+ "," + this.responseUUID
|
||||||
+ ',' + responseBoolean
|
+ "," + this.responseBoolean
|
||||||
+ ',' + responseInteger
|
+ "," + this.responseInteger
|
||||||
+ ',' + responseManaType
|
+ "," + this.responseManaType
|
||||||
+ ',' + responseManaTypePlayerId
|
+ "," + this.responseManaPlayerId
|
||||||
+ ',' + responseConcedeCheck;
|
+ "," + this.asyncWantConcede
|
||||||
|
+ "," + this.asyncWantCheat
|
||||||
|
+ " (" + (this.activeAction == null ? "sleep" : this.activeAction) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerResponse(PlayerResponse other) {
|
protected PlayerResponse(final PlayerResponse response) {
|
||||||
copy(other);
|
this.copyFrom(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copy(PlayerResponse other) {
|
@Override
|
||||||
responseString = other.responseString;
|
public PlayerResponse copy() {
|
||||||
responseUUID = other.responseUUID;
|
return new PlayerResponse(this);
|
||||||
responseBoolean = other.responseBoolean;
|
}
|
||||||
responseInteger = other.responseInteger;
|
|
||||||
responseManaType = other.responseManaType;
|
public void copyFrom(final PlayerResponse response) {
|
||||||
responseManaTypePlayerId = other.responseManaTypePlayerId;
|
this.activeAction = response.activeAction;
|
||||||
responseConcedeCheck = other.responseConcedeCheck;
|
this.responseString = response.responseString;
|
||||||
|
this.responseUUID = response.responseUUID;
|
||||||
|
this.responseBoolean = response.responseBoolean;
|
||||||
|
this.responseInteger = response.responseInteger;
|
||||||
|
this.responseManaType = response.responseManaType;
|
||||||
|
this.responseManaPlayerId = response.responseManaPlayerId;
|
||||||
|
this.asyncWantConcede = response.asyncWantConcede;
|
||||||
|
this.asyncWantCheat = response.asyncWantCheat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
responseString = null;
|
this.activeAction = null;
|
||||||
responseUUID = null;
|
this.responseString = null;
|
||||||
responseBoolean = null;
|
this.responseUUID = null;
|
||||||
responseInteger = null;
|
this.responseBoolean = null;
|
||||||
responseManaType = null;
|
this.responseInteger = null;
|
||||||
responseManaTypePlayerId = null;
|
this.responseManaType = null;
|
||||||
responseConcedeCheck = null;
|
this.responseManaPlayerId = null;
|
||||||
|
this.asyncWantConcede = null;
|
||||||
|
this.asyncWantCheat = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActiveAction() {
|
||||||
|
return this.activeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActiveAction(String activeAction) {
|
||||||
|
this.activeAction = activeAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getString() {
|
public String getString() {
|
||||||
return responseString;
|
return this.responseString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setString(String responseString) {
|
public void setString(String newString) {
|
||||||
this.responseString = responseString;
|
this.responseString = newString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUUID() {
|
public UUID getUUID() {
|
||||||
return responseUUID;
|
return this.responseUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUUID(UUID responseUUID) {
|
public void setUUID(UUID newUUID) {
|
||||||
this.responseUUID = responseUUID;
|
this.responseUUID = newUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getBoolean() {
|
public Boolean getBoolean() {
|
||||||
return responseBoolean;
|
return this.responseBoolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBoolean(Boolean responseBoolean) {
|
public void setBoolean(Boolean newBoolean) {
|
||||||
this.responseBoolean = responseBoolean;
|
this.responseBoolean = newBoolean;
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean getResponseConcedeCheck() {
|
|
||||||
if (responseConcedeCheck == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return responseConcedeCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResponseConcedeCheck() {
|
|
||||||
this.responseConcedeCheck = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getInteger() {
|
public Integer getInteger() {
|
||||||
return responseInteger;
|
return responseInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setInteger(Integer responseInteger) {
|
public void setInteger(Integer newInteger) {
|
||||||
this.responseInteger = responseInteger;
|
this.responseInteger = newInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ManaType getManaType() {
|
public ManaType getManaType() {
|
||||||
return responseManaType;
|
return this.responseManaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setManaType(ManaType responseManaType) {
|
public void setManaType(ManaType newManaType) {
|
||||||
this.responseManaType = responseManaType;
|
this.responseManaType = newManaType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getResponseManaTypePlayerId() {
|
public UUID getManaPlayerId() {
|
||||||
return responseManaTypePlayerId;
|
return this.responseManaPlayerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResponseManaTypePlayerId(UUID responseManaTypePlayerId) {
|
public void setResponseManaPlayerId(UUID newManaPlayerId) {
|
||||||
this.responseManaTypePlayerId = responseManaTypePlayerId;
|
this.responseManaPlayerId = newManaPlayerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getAsyncWantConcede() {
|
||||||
|
return this.asyncWantConcede != null && this.asyncWantConcede;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAsyncWantConcede() {
|
||||||
|
this.asyncWantConcede = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getAsyncWantCheat() {
|
||||||
|
return this.asyncWantCheat != null && this.asyncWantCheat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start cheat dialog on next player's priority
|
||||||
|
*/
|
||||||
|
public void setAsyncWantCheat() {
|
||||||
|
this.asyncWantCheat = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import mage.server.exceptions.UserNotFoundException;
|
||||||
import mage.server.game.GameController;
|
import mage.server.game.GameController;
|
||||||
import mage.server.managers.ChatManager;
|
import mage.server.managers.ChatManager;
|
||||||
import mage.server.managers.ManagerFactory;
|
import mage.server.managers.ManagerFactory;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.view.ChatMessage.MessageColor;
|
import mage.view.ChatMessage.MessageColor;
|
||||||
import mage.view.ChatMessage.MessageType;
|
import mage.view.ChatMessage.MessageType;
|
||||||
import mage.view.ChatMessage.SoundToPlay;
|
import mage.view.ChatMessage.SoundToPlay;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import mage.server.managers.ManagerFactory;
|
||||||
import mage.server.services.impl.FeedbackServiceImpl;
|
import mage.server.services.impl.FeedbackServiceImpl;
|
||||||
import mage.server.tournament.TournamentFactory;
|
import mage.server.tournament.TournamentFactory;
|
||||||
import mage.server.util.ServerMessagesUtil;
|
import mage.server.util.ServerMessagesUtil;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.utils.*;
|
import mage.utils.*;
|
||||||
import mage.view.*;
|
import mage.view.*;
|
||||||
import mage.view.ChatMessage.MessageColor;
|
import mage.view.ChatMessage.MessageColor;
|
||||||
|
|
@ -974,36 +974,17 @@ public class MageServerImpl implements MageServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cheatMultiple(final UUID gameId, final String sessionId, final UUID playerId, final DeckCardLists deckList) throws MageException {
|
public void cheatShow(final UUID gameId, final String sessionId, final UUID playerId) throws MageException {
|
||||||
execute("cheat", sessionId, () -> {
|
execute("cheatShow", sessionId, () -> {
|
||||||
if (testMode) {
|
if (testMode) {
|
||||||
managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> {
|
managerFactory.sessionManager().getSession(sessionId).ifPresent(session -> {
|
||||||
UUID userId = session.getUserId();
|
UUID userId = session.getUserId();
|
||||||
managerFactory.gameManager().cheat(gameId, userId, playerId, deckList);
|
managerFactory.gameManager().cheatShow(gameId, userId, playerId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean cheatOne(final UUID gameId, final String sessionId, final UUID playerId, final String cardName) throws MageException {
|
|
||||||
return executeWithResult("cheatOne", sessionId, new ActionWithBooleanResult() {
|
|
||||||
@Override
|
|
||||||
public Boolean execute() {
|
|
||||||
if (testMode) {
|
|
||||||
Optional<Session> session = managerFactory.sessionManager().getSession(sessionId);
|
|
||||||
if (!session.isPresent()) {
|
|
||||||
logger.error("Session not found : " + sessionId);
|
|
||||||
} else {
|
|
||||||
UUID userId = session.get().getUserId();
|
|
||||||
return managerFactory.gameManager().cheat(gameId, userId, playerId, cardName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handleException(Exception ex) throws MageException {
|
public void handleException(Exception ex) throws MageException {
|
||||||
if (!ex.getMessage().equals("No message")) {
|
if (!ex.getMessage().equals("No message")) {
|
||||||
logger.fatal("", ex);
|
logger.fatal("", ex);
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import mage.server.util.*;
|
||||||
import mage.server.util.config.GamePlugin;
|
import mage.server.util.config.GamePlugin;
|
||||||
import mage.server.util.config.Plugin;
|
import mage.server.util.config.Plugin;
|
||||||
import mage.utils.MageVersion;
|
import mage.utils.MageVersion;
|
||||||
|
import mage.utils.SystemUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.jboss.remoting.*;
|
import org.jboss.remoting.*;
|
||||||
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
import org.jboss.remoting.callback.InvokerCallbackHandler;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import mage.players.net.UserGroup;
|
||||||
import mage.server.game.GamesRoom;
|
import mage.server.game.GamesRoom;
|
||||||
import mage.server.managers.ConfigSettings;
|
import mage.server.managers.ConfigSettings;
|
||||||
import mage.server.managers.ManagerFactory;
|
import mage.server.managers.ManagerFactory;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.util.RandomUtil;
|
import mage.util.RandomUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
import org.jboss.remoting.callback.AsynchInvokerCallbackHandler;
|
import org.jboss.remoting.callback.AsynchInvokerCallbackHandler;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import mage.server.record.UserStatsRepository;
|
||||||
import mage.server.tournament.TournamentController;
|
import mage.server.tournament.TournamentController;
|
||||||
import mage.server.tournament.TournamentSession;
|
import mage.server.tournament.TournamentSession;
|
||||||
import mage.server.util.ServerMessagesUtil;
|
import mage.server.util.ServerMessagesUtil;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.view.TableClientMessage;
|
import mage.view.TableClientMessage;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import mage.server.Main;
|
||||||
import mage.server.User;
|
import mage.server.User;
|
||||||
import mage.server.managers.ManagerFactory;
|
import mage.server.managers.ManagerFactory;
|
||||||
import mage.server.util.Splitter;
|
import mage.server.util.Splitter;
|
||||||
import mage.server.util.SystemUtil;
|
|
||||||
import mage.util.MultiAmountMessage;
|
import mage.util.MultiAmountMessage;
|
||||||
import mage.utils.StreamUtils;
|
import mage.utils.StreamUtils;
|
||||||
import mage.utils.timer.PriorityTimer;
|
import mage.utils.timer.PriorityTimer;
|
||||||
|
|
@ -502,6 +501,10 @@ public class GameController implements GameCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPlayerAction(PlayerAction playerAction, UUID userId, Object data) {
|
public void sendPlayerAction(PlayerAction playerAction, UUID userId, Object data) {
|
||||||
|
// TODO: critical bug, must be enabled and research/rework due:
|
||||||
|
// * game change commands must be executed by game thread (example: undo)
|
||||||
|
// * user change commands can be executed by network thread??? (example: change skip settings)
|
||||||
|
//SystemUtil.ensureRunInGameThread();
|
||||||
switch (playerAction) {
|
switch (playerAction) {
|
||||||
case UNDO:
|
case UNDO:
|
||||||
game.undo(getPlayerId(userId));
|
game.undo(getPlayerId(userId));
|
||||||
|
|
@ -705,31 +708,10 @@ public class GameController implements GameCallback {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cheat(UUID userId, UUID playerId, DeckCardLists deckList) {
|
public void cheatShow(UUID playerId) {
|
||||||
try {
|
Player player = game.getPlayer(playerId);
|
||||||
Deck deck = Deck.load(deckList, false, false);
|
if (player != null) {
|
||||||
game.loadCards(deck.getCards(), playerId);
|
player.signalPlayerCheat();
|
||||||
for (Card card : deck.getCards()) {
|
|
||||||
card.putOntoBattlefield(game, Zone.OUTSIDE, null, playerId);
|
|
||||||
}
|
|
||||||
} catch (GameException ex) {
|
|
||||||
logger.warn(ex.getMessage());
|
|
||||||
}
|
|
||||||
addCardsForTesting(game, playerId);
|
|
||||||
updateGame();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean cheat(UUID userId, UUID playerId, String cardName) {
|
|
||||||
CardInfo cardInfo = CardRepository.instance.findCard(cardName);
|
|
||||||
Card card = cardInfo != null ? cardInfo.getCard() : null;
|
|
||||||
if (card != null) {
|
|
||||||
Set<Card> cards = new HashSet<>();
|
|
||||||
cards.add(card);
|
|
||||||
game.loadCards(cards, playerId);
|
|
||||||
card.moveToZone(Zone.HAND, null, game, false);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -985,13 +967,6 @@ public class GameController implements GameCallback {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds cards in player's hands that are specified in config/init.txt.
|
|
||||||
*/
|
|
||||||
private void addCardsForTesting(Game game, UUID playerId) {
|
|
||||||
SystemUtil.addCardsForTesting(game, null, game.getPlayer(playerId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a request to a player
|
* Performs a request to a player
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -146,22 +146,13 @@ public class GameManagerImpl implements GameManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void cheat(UUID gameId, UUID userId, UUID playerId, DeckCardLists deckList) {
|
public void cheatShow(UUID gameId, UUID userId, UUID playerId) {
|
||||||
GameController gameController = getGameControllerSafe(gameId);
|
GameController gameController = getGameControllerSafe(gameId);
|
||||||
if (gameController != null) {
|
if (gameController != null) {
|
||||||
gameController.cheat(userId, playerId, deckList);
|
gameController.cheatShow(playerId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean cheat(UUID gameId, UUID userId, UUID playerId, String cardName) {
|
|
||||||
GameController gameController = getGameControllerSafe(gameId);
|
|
||||||
if (gameController != null) {
|
|
||||||
return gameController.cheat(userId, playerId, cardName);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeGame(UUID gameId) {
|
public void removeGame(UUID gameId) {
|
||||||
GameController gameController = getGameControllerSafe(gameId);
|
GameController gameController = getGameControllerSafe(gameId);
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ public interface GameManager {
|
||||||
|
|
||||||
void stopWatching(UUID gameId, UUID userId);
|
void stopWatching(UUID gameId, UUID userId);
|
||||||
|
|
||||||
void cheat(UUID gameId, UUID userId, UUID playerId, DeckCardLists deckList);
|
void cheatShow(UUID gameId, UUID userId, UUID playerId);
|
||||||
|
|
||||||
boolean cheat(UUID gameId, UUID userId, UUID playerId, String cardName);
|
|
||||||
|
|
||||||
void removeGame(UUID gameId);
|
void removeGame(UUID gameId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,19 @@ package mage.server.util;
|
||||||
|
|
||||||
import mage.server.managers.ConfigSettings;
|
import mage.server.managers.ConfigSettings;
|
||||||
import mage.server.managers.ThreadExecutor;
|
import mage.server.managers.ThreadExecutor;
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class ThreadExecutorImpl implements ThreadExecutor {
|
public class ThreadExecutorImpl implements ThreadExecutor {
|
||||||
private final ExecutorService callExecutor;
|
|
||||||
private final ExecutorService gameExecutor;
|
private static final Logger logger = Logger.getLogger(ThreadExecutorImpl.class);
|
||||||
|
|
||||||
|
private final ExecutorService callExecutor; // shareable threads to run single task (example: save new game settings from a user, send chat message, etc)
|
||||||
|
private final ExecutorService gameExecutor; // game threads to run long tasks, one per game (example: run game and wait user's feedback)
|
||||||
private final ScheduledExecutorService timeoutExecutor;
|
private final ScheduledExecutorService timeoutExecutor;
|
||||||
private final ScheduledExecutorService timeoutIdleExecutor;
|
private final ScheduledExecutorService timeoutIdleExecutor;
|
||||||
|
|
||||||
|
|
@ -26,8 +30,10 @@ public class ThreadExecutorImpl implements ThreadExecutor {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public ThreadExecutorImpl(ConfigSettings config) {
|
public ThreadExecutorImpl(ConfigSettings config) {
|
||||||
callExecutor = Executors.newCachedThreadPool();
|
//callExecutor = Executors.newCachedThreadPool();
|
||||||
gameExecutor = Executors.newFixedThreadPool(config.getMaxGameThreads());
|
callExecutor = new CachedThreadPoolWithException();
|
||||||
|
//gameExecutor = Executors.newFixedThreadPool(config.getMaxGameThreads());
|
||||||
|
gameExecutor = new FixedThreadPoolWithException(config.getMaxGameThreads());
|
||||||
timeoutExecutor = Executors.newScheduledThreadPool(4);
|
timeoutExecutor = Executors.newScheduledThreadPool(4);
|
||||||
timeoutIdleExecutor = Executors.newScheduledThreadPool(4);
|
timeoutIdleExecutor = Executors.newScheduledThreadPool(4);
|
||||||
|
|
||||||
|
|
@ -45,6 +51,42 @@ public class ThreadExecutorImpl implements ThreadExecutor {
|
||||||
((ThreadPoolExecutor) timeoutIdleExecutor).setThreadFactory(new XMageThreadFactory("TIMEOUT_IDLE"));
|
((ThreadPoolExecutor) timeoutIdleExecutor).setThreadFactory(new XMageThreadFactory("TIMEOUT_IDLE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CachedThreadPoolWithException extends ThreadPoolExecutor {
|
||||||
|
|
||||||
|
CachedThreadPoolWithException() {
|
||||||
|
// use same params as Executors.newCachedThreadPool()
|
||||||
|
super(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
|
super.afterExecute(r, t);
|
||||||
|
|
||||||
|
// catch errors in CALL threads (from client commands)
|
||||||
|
if (t != null) {
|
||||||
|
logger.error("Catch unhandled error in CALL thread: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class FixedThreadPoolWithException extends ThreadPoolExecutor {
|
||||||
|
|
||||||
|
FixedThreadPoolWithException(int nThreads) {
|
||||||
|
// use same params as Executors.newFixedThreadPool()
|
||||||
|
super(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterExecute(Runnable r, Throwable t) {
|
||||||
|
super.afterExecute(r, t);
|
||||||
|
|
||||||
|
// catch errors in GAME threads (from game processing)
|
||||||
|
if (t != null) {
|
||||||
|
// it's impossible to brake game thread in normal use case, so each bad use case must be researched
|
||||||
|
logger.error("Catch unhandled error in GAME thread: " + t.getMessage(), t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getActiveThreads(ExecutorService executerService) {
|
public int getActiveThreads(ExecutorService executerService) {
|
||||||
|
|
@ -90,5 +132,4 @@ class XMageThreadFactory implements ThreadFactory {
|
||||||
thread.setName(prefix + ' ' + thread.getThreadGroup().getName() + '-' + thread.getId());
|
thread.setName(prefix + ' ' + thread.getThreadGroup().getName() + '-' + thread.getId());
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2931,6 +2931,11 @@ public class TestPlayer implements Player {
|
||||||
computerPlayer.signalPlayerConcede();
|
computerPlayer.signalPlayerConcede();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalPlayerCheat() {
|
||||||
|
computerPlayer.signalPlayerCheat();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abortReset() {
|
public void abortReset() {
|
||||||
computerPlayer.abortReset();
|
computerPlayer.abortReset();
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import mage.server.managers.ConfigSettings;
|
||||||
import mage.server.util.ConfigFactory;
|
import mage.server.util.ConfigFactory;
|
||||||
import mage.server.util.ConfigWrapper;
|
import mage.server.util.ConfigWrapper;
|
||||||
import mage.server.util.PluginClassLoader;
|
import mage.server.util.PluginClassLoader;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.server.util.config.GamePlugin;
|
import mage.server.util.config.GamePlugin;
|
||||||
import mage.server.util.config.Plugin;
|
import mage.server.util.config.Plugin;
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import mage.player.ai.ComputerPlayerMCTS;
|
||||||
import mage.players.ManaPool;
|
import mage.players.ManaPool;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.server.game.GameSessionPlayer;
|
import mage.server.game.GameSessionPlayer;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import mage.view.GameView;
|
import mage.view.GameView;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package org.mage.test.serverside.cheats;
|
package org.mage.test.serverside.cheats;
|
||||||
|
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
@ -56,7 +56,7 @@ public class LoadCheatsTest extends CardTestPlayerBase {
|
||||||
execute();
|
execute();
|
||||||
|
|
||||||
setChoice(playerA, "5"); // choose [group 3]: 5 = 2 default menus + 3 group
|
setChoice(playerA, "5"); // choose [group 3]: 5 = 2 default menus + 3 group
|
||||||
SystemUtil.addCardsForTesting(currentGame, commandsFile, playerA);
|
SystemUtil.executeCheatCommands(currentGame, commandsFile, playerA);
|
||||||
|
|
||||||
assertHandCount(playerA, "Razorclaw Bear", 1);
|
assertHandCount(playerA, "Razorclaw Bear", 1);
|
||||||
assertPermanentCount(playerA, "Mountain", 3);
|
assertPermanentCount(playerA, "Mountain", 3);
|
||||||
|
|
|
||||||
|
|
@ -724,6 +724,11 @@ public class PlayerStub implements Player {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalPlayerCheat() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void abortReset() {
|
public void abortReset() {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package org.mage.test.testapi;
|
||||||
|
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import mage.game.draft.DraftCube;
|
||||||
import mage.game.permanent.token.Token;
|
import mage.game.permanent.token.Token;
|
||||||
import mage.game.permanent.token.TokenImpl;
|
import mage.game.permanent.token.TokenImpl;
|
||||||
import mage.game.permanent.token.custom.CreatureToken;
|
import mage.game.permanent.token.custom.CreatureToken;
|
||||||
import mage.server.util.SystemUtil;
|
import mage.utils.SystemUtil;
|
||||||
import mage.sets.TherosBeyondDeath;
|
import mage.sets.TherosBeyondDeath;
|
||||||
import mage.target.targetpointer.TargetPointer;
|
import mage.target.targetpointer.TargetPointer;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,21 @@
|
||||||
|
|
||||||
package mage.cards.decks;
|
package mage.cards.decks;
|
||||||
|
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
import mage.util.Copyable;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||||
*/
|
*/
|
||||||
public class DeckCardLists implements Serializable {
|
public class DeckCardLists implements Serializable, Copyable<DeckCardLists> {
|
||||||
|
|
||||||
private String name;
|
private String name = null;
|
||||||
private String author;
|
private String author = null;
|
||||||
private List<DeckCardInfo> cards = new ArrayList<>();
|
private List<DeckCardInfo> cards = new ArrayList<>();
|
||||||
private List<DeckCardInfo> sideboard = new ArrayList<>();
|
private List<DeckCardInfo> sideboard = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -20,6 +23,23 @@ public class DeckCardLists implements Serializable {
|
||||||
private DeckCardLayout cardLayout = null;
|
private DeckCardLayout cardLayout = null;
|
||||||
private DeckCardLayout sideboardLayout = null;
|
private DeckCardLayout sideboardLayout = null;
|
||||||
|
|
||||||
|
public DeckCardLists() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DeckCardLists(final DeckCardLists deck) {
|
||||||
|
this.name = deck.name;
|
||||||
|
this.author = deck.author;
|
||||||
|
this.cards = CardUtil.deepCopyObject(deck.cards);
|
||||||
|
this.sideboard = CardUtil.deepCopyObject(deck.sideboard);
|
||||||
|
this.cardLayout = CardUtil.deepCopyObject(deck.cardLayout);
|
||||||
|
this.sideboardLayout = CardUtil.deepCopyObject(deck.sideboardLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeckCardLists copy() {
|
||||||
|
return new DeckCardLists(this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The layout of the cards
|
* @return The layout of the cards
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -534,7 +534,6 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
||||||
|
|
||||||
// game cheats (for tests only)
|
// game cheats (for tests only)
|
||||||
void cheat(UUID ownerId, Map<Zone, String> commands);
|
void cheat(UUID ownerId, Map<Zone, String> commands);
|
||||||
|
|
||||||
void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard, List<Card> command);
|
void cheat(UUID ownerId, List<Card> library, List<Card> hand, List<PermanentCard> battlefield, List<Card> graveyard, List<Card> command);
|
||||||
|
|
||||||
// controlling the behaviour of replacement effects while permanents entering the battlefield
|
// controlling the behaviour of replacement effects while permanents entering the battlefield
|
||||||
|
|
|
||||||
|
|
@ -794,7 +794,7 @@ public abstract class GameImpl implements Game {
|
||||||
if (!concedingPlayers.contains(playerId)) {
|
if (!concedingPlayers.contains(playerId)) {
|
||||||
logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
|
logger.debug("Game over for player Id: " + playerId + " gameId " + getId());
|
||||||
concedingPlayers.add(playerId);
|
concedingPlayers.add(playerId);
|
||||||
player.signalPlayerConcede();
|
player.signalPlayerConcede(); // will be executed on next priority
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// no asynchronous action so check directly
|
// no asynchronous action so check directly
|
||||||
|
|
@ -3799,8 +3799,9 @@ public abstract class GameImpl implements Game {
|
||||||
for (Player playerObject : getPlayers().values()) {
|
for (Player playerObject : getPlayers().values()) {
|
||||||
if (playerObject.isHuman() && playerObject.canRespond()) {
|
if (playerObject.isHuman() && playerObject.canRespond()) {
|
||||||
playerObject.resetStoredBookmark(this);
|
playerObject.resetStoredBookmark(this);
|
||||||
playerObject.abort();
|
|
||||||
playerObject.resetPlayerPassedActions();
|
playerObject.resetPlayerPassedActions();
|
||||||
|
playerObject.abort();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fireUpdatePlayersEvent();
|
fireUpdatePlayersEvent();
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ public class GameState implements Serializable, Copyable<GameState> {
|
||||||
private Turn turn;
|
private Turn turn;
|
||||||
private TurnMods turnMods; // one time turn modifications (turn, phase or step)
|
private TurnMods turnMods; // one time turn modifications (turn, phase or step)
|
||||||
private UUID activePlayerId; // playerId which turn it is
|
private UUID activePlayerId; // playerId which turn it is
|
||||||
private UUID priorityPlayerId; // player that has currently priority
|
private UUID priorityPlayerId; // player that has currently priority (setup before any choose)
|
||||||
private UUID playerByOrderId; // player that has currently priority
|
private UUID playerByOrderId; // player that has currently priority
|
||||||
private UUID monarchId; // player that is the monarch
|
private UUID monarchId; // player that is the monarch
|
||||||
private UUID initiativeId; // player that has the initiative
|
private UUID initiativeId; // player that has the initiative
|
||||||
|
|
|
||||||
|
|
@ -550,6 +550,8 @@ public interface Player extends MageItem, Copyable<Player> {
|
||||||
|
|
||||||
void signalPlayerConcede();
|
void signalPlayerConcede();
|
||||||
|
|
||||||
|
void signalPlayerCheat();
|
||||||
|
|
||||||
void skip();
|
void skip();
|
||||||
|
|
||||||
// priority, undo, ...
|
// priority, undo, ...
|
||||||
|
|
|
||||||
|
|
@ -5088,7 +5088,10 @@ public abstract class PlayerImpl implements Player, Serializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void signalPlayerConcede() {
|
public void signalPlayerConcede() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void signalPlayerCheat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package mage.util;
|
package mage.util;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Devs only: enable or disable debug features
|
* Devs only: enable or disable debug features
|
||||||
* <p>
|
* <p>
|
||||||
|
|
@ -28,4 +30,47 @@ public class DebugUtil {
|
||||||
public static boolean GUI_GAME_DRAW_BATTLEFIELD_BORDER = false;
|
public static boolean GUI_GAME_DRAW_BATTLEFIELD_BORDER = false;
|
||||||
public static boolean GUI_GAME_DRAW_HAND_AND_STACK_BORDER = false;
|
public static boolean GUI_GAME_DRAW_HAND_AND_STACK_BORDER = false;
|
||||||
public static boolean GUI_GAME_DIALOGS_DRAW_CARDS_AREA_BORDER = false;
|
public static boolean GUI_GAME_DIALOGS_DRAW_CARDS_AREA_BORDER = false;
|
||||||
|
|
||||||
|
public static String getMethodNameWithSource(final int depth) {
|
||||||
|
return TraceHelper.getMethodNameWithSource(depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debug: allows to find a caller's method name
|
||||||
|
* <a href="https://stackoverflow.com/a/11726687/1276632">Original code</a>
|
||||||
|
*/
|
||||||
|
class TraceHelper {
|
||||||
|
|
||||||
|
private static Method m;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
m = Throwable.class.getDeclaredMethod("getStackTraceElement", int.class);
|
||||||
|
m.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMethodName(final int depth) {
|
||||||
|
try {
|
||||||
|
StackTraceElement element = (StackTraceElement) m.invoke(new Throwable(), depth + 1);
|
||||||
|
return element.getMethodName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMethodNameWithSource(final int depth) {
|
||||||
|
try {
|
||||||
|
StackTraceElement element = (StackTraceElement) m.invoke(new Throwable(), depth + 1);
|
||||||
|
return String.format("%s - %s:%d", element.getMethodName(), element.getFileName(), element.getLineNumber());
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue