Skip to content

Commit

Permalink
Turn under control reworked:
Browse files Browse the repository at this point in the history
 - game: fixed game freezes when computer try to take control over another computer or human (added game logs, related to #12878);
 - cheats: improved take and give control commands, now you can give control under yourself to another player;
 - cheats: improved take and give control commands, now you can return control to computer in the same priority;
 - cheats: deleted useless and unused command to activate opponent's ability;
  • Loading branch information
JayDi85 committed Jan 11, 2025
1 parent a5c354f commit 1f1d108
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 73 deletions.
84 changes: 20 additions & 64 deletions Mage.Common/src/main/java/mage/utils/SystemUtil.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,6 +24,7 @@
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;
Expand Down Expand Up @@ -68,30 +67,28 @@ private 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<String, String> supportedCommands = new HashMap<>();

static {
// special commands names in choose dialog
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]
Expand Down Expand Up @@ -307,8 +304,8 @@ public static void executeCheatCommands(Game game, String commandsFilePath, Play
// 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;
Expand Down Expand Up @@ -427,53 +424,6 @@ public static void executeCheatCommands(Game game, String commandsFilePath, Play
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<ActivatedAbility> abilities = opponent.getPlayable(game, true);
Map<String, String> 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<ActivatedAbility> 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
Expand Down Expand Up @@ -552,12 +502,12 @@ public static void executeCheatCommands(Game game, String commandsFilePath, Play
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();
Expand All @@ -568,13 +518,19 @@ public static void executeCheatCommands(Game game, String commandsFilePath, Play
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,9 +360,14 @@ protected void waitForResponse(Game game) {

// 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;
Expand Down
4 changes: 2 additions & 2 deletions Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3080,8 +3080,8 @@ public Set<UUID> getPlayersUnderYourControl() {
}

@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
Expand Down
3 changes: 2 additions & 1 deletion Mage/src/main/java/mage/players/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,9 @@ default boolean isComputer() {
* @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.
Expand Down
17 changes: 14 additions & 3 deletions Mage/src/main/java/mage/players/PlayerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -598,8 +598,17 @@ public Set<UUID> getPlayersUnderYourControl() {
}

@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())) {
Expand All @@ -609,6 +618,8 @@ public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info
}
// control will reset on start of the turn
}

return true;
}

@Override
Expand Down Expand Up @@ -777,10 +788,10 @@ public int drawCards(int num, Ability source, Game game, GameEvent event) {
}
// 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;
Expand Down
6 changes: 5 additions & 1 deletion Mage/src/main/java/mage/util/CardUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1358,7 +1358,11 @@ public static List<String> getCardRulesWithAdditionalInfo(Game game, MageObject
*/
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()
Expand Down

0 comments on commit 1f1d108

Please sign in to comment.