diff --git a/Mage.Common/src/main/java/mage/utils/SystemUtil.java b/Mage.Common/src/main/java/mage/utils/SystemUtil.java index 4f71a78bca2..aff66539187 100644 --- a/Mage.Common/src/main/java/mage/utils/SystemUtil.java +++ b/Mage.Common/src/main/java/mage/utils/SystemUtil.java @@ -1,8 +1,6 @@ package mage.utils; -import mage.MageObject; import mage.abilities.Ability; -import mage.abilities.ActivatedAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; @@ -26,6 +24,7 @@ import mage.game.permanent.token.Token; import mage.players.Player; import mage.target.Target; import mage.target.TargetPlayer; +import mage.target.common.TargetOpponent; import mage.util.CardUtil; import mage.util.MultiAmountMessage; import mage.util.RandomUtil; @@ -68,15 +67,14 @@ public final class SystemUtil { // [@mana add] -> MANA ADD private static final String COMMAND_CARDS_ADD_TO_HAND = "@card add to hand"; private static final String COMMAND_LANDS_ADD_TO_BATTLEFIELD = "@lands add"; - private static final String COMMAND_OPPONENT_UNDER_CONTROL_START = "@opponent under control start"; - private static final String COMMAND_OPPONENT_UNDER_CONTROL_END = "@opponent under control end"; + private static final String COMMAND_UNDER_CONTROL_TAKE = "@under control take"; + private static final String COMMAND_UNDER_CONTROL_GIVE = "@under control give"; private static final String COMMAND_MANA_ADD = "@mana add"; // TODO: not implemented private static final String COMMAND_RUN_CUSTOM_CODE = "@run custom code"; // TODO: not implemented private static final String COMMAND_SHOW_OPPONENT_HAND = "@show opponent hand"; private static final String COMMAND_SHOW_OPPONENT_LIBRARY = "@show opponent library"; private static final String COMMAND_SHOW_MY_HAND = "@show my hand"; private static final String COMMAND_SHOW_MY_LIBRARY = "@show my library"; - private static final String COMMAND_ACTIVATE_OPPONENT_ABILITY = "@activate opponent ability"; private static final Map supportedCommands = new HashMap<>(); static { @@ -84,14 +82,13 @@ public final class SystemUtil { supportedCommands.put(COMMAND_CARDS_ADD_TO_HAND, "CARDS: ADD TO HAND"); supportedCommands.put(COMMAND_MANA_ADD, "MANA ADD"); supportedCommands.put(COMMAND_LANDS_ADD_TO_BATTLEFIELD, "LANDS: ADD TO BATTLEFIELD"); - supportedCommands.put(COMMAND_OPPONENT_UNDER_CONTROL_START, "OPPONENT CONTROL: ENABLE"); - supportedCommands.put(COMMAND_OPPONENT_UNDER_CONTROL_END, "OPPONENT CONTROL: DISABLE"); + supportedCommands.put(COMMAND_UNDER_CONTROL_TAKE, "UNDER CONTROL: TAKE"); + supportedCommands.put(COMMAND_UNDER_CONTROL_GIVE, "UNDER CONTROL: GIVE"); supportedCommands.put(COMMAND_RUN_CUSTOM_CODE, "RUN CUSTOM CODE"); supportedCommands.put(COMMAND_SHOW_OPPONENT_HAND, "SHOW OPPONENT HAND"); supportedCommands.put(COMMAND_SHOW_OPPONENT_LIBRARY, "SHOW OPPONENT LIBRARY"); supportedCommands.put(COMMAND_SHOW_MY_HAND, "SHOW MY HAND"); supportedCommands.put(COMMAND_SHOW_MY_LIBRARY, "SHOW MY LIBRARY"); - supportedCommands.put(COMMAND_ACTIVATE_OPPONENT_ABILITY, "ACTIVATE OPPONENT ABILITY"); } private static final Pattern patternGroup = Pattern.compile("\\[(.+)\\]"); // [test new card] @@ -307,8 +304,8 @@ public final class SystemUtil { // add default commands initLines.add(0, String.format("[%s]", COMMAND_LANDS_ADD_TO_BATTLEFIELD)); initLines.add(1, String.format("[%s]", COMMAND_CARDS_ADD_TO_HAND)); - initLines.add(2, String.format("[%s]", COMMAND_OPPONENT_UNDER_CONTROL_START)); - initLines.add(3, String.format("[%s]", COMMAND_OPPONENT_UNDER_CONTROL_END)); + initLines.add(2, String.format("[%s]", COMMAND_UNDER_CONTROL_TAKE)); + initLines.add(3, String.format("[%s]", COMMAND_UNDER_CONTROL_GIVE)); // collect all commands CommandGroup currentGroup = null; @@ -427,53 +424,6 @@ public final class SystemUtil { break; } - case COMMAND_ACTIVATE_OPPONENT_ABILITY: { - // WARNING, maybe very bugged if called in wrong priority - // uses choose triggered ability dialog to select it - UUID savedPriorityPlayer = null; - if (game.getActivePlayerId() != opponent.getId()) { - savedPriorityPlayer = game.getActivePlayerId(); - } - - // change active player to find and play selected abilities (it's danger and buggy code) - if (savedPriorityPlayer != null) { - game.getState().setPriorityPlayerId(opponent.getId()); - game.firePriorityEvent(opponent.getId()); - } - - List abilities = opponent.getPlayable(game, true); - Map choices = new HashMap<>(); - abilities.forEach(ability -> { - MageObject object = ability.getSourceObject(game); - choices.put(ability.getId().toString(), object.getName() + ": " + ability.toString()); - }); - // TODO: set priority for us? - Choice choice = new ChoiceImpl(false); - choice.setMessage("Choose playable ability to activate by opponent " + opponent.getName()); - choice.setKeyChoices(choices); - if (feedbackPlayer.choose(Outcome.Detriment, choice, game) && choice.getChoiceKey() != null) { - String needId = choice.getChoiceKey(); - Optional ability = abilities.stream().filter(a -> a.getId().toString().equals(needId)).findFirst(); - if (ability.isPresent()) { - // TODO: set priority for player? - ActivatedAbility activatedAbility = ability.get(); - game.informPlayers(feedbackPlayer.getLogName() + " as another player " + opponent.getLogName() - + " trying to force an activate ability: " + activatedAbility.getGameLogMessage(game)); - if (opponent.activateAbility(activatedAbility, game)) { - game.informPlayers("Force to activate ability: DONE"); - } else { - game.informPlayers("Force to activate ability: FAIL"); - } - } - } - // restore original priority player - if (savedPriorityPlayer != null) { - game.getState().setPriorityPlayerId(savedPriorityPlayer); - game.firePriorityEvent(savedPriorityPlayer); - } - break; - } - case COMMAND_CARDS_ADD_TO_HAND: { // card @@ -552,12 +502,12 @@ public final class SystemUtil { break; } - case COMMAND_OPPONENT_UNDER_CONTROL_START: { - Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to take under your control"); + case COMMAND_UNDER_CONTROL_TAKE: { + Target target = new TargetOpponent().withNotTarget(true).withChooseHint("to take under your control"); Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) { Player targetPlayer = game.getPlayer(target.getFirstTarget()); - if (targetPlayer != null && targetPlayer != feedbackPlayer) { + if (!targetPlayer.getId().equals(feedbackPlayer.getId())) { CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, feedbackPlayer, targetPlayer, false); // allow priority play again in same step (for better cheat UX) targetPlayer.resetPassed(); @@ -568,13 +518,19 @@ public final class SystemUtil { break; } - case COMMAND_OPPONENT_UNDER_CONTROL_END: { - Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to free from your control"); + case COMMAND_UNDER_CONTROL_GIVE: { + Target target = new TargetPlayer().withNotTarget(true).withChooseHint("to give control of your player"); Ability fakeSourceAbility = fakeSourceAbilityTemplate.copy(); if (feedbackPlayer.chooseTarget(Outcome.GainControl, target, fakeSourceAbility, game)) { Player targetPlayer = game.getPlayer(target.getFirstTarget()); - if (targetPlayer != null && targetPlayer != feedbackPlayer && !targetPlayer.isGameUnderControl()) { - CardUtil.takeControlUnderPlayerEnd(game, fakeSourceAbility, feedbackPlayer, targetPlayer); + if (targetPlayer != null) { + if (!targetPlayer.getId().equals(feedbackPlayer.getId())) { + // give control to another player + CardUtil.takeControlUnderPlayerStart(game, fakeSourceAbility, targetPlayer, feedbackPlayer, false); + } else { + // return control to itself + CardUtil.takeControlUnderPlayerEnd(game, fakeSourceAbility, feedbackPlayer, targetPlayer); + } } // workaround for refresh priority dialog like avatar click (cheats called from priority in 99%) game.firePriorityEvent(feedbackPlayer.getId()); 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 705f2479182..f6618532f77 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 @@ -360,9 +360,14 @@ public class HumanPlayer extends PlayerImpl { // async command: cheat by current player if (response.getAsyncWantCheat()) { - // execute cheats and continue + // run cheats SystemUtil.executeCheatCommands(game, null, this); - game.fireUpdatePlayersEvent(); // need force to game update for new possible data + // force to game update for new possible data + game.fireUpdatePlayersEvent(); + // must stop current dialog on changed control, so game can give priority to actual player + if (this.isGameUnderControl() != game.getPlayer(this.getId()).isGameUnderControl()) { + return; + } // wait another answer if (canRespond()) { loop = true; diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index b2270995726..2d086bc2b0f 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -3080,8 +3080,8 @@ public class TestPlayer implements Player { } @Override - public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { - computerPlayer.controlPlayersTurn(game, playerUnderControlId, info); + public boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + return computerPlayer.controlPlayersTurn(game, playerUnderControlId, info); } @Override diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index df84549735d..9566348bc7c 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -341,8 +341,9 @@ public interface Player extends MageItem, Copyable { * @param game * @param playerUnderControlId * @param info additional info to show in game logs like source + * @return false on failed taken control, e.g. on unsupported player type */ - void controlPlayersTurn(Game game, UUID playerUnderControlId, String info); + boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info); /** * Sets player {@link UUID} who controls this player's turn. diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 4eaea5fff07..fc1a1ad0f51 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -598,8 +598,17 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + public boolean controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { Player playerUnderControl = game.getPlayer(playerUnderControlId); + + // TODO: add support computer over computer + // TODO: add support computer over human + if (this.isComputer()) { + // not supported yet + game.informPlayers(getLogName() + " is AI and can't take control over " + playerUnderControl.getLogName() + info); + return false; + } + playerUnderControl.setTurnControlledBy(this.getId()); game.informPlayers(getLogName() + " taken turn control of " + playerUnderControl.getLogName() + info); if (!playerUnderControlId.equals(this.getId())) { @@ -609,6 +618,8 @@ public abstract class PlayerImpl implements Player, Serializable { } // control will reset on start of the turn } + + return true; } @Override @@ -777,10 +788,10 @@ public abstract class PlayerImpl implements Player, Serializable { } // if this method was called from a replacement event, pass the number of cards back through // (uncomment conditions if correct ruling is to only count cards drawn by the same player) - if (event instanceof DrawCardEvent /* && event.getPlayerId().equals(getId()) */ ) { + if (event instanceof DrawCardEvent /* && event.getPlayerId().equals(getId()) */) { ((DrawCardEvent) event).incrementCardsDrawn(numDrawn); } - if (event instanceof DrawTwoOrMoreCardsEvent /* && event.getPlayerId().equals(getId()) */ ) { + if (event instanceof DrawTwoOrMoreCardsEvent /* && event.getPlayerId().equals(getId()) */) { ((DrawTwoOrMoreCardsEvent) event).incrementCardsDrawn(numDrawn); } return numDrawn; diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 3f85a6fa591..dcabf68a6db 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1358,7 +1358,11 @@ public final class CardUtil { */ public static void takeControlUnderPlayerStart(Game game, Ability source, Player controller, Player playerUnderControl, boolean givePauseForResponse) { // game logs added in child's call - controller.controlPlayersTurn(game, playerUnderControl.getId(), CardUtil.getSourceLogName(game, source)); + if (!controller.controlPlayersTurn(game, playerUnderControl.getId(), CardUtil.getSourceLogName(game, source))) { + return; + } + + // give pause, so new controller can look around battlefield and hands before finish controlling choose dialog if (givePauseForResponse) { while (controller.canRespond()) { if (controller.chooseUse(Outcome.Benefit, "You got control of " + playerUnderControl.getLogName()