mirror of
https://github.com/magefree/mage.git
synced 2025-12-28 14:32:06 -08:00
Turn under control reworked:
- game: added support for human games (cards like Emrakul, the Promised End, #12878); - game: added support of 720.1. to reset control in the turn beginning instead cleanup step (related to #12115); - game: added game logs for priorities in cleanup step; - game: fixed game freezes and wrong skip settings usages (related to #12878); - gui: added playable and choose-able marks for controlling player's cards and permanents, including switched hands; - gui: added controlling player name in all choice dialogs; - info: control of computer players is it not yet supported;
This commit is contained in:
parent
75d241d541
commit
c076f4925f
17 changed files with 177 additions and 140 deletions
|
|
@ -1,42 +0,0 @@
|
|||
|
||||
package mage.abilities.common.delayed;
|
||||
|
||||
import mage.abilities.DelayedTriggeredAbility;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
|
||||
/**
|
||||
* @author nantuko
|
||||
*/
|
||||
public class AtTheEndOfTurnStepPostDelayedTriggeredAbility extends DelayedTriggeredAbility {
|
||||
|
||||
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(Effect effect) {
|
||||
this(effect, false);
|
||||
}
|
||||
|
||||
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(Effect effect, boolean usesStack) {
|
||||
super(effect);
|
||||
this.usesStack = usesStack;
|
||||
setTriggerPhrase("At end of turn ");
|
||||
}
|
||||
|
||||
public AtTheEndOfTurnStepPostDelayedTriggeredAbility(AtTheEndOfTurnStepPostDelayedTriggeredAbility ability) {
|
||||
super(ability);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AtTheEndOfTurnStepPostDelayedTriggeredAbility copy() {
|
||||
return new AtTheEndOfTurnStepPostDelayedTriggeredAbility(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.END_TURN_STEP_POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -550,6 +550,12 @@ public interface Game extends MageItem, Serializable, Copyable<Game> {
|
|||
@Deprecated // TODO: must research usage and remove it from all non engine code (example: Bestow ability, ProcessActions must be used instead)
|
||||
boolean checkStateAndTriggered();
|
||||
|
||||
/**
|
||||
* Play priority by all players
|
||||
*
|
||||
* @param activePlayerId starting priority player
|
||||
* @param resuming false to reset passed priority and ask it again
|
||||
*/
|
||||
void playPriority(UUID activePlayerId, boolean resuming);
|
||||
|
||||
void resetControlAfterSpellResolve(UUID topId);
|
||||
|
|
|
|||
|
|
@ -2987,21 +2987,35 @@ public abstract class GameImpl implements Game {
|
|||
}
|
||||
String message;
|
||||
if (this.canPlaySorcery(playerId)) {
|
||||
message = "Play spells and abilities.";
|
||||
message = "Play spells and abilities";
|
||||
} else {
|
||||
message = "Play instants and activated abilities.";
|
||||
message = "Play instants and activated abilities";
|
||||
}
|
||||
playerQueryEventSource.select(playerId, message);
|
||||
|
||||
message += getControllingPlayerHint(playerId);
|
||||
|
||||
Player player = this.getPlayer(playerId);
|
||||
playerQueryEventSource.select(player.getTurnControlledBy(), message);
|
||||
getState().clearLookedAt();
|
||||
getState().clearRevealed();
|
||||
}
|
||||
|
||||
private String getControllingPlayerHint(UUID playerId) {
|
||||
Player player = this.getPlayer(playerId);
|
||||
Player controllingPlayer = this.getPlayer(player.getTurnControlledBy());
|
||||
if (player != controllingPlayer) {
|
||||
return " (as " + player.getLogName() + ")";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void fireSelectEvent(UUID playerId, String message) {
|
||||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.select(playerId, message);
|
||||
playerQueryEventSource.select(playerId, message + getControllingPlayerHint(playerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3009,7 +3023,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.select(playerId, message, options);
|
||||
playerQueryEventSource.select(playerId, message + getControllingPlayerHint(playerId), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3017,7 +3031,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.playMana(playerId, message, options);
|
||||
playerQueryEventSource.playMana(playerId, message + getControllingPlayerHint(playerId), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3025,7 +3039,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.playXMana(playerId, message);
|
||||
playerQueryEventSource.playXMana(playerId, message + getControllingPlayerHint(playerId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3038,7 +3052,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.ask(playerId, message.getMessage(), source, addMessageToOptions(message, options));
|
||||
playerQueryEventSource.ask(playerId, message.getMessage() + getControllingPlayerHint(playerId), source, addMessageToOptions(message, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3050,7 +3064,7 @@ public abstract class GameImpl implements Game {
|
|||
if (object != null) {
|
||||
objectName = object.getName();
|
||||
}
|
||||
playerQueryEventSource.chooseAbility(playerId, message, objectName, choices);
|
||||
playerQueryEventSource.chooseAbility(playerId, message + getControllingPlayerHint(playerId), objectName, choices);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3058,7 +3072,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.chooseMode(playerId, message, modes);
|
||||
playerQueryEventSource.chooseMode(playerId, message + getControllingPlayerHint(playerId), modes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3066,7 +3080,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.target(playerId, message.getMessage(), targets, required, addMessageToOptions(message, options));
|
||||
playerQueryEventSource.target(playerId, message.getMessage() + getControllingPlayerHint(playerId), targets, required, addMessageToOptions(message, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3074,7 +3088,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.target(playerId, message.getMessage(), cards, required, addMessageToOptions(message, options));
|
||||
playerQueryEventSource.target(playerId, message.getMessage() + getControllingPlayerHint(playerId), cards, required, addMessageToOptions(message, options));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -3087,7 +3101,7 @@ public abstract class GameImpl implements Game {
|
|||
*/
|
||||
@Override
|
||||
public void fireSelectTargetTriggeredAbilityEvent(UUID playerId, String message, List<TriggeredAbility> abilities) {
|
||||
playerQueryEventSource.target(playerId, message, abilities);
|
||||
playerQueryEventSource.target(playerId, message + getControllingPlayerHint(playerId), abilities);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3095,7 +3109,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.target(playerId, message, perms, required);
|
||||
playerQueryEventSource.target(playerId, message + getControllingPlayerHint(playerId), perms, required);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3103,7 +3117,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.amount(playerId, message, min, max);
|
||||
playerQueryEventSource.amount(playerId, message + getControllingPlayerHint(playerId), min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3128,7 +3142,7 @@ public abstract class GameImpl implements Game {
|
|||
if (simulation) {
|
||||
return;
|
||||
}
|
||||
playerQueryEventSource.choosePile(playerId, message, pile1, pile2);
|
||||
playerQueryEventSource.choosePile(playerId, message + getControllingPlayerHint(playerId), pile1, pile2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
package mage.game.turn;
|
||||
|
||||
import mage.constants.TurnPhase;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
|
||||
|
||||
package mage.game.turn;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent.EventType;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
*/
|
||||
|
|
@ -30,19 +28,31 @@ public class CleanupStep extends Step {
|
|||
super.beginStep(game, activePlayerId);
|
||||
Player activePlayer = game.getPlayer(activePlayerId);
|
||||
game.getState().setPriorityPlayerId(activePlayer.getId());
|
||||
//20091005 - 514.1
|
||||
|
||||
// 514.1
|
||||
// First, if the active player’s hand contains more cards than his or her maximum hand size
|
||||
// (normally seven), he or she discards enough cards to reduce his or her hand size to that number.
|
||||
// This turn-based action doesn’t use the stack.
|
||||
if (activePlayer.isInGame()) {
|
||||
activePlayer.discardToMax(game);
|
||||
}
|
||||
//20100423 - 514.2
|
||||
|
||||
// 514.2
|
||||
// Second, the following actions happen simultaneously: all damage marked on permanents
|
||||
// (including phased-out permanents) is removed and all "until end of turn" and "this turn"
|
||||
// effects end. This turn-based action doesn’t use the stack.
|
||||
game.getBattlefield().endOfTurn(game);
|
||||
game.getState().removeEotEffects(game);
|
||||
|
||||
// 514.3
|
||||
// Normally, no player receives priority during the cleanup step, so no spells can be cast
|
||||
// and no abilities can be activated. However, this rule is subject to the following exception: 514.3a
|
||||
//
|
||||
// Look at EndPhase code to process 514.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endStep(Game game, UUID activePlayerId) {
|
||||
Player activePlayer = game.getPlayer(activePlayerId);
|
||||
activePlayer.setGameUnderYourControl(true);
|
||||
super.endStep(game, activePlayerId);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,16 +31,21 @@ public class EndPhase extends Phase {
|
|||
game.getState().increaseStepNum();
|
||||
game.getTurn().setEndTurnRequested(false); // so triggers trigger again
|
||||
prePriority(game, activePlayerId);
|
||||
// 514.3a At this point, the game checks to see if any state-based actions would be performed
|
||||
|
||||
// 514.3.
|
||||
// Normally, no player receives priority during the cleanup step, so no spells can be cast and
|
||||
// no abilities can be activated. However, this rule is subject to the following exception:
|
||||
// 514.3a
|
||||
// At this point, the game checks to see if any state-based actions would be performed
|
||||
// and/or any triggered abilities are waiting to be put onto the stack (including those that
|
||||
// trigger "at the beginning of the next cleanup step"). If so, those state-based actions are
|
||||
// performed, then those triggered abilities are put on the stack, then the active player gets
|
||||
// priority. Players may cast spells and activate abilities. Once the stack is empty and all players
|
||||
// pass in succession, another cleanup step begins
|
||||
if (game.checkStateAndTriggered()) {
|
||||
// Queues a new cleanup step
|
||||
game.informPlayers("State-based actions or triggers happened on cleanup step, so players get priority due 514.3a");
|
||||
// queues a new cleanup step and request new priorities
|
||||
game.getState().getTurnMods().add(new TurnMod(activePlayerId).withExtraStep(new CleanupStep()));
|
||||
// resume priority
|
||||
if (!game.isPaused() && !game.checkIfGameIsOver() && !game.executingRollback()) {
|
||||
currentStep.priority(game, activePlayerId, false);
|
||||
if (game.executingRollback()) {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,12 @@ public abstract class Step implements Serializable, Copyable<Step> {
|
|||
stepPart = StepPart.PRE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play priority by all players
|
||||
*
|
||||
* @param activePlayerId starting priority player
|
||||
* @param resuming false to reset passed priority and ask it again
|
||||
*/
|
||||
public void priority(Game game, UUID activePlayerId, boolean resuming) {
|
||||
if (hasPriority) {
|
||||
stepPart = StepPart.PRIORITY;
|
||||
|
|
|
|||
|
|
@ -107,12 +107,16 @@ public class Turn implements Serializable {
|
|||
));
|
||||
return true;
|
||||
}
|
||||
logStartOfTurn(game, activePlayer);
|
||||
|
||||
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
|
||||
logStartOfTurn(game, activePlayer);
|
||||
resetCounts();
|
||||
|
||||
this.activePlayerId = activePlayer.getId();
|
||||
resetCounts();
|
||||
this.currentPhase = null;
|
||||
|
||||
// turn control must be called after potential turn skip due 720.1.
|
||||
checkTurnIsControlledByOtherPlayer(game, activePlayer.getId());
|
||||
|
||||
game.getPlayer(activePlayer.getId()).beginTurn(game);
|
||||
for (Phase phase : phases) {
|
||||
if (game.isPaused() || game.checkIfGameIsOver()) {
|
||||
|
|
@ -220,8 +224,31 @@ public class Turn implements Serializable {
|
|||
}
|
||||
|
||||
private void checkTurnIsControlledByOtherPlayer(Game game, UUID activePlayerId) {
|
||||
// 720.1.
|
||||
// Some cards allow a player to control another player during that player’s next turn.
|
||||
// This effect applies to the next turn that the affected player actually takes.
|
||||
// The affected player is controlled during the entire turn; the effect doesn’t end until
|
||||
// the beginning of the next turn.
|
||||
//
|
||||
// 720.1b
|
||||
// If a turn is skipped, any pending player-controlling effects wait until the player who would be
|
||||
// affected actually takes a turn.
|
||||
|
||||
// remove old under control
|
||||
game.getPlayers().values().forEach(player -> {
|
||||
if (player.isInGame() && !player.isGameUnderControl()) {
|
||||
Player controllingPlayer = game.getPlayer(player.getTurnControlledBy());
|
||||
if (player != controllingPlayer && controllingPlayer != null) {
|
||||
game.informPlayers(controllingPlayer.getLogName() + " lost control over " + player.getLogName());
|
||||
}
|
||||
player.setGameUnderYourControl(true);
|
||||
}
|
||||
});
|
||||
|
||||
// add new under control
|
||||
TurnMod newControllerMod = game.getState().getTurnMods().useNextNewController(activePlayerId);
|
||||
if (newControllerMod != null && !newControllerMod.getNewControllerId().equals(activePlayerId)) {
|
||||
// set player under new control
|
||||
// game logs added in child's call (controlPlayersTurn)
|
||||
game.getPlayer(newControllerMod.getNewControllerId()).controlPlayersTurn(game, activePlayerId, newControllerMod.getInfo());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
boolean isGameUnderControl();
|
||||
|
||||
/**
|
||||
* Returns false in case you don't control the game.
|
||||
* False in case you don't control the game.
|
||||
* <p>
|
||||
* Note: For effects like "You control target player during that player's
|
||||
* next turn".
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import mage.abilities.ActivatedAbility.ActivationStatus;
|
|||
import mage.abilities.common.PassAbility;
|
||||
import mage.abilities.common.PlayLandAsCommanderAbility;
|
||||
import mage.abilities.common.WhileSearchingPlayFromLibraryAbility;
|
||||
import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility;
|
||||
import mage.abilities.costs.*;
|
||||
import mage.abilities.costs.mana.AlternateManaPaymentAbility;
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
|
|
@ -15,7 +14,6 @@ import mage.abilities.costs.mana.ManaCosts;
|
|||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.RestrictionEffect;
|
||||
import mage.abilities.effects.RestrictionUntapNotMoreThanEffect;
|
||||
import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect;
|
||||
import mage.abilities.keyword.*;
|
||||
import mage.abilities.mana.ActivatedManaAbilityImpl;
|
||||
import mage.abilities.mana.ManaOptions;
|
||||
|
|
@ -609,11 +607,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
if (!playerUnderControl.hasLeft() && !playerUnderControl.hasLost()) {
|
||||
playerUnderControl.setGameUnderYourControl(false);
|
||||
}
|
||||
DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility(
|
||||
new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), playerUnderControl.getLogName()));
|
||||
ability.setSourceId(getId());
|
||||
ability.setControllerId(getId());
|
||||
game.addDelayedTriggeredAbility(ability, null);
|
||||
// control will reset on start of the turn
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2127,9 +2127,10 @@ public final class CardUtil {
|
|||
return null;
|
||||
}
|
||||
|
||||
// not started game
|
||||
// T0 - for not started game
|
||||
// T2 - for starting of the turn
|
||||
if (gameState.getTurn().getStep() == null) {
|
||||
return "T0";
|
||||
return "T" + gameState.getTurnNum();
|
||||
}
|
||||
|
||||
// normal game
|
||||
|
|
|
|||
|
|
@ -160,10 +160,6 @@ public final class GameLog {
|
|||
return "<font color='" + LOG_COLOR_PLAYER_CONFIRM + "'>" + name + "</font>";
|
||||
}
|
||||
|
||||
public static String getSmallSecondLineText(String text) {
|
||||
return "<div style='font-size:11pt'>" + text + "</div>";
|
||||
}
|
||||
|
||||
private static String getColorName(ObjectColor objectColor) {
|
||||
if (objectColor.isMulticolored()) {
|
||||
return LOG_COLOR_MULTI;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue