diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index a3fdfb00f6..79ef6e09da 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -26,6 +26,7 @@ import mage.client.plugins.impl.Plugins; import mage.client.preference.MagePreferences; import mage.client.remote.CallbackClientImpl; import mage.client.table.TablesPane; +import mage.client.table.TablesPanel; import mage.client.tournament.TournamentPane; import mage.client.util.*; import mage.client.util.audio.MusicPlayer; @@ -261,7 +262,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { desktopPane.add(errorDialog, JLayeredPane.MODAL_LAYER); UI.addComponent(MageComponents.DESKTOP_PANE, desktopPane); - PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), 60, 60, TimeUnit.SECONDS); + PING_TASK_EXECUTOR.scheduleAtFixedRate(() -> SessionHandler.ping(), TablesPanel.PING_SERVER_SECS, TablesPanel.PING_SERVER_SECS, TimeUnit.SECONDS); updateMemUsageTask = new UpdateMemUsageTask(jMemUsageLabel); diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 5f48cefadc..92cc8f2f8d 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -18,7 +18,6 @@ import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.remote.ActionData; import mage.remote.Session; -import mage.utils.CompressUtil; import mage.view.*; import mage.view.ChatMessage.MessageType; import org.apache.log4j.Logger; @@ -44,8 +43,8 @@ public class CallbackClientImpl implements CallbackClient { @Override public synchronized void processCallback(final ClientCallback callback) { + callback.decompressData(); SaveObjectUtil.saveObject(callback.getData(), callback.getMethod().toString()); - callback.setData(CompressUtil.decompress(callback.getData())); SwingUtilities.invokeLater(() -> { try { logger.debug(callback.getMessageId() + " -- " + callback.getMethod()); diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 62326c77a8..b0533fd92e 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -64,6 +64,9 @@ public class TablesPanel extends javax.swing.JPanel { private static final Logger LOGGER = Logger.getLogger(TablesPanel.class); private static final int[] DEFAULT_COLUMNS_WIDTH = {35, 150, 100, 50, 120, 180, 80, 120, 80, 60, 40, 40, 60}; + // ping timeout (warning, must be less than SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS) + public static final int PING_SERVER_SECS = 20; + // refresh timeouts for data downloads from server public static final int REFRESH_ACTIVE_TABLES_SECS = 5; public static final int REFRESH_FINISHED_TABLES_SECS = 30; diff --git a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java index ca69ff740d..f7582fc158 100644 --- a/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java +++ b/Mage.Common/src/main/java/mage/interfaces/callback/ClientCallback.java @@ -1,12 +1,12 @@ - - package mage.interfaces.callback; +import mage.remote.traffic.ZippedObject; +import mage.utils.CompressUtil; + import java.io.Serializable; import java.util.UUID; /** - * * @author BetaSteward_at_googlemail.com */ public class ClientCallback implements Serializable { @@ -16,12 +16,14 @@ public class ClientCallback implements Serializable { private ClientCallbackMethod method; private int messageId; - public ClientCallback() {} - public ClientCallback(ClientCallbackMethod method, UUID objectId, Object data) { + this(method, objectId, data, true); + } + + public ClientCallback(ClientCallbackMethod method, UUID objectId, Object data, boolean useCompress) { this.method = method; this.objectId = objectId; - this.data = data; + this.setData(data, useCompress); } public ClientCallback(ClientCallbackMethod method, UUID objectId) { @@ -42,13 +44,28 @@ public class ClientCallback implements Serializable { } public Object getData() { + if (this.data instanceof ZippedObject) { + throw new IllegalStateException("Client data must be decompressed first"); + } return data; } - public void setData(Object data) { + public void setData(Object data, boolean useCompress) { + if (!useCompress || data == null || data instanceof ZippedObject) { + this.data = data; + } else { + this.data = CompressUtil.compress(data); + } + this.data = data; } + public void decompressData() { + if (this.data instanceof ZippedObject) { + this.data = CompressUtil.decompress(this.data); + } + } + public ClientCallbackMethod getMethod() { return method; } diff --git a/Mage.Common/src/main/java/mage/utils/CompressUtil.java b/Mage.Common/src/main/java/mage/utils/CompressUtil.java index eeef7533de..fdd45dd7d5 100644 --- a/Mage.Common/src/main/java/mage/utils/CompressUtil.java +++ b/Mage.Common/src/main/java/mage/utils/CompressUtil.java @@ -14,7 +14,7 @@ public final class CompressUtil { * Defines should data be compressed or not. True by default. Read from * system property: */ - private static boolean compressData = true; + private static boolean compressData; /** * Defines the system property name to disable any compressing. diff --git a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java index 1ee9285c39..df46fb279e 100644 --- a/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java +++ b/Mage.Server.Console/src/main/java/mage/server/console/ConsoleFrame.java @@ -67,7 +67,7 @@ public class ConsoleFrame extends javax.swing.JFrame implements MageClient { logger.fatal("", ex); } - pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 60, 60, TimeUnit.SECONDS); + pingTaskExecutor.scheduleAtFixedRate(() -> session.ping(), 20, 20, TimeUnit.SECONDS); } public boolean connect(Connection connection) { diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index 5dcb1d38b9..116ce13c7a 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -1,29 +1,8 @@ package mage.player.human; -import java.awt.Color; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Queue; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; import jdk.nashorn.internal.objects.NativeError; import mage.MageObject; -import mage.abilities.Ability; -import mage.abilities.ActivatedAbility; -import mage.abilities.Mode; -import mage.abilities.Modes; -import mage.abilities.PlayLandAbility; -import mage.abilities.SpecialAction; -import mage.abilities.SpellAbility; -import mage.abilities.TriggeredAbility; +import mage.abilities.*; import mage.abilities.costs.VariableCost; import mage.abilities.costs.common.SacrificeSourceCost; import mage.abilities.costs.common.TapSourceCost; @@ -38,11 +17,6 @@ import mage.cards.decks.Deck; import mage.choices.Choice; import mage.choices.ChoiceImpl; import mage.constants.*; -import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL; -import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL; -import static mage.constants.SpellAbilityType.SPLIT; -import static mage.constants.SpellAbilityType.SPLIT_AFTERMATH; -import static mage.constants.SpellAbilityType.SPLIT_FUSED; import mage.filter.StaticFilters; import mage.filter.common.FilterAttackingCreature; import mage.filter.common.FilterBlockingCreature; @@ -73,6 +47,16 @@ import mage.util.ManaUtil; import mage.util.MessageToClient; import org.apache.log4j.Logger; +import java.awt.*; +import java.io.Serializable; +import java.util.List; +import java.util.Queue; +import java.util.*; +import java.util.stream.Collectors; + +import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL; +import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL; + /** * @author BetaSteward_at_googlemail.com */ @@ -237,7 +221,7 @@ public class HumanPlayer extends PlayerImpl { return; } - if (isInGame()) { + if (canRespond()) { // wait another answer loop = true; } @@ -254,9 +238,9 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } - updateGameStatePriority("chooseMulligan", game); - int nextHandSize = game.mulliganDownTo(playerId); - do { + + while (canRespond()) { + int nextHandSize = game.mulliganDownTo(playerId); String cardsCountInfo = nextHandSize + (nextHandSize == 1 ? " card" : " cards"); String message; if (getHand().size() > nextHandSize) { @@ -269,15 +253,19 @@ public class HumanPlayer extends PlayerImpl { Map options = new HashMap<>(); options.put("UI.left.btn.text", "Mulligan"); options.put("UI.right.btn.text", "Keep"); + + updateGameStatePriority("chooseMulligan", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireAskPlayerEvent(playerId, new MessageToClient(message), null, options); } waitForResponse(game); - } while (response.getBoolean() == null && !abort); - if (!abort) { - return response.getBoolean(); + + if (response.getBoolean() != null) { + return response.getBoolean(); + } } + return false; } @@ -291,6 +279,7 @@ public class HumanPlayer extends PlayerImpl { if (game.inCheckPlayableState()) { return true; } + MessageToClient messageToClient = new MessageToClient(message, secondMessage); Map options = new HashMap<>(2); if (trueText != null) { @@ -310,20 +299,25 @@ public class HumanPlayer extends PlayerImpl { } } } - updateGameStatePriority("chooseUse", game); - do { + + + while (canRespond()) { if (messageToClient.getSecondMessage() == null) { messageToClient.setSecondMessage(getRelatedObjectName(source, game)); } + + updateGameStatePriority("chooseUse", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireAskPlayerEvent(playerId, messageToClient, source, options); } waitForResponse(game); - } while (response.getBoolean() == null && !abort); - if (!abort) { - return response.getBoolean(); + + if (response.getBoolean() != null) { + return response.getBoolean(); + } } + return false; } @@ -360,18 +354,11 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return 0; } - if (game.inCheckPlayableState()) { - logger.warn("player interaction in checkPlayableState."); - if (rEffects.size() <= 1) { - return 0; - } else { - return 1; - } - } - updateGameStatePriority("chooseEffect", game); + if (rEffects.size() <= 1) { return 0; } + if (!autoSelectReplacementEffects.isEmpty()) { for (String autoKey : autoSelectReplacementEffects) { int count = 0; @@ -396,14 +383,18 @@ public class HumanPlayer extends PlayerImpl { differentChoices++; } } + if (differentChoices <= 1) { + return 0; + } - while (!abort && differentChoices > 1) { + while (canRespond()) { updateGameStatePriority("chooseEffect", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireChooseChoiceEvent(playerId, replacementEffectChoice); } waitForResponse(game); + logger.debug("Choose effect: " + response.getString()); if (response.getString() != null) { if (response.getString().startsWith("#")) { @@ -412,6 +403,7 @@ public class HumanPlayer extends PlayerImpl { } else { replacementEffectChoice.setChoiceByKey(response.getString()); } + if (replacementEffectChoice.getChoiceKey() != null) { int index = 0; for (String key : rEffects.keySet()) { @@ -423,6 +415,7 @@ public class HumanPlayer extends PlayerImpl { } } } + return 0; } @@ -431,19 +424,22 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } + if (Outcome.PutManaInPool == outcome) { if (currentlyUnpaidMana != null && ManaUtil.tryToAutoSelectAManaColor(choice, currentlyUnpaidMana)) { return true; } } - updateGameStatePriority("choose(3)", game); + while (canRespond()) { + updateGameStatePriority("choose(3)", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireChooseChoiceEvent(playerId, choice); } waitForResponse(game); + String val = response.getString(); if (val != null && !val.isEmpty()) { if (choice.isKeyChoice()) { @@ -453,9 +449,11 @@ public class HumanPlayer extends PlayerImpl { } return true; } else if (!choice.isRequired()) { + // cancel return false; } } + return false; } @@ -469,8 +467,8 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } + // choose one or multiple permanents - updateGameStatePriority("choose(5)", game); UUID abilityControllerId = playerId; if (target.getTargetController() != null && target.getAbilityController() != null) { @@ -479,12 +477,13 @@ public class HumanPlayer extends PlayerImpl { if (options == null) { options = new HashMap<>(); } - while (!abort) { + + while (canRespond()) { Set targetIds = target.possibleTargets(sourceId, abilityControllerId, game); - if (targetIds == null - || targetIds.isEmpty()) { + if (targetIds == null || targetIds.isEmpty()) { return target.getTargets().size() >= target.getNumberOfTargets(); } + boolean required = target.isRequired(sourceId, game); if (target.getTargets().size() >= target.getNumberOfTargets()) { required = false; @@ -493,11 +492,13 @@ public class HumanPlayer extends PlayerImpl { List chosen = target.getTargets(); options.put("chosen", (Serializable) chosen); + updateGameStatePriority("choose(5)", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(sourceId, game)), targetIds, required, getOptions(target, options)); } waitForResponse(game); + if (response.getUUID() != null) { // selected some target @@ -510,6 +511,7 @@ public class HumanPlayer extends PlayerImpl { if (!targetIds.contains(response.getUUID())) { continue; } + if (target instanceof TargetPermanent) { if (((TargetPermanent) target).canTarget(abilityControllerId, response.getUUID(), sourceId, game, false)) { target.add(response.getUUID(), game); @@ -555,6 +557,7 @@ public class HumanPlayer extends PlayerImpl { } } } + return false; } @@ -563,13 +566,14 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } + // choose one or multiple targets - updateGameStatePriority("chooseTarget", game); UUID abilityControllerId = playerId; if (target.getAbilityController() != null) { abilityControllerId = target.getAbilityController(); } - while (!abort) { + + while (canRespond()) { Set possibleTargets = target.possibleTargets(source == null ? null : source.getSourceId(), abilityControllerId, game); boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); if (possibleTargets.isEmpty() @@ -577,14 +581,15 @@ public class HumanPlayer extends PlayerImpl { required = false; } + updateGameStatePriority("chooseTarget", game); prepareForResponse(game); if (!isExecutingMacro()) { - // hmm - game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargets, required, getOptions(target, null)); + game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), + possibleTargets, required, getOptions(target, null)); } waitForResponse(game); - if (response.getUUID() != null) { + if (response.getUUID() != null) { // remove selected if (target.getTargets().contains(response.getUUID())) { target.remove(response.getUUID()); @@ -608,6 +613,7 @@ public class HumanPlayer extends PlayerImpl { } } } + return false; } @@ -628,27 +634,32 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } + // choose one or multiple cards if (cards == null) { return false; } - updateGameStatePriority("choose(4)", game); - while (!abort) { - boolean required = target.isRequired(); - // if there is no cards to select from, then add possibility to cancel choosing action - int count = cards.count(target.getFilter(), game); - if (count == 0) { - required = false; - } - if (target.getTargets().size() >= target.getNumberOfTargets()) { + + UUID abilityControllerId = playerId; + if (target.getTargetController() != null + && target.getAbilityController() != null) { + abilityControllerId = target.getAbilityController(); + } + + while (canRespond()) { + boolean required = target.isRequired(null, game); + int count = cards.count(target.getFilter(), abilityControllerId, game); + if (count == 0 + || target.getTargets().size() >= target.getNumberOfTargets()) { required = false; } + Map options = getOptions(target, null); List chosen = target.getTargets(); options.put("chosen", (Serializable) chosen); List choosable = new ArrayList<>(); for (UUID cardId : cards) { - if (target.canTarget(cardId, cards, game)) { + if (target.canTarget(abilityControllerId, cardId, null, cards, game)) { choosable.add(cardId); } } @@ -656,13 +667,15 @@ public class HumanPlayer extends PlayerImpl { options.put("choosable", (Serializable) choosable); } + updateGameStatePriority("choose(4)", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options); } waitForResponse(game); + if (response.getUUID() != null) { - if (target.canTarget(response.getUUID(), cards, game)) { + if (target.canTarget(abilityControllerId, response.getUUID(), null, cards, game)) { if (target.getTargets().contains(response.getUUID())) { // if already included remove it with target.remove(response.getUUID()); } else { @@ -681,6 +694,7 @@ public class HumanPlayer extends PlayerImpl { } } } + return false; } @@ -690,32 +704,31 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } - updateGameStatePriority("chooseTarget(5)", game); - while (!abort) { - boolean required; - if (target.isRequiredExplicitlySet()) { - required = target.isRequired(); - } else { - required = target.isRequired(source); - } - // if there is no cards to select from, then add possibility to cancel choosing action - if (cards == null) { - required = false; - } else { - int count = cards.count(target.getFilter(), game); - if (count == 0) { - required = false; - } - } - if (target.getTargets().size() >= target.getNumberOfTargets()) { + + if (cards == null) { + return false; + } + + UUID abilityControllerId = playerId; + if (target.getTargetController() != null + && target.getAbilityController() != null) { + abilityControllerId = target.getAbilityController(); + } + + while (canRespond()) { + boolean required = target.isRequiredExplicitlySet() ? target.isRequired() : target.isRequired(source); + int count = cards.count(target.getFilter(), abilityControllerId, game); + if (count == 0 + || target.getTargets().size() >= target.getNumberOfTargets()) { required = false; } + Map options = getOptions(target, null); List chosen = target.getTargets(); options.put("chosen", (Serializable) chosen); List choosable = new ArrayList<>(); for (UUID cardId : cards) { - if (target.canTarget(cardId, cards, game)) { + if (target.canTarget(abilityControllerId, cardId, source, cards, game)) { choosable.add(cardId); } } @@ -723,6 +736,7 @@ public class HumanPlayer extends PlayerImpl { options.put("choosable", (Serializable) choosable); } + updateGameStatePriority("chooseTarget(5)", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options); @@ -732,7 +746,7 @@ public class HumanPlayer extends PlayerImpl { if (response.getUUID() != null) { if (target.getTargets().contains(response.getUUID())) { // if already included remove it target.remove(response.getUUID()); - } else if (target.canTarget(response.getUUID(), cards, game)) { + } else if (target.canTarget(abilityControllerId, response.getUUID(), source, cards, game)) { target.addTarget(response.getUUID(), source, game); if (target.doneChosing()) { return true; @@ -747,6 +761,7 @@ public class HumanPlayer extends PlayerImpl { } } } + return false; } @@ -756,51 +771,72 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } - updateGameStatePriority("chooseTargetAmount", game); - while (!abort) { + + UUID abilityControllerId = playerId; + if (target.getAbilityController() != null) { + abilityControllerId = target.getAbilityController(); + } + + while (canRespond()) { + Set possibleTargets = target.possibleTargets(source == null ? null : source.getSourceId(), abilityControllerId, game); + boolean required = target.isRequired(source != null ? source.getSourceId() : null, game); + if (possibleTargets.isEmpty() + || target.getTargets().size() >= target.getNumberOfTargets()) { + required = false; + } + + updateGameStatePriority("chooseTargetAmount", game); prepareForResponse(game); if (!isExecutingMacro()) { String selectedNames = target.getTargetedName(game); game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage() - + "
Amount remaining: " + target.getAmountRemaining() - + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), - getRelatedObjectName(source, game)), - target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game), - target.isRequired(source), + + "
Amount remaining: " + target.getAmountRemaining() + + (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames), + getRelatedObjectName(source, game)), + possibleTargets, + required, getOptions(target, null)); } waitForResponse(game); + if (response.getUUID() != null) { - if (target.canTarget(response.getUUID(), source, game)) { + if (target.canTarget(abilityControllerId, response.getUUID(), source, game)) { UUID targetId = response.getUUID(); MageObject targetObject = game.getObject(targetId); boolean removeMode = target.getTargets().contains(targetId) - && chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : "target") + "?", "", - "Remove from selected", "Add extra amount", source, game); + && chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : " target") + "?", "", + "Remove from selected", "Add extra amount (remaining " + target.getAmountRemaining() + ")", source, game); if (removeMode) { target.remove(targetId); } else { - int amountSelected = getAmount(1, target.getAmountRemaining(), "Select amount", game); - target.addTarget(targetId, amountSelected, source, game); + if (target.getAmountRemaining() > 0) { + int amountSelected = getAmount(1, target.getAmountRemaining(), "Select amount", game); + target.addTarget(targetId, amountSelected, source, game); + } } return true; } - } else if (!target.isRequired(source)) { + } else if (!required) { return false; } } + return false; } @Override public boolean priority(Game game) { passed = false; - if (!abort) { + // TODO: fix problems with turn under out control: + // 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?!) + if (canRespond()) { HumanPlayer controllingPlayer = this; - if (isGameUnderControl()) { + if (isGameUnderControl()) { // TODO: must be ! to get real controlling player Player player = game.getPlayer(getTurnControlledBy()); if (player instanceof HumanPlayer) { controllingPlayer = (HumanPlayer) player; @@ -824,12 +860,10 @@ public class HumanPlayer extends PlayerImpl { // STOP conditions (temporary stop without skip reset) boolean quickStop = false; - if (isGameUnderControl()) { - + if (isGameUnderControl()) { // TODO: remove to enable quick stop for controlling player // if was attacked - always stop BEFORE blocker step (to cast extra spells) if (game.getTurn().getStepType() == PhaseStep.DECLARE_ATTACKERS && game.getCombat().getPlayerDefenders(game).contains(playerId)) { - FilterCreatureForCombatBlock filter = filterCreatureForCombatBlock.copy(); filter.add(new ControllerIdPredicate(playerId)); // stop skip on any/zero permanents available @@ -841,7 +875,7 @@ public class HumanPlayer extends PlayerImpl { } // SKIP - use the skip actions only if the player itself controls its turn - if (!quickStop && isGameUnderControl()) { + if (!quickStop && isGameUnderControl()) { // TODO: remove to enable skips for controlling player if (passedAllTurns || passedTurnSkipStack) { if (passWithManaPoolCheck(game)) { @@ -914,9 +948,9 @@ public class HumanPlayer extends PlayerImpl { if (!skippedAtLeastOnce || (playerId.equals(game.getActivePlayerId()) && !controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnAllEndPhases())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnAllEndPhases())) { skippedAtLeastOnce = true; if (passWithManaPoolCheck(game)) { return false; @@ -948,9 +982,9 @@ public class HumanPlayer extends PlayerImpl { if (haveNewObjectsOnStack && (playerId.equals(game.getActivePlayerId()) && controllingPlayer - .getUserData() - .getUserSkipPrioritySteps() - .isStopOnStackNewObjects())) { + .getUserData() + .getUserSkipPrioritySteps() + .isStopOnStackNewObjects())) { // new objects on stack -- disable "pass until stack resolved" passedUntilStackResolved = false; } else { @@ -966,16 +1000,19 @@ public class HumanPlayer extends PlayerImpl { } while (canRespond()) { - updateGameStatePriority("priority", game); holdingPriority = false; + + updateGameStatePriority("priority", game); prepareForResponse(game); if (!isExecutingMacro()) { game.firePriorityEvent(playerId); } waitForResponse(game); + if (game.executingRollback()) { return true; } + if (response.getBoolean() != null || response.getInteger() != null) { if (!activatingMacro && passWithManaPoolCheck(game)) { @@ -1032,6 +1069,7 @@ public class HumanPlayer extends PlayerImpl { } return true; } + return false; } @@ -1069,9 +1107,10 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } + String autoOrderRuleText = null; boolean autoOrderUse = getControllingPlayersUserData(game).isAutoOrderTrigger(); - while (!abort) { + while (canRespond()) { // try to set trigger auto order List abilitiesWithNoOrderSet = new ArrayList<>(); TriggeredAbility abilityOrderLast = null; @@ -1101,13 +1140,16 @@ public class HumanPlayer extends PlayerImpl { } abilitiesWithNoOrderSet.add(ability); } + if (abilitiesWithNoOrderSet.isEmpty()) { return abilityOrderLast; } + if (abilitiesWithNoOrderSet.size() == 1 || autoOrderUse) { return abilitiesWithNoOrderSet.iterator().next(); } + macroTriggeredSelectionFlag = true; updateGameStatePriority("chooseTriggeredAbility", game); prepareForResponse(game); @@ -1115,6 +1157,7 @@ public class HumanPlayer extends PlayerImpl { game.fireSelectTargetTriggeredAbilityEvent(playerId, "Pick triggered ability (goes to the stack first)", abilitiesWithNoOrderSet); } waitForResponse(game); + if (response.getUUID() != null) { for (TriggeredAbility ability : abilitiesWithNoOrderSet) { if (ability.getId().equals(response.getUUID()) @@ -1132,6 +1175,7 @@ public class HumanPlayer extends PlayerImpl { } } } + macroTriggeredSelectionFlag = false; return null; } @@ -1139,9 +1183,11 @@ public class HumanPlayer extends PlayerImpl { @Override public boolean playMana(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { payManaMode = true; - boolean result = playManaHandling(abilityToCast, unpaid, promptText, game); - payManaMode = false; - return result; + try { + return playManaHandling(abilityToCast, unpaid, promptText, game); + } finally { + payManaMode = false; + } } protected boolean playManaHandling(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { @@ -1149,33 +1195,38 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } - updateGameStatePriority("playMana", game); - Map options = new HashMap<>(); - prepareForResponse(game); - if (!isExecutingMacro()) { - game.firePlayManaEvent(playerId, "Pay " + promptText, options); - } - waitForResponse(game); - if (!this.canRespond()) { - return false; - } - if (response.getBoolean() != null) { - return false; - } else if (response.getUUID() != null) { - playManaAbilities(abilityToCast, unpaid, game); - } else if (response.getString() != null - && response.getString().equals("special")) { - if (unpaid instanceof ManaCostsImpl) { - specialManaAction(unpaid, game); + + // TODO: make canRespond cycle? + if (canRespond()) { + Map options = new HashMap<>(); + updateGameStatePriority("playMana", game); + prepareForResponse(game); + if (!isExecutingMacro()) { + game.firePlayManaEvent(playerId, "Pay " + promptText, options); } - } else if (response.getManaType() != null) { - // this mana type can be paid once from pool - if (response.getResponseManaTypePlayerId().equals(this.getId())) { - this.getManaPool().unlockManaType(response.getManaType()); + waitForResponse(game); + + if (response.getBoolean() != null) { + return false; + } else if (response.getUUID() != null) { + playManaAbilities(abilityToCast, unpaid, game); + } else if (response.getString() != null + && response.getString().equals("special")) { + if (unpaid instanceof ManaCostsImpl) { + specialManaAction(unpaid, game); + } + } else if (response.getManaType() != null) { + // this mana type can be paid once from pool + if (response.getResponseManaTypePlayerId().equals(this.getId())) { + this.getManaPool().unlockManaType(response.getManaType()); + } + // TODO: Handle if mana pool } - // TODO: Handle if mana pool + + return true; } - return true; + + return false; // must return false to stop mana pay cycle with non responding player } /** @@ -1188,16 +1239,20 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return 0; } + int xValue = 0; - updateGameStatePriority("announceRepetitions", game); - do { + while (canRespond()) { + updateGameStatePriority("announceRepetitions", game); prepareForResponse(game); game.fireGetAmountEvent(playerId, "How many times do you want to repeat your shortcut?", 0, 999); waitForResponse(game); - } while (response.getInteger() == null - && !abort); - if (response != null - && response.getInteger() != null) { + + if (response.getInteger() != null) { + break; + } + } + + if (response.getInteger() != null) { xValue = response.getInteger(); } return xValue; @@ -1222,17 +1277,20 @@ public class HumanPlayer extends PlayerImpl { int xValue = 0; String extraMessage = (multiplier == 1 ? "" : ", X will be increased by " + multiplier + " times"); - updateGameStatePriority("announceXMana", game); - do { + while (canRespond()) { + updateGameStatePriority("announceXMana", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message + extraMessage, min, max); } waitForResponse(game); - } while (response.getInteger() == null - && !abort); - if (response != null - && response.getInteger() != null) { + + if (response.getInteger() != null) { + break; + } + } + + if (response.getInteger() != null) { xValue = response.getInteger(); } return xValue; @@ -1243,18 +1301,22 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return 0; } + int xValue = 0; - updateGameStatePriority("announceXCost", game); - do { + while (canRespond()) { + updateGameStatePriority("announceXCost", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message, min, max); } waitForResponse(game); - } while (response.getInteger() == null - && !abort); - if (response != null - && response.getInteger() != null) { + + if (response.getInteger() != null) { + break; + } + } + + if (response.getInteger() != null) { xValue = response.getInteger(); } return xValue; @@ -1292,12 +1354,11 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } - updateGameStatePriority("selectAttackers", game); + FilterCreatureForCombat filter = filterCreatureForCombat.copy(); filter.add(new ControllerIdPredicate(attackingPlayerId)); - while (!abort) { - + while (canRespond()) { List possibleAttackers = new ArrayList<>(); for (Permanent possibleAttacker : game.getBattlefield().getActivePermanents(filter, attackingPlayerId, game)) { if (possibleAttacker.canAttack(null, game)) { @@ -1312,8 +1373,8 @@ public class HumanPlayer extends PlayerImpl { if (passedAllTurns || passedUntilEndStepBeforeMyTurn || (!getControllingPlayersUserData(game) - .getUserSkipPrioritySteps() - .isStopOnDeclareAttackers() + .getUserSkipPrioritySteps() + .isStopOnDeclareAttackers() && (passedTurn || passedTurnSkipStack || passedUntilEndOfTurn @@ -1341,11 +1402,13 @@ public class HumanPlayer extends PlayerImpl { options.put(Constants.Option.SPECIAL_BUTTON, "All attack"); } + updateGameStatePriority("selectAttackers", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectEvent(playerId, "Select attackers", options); } waitForResponse(game); + if (response.getString() != null && response.getString().equals("special")) { // All attack setStoredBookmark(game.bookmarkState()); @@ -1496,7 +1559,7 @@ public class HumanPlayer extends PlayerImpl { /** * Selects a defender for an attacker and adds the attacker to combat * - * @param defenders - list of possible defender + * @param defenders - list of possible defender * @param attackerId - UUID of attacker * @param game * @return @@ -1554,7 +1617,7 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } - updateGameStatePriority("selectBlockers", game); + FilterCreatureForCombatBlock filter = filterCreatureForCombatBlock.copy(); filter.add(new ControllerIdPredicate(defendingPlayerId)); @@ -1566,7 +1629,8 @@ public class HumanPlayer extends PlayerImpl { return; } - while (!abort) { + while (canRespond()) { + updateGameStatePriority("selectBlockers", game); prepareForResponse(game); if (!isExecutingMacro()) { Map options = new HashMap<>(); @@ -1577,6 +1641,7 @@ public class HumanPlayer extends PlayerImpl { game.fireSelectEvent(playerId, "Select blockers", options); } waitForResponse(game); + if (response.getBoolean() != null) { return; } else if (response.getInteger() != null) { @@ -1606,13 +1671,15 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } - updateGameStatePriority("chooseAttackerOrder", game); - while (!abort) { + + while (canRespond()) { + updateGameStatePriority("chooseAttackerOrder", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, "Pick attacker", attackers, true); } waitForResponse(game); + if (response.getUUID() != null) { for (Permanent perm : attackers) { if (perm.getId().equals(response.getUUID())) { @@ -1629,13 +1696,15 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } - updateGameStatePriority("chooseBlockerOrder", game); - while (!abort) { + + while (canRespond()) { + updateGameStatePriority("chooseBlockerOrder", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireSelectTargetEvent(playerId, "Pick blocker", blockers, true); } waitForResponse(game); + if (response.getUUID() != null) { for (Permanent perm : blockers) { if (perm.getId().equals(response.getUUID())) { @@ -1651,8 +1720,14 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } - updateGameStatePriority("selectCombatGroup", game); TargetAttackingCreature target = new TargetAttackingCreature(); + + // TODO: add canRespond cycle? + if (!canRespond()) { + return; + } + + updateGameStatePriority("selectCombatGroup", game); prepareForResponse(game); if (!isExecutingMacro()) { // possible attackers to block @@ -1665,12 +1740,11 @@ public class HumanPlayer extends PlayerImpl { possibleTargets.add(attackerId); } } - game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)), possibleTargets, false, getOptions(target, null)); } - waitForResponse(game); + if (response.getBoolean() != null) { // do nothing } else if (response.getUUID() != null) { @@ -1719,15 +1793,21 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return 0; } - updateGameStatePriority("getAmount", game); - do { + + while (canRespond()) { + updateGameStatePriority("getAmount", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetAmountEvent(playerId, message, min, max); } waitForResponse(game); - } while (response.getInteger() == null && !abort); - if (response != null && response.getInteger() != null) { + + if (response.getInteger() != null) { + break; + } + } + + if (response.getInteger() != null) { return response.getInteger(); } else { return 0; @@ -1753,14 +1833,21 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } + + if (!canRespond()) { + return; + } + Map specialActions = game.getState().getSpecialActions().getControlledBy(playerId, false); if (!specialActions.isEmpty()) { + updateGameStatePriority("specialAction", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values())); } waitForResponse(game); + if (response.getUUID() != null) { if (specialActions.containsKey(response.getUUID())) { activateAbility(specialActions.get(response.getUUID()), game); @@ -1773,6 +1860,11 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } + + if (!canRespond()) { + return; + } + Map specialActions = game.getState().getSpecialActions().getControlledBy(playerId, true); if (!specialActions.isEmpty()) { updateGameStatePriority("specialAction", game); @@ -1781,6 +1873,7 @@ public class HumanPlayer extends PlayerImpl { game.fireGetChoiceEvent(playerId, name, null, new ArrayList<>(specialActions.values())); } waitForResponse(game); + if (response.getUUID() != null) { if (specialActions.containsKey(response.getUUID())) { SpecialAction specialAction = specialActions.get(response.getUUID()); @@ -1803,7 +1896,13 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return; } + + if (!canRespond()) { + return; + } + updateGameStatePriority("activateAbility", game); + if (abilities.size() == 1 && suppressAbilityPicker(abilities.values().iterator().next(), game)) { ActivatedAbility ability = abilities.values().iterator().next(); @@ -1825,12 +1924,13 @@ public class HumanPlayer extends PlayerImpl { } } + // TODO: add canRespond cycle? prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(abilities.values())); } - waitForResponse(game); + if (response.getUUID() != null && isInGame()) { if (abilities.containsKey(response.getUUID())) { activateAbility(abilities.get(response.getUUID()), game); @@ -1857,6 +1957,12 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } + + // TODO: add canRespond cycle? + if (!canRespond()) { + return null; + } + switch (ability.getSpellAbilityType()) { case SPLIT: case SPLIT_FUSED: @@ -1869,11 +1975,14 @@ public class HumanPlayer extends PlayerImpl { return (SpellAbility) useableAbilities.values().iterator().next(); } else if (useableAbilities != null && !useableAbilities.isEmpty()) { + + updateGameStatePriority("chooseSpellAbilityForCast", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values())); } waitForResponse(game); + if (response.getUUID() != null) { if (useableAbilities.containsKey(response.getUUID())) { return (SpellAbility) useableAbilities.get(response.getUUID()); @@ -1892,6 +2001,12 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } + + // TODO: add canRespond cycle? + if (!canRespond()) { + return null; + } + MageObject object = game.getObject(card.getId()); if (object != null) { LinkedHashMap useableAbilities = getSpellAbilities(object, game.getState().getZone(object.getId()), game); @@ -1900,11 +2015,14 @@ public class HumanPlayer extends PlayerImpl { return (SpellAbility) useableAbilities.values().iterator().next(); } else if (useableAbilities != null && !useableAbilities.isEmpty()) { + + updateGameStatePriority("chooseAbilityForCast", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetChoiceEvent(playerId, name, object, new ArrayList<>(useableAbilities.values())); } waitForResponse(game); + if (response.getUUID() != null) { if (useableAbilities.containsKey(response.getUUID())) { return (SpellAbility) useableAbilities.get(response.getUUID()); @@ -1921,7 +2039,7 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return null; } - updateGameStatePriority("chooseMode", game); + if (modes.size() > 1) { MageObject obj = game.getObject(source.getSourceId()); Map modeMap = new LinkedHashMap<>(); @@ -1954,14 +2072,18 @@ public class HumanPlayer extends PlayerImpl { modeMap.put(mode.getId(), modeText); } } + if (!modeMap.isEmpty()) { boolean done = false; - while (!done) { + while (!done && canRespond()) { + + updateGameStatePriority("chooseMode", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireGetModeEvent(playerId, "Choose Mode", modeMap); } waitForResponse(game); + if (response.getUUID() != null) { for (Mode mode : modes.getAvailableModes(source, game)) { if (mode.getId().equals(response.getUUID())) { @@ -1977,9 +2099,6 @@ public class HumanPlayer extends PlayerImpl { if (source.getAbilityType() != AbilityType.TRIGGERED) { done = true; } - if (!canRespond()) { - return null; - } } } return null; @@ -1993,18 +2112,25 @@ public class HumanPlayer extends PlayerImpl { if (gameInCheckPlayableState(game)) { return true; } - updateGameStatePriority("choosePile", game); - do { + + while (canRespond()) { + updateGameStatePriority("choosePile", game); prepareForResponse(game); if (!isExecutingMacro()) { game.fireChoosePileEvent(playerId, message, pile1, pile2); } waitForResponse(game); - } while (response.getBoolean() == null && !abort); - if (!abort) { - return response.getBoolean(); + + if (response.getBoolean() != null) { + break; + } + } + + if (response.getBoolean() != null) { + return response.getBoolean(); + } else { + return false; } - return false; } @Override @@ -2094,6 +2220,8 @@ public class HumanPlayer extends PlayerImpl { } 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); @@ -2248,6 +2376,7 @@ public class HumanPlayer extends PlayerImpl { 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. Pass regardless?") + GameLog.getSmallSecondLineText(activePlayerText + " / " + game.getStep().getType().toString() + priorityPlayerText), null, game)) { sendPlayerAction(PlayerAction.PASS_PRIORITY_CANCEL_ALL_ACTIONS, game, null); diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index 755571becf..be02de0ed5 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -5,6 +5,7 @@ import mage.cards.repository.CardRepository; import mage.server.exceptions.UserNotFoundException; import mage.server.game.GameController; import mage.server.game.GameManager; +import mage.server.game.GamesRoomManager; import mage.server.util.SystemUtil; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; @@ -316,12 +317,16 @@ public enum ChatManager { } public void sendLostConnectionMessage(UUID userId, DisconnectReason reason) { + UserManager.instance.getUser(userId).ifPresent(user -> sendMessageToUserChats(userId, user.getName() + " " + reason.getMessage())); + } + + public void sendMessageToUserChats(UUID userId, String message) { UserManager.instance.getUser(userId).ifPresent(user -> getChatSessions() .stream() + .filter(chat -> !chat.getChatId().equals(GamesRoomManager.instance.getMainChatId())) // ignore main lobby .filter(chat -> chat.hasUser(userId)) - .forEach(chatSession -> chatSession.broadcast(null, user.getName() + reason.getMessage(), MessageColor.BLUE, true, MessageType.STATUS, null))); - + .forEach(chatSession -> chatSession.broadcast(null, message, MessageColor.BLUE, true, MessageType.STATUS, null))); } public void removeUser(UUID userId, DisconnectReason reason) { diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 21ca15d278..2c7cdea698 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -22,6 +22,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; public enum UserManager { instance; + private static final int SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS = 30; // send to chat info about disconnection troubles, must be more than ping timeout private static final int SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS = 3 * 60; // removes from all games and chats too (can be seen in users list with disconnected status) private static final int SERVER_TIMEOUTS_USER_REMOVE_FROM_SERVER_AFTER_SECS = 8 * 60; // removes from users list @@ -145,6 +146,22 @@ public enum UserManager { } } + public void informUserOpponents(final UUID userId, final String message) { + if (userId != null) { + getUser(userId).ifPresent(user + -> USER_EXECUTOR.execute( + () -> { + try { + logger.info("INFORM OPPONENTS by " + user.getName() + ": " + message); + ChatManager.instance.sendMessageToUserChats(user.getId(), message); + } catch (Exception ex) { + handleException(ex); + } + } + )); + } + } + public boolean extendUserSession(UUID userId, String pingInfo) { if (userId != null) { User user = users.get(userId); @@ -163,6 +180,8 @@ public enum UserManager { */ private void checkExpired() { try { + Calendar calendarInform = Calendar.getInstance(); + calendarInform.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_INFORM_OPPONENTS_ABOUT_DISCONNECT_AFTER_SECS); Calendar calendarExp = Calendar.getInstance(); calendarExp.add(Calendar.SECOND, -1 * SERVER_TIMEOUTS_USER_DISCONNECT_FROM_SERVER_AFTER_SECS); Calendar calendarRemove = Calendar.getInstance(); @@ -179,6 +198,12 @@ public enum UserManager { } for (User user : userList) { try { + if (user.getUserState() != UserState.Offline + && user.isExpired(calendarInform.getTime())) { + long secsInfo = (Calendar.getInstance().getTimeInMillis() - user.getLastActivity().getTime()) / 1000; + informUserOpponents(user.getId(), user.getName() + " got connection problem for " + secsInfo + " secs"); + } + if (user.getUserState() == UserState.Offline) { if (user.isExpired(calendarRemove.getTime())) { // removes from users list diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 356b57ac33..7374ddb6a8 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -50,7 +50,7 @@ import java.util.zip.GZIPOutputStream; public class GameController implements GameCallback { private static final int GAME_TIMEOUTS_CHECK_JOINING_STATUS_EVERY_SECS = 15; // checks and inform players about joining status - private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 4 * 60; // leave player from game if it don't join and inactive on server + private static final int GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS = 2 * 60; // leave player from game if it don't join and inactive on server private static final ExecutorService gameExecutor = ThreadExecutor.instance.getGameExecutor(); private static final Logger logger = Logger.getLogger(GameController.class); @@ -336,7 +336,10 @@ public class GameController implements GameCallback { GameManager.instance.joinGame(game.getId(), user.getId()); logger.debug("Player " + player.getName() + " (disconnected) has joined gameId: " + game.getId()); } - ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + " is pending to join the game", MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); + ChatManager.instance.broadcast(chatId, player.getName(), user.getPingInfo() + + " is pending to join the game (waiting " + user.getSecondsDisconnected() + " of " + + GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS + " secs)", + MessageColor.BLUE, true, ChatMessage.MessageType.STATUS, null); if (user.getSecondsDisconnected() > GAME_TIMEOUTS_CANCEL_PLAYER_GAME_JOINING_AFTER_INACTIVE_SECS) { // TODO: 2019.04.22 - if user playing another game on server but not joining (that's the reason?), then that's check will never trigger // Cancel player join possibility lately after 4 minutes @@ -1201,6 +1204,7 @@ public class GameController implements GameCallback { if (activePlayer != null && activePlayer.hasLeft()) { sb.append("
Found disconnected player! Concede..."); activePlayer.concede(game); + activePlayer.leave(); // abort any wait response actions Phase currentPhase = game.getPhase(); if (currentPhase != null) { @@ -1221,6 +1225,7 @@ public class GameController implements GameCallback { Player p = game.getPlayer(state.getChoosingPlayerId()); if (p != null) { p.concede(game); + p.leave(); // abort any wait response actions } Phase currentPhase = game.getPhase(); if (currentPhase != null && !fixedAlready) { @@ -1235,14 +1240,14 @@ public class GameController implements GameCallback { } // fix lost priority + Player p = game.getPlayer(state.getPriorityPlayerId()); sb.append("
Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId()))); - if (state.getPriorityPlayerId() != null) { - if (game.getPlayer(state.getPriorityPlayerId()).hasLeft()) { + if (p != null) { + if (p.hasLeft()) { sb.append("
Found disconnected player! Concede..."); - Player p = game.getPlayer(state.getPriorityPlayerId()); - if (p != null) { - p.concede(game); - } + p.concede(game); + p.leave(); // abort any wait response actions + Phase currentPhase = game.getPhase(); if (currentPhase != null && !fixedAlready) { currentPhase.getStep().skipStep(game, state.getActivePlayerId()); @@ -1250,7 +1255,6 @@ public class GameController implements GameCallback { sb.append("
Forcibly passing the phase!"); } } - sb.append(game.getPlayer(state.getPriorityPlayerId()).getName()); sb.append(""); } @@ -1272,6 +1276,8 @@ public class GameController implements GameCallback { sb.append("Not using future Timeout!"); } sb.append(""); + + return sb.toString(); } } diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java index 086a1cc04d..ebe711cfcf 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionWatcher.java @@ -1,5 +1,3 @@ - - package mage.server.game; import mage.game.Game; @@ -101,7 +99,6 @@ public class GameSessionWatcher { GameView gameView = new GameView(game.getState(), game, null, userId); processWatchedHands(userId, gameView); return gameView; - } protected void processWatchedHands(UUID userId, GameView gameView) { diff --git a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java b/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java index 83e78ed57e..73eaeba89b 100644 --- a/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GamesRoomManager.java @@ -1,5 +1,3 @@ - - package mage.server.game; import org.apache.log4j.Logger; @@ -16,12 +14,14 @@ public enum GamesRoomManager { private final ConcurrentHashMap rooms = new ConcurrentHashMap<>(); private final UUID mainRoomId; + private final UUID mainChatId; private static final Logger logger = Logger.getLogger(GamesRoomManager.class); GamesRoomManager() { GamesRoom mainRoom = new GamesRoomImpl(); mainRoomId = mainRoom.getRoomId(); + mainChatId = mainRoom.getChatId(); rooms.put(mainRoomId, mainRoom); } @@ -35,8 +35,12 @@ public enum GamesRoomManager { return mainRoomId; } + public UUID getMainChatId() { + return mainChatId; + } + public Optional getRoom(UUID roomId) { - if(rooms.containsKey(roomId)) { + if (rooms.containsKey(roomId)) { return Optional.of(rooms.get(roomId)); } logger.error("room not found : " + roomId); diff --git a/Mage.Sets/src/mage/cards/d/DeathbellowWarCry.java b/Mage.Sets/src/mage/cards/d/DeathbellowWarCry.java index 2a70c91197..fddedf90f3 100644 --- a/Mage.Sets/src/mage/cards/d/DeathbellowWarCry.java +++ b/Mage.Sets/src/mage/cards/d/DeathbellowWarCry.java @@ -1,5 +1,6 @@ package mage.cards.d; +import mage.abilities.Ability; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -60,10 +61,10 @@ class DeathbellowWarCryTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); return card != null - && filter.match(card, game) + && filter.match(card, playerId, game) && this .getTargets() .stream() diff --git a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java index c99f4c54f1..ea23326f71 100644 --- a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java +++ b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java @@ -1,7 +1,5 @@ - package mage.cards.g; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.*; @@ -15,14 +13,15 @@ import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class GiftsUngiven extends CardImpl { public GiftsUngiven(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{3}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // Search your library for up to four cards with different names and reveal them. Target opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. this.getSpellAbility().addEffect(new GiftsUngivenEffect()); @@ -115,7 +114,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { for (UUID targetId : this.getTargets()) { @@ -124,7 +123,7 @@ class GiftsUngivenTarget extends TargetCardInLibrary { return false; } } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/m/MyriadLandscape.java b/Mage.Sets/src/mage/cards/m/MyriadLandscape.java index be5c3e2e26..a88e3285de 100644 --- a/Mage.Sets/src/mage/cards/m/MyriadLandscape.java +++ b/Mage.Sets/src/mage/cards/m/MyriadLandscape.java @@ -88,8 +88,8 @@ class TargetCardInLibrarySharingLandType extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { - if (super.canTarget(id, cards, game)) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { + if (super.canTarget(playerId, id, source, cards, game)) { if (!getTargets().isEmpty()) { // check if new target shares a Land Type SubTypeList landTypes = new SubTypeList(); diff --git a/Mage.Sets/src/mage/cards/n/NissasEncouragement.java b/Mage.Sets/src/mage/cards/n/NissasEncouragement.java index a94f770385..63466a9dda 100644 --- a/Mage.Sets/src/mage/cards/n/NissasEncouragement.java +++ b/Mage.Sets/src/mage/cards/n/NissasEncouragement.java @@ -1,15 +1,8 @@ - package mage.cards.n; -import java.util.HashMap; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -21,8 +14,10 @@ import mage.players.Player; import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; +import java.util.HashMap; +import java.util.UUID; + /** - * * @author spjspj */ public final class NissasEncouragement extends CardImpl { @@ -89,8 +84,8 @@ class NissasEncouragementEffect extends OneShotEffect { foundCards.put("Brambleweft Behemoth", 0); foundCards.put("Nissa, Genesis Mage", 0); Cards cards = new CardsImpl(); - - if (!target.getTargets().isEmpty()) { + + if (!target.getTargets().isEmpty()) { for (UUID cardId : target.getTargets()) { Card card = player.getLibrary().remove(cardId, game); @@ -120,7 +115,7 @@ class NissasEncouragementEffect extends OneShotEffect { } } } - + if (!cards.isEmpty()) { player.revealCards(sourceCard.getIdName(), cards, game); player.moveCards(cards, Zone.HAND, source, game); @@ -149,7 +144,7 @@ class NissasEncouragementTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { for (UUID targetId : this.getTargets()) { @@ -158,7 +153,7 @@ class NissasEncouragementTarget extends TargetCardInLibrary { return false; } } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/o/OpenTheArmory.java b/Mage.Sets/src/mage/cards/o/OpenTheArmory.java index a0063c18b6..8e04b2959c 100644 --- a/Mage.Sets/src/mage/cards/o/OpenTheArmory.java +++ b/Mage.Sets/src/mage/cards/o/OpenTheArmory.java @@ -1,31 +1,35 @@ - package mage.cards.o; -import java.util.UUID; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; -import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterCard; import mage.filter.predicate.Predicates; import mage.filter.predicate.mageobject.SubtypePredicate; -import mage.game.Game; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ public final class OpenTheArmory extends CardImpl { + private static final FilterCard auraOrEquipmentTarget = new FilterCard("Aura or Equipment card"); + + static { + auraOrEquipmentTarget.add(Predicates.or( + new SubtypePredicate(SubType.EQUIPMENT), + new SubtypePredicate(SubType.AURA))); + } + public OpenTheArmory(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{1}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}"); // Search your library for an Aura or Equipment card, reveal it, and put it into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new OpenTheArmoryTarget(), true, true)); + this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(1, 1, auraOrEquipmentTarget), true, true)); } public OpenTheArmory(final OpenTheArmory card) { @@ -36,36 +40,4 @@ public final class OpenTheArmory extends CardImpl { public OpenTheArmory copy() { return new OpenTheArmory(this); } -} - -class OpenTheArmoryTarget extends TargetCardInLibrary { - - private static final FilterCard auraOrEquipmentTarget = new FilterCard("Aura or Equipment card"); - static { - auraOrEquipmentTarget.add(Predicates.or( - new SubtypePredicate(SubType.EQUIPMENT), - new SubtypePredicate(SubType.AURA))); - } - - public OpenTheArmoryTarget() { - super(1, 1, auraOrEquipmentTarget.copy()); - } - - public OpenTheArmoryTarget(final OpenTheArmoryTarget target) { - super(target); - } - - @Override - public OpenTheArmoryTarget copy() { - return new OpenTheArmoryTarget(this); - } - - @Override - public boolean canTarget(UUID id, Cards cards, Game game) { - Card card = cards.get(id, game); - if (card != null) { - return auraOrEquipmentTarget.match(card, game); - } - return false; - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java index 399fb4494f..09752eba1a 100644 --- a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java +++ b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java @@ -1,16 +1,9 @@ - package mage.cards.r; -import java.util.Set; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -23,14 +16,16 @@ import mage.target.TargetCard; import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetOpponent; +import java.util.Set; +import java.util.UUID; + /** - * * @author North */ public final class RealmsUncharted extends CardImpl { public RealmsUncharted(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{2}{G}"); + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Search your library for four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. this.getSpellAbility().addEffect(new RealmsUnchartedEffect()); @@ -128,7 +123,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { for (UUID targetId : this.getTargets()) { @@ -137,7 +132,7 @@ class RealmsUnchartedTarget extends TargetCardInLibrary { return false; } } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SaheeliRai.java b/Mage.Sets/src/mage/cards/s/SaheeliRai.java index 29dba31788..cc3a069b3b 100644 --- a/Mage.Sets/src/mage/cards/s/SaheeliRai.java +++ b/Mage.Sets/src/mage/cards/s/SaheeliRai.java @@ -1,7 +1,5 @@ - package mage.cards.s; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.DelayedTriggeredAbility; import mage.abilities.LoyaltyAbility; @@ -9,20 +7,16 @@ import mage.abilities.common.PlaneswalkerEntersWithLoyaltyCountersAbility; import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility; import mage.abilities.effects.Effect; import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.DamagePlayersEffect; import mage.abilities.effects.common.ExileTargetEffect; -import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.cards.Cards; -import mage.constants.CardType; -import mage.constants.SubType; -import mage.constants.Outcome; -import mage.constants.SuperType; -import mage.constants.TargetController; +import mage.constants.*; import mage.filter.StaticFilters; import mage.filter.common.FilterArtifactCard; import mage.game.Game; @@ -31,14 +25,15 @@ import mage.target.common.TargetCardInLibrary; import mage.target.common.TargetControlledPermanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** - * * @author emerald000 */ public final class SaheeliRai extends CardImpl { public SaheeliRai(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.PLANESWALKER},"{1}{U}{R}"); + super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{1}{U}{R}"); this.addSuperType(SuperType.LEGENDARY); this.subtype.add(SubType.SAHEELI); @@ -121,7 +116,7 @@ class SaheeliRaiTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { for (UUID targetId : this.getTargets()) { @@ -130,7 +125,7 @@ class SaheeliRaiTarget extends TargetCardInLibrary { return false; } } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/s/SharedSummons.java b/Mage.Sets/src/mage/cards/s/SharedSummons.java index 62c9d5edf9..f49b10117c 100644 --- a/Mage.Sets/src/mage/cards/s/SharedSummons.java +++ b/Mage.Sets/src/mage/cards/s/SharedSummons.java @@ -1,5 +1,6 @@ package mage.cards.s; +import mage.abilities.Ability; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -55,15 +56,20 @@ class SharedSummonsTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card == null || !card.isCreature()) { return false; } - return !this + + if (!filter.match(card, playerId, game)) { + return false; + } + + return this .getTargets() .stream() - .map(uuid -> game.getCard(uuid)) - .anyMatch(c -> c != null && c.getName().equals(card.getName())); + .map(game::getCard) + .noneMatch(c -> c != null && c.getName().equals(card.getName())); } } diff --git a/Mage.Sets/src/mage/cards/t/ThreeDreams.java b/Mage.Sets/src/mage/cards/t/ThreeDreams.java index 6ca476d0b4..b8543af3f8 100644 --- a/Mage.Sets/src/mage/cards/t/ThreeDreams.java +++ b/Mage.Sets/src/mage/cards/t/ThreeDreams.java @@ -1,7 +1,6 @@ - package mage.cards.t; -import java.util.UUID; +import mage.abilities.Ability; import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect; import mage.cards.Card; import mage.cards.CardImpl; @@ -14,14 +13,15 @@ import mage.filter.predicate.mageobject.SubtypePredicate; import mage.game.Game; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author LevelX2 */ public final class ThreeDreams extends CardImpl { public ThreeDreams(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{4}{W}"); + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{W}"); // Search your library for up to three Aura cards with different names, reveal them, and put them into your hand. Then shuffle your library. @@ -41,6 +41,7 @@ public final class ThreeDreams extends CardImpl { class ThreeDreamsTarget extends TargetCardInLibrary { private static final FilterCard aurafilter = new FilterCard("Aura cards with different names"); + static { aurafilter.add(new SubtypePredicate(SubType.AURA)); } @@ -59,7 +60,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { // check if card with that name was selected before @@ -69,7 +70,7 @@ class ThreeDreamsTarget extends TargetCardInLibrary { return false; } } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java b/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java index 840fbbe22e..e45a6432a5 100644 --- a/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java +++ b/Mage.Sets/src/mage/cards/u/UncageTheMenagerie.java @@ -1,14 +1,8 @@ - package mage.cards.u; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; -import mage.cards.Card; -import mage.cards.CardImpl; -import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; +import mage.cards.*; import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; @@ -19,8 +13,9 @@ import mage.game.Game; import mage.players.Player; import mage.target.common.TargetCardInLibrary; +import java.util.UUID; + /** - * * @author spjspj */ public final class UncageTheMenagerie extends CardImpl { @@ -115,7 +110,7 @@ class UncageTheMenagerieTarget extends TargetCardInLibrary { } @Override - public boolean canTarget(UUID id, Cards cards, Game game) { + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { Card card = cards.get(id, game); if (card != null) { for (UUID targetId : this.getTargets()) { @@ -124,11 +119,12 @@ class UncageTheMenagerieTarget extends TargetCardInLibrary { return false; } } + if (!(card.isCreature() && card.getConvertedManaCost() == xValue)) { return false; } - return filter.match(card, game); + return filter.match(card, playerId, game); } return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java index 7321a27ffe..f74a0b360f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java @@ -4,7 +4,6 @@ import mage.constants.PlayerAction; import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.remote.Session; -import mage.utils.CompressUtil; import mage.view.*; import org.apache.log4j.Logger; @@ -36,9 +35,18 @@ public class LoadCallbackClient implements CallbackClient { @Override public void processCallback(ClientCallback callback) { + callback.decompressData(); controlCount = 0; - callback.setData(CompressUtil.decompress(callback.getData())); - log.info(getLogStartInfo() + "callback: " + callback.getMethod()); + + // ignore bloaded logs + switch (callback.getMethod()) { + case CHATMESSAGE: + case GAME_INFORM: + case GAME_UPDATE: + break; + default: + log.info(getLogStartInfo() + "callback: " + callback.getMethod()); + } switch (callback.getMethod()) { @@ -69,7 +77,7 @@ public class LoadCallbackClient implements CallbackClient { case GAME_INFORM_PERSONAL: { GameClientMessage message = (GameClientMessage) callback.getData(); gameView = message.getGameView(); - log.info(getLogStartInfo() + "Inform: " + message.getMessage()); + //log.info(getLogStartInfo() + "Inform: " + message.getMessage()); break; } diff --git a/Mage/src/main/java/mage/target/TargetCard.java b/Mage/src/main/java/mage/target/TargetCard.java index a1d00b7f82..464b14ff22 100644 --- a/Mage/src/main/java/mage/target/TargetCard.java +++ b/Mage/src/main/java/mage/target/TargetCard.java @@ -208,8 +208,8 @@ public class TargetCard extends TargetObject { && getFilter() != null && getFilter().match(card, playerId, game); } - public boolean canTarget(UUID id, Cards cards, Game game) { - return cards.contains(id) && canTarget(id, game); + public boolean canTarget(UUID playerId, UUID id, Ability source, Cards cards, Game game) { + return cards.contains(id) && canTarget(playerId, id, source, game); } @Override