* Fixed that effects with custom duration are not automatically removed from the game if source permanents leaves the game (fixes #6997).

This commit is contained in:
LevelX2 2020-12-28 18:36:05 +01:00
parent dbea1e35eb
commit 8098dd690c
4 changed files with 76 additions and 13 deletions

View file

@ -31,6 +31,8 @@ public final class ArborElf extends CardImpl {
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// (T): Untap target Forest.
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new UntapTargetEffect(), new TapSourceCost());
TargetLandPermanent target = new TargetLandPermanent(filter);
ability.addTarget(target);

View file

@ -110,7 +110,11 @@ public class PlayerLeavesGameTest extends CardTestMultiPlayerBaseWithRangeAll {
@Test
public void test_PlayerLeaveGameWithOwnPermanentAndCustomEffect() {
prepareAndRunLeaveGameWithLongEffectTest(Duration.Custom);
// Using Custom duration without implementing specific duration rules makes no sense
// This conflicts rule 800.4a (only any effects which give that player control of any objects or players end.)
// See PlayerLeftGameRangeAllTest.TestContinuousEffectStaysAfterCreatingPlayerLeft as example why custom effects may not end, if the source permanent of the effect leaves the game
// prepareAndRunLeaveGameWithLongEffectTest(Duration.Custom);
}
@Test

View file

@ -1,5 +1,6 @@
package org.mage.test.multiplayer;
import java.io.FileNotFoundException;
import mage.constants.MultiplayerAttackOption;
import mage.constants.PhaseStep;
import mage.constants.RangeOfInfluence;
@ -13,8 +14,6 @@ import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestMultiPlayerBase;
import java.io.FileNotFoundException;
/**
* @author LevelX2
*/
@ -405,7 +404,7 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
setStopAt(4, PhaseStep.BEGIN_COMBAT);
execute();
Assert.assertFalse("Player D is no longer in the game", playerA.isInGame());
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame());
assertLife(playerA, 2);
assertPermanentCount(playerD, "Juggernaut", 1);
@ -418,4 +417,61 @@ public class PlayerLeftGameRangeAllTest extends CardTestMultiPlayerBase {
}
/**
* https://github.com/magefree/mage/issues/6997
Some continuous effects should stay in play even after the player that set them leaves the game.
Example:
* Player A: Casts Vorinclex, Voice of Hunger
* Player D: Taps all lands and do stuff (lands shouldn't untap during his next untap step)
* Player C: Kills Player A Player D: Lands untapped normally, though they shouldn't
*
* This happened playing commander against 3 AIs. One of the AIs played Vorinclex, I tapped all my lands during my turn to do stuff.
* Next AI killed the one that had Vorinclex. When the game got to my turn, my lands untapped normally.
*/
@Test
public void TestContinuousEffectStaysAfterCreatingPlayerLeft() {
// Player order: A -> D -> C -> B
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 8);
// Trample
// Whenever you tap a land for mana, add one mana of any type that land produced.
// Whenever an opponent taps a land for mana, that land doesn't untap during its controller's next untap step.
addCard(Zone.HAND, playerA, "Vorinclex, Voice of Hunger"); // Creature 7/6 {6}{G}{G}
addCard(Zone.HAND, playerD, "Silvercoat Lion");
addCard(Zone.BATTLEFIELD, playerD, "Plains", 2);
addCard(Zone.HAND, playerC, "Lightning Bolt", 1);
addCard(Zone.BATTLEFIELD, playerC, "Mountain", 1);
addCard(Zone.HAND, playerB, "Silvercoat Lion", 1);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Vorinclex, Voice of Hunger");
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, "Silvercoat Lion");
setChoice(playerA, "Whenever an opponent taps a land for mana");
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerC, "Lightning Bolt", playerA);
setStopAt(5, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerD, "Silvercoat Lion", 1);
assertGraveyardCount(playerC, "Lightning Bolt", 1);
Assert.assertFalse("Player A is no longer in the game", playerA.isInGame());
Assert.assertTrue("Player D is the active player",currentGame.getActivePlayerId().equals(playerD.getId()));
assertTappedCount("Plains", true, 2); // Do not untap because of Vorinclex do not untap effect
}
}

View file

@ -1,5 +1,6 @@
package mage.abilities.effects;
import java.util.*;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.MageSingleton;
@ -10,8 +11,6 @@ import mage.game.Game;
import mage.players.Player;
import org.apache.log4j.Logger;
import java.util.*;
/**
* @param <T>
* @author BetaSteward_at_googlemail.com
@ -121,10 +120,10 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
// They neither expire immediately nor last indefinitely.
MageObject object = game.getObject(ability.getSourceId());
boolean isObjectInGame = ability.getSourceId() == null || object != null; // Commander effects have no sourceId
boolean isOwnerLeaveGame = false;
boolean hasOwnerLeftGame = false;
if (object instanceof Card) {
Player owner = game.getPlayer(((Card) object).getOwnerId());
isOwnerLeaveGame = !owner.isInGame();
hasOwnerLeftGame = !owner.isInGame();
}
switch (effect.getDuration()) {
@ -136,18 +135,20 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
case EndOfCombat:
case EndOfGame:
// if the related source object does no longer exist in game - the effect has to be removed
if (isOwnerLeaveGame || !isObjectInGame) {
if (hasOwnerLeftGame || !isObjectInGame) {
it.remove();
}
break;
case OneUse:
if (isOwnerLeaveGame || effect.isUsed()) {
if (hasOwnerLeftGame || effect.isUsed()) {
it.remove();
}
break;
case Custom:
// custom effects must process it's own inactive method (override), but can'be missied by devs
if (isOwnerLeaveGame || effect.isInactive(ability, game)) {
// custom effects must process it's own inactive method (override)
// custom effects may not end, if the source permanent of the effect has left the game
// 800.4a (only any effects which give that player control of any objects or players end)
if (effect.isInactive(ability, game)) {
it.remove();
}
break;
@ -166,7 +167,7 @@ public class ContinuousEffectsList<T extends ContinuousEffect> extends ArrayList
}
break;
case UntilSourceLeavesBattlefield:
if (isOwnerLeaveGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
if (hasOwnerLeftGame || Zone.BATTLEFIELD != game.getState().getZone(ability.getSourceId())) {
it.remove();
}
break;