From 0505f5159e39d87272629fd0c1e29ca8c4fb8029 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 10 Jan 2025 20:18:00 +0400 Subject: [PATCH] Turn under control reworked: - game: added support when a human is take control over a computer player (related to #12878); - game: fixed game freezes while controlling player leaves/disconnect on active priority/choose of another player; --- .../ai/ComputerPlayerControllableProxy.java | 401 ++++++++++++++++++ .../src/mage/player/human/HumanPlayer.java | 37 +- Mage.Server/config/config.xml | 2 +- Mage.Server/release/config/config.xml | 2 +- .../java/mage/server/game/GameController.java | 89 +++- .../mage/server/game/GameSessionPlayer.java | 2 +- Mage.Server/src/test/data/config_error.xml | 2 +- Mage.Tests/config/config.xml | 2 +- .../java/org/mage/test/player/TestPlayer.java | 4 - Mage/src/main/java/mage/game/GameImpl.java | 4 + Mage/src/main/java/mage/players/Player.java | 16 +- .../main/java/mage/players/PlayerImpl.java | 5 - 12 files changed, 534 insertions(+), 32 deletions(-) create mode 100644 Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java diff --git a/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java new file mode 100644 index 00000000000..dd63da4bdf0 --- /dev/null +++ b/Mage.Server.Plugins/Mage.Player.AI.MA/src/mage/player/ai/ComputerPlayerControllableProxy.java @@ -0,0 +1,401 @@ +package mage.player.ai; + +import mage.MageObject; +import mage.abilities.*; +import mage.abilities.costs.VariableCost; +import mage.abilities.costs.mana.ManaCost; +import mage.cards.Card; +import mage.cards.Cards; +import mage.cards.decks.Deck; +import mage.choices.Choice; +import mage.constants.ManaType; +import mage.constants.MultiAmountType; +import mage.constants.Outcome; +import mage.constants.RangeOfInfluence; +import mage.game.Game; +import mage.game.combat.CombatGroup; +import mage.game.draft.Draft; +import mage.game.match.Match; +import mage.game.permanent.Permanent; +import mage.game.tournament.Tournament; +import mage.players.Player; +import mage.target.Target; +import mage.target.TargetAmount; +import mage.target.TargetCard; +import mage.util.MultiAmountMessage; +import org.apache.log4j.Logger; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * AI player that can be taken under control by another player (AI or human). + *

+ * Under control logic on choose dialog (under human): + * - create fake human player and assign it to real human data transfer object (for income answers); + * - call choose dialog from fake human (e.g. send choose data to real player); + * - game will process all sending and answering logic as "human under human" logic; + * - return choose dialog result without AI code processing; + * + * @author JayDi85 + */ +public class ComputerPlayerControllableProxy extends ComputerPlayer7 { + + private static final Logger logger = Logger.getLogger(ComputerPlayerControllableProxy.class); + + Player lastControllingPlayer = null; + + public ComputerPlayerControllableProxy(String name, RangeOfInfluence range, int skill) { + super(name, range, skill); + } + + public ComputerPlayerControllableProxy(final ComputerPlayerControllableProxy player) { + super(player); + this.lastControllingPlayer = player; + } + + @Override + public ComputerPlayerControllableProxy copy() { + return new ComputerPlayerControllableProxy(this); + } + + private boolean isUnderMe(Game game) { + return game.isSimulation() || this.isGameUnderControl(); + } + + private Player getControllingPlayer(Game game) { + Player player = game.getPlayer(this.getTurnControlledBy()); + this.lastControllingPlayer = player.prepareControllableProxy(this); + return this.lastControllingPlayer; + } + + @Override + public void setResponseString(String responseString) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.setResponseString(responseString); + } + } + + @Override + public void setResponseManaType(UUID manaTypePlayerId, ManaType responseManaType) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.setResponseManaType(manaTypePlayerId, responseManaType); + } + } + + @Override + public void setResponseUUID(UUID responseUUID) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.setResponseUUID(responseUUID); + } + } + + @Override + public void setResponseBoolean(Boolean responseBoolean) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.setResponseBoolean(responseBoolean); + } + } + + @Override + public void setResponseInteger(Integer responseInteger) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.setResponseInteger(responseInteger); + } + } + + @Override + public void signalPlayerCheat() { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.signalPlayerCheat(); + } + } + + @Override + public void signalPlayerConcede(boolean stopCurrentChooseDialog) { + if (this.lastControllingPlayer != null) { + this.lastControllingPlayer.signalPlayerConcede(stopCurrentChooseDialog); + } + } + + @Override + public boolean priority(Game game) { + if (isUnderMe(game)) { + return super.priority(game); + } else { + Player player = getControllingPlayer(game); + try { + return player.priority(game); + } finally { + this.passed = player.isPassed(); // TODO: wtf, no needs? + } + } + } + + @Override + public boolean chooseMulligan(Game game) { + if (isUnderMe(game)) { + return super.chooseMulligan(game); + } else { + return getControllingPlayer(game).chooseMulligan(game); + } + } + + @Override + public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseUse(outcome, message, source, game); + } else { + return getControllingPlayer(game).chooseUse(outcome, message, source, game); + } + } + + @Override + public boolean chooseUse(Outcome outcome, String message, String secondMessage, String trueText, String falseText, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseUse(outcome, message, secondMessage, trueText, falseText, source, game); + } else { + return getControllingPlayer(game).chooseUse(outcome, message, secondMessage, trueText, falseText, source, game); + } + } + + @Override + public int chooseReplacementEffect(Map effectsMap, Map objectsMap, Game game) { + if (isUnderMe(game)) { + return super.chooseReplacementEffect(effectsMap, objectsMap, game); + } else { + return getControllingPlayer(game).chooseReplacementEffect(effectsMap, objectsMap, game); + } + } + + @Override + public boolean choose(Outcome outcome, Choice choice, Game game) { + if (isUnderMe(game)) { + return super.choose(outcome, choice, game); + } else { + return getControllingPlayer(game).choose(outcome, choice, game); + } + } + + @Override + public boolean choose(Outcome outcome, Target target, Ability source, Game game) { + if (isUnderMe(game)) { + return super.choose(outcome, target, source, game); + } else { + return getControllingPlayer(game).choose(outcome, target, source, game); + } + } + + @Override + public boolean choose(Outcome outcome, Target target, Ability source, Game game, Map options) { + if (isUnderMe(game)) { + return super.choose(outcome, target, source, game, options); + } else { + return getControllingPlayer(game).choose(outcome, target, source, game, options); + } + } + + @Override + public boolean chooseTarget(Outcome outcome, Target target, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseTarget(outcome, target, source, game); + } else { + return getControllingPlayer(game).chooseTarget(outcome, target, source, game); + } + } + + @Override + public boolean choose(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + if (isUnderMe(game)) { + return super.choose(outcome, cards, target, source, game); + } else { + return getControllingPlayer(game).choose(outcome, cards, target, source, game); + } + } + + @Override + public boolean chooseTarget(Outcome outcome, Cards cards, TargetCard target, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseTarget(outcome, cards, target, source, game); + } else { + return getControllingPlayer(game).chooseTarget(outcome, cards, target, source, game); + } + } + + @Override + public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseTargetAmount(outcome, target, source, game); + } else { + return getControllingPlayer(game).chooseTargetAmount(outcome, target, source, game); + } + } + + @Override + public TriggeredAbility chooseTriggeredAbility(java.util.List abilities, Game game) { + if (isUnderMe(game)) { + return super.chooseTriggeredAbility(abilities, game); + } else { + return getControllingPlayer(game).chooseTriggeredAbility(abilities, game); + } + } + + @Override + public boolean playMana(Ability abilityToCast, ManaCost unpaid, String promptText, Game game) { + if (isUnderMe(game)) { + return super.playMana(abilityToCast, unpaid, promptText, game); + } else { + return getControllingPlayer(game).playMana(abilityToCast, unpaid, promptText, game); + } + } + + @Override + public int announceXMana(int min, int max, String message, Game game, Ability ability) { + if (isUnderMe(game)) { + return super.announceXMana(min, max, message, game, ability); + } else { + return getControllingPlayer(game).announceXMana(min, max, message, game, ability); + } + } + + @Override + public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) { + if (isUnderMe(game)) { + return super.announceXCost(min, max, message, game, ability, variableCost); + } else { + return getControllingPlayer(game).announceXCost(min, max, message, game, ability, variableCost); + } + } + + @Override + public void selectAttackers(Game game, UUID attackingPlayerId) { + if (isUnderMe(game)) { + super.selectAttackers(game, attackingPlayerId); + } else { + getControllingPlayer(game).selectAttackers(game, attackingPlayerId); + } + } + + @Override + public void selectBlockers(Ability source, Game game, UUID defendingPlayerId) { + if (isUnderMe(game)) { + super.selectBlockers(source, game, defendingPlayerId); + } else { + getControllingPlayer(game).selectBlockers(source, game, defendingPlayerId); + } + } + + @Override + public UUID chooseAttackerOrder(java.util.List attackers, Game game) { + if (isUnderMe(game)) { + return super.chooseAttackerOrder(attackers, game); + } else { + return getControllingPlayer(game).chooseAttackerOrder(attackers, game); + } + } + + @Override + public UUID chooseBlockerOrder(java.util.List blockers, CombatGroup combatGroup, java.util.List blockerOrder, Game game) { + if (isUnderMe(game)) { + return super.chooseBlockerOrder(blockers, combatGroup, blockerOrder, game); + } else { + return getControllingPlayer(game).chooseBlockerOrder(blockers, combatGroup, blockerOrder, game); + } + } + + @Override + public int getAmount(int min, int max, String message, Game game) { + if (isUnderMe(game)) { + return super.getAmount(min, max, message, game); + } else { + return getControllingPlayer(game).getAmount(min, max, message, game); + } + } + + @Override + public List getMultiAmountWithIndividualConstraints( + Outcome outcome, + List messages, + int totalMin, + int totalMax, + MultiAmountType type, + Game game + ) { + if (isUnderMe(game)) { + return super.getMultiAmountWithIndividualConstraints(outcome, messages, totalMin, totalMax, type, game); + } else { + return getControllingPlayer(game).getMultiAmountWithIndividualConstraints(outcome, messages, totalMin, totalMax, type, game); + } + } + + @Override + public void sideboard(Match match, Deck deck) { + super.sideboard(match, deck); + } + + @Override + public void construct(Tournament tournament, Deck deck) { + super.construct(tournament, deck); + } + + @Override + public void pickCard(java.util.List cards, Deck deck, Draft draft) { + super.pickCard(cards, deck, draft); + } + + @Override + public boolean activateAbility(ActivatedAbility ability, Game game) { + // TODO: need research, see HumanPlayer's code + return super.activateAbility(ability, game); + } + + @Override + public SpellAbility chooseAbilityForCast(Card card, Game game, boolean noMana) { + if (isUnderMe(game)) { + return super.chooseAbilityForCast(card, game, noMana); + } else { + return getControllingPlayer(game).chooseAbilityForCast(card, game, noMana); + } + } + + @Override + public ActivatedAbility chooseLandOrSpellAbility(Card card, Game game, boolean noMana) { + if (isUnderMe(game)) { + return super.chooseLandOrSpellAbility(card, game, noMana); + } else { + return getControllingPlayer(game).chooseLandOrSpellAbility(card, game, noMana); + } + } + + @Override + public Mode chooseMode(Modes modes, Ability source, Game game) { + if (isUnderMe(game)) { + return super.chooseMode(modes, source, game); + } else { + return getControllingPlayer(game).chooseMode(modes, source, game); + } + } + + @Override + public boolean choosePile(Outcome outcome, String message, java.util.List pile1, java.util.List pile2, Game game) { + if (isUnderMe(game)) { + return super.choosePile(outcome, message, pile1, pile2, game); + } else { + return getControllingPlayer(game).choosePile(outcome, message, pile1, pile2, game); + } + } + + @Override + public void abort() { + // TODO: need research, is it require real player call? Concede/leave/timeout works by default + super.abort(); + } + + @Override + public void skip() { + // TODO: see abort comments above + super.skip(); + } +} 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 f55dcf7b402..705f2479182 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 @@ -97,7 +97,7 @@ public class HumanPlayer extends PlayerImpl { // * - GAME thread: on notify from response - check new answer value and process it (if it bad then repeat and wait the next one); private transient Boolean responseOpenedForAnswer = false; // GAME thread waiting new answer private transient long responseLastWaitingThreadId = 0; - private final transient PlayerResponse response = new PlayerResponse(); + private final transient PlayerResponse response; // data receiver from a client side (must be shared for one player between multiple clients) 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 @@ -105,7 +105,7 @@ public class HumanPlayer extends PlayerImpl { protected static FilterCreatureForCombat filterCreatureForCombat = new FilterCreatureForCombat(); protected static FilterAttackingCreature filterAttack = new FilterAttackingCreature(); protected static FilterBlockingCreature filterBlock = new FilterBlockingCreature(); - protected final Choice replacementEffectChoice; + protected Choice replacementEffectChoice = null; private static final Logger logger = Logger.getLogger(HumanPlayer.class); protected HashSet autoSelectReplacementEffects = new LinkedHashSet<>(); // must be sorted @@ -131,8 +131,12 @@ public class HumanPlayer extends PlayerImpl { public HumanPlayer(String name, RangeOfInfluence range, int skill) { super(name, range); - human = true; + this.human = true; + this.response = new PlayerResponse(); + initReplacementDialog(); + } + private void initReplacementDialog() { replacementEffectChoice = new ChoiceImpl(true); replacementEffectChoice.setMessage("Choose replacement effect to resolve first"); replacementEffectChoice.setSpecial( @@ -143,8 +147,20 @@ public class HumanPlayer extends PlayerImpl { ); } + /** + * Make fake player from any other + */ + public HumanPlayer(final PlayerImpl sourcePlayer, final PlayerResponse sourceResponse) { + super(sourcePlayer); + this.human = true; + this.response = sourceResponse; // need for sync and wait user's response from a network + initReplacementDialog(); + } + public HumanPlayer(final HumanPlayer player) { super(player); + this.response = player.response; + this.replacementEffectChoice = player.replacementEffectChoice; this.autoSelectReplacementEffects.addAll(player.autoSelectReplacementEffects); this.currentlyUnpaidMana = player.currentlyUnpaidMana; @@ -2940,11 +2956,6 @@ public class HumanPlayer extends PlayerImpl { return true; } - @Override - public String getHistory() { - return "no available"; - } - private boolean gameInCheckPlayableState(Game game) { return gameInCheckPlayableState(game, false); } @@ -2962,4 +2973,14 @@ public class HumanPlayer extends PlayerImpl { } return false; } + + @Override + public Player prepareControllableProxy(Player playerUnderControl) { + // make fake player, e.g. transform computer player to human player for choose dialogs under control + HumanPlayer fakePlayer = new HumanPlayer((PlayerImpl) playerUnderControl, this.response); + if (!fakePlayer.getTurnControlledBy().equals(this.getId())) { + throw new IllegalArgumentException("Wrong code usage: controllable proxy must be controlled by " + this.getName()); + } + return fakePlayer; + } } diff --git a/Mage.Server/config/config.xml b/Mage.Server/config/config.xml index bb39136505b..3f1eba60fb0 100644 --- a/Mage.Server/config/config.xml +++ b/Mage.Server/config/config.xml @@ -66,7 +66,7 @@ /> - + diff --git a/Mage.Server/release/config/config.xml b/Mage.Server/release/config/config.xml index e19d19ca6dd..1f77ab3d9b7 100644 --- a/Mage.Server/release/config/config.xml +++ b/Mage.Server/release/config/config.xml @@ -62,7 +62,7 @@ /> - + 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 868c161ae34..2ffbf7e00b6 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -786,23 +786,23 @@ public class GameController implements GameCallback { } public void sendPlayerUUID(UUID userId, final UUID data) { - sendMessage(userId, playerId -> getGameSession(playerId).sendPlayerUUID(data)); + sendMessage(userId, playerId -> sendDirectPlayerUUID(playerId, data)); } public void sendPlayerString(UUID userId, final String data) { - sendMessage(userId, playerId -> getGameSession(playerId).sendPlayerString(data)); + sendMessage(userId, playerId -> sendDirectPlayerString(playerId, data)); } public void sendPlayerManaType(UUID userId, final UUID manaTypePlayerId, final ManaType data) { - sendMessage(userId, playerId -> getGameSession(playerId).sendPlayerManaType(data, manaTypePlayerId)); + sendMessage(userId, playerId -> sendDirectPlayerManaType(playerId, manaTypePlayerId, data)); } public void sendPlayerBoolean(UUID userId, final Boolean data) { - sendMessage(userId, playerId -> getGameSession(playerId).sendPlayerBoolean(data)); + sendMessage(userId, playerId -> sendDirectPlayerBoolean(playerId, data)); } public void sendPlayerInteger(UUID userId, final Integer data) { - sendMessage(userId, playerId -> getGameSession(playerId).sendPlayerInteger(data)); + sendMessage(userId, playerId -> sendDirectPlayerInteger(playerId, data)); } private void updatePriorityTimers() { @@ -1055,7 +1055,8 @@ public class GameController implements GameCallback { } else { // otherwise execute the action under other player's control for (UUID controlled : player.getPlayersUnderYourControl()) { - if (gameSessions.containsKey(controlled) && game.getPriorityPlayerId().equals(controlled)) { + Player controlledPlayer = game.getPlayer(controlled); + if ((gameSessions.containsKey(controlled) || controlledPlayer.isComputer()) && game.getPriorityPlayerId().equals(controlled)) { stopResponseIdleTimeout(); command.execute(controlled); } @@ -1098,7 +1099,6 @@ public class GameController implements GameCallback { @FunctionalInterface interface Command { - void execute(UUID player); } @@ -1138,6 +1138,81 @@ public class GameController implements GameCallback { return newGameSessionWatchers; } + private void sendDirectPlayerUUID(UUID playerId, UUID data) { + // real player + GameSessionPlayer session = getGameSession(playerId); + if (session != null) { + session.sendPlayerUUID(data); + return; + } + + // computer under control + Player player = game.getPlayer(playerId); + if (player != null && player.isComputer()) { + player.setResponseUUID(data); + } + } + + private void sendDirectPlayerString(UUID playerId, String data) { + // real player + GameSessionPlayer session = getGameSession(playerId); + if (session != null) { + session.sendPlayerString(data); + return; + } + + // computer under control + Player player = game.getPlayer(playerId); + if (player != null && player.isComputer()) { + player.setResponseString(data); + } + } + + private void sendDirectPlayerManaType(UUID playerId, UUID manaTypePlayerId, ManaType manaType) { + // real player + GameSessionPlayer session = getGameSession(playerId); + if (session != null) { + session.sendPlayerManaType(manaTypePlayerId, manaType); + return; + } + + // computer under control + Player player = game.getPlayer(playerId); + if (player != null && player.isComputer()) { + player.setResponseManaType(manaTypePlayerId, manaType); + } + } + + private void sendDirectPlayerBoolean(UUID playerId, Boolean data) { + // real player + GameSessionPlayer session = getGameSession(playerId); + if (session != null) { + session.sendPlayerBoolean(data); + return; + } + + // computer under control + Player player = game.getPlayer(playerId); + if (player != null && player.isComputer()) { + player.setResponseBoolean(data); + } + } + + private void sendDirectPlayerInteger(UUID playerId, Integer data) { + // real player + GameSessionPlayer session = getGameSession(playerId); + if (session != null) { + session.sendPlayerInteger(data); + return; + } + + // computer under control + Player player = game.getPlayer(playerId); + if (player != null && player.isComputer()) { + player.setResponseInteger(data); + } + } + private GameSessionPlayer getGameSession(UUID playerId) { // TODO: check parent callers - there are possible problems with sync, can be related to broken "fix" logs too // It modify players data, but: diff --git a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java index b4b7bd6c7c3..db7fa74d173 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSessionPlayer.java @@ -183,7 +183,7 @@ public class GameSessionPlayer extends GameSessionWatcher { game.getPlayer(playerId).setResponseString(data); } - public void sendPlayerManaType(ManaType manaType, UUID manaTypePlayerId) { + public void sendPlayerManaType(UUID manaTypePlayerId, ManaType manaType) { game.getPlayer(playerId).setResponseManaType(manaTypePlayerId, manaType); } diff --git a/Mage.Server/src/test/data/config_error.xml b/Mage.Server/src/test/data/config_error.xml index 517fc0f9328..fd5bffb2f95 100644 --- a/Mage.Server/src/test/data/config_error.xml +++ b/Mage.Server/src/test/data/config_error.xml @@ -32,7 +32,7 @@ /> - + diff --git a/Mage.Tests/config/config.xml b/Mage.Tests/config/config.xml index b6d386674a2..568825bb274 100644 --- a/Mage.Tests/config/config.xml +++ b/Mage.Tests/config/config.xml @@ -3,7 +3,7 @@ - + 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 be036846ebc..b2270995726 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 @@ -4544,10 +4544,6 @@ public class TestPlayer implements Player { return AIPlayer; } - public String getHistory() { - return computerPlayer.getHistory(); - } - @Override public PlanarDieRollResult rollPlanarDie(Outcome outcome, Ability source, Game game, int numberChaosSides, int numberPlanarSides) { return computerPlayer.rollPlanarDie(outcome, source, game, numberChaosSides, numberPlanarSides); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index dc2cb22fbaa..1bece076bd7 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -845,6 +845,10 @@ public abstract class GameImpl implements Game { // concede for itself // stop current player dialog and execute concede currentPriorityPlayer.signalPlayerConcede(true); + } else if (currentPriorityPlayer.getTurnControlledBy().equals(playerId)) { + // concede for itself while controlling another player + // stop current player dialog and execute concede + currentPriorityPlayer.signalPlayerConcede(true); } else { // concede for another player // allow current player to continue and check concede on any next priority diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index 3f3073986d2..df84549735d 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -46,7 +46,15 @@ import java.util.UUID; import java.util.stream.Collectors; /** - * @author BetaSteward_at_googlemail.com + * Warning, if you add new choose dialogs then must implement it for: + * - PlayerImpl (only if it use another default dialogs inside) + * - HumanPlayer (support client-server in human games) + * - ComputerPlayer (support AI in computer games) + * - StubPlayer (temp) + * - ComputerPlayerControllableProxy (support control of one player type over another player type) + * - TestPlayer (support unit tests) + * + * @author BetaSteward_at_googlemail.com, JayDi85 */ public interface Player extends MageItem, Copyable { @@ -1212,8 +1220,6 @@ public interface Player extends MageItem, Copyable { */ boolean addTargets(Ability ability, Game game); - String getHistory(); - boolean hasDesignation(DesignationType designationName); void addDesignation(Designation designation); @@ -1253,4 +1259,8 @@ public interface Player extends MageItem, Copyable { * so that's method helps to find real player that used by a game (in most use cases it's a PlayerImpl) */ Player getRealPlayer(); + + default Player prepareControllableProxy(Player playerUnderControl) { + return this; + } } diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 87e0bac8875..4eaea5fff07 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -5372,11 +5372,6 @@ public abstract class PlayerImpl implements Player, Serializable { return true; } - @Override - public String getHistory() { - return "no available"; - } - @Override public boolean hasDesignation(DesignationType designationName) { for (Designation designation : designations) {