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 extends Card> pile1, java.util.List extends Card> 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) {