mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -08:00
cleanup DelayedTriggeredAbility on player leave
This commit is contained in:
parent
6cc3c5384a
commit
8db7a4d307
4 changed files with 88 additions and 35 deletions
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.mage.test.multiplayer;
|
||||||
|
|
||||||
|
import mage.constants.MultiplayerAttackOption;
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.RangeOfInfluence;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.game.FreeForAll;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.GameException;
|
||||||
|
import mage.game.mulligan.MulliganType;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Susucr
|
||||||
|
*/
|
||||||
|
public class DelayedTriggerLastingAfterControllerLeavesTest extends CardTestMultiPlayerBase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Game createNewGameAndPlayers() throws GameException, FileNotFoundException {
|
||||||
|
Game game = new FreeForAll(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 40, 7);
|
||||||
|
// Player order: A -> D -> C -> B
|
||||||
|
playerA = createPlayer(game, "PlayerA");
|
||||||
|
playerB = createPlayer(game, "PlayerB");
|
||||||
|
playerC = createPlayer(game, "PlayerC");
|
||||||
|
playerD = createPlayer(game, "PlayerD");
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_UntilYourNextTurn_AfterLeave() {
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
|
||||||
|
// +1: Until your next turn, whenever a creature an opponent controls attacks, it gets -1/-0 until end of turn.
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Jace, Architect of Thought");
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerD, "Grizzly Bears");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerD, "Elite Vanguard");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerC, "Grizzly Bears");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerC, "Elite Vanguard");
|
||||||
|
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "+1:");
|
||||||
|
|
||||||
|
attack(2, playerD, "Grizzly Bears", playerB);
|
||||||
|
attack(2, playerD, "Elite Vanguard", playerB);
|
||||||
|
setChoice(playerA, "Until your next turn"); // order trigger
|
||||||
|
|
||||||
|
checkLife("2: after D attack affected by Delayed triggers", 2, PhaseStep.POSTCOMBAT_MAIN, playerB, 40 - 2);
|
||||||
|
concede(2, PhaseStep.END_TURN, playerA);
|
||||||
|
|
||||||
|
attack(3, playerC, "Grizzly Bears", playerB);
|
||||||
|
attack(3, playerC, "Elite Vanguard", playerB);
|
||||||
|
|
||||||
|
checkLife("3: after C attack affected by Delayed triggers", 3, PhaseStep.POSTCOMBAT_MAIN, playerB, 40 - 2 - 4);
|
||||||
|
// No trigger, as triggers from leaved players don't trigger
|
||||||
|
|
||||||
|
attack(5, playerD, "Grizzly Bears", playerB);
|
||||||
|
attack(5, playerD, "Elite Vanguard", playerB);
|
||||||
|
|
||||||
|
setStopAt(5, PhaseStep.POSTCOMBAT_MAIN);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertLife(playerB, 40 - 2 - 4 - 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,12 +27,10 @@ public class DelayedTriggeredAbilities extends AbilitiesImpl<DelayedTriggeredAbi
|
||||||
// TODO: add same integrity checks as TriggeredAbilities?!
|
// TODO: add same integrity checks as TriggeredAbilities?!
|
||||||
for (Iterator<DelayedTriggeredAbility> it = this.iterator(); it.hasNext(); ) {
|
for (Iterator<DelayedTriggeredAbility> it = this.iterator(); it.hasNext(); ) {
|
||||||
DelayedTriggeredAbility ability = it.next();
|
DelayedTriggeredAbility ability = it.next();
|
||||||
if (ability.getDuration() == Duration.Custom) {
|
|
||||||
if (ability.isInactive(game)) {
|
if (ability.isInactive(game)) {
|
||||||
it.remove();
|
it.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!ability.checkEventType(event, game)) {
|
if (!ability.checkEventType(event, game)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import mage.abilities.effects.Effect;
|
||||||
import mage.constants.Duration;
|
import mage.constants.Duration;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.players.Player;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
|
|
@ -64,6 +65,11 @@ public abstract class DelayedTriggeredAbility extends TriggeredAbilityImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInactive(Game game) {
|
public boolean isInactive(Game game) {
|
||||||
return false;
|
// discard as soon as possible for leaved player
|
||||||
|
// 800.4d. If an object that would be owned by a player who has left the game would be created in any zone, it isn't created.
|
||||||
|
// If a triggered ability that would be controlled by a player who has left the game would be put onto the stack, it isn't put on the stack.
|
||||||
|
Player player = game.getPlayer(getControllerId());
|
||||||
|
boolean canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving());
|
||||||
|
return canDelete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -310,22 +310,12 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean canDelete;
|
|
||||||
Player player = game.getPlayer(startingControllerId);
|
Player player = game.getPlayer(startingControllerId);
|
||||||
|
|
||||||
// discard on start of turn for leaved player
|
// discard on start of turn for leaved player
|
||||||
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
|
// 800.4i When a player leaves the game, any continuous effects with durations that last until that player's next turn
|
||||||
// or until a specific point in that turn will last until that turn would have begun.
|
// or until a specific point in that turn will last until that turn would have begun.
|
||||||
// They neither expire immediately nor last indefinitely.
|
// They neither expire immediately nor last indefinitely.
|
||||||
switch (duration) {
|
boolean canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving());
|
||||||
case UntilYourNextTurn:
|
|
||||||
case UntilEndOfYourNextTurn:
|
|
||||||
canDelete = player == null || (!player.isInGame() && player.hasReachedNextTurnAfterLeaving());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
canDelete = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canDelete) {
|
if (canDelete) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -333,28 +323,20 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
|
||||||
// discard on another conditions (start of your turn)
|
// discard on another conditions (start of your turn)
|
||||||
switch (duration) {
|
switch (duration) {
|
||||||
case UntilYourNextTurn:
|
case UntilYourNextTurn:
|
||||||
if (player != null && player.isInGame()) {
|
|
||||||
return this.isYourNextTurn(game);
|
return this.isYourNextTurn(game);
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UntilYourNextEndStep:
|
case UntilYourNextEndStep:
|
||||||
if (player != null && player.isInGame()) {
|
|
||||||
return this.isYourNextEndStep(game);
|
return this.isYourNextEndStep(game);
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UntilEndCombatOfYourNextTurn:
|
case UntilEndCombatOfYourNextTurn:
|
||||||
if (player != null && player.isInGame()) {
|
|
||||||
return this.isEndCombatOfYourNextTurn(game);
|
return this.isEndCombatOfYourNextTurn(game);
|
||||||
}
|
|
||||||
break;
|
|
||||||
case UntilYourNextUpkeepStep:
|
case UntilYourNextUpkeepStep:
|
||||||
if (player != null && player.isInGame()) {
|
|
||||||
return this.isYourNextUpkeepStep(game);
|
return this.isYourNextUpkeepStep(game);
|
||||||
|
case UntilEndOfYourNextTurn:
|
||||||
|
// cleanup handled by ContinuousEffectsList::removeEndOfTurnEffects
|
||||||
|
// TODO: should those be aligned to all be handled in the same place?
|
||||||
|
return false;
|
||||||
|
default: // Should handle all the duration that do pass the first switch.
|
||||||
|
throw new IllegalStateException("Missing case for isInactive's Duration:" + duration);
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return canDelete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue