mirror of
https://github.com/correl/mage.git
synced 2024-11-25 11:09:53 +00:00
fixed issue with Kruphix, God of Horizons and Horizon Stone causing endless replacement effect loop
This commit is contained in:
parent
51c9121f5e
commit
2ffa719278
7 changed files with 137 additions and 75 deletions
|
@ -2,14 +2,12 @@ package mage.cards.h;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.effects.ReplacementEffectImpl;
|
import mage.abilities.effects.ContinuousEffectImpl;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.CardType;
|
import mage.constants.*;
|
||||||
import mage.constants.Duration;
|
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.events.GameEvent;
|
import mage.players.Player;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -35,11 +33,11 @@ public final class HorizonStone extends CardImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HorizonStoneEffect extends ReplacementEffectImpl {
|
class HorizonStoneEffect extends ContinuousEffectImpl {
|
||||||
|
|
||||||
HorizonStoneEffect() {
|
HorizonStoneEffect() {
|
||||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
|
||||||
staticText = "If you would lose unspent mana, that mana becomes colorless instead.";
|
staticText = "if you would lose unspent mana, that mana becomes colorless instead";
|
||||||
}
|
}
|
||||||
|
|
||||||
private HorizonStoneEffect(final HorizonStoneEffect effect) {
|
private HorizonStoneEffect(final HorizonStoneEffect effect) {
|
||||||
|
@ -53,21 +51,10 @@ class HorizonStoneEffect extends ReplacementEffectImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
return false;
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
if (player != null) {
|
||||||
|
player.getManaPool().setManaBecomesColorless(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checksEventType(GameEvent event, Game game) {
|
|
||||||
return event.getType() == GameEvent.EventType.EMPTY_MANA_POOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
|
||||||
return event.getPlayerId().equals(source.getControllerId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import mage.MageInt;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.common.SimpleStaticAbility;
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
import mage.abilities.dynamicvalue.common.DevotionCount;
|
import mage.abilities.dynamicvalue.common.DevotionCount;
|
||||||
import mage.abilities.effects.ReplacementEffectImpl;
|
import mage.abilities.effects.ContinuousEffectImpl;
|
||||||
import mage.abilities.effects.common.continuous.LoseCreatureTypeSourceEffect;
|
import mage.abilities.effects.common.continuous.LoseCreatureTypeSourceEffect;
|
||||||
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
|
import mage.abilities.effects.common.continuous.MaximumHandSizeControllerEffect;
|
||||||
import mage.abilities.keyword.IndestructibleAbility;
|
import mage.abilities.keyword.IndestructibleAbility;
|
||||||
|
@ -12,7 +12,7 @@ import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.constants.*;
|
import mage.constants.*;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.events.GameEvent;
|
import mage.players.Player;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -41,9 +41,9 @@ public final class KruphixGodOfHorizons extends CardImpl {
|
||||||
Integer.MAX_VALUE, Duration.WhileOnBattlefield,
|
Integer.MAX_VALUE, Duration.WhileOnBattlefield,
|
||||||
MaximumHandSizeControllerEffect.HandSizeModification.SET
|
MaximumHandSizeControllerEffect.HandSizeModification.SET
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// If unused mana would empty from your mana pool, that mana becomes colorless instead.
|
// If unused mana would empty from your mana pool, that mana becomes colorless instead.
|
||||||
this.addAbility(new SimpleStaticAbility(new KruphixGodOfHorizonsEffect()));
|
this.addAbility(new SimpleStaticAbility(new KruphixGodOfHorizonsEffect()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private KruphixGodOfHorizons(final KruphixGodOfHorizons card) {
|
private KruphixGodOfHorizons(final KruphixGodOfHorizons card) {
|
||||||
|
@ -56,11 +56,11 @@ public final class KruphixGodOfHorizons extends CardImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class KruphixGodOfHorizonsEffect extends ReplacementEffectImpl {
|
class KruphixGodOfHorizonsEffect extends ContinuousEffectImpl {
|
||||||
|
|
||||||
KruphixGodOfHorizonsEffect() {
|
KruphixGodOfHorizonsEffect() {
|
||||||
super(Duration.WhileOnBattlefield, Outcome.Benefit);
|
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
|
||||||
staticText = "If you would lose unspent mana, that mana becomes colorless instead.";
|
staticText = "if you would lose unspent mana, that mana becomes colorless instead";
|
||||||
}
|
}
|
||||||
|
|
||||||
private KruphixGodOfHorizonsEffect(final KruphixGodOfHorizonsEffect effect) {
|
private KruphixGodOfHorizonsEffect(final KruphixGodOfHorizonsEffect effect) {
|
||||||
|
@ -74,21 +74,10 @@ class KruphixGodOfHorizonsEffect extends ReplacementEffectImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
return false;
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
if (player != null) {
|
||||||
|
player.getManaPool().setManaBecomesColorless(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checksEventType(GameEvent event, Game game) {
|
|
||||||
return event.getType() == GameEvent.EventType.EMPTY_MANA_POOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
|
||||||
return event.getPlayerId().equals(source.getControllerId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package org.mage.test.cards.single.jou;
|
||||||
|
|
||||||
|
import mage.constants.ManaType;
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public class KruphixGodOfHorizonsTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
private static final String kruphix = "Kruphix, God of Horizons";
|
||||||
|
private static final String sliver = "Metallic Sliver";
|
||||||
|
private static final String repeal = "Mystic Repeal";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKruphixNormal() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, kruphix);
|
||||||
|
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertManaPool(playerA, ManaType.COLORLESS, 3);
|
||||||
|
assertManaPool(playerA, ManaType.GREEN, 0);
|
||||||
|
assertPermanentCount(playerA, kruphix, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKruphixRemoved() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Forest", 3);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, kruphix);
|
||||||
|
addCard(Zone.HAND, playerA, sliver);
|
||||||
|
addCard(Zone.HAND, playerA, repeal);
|
||||||
|
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}");
|
||||||
|
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, sliver);
|
||||||
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, repeal, kruphix);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(3, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertManaPool(playerA, ManaType.COLORLESS, 0);
|
||||||
|
assertManaPool(playerA, ManaType.GREEN, 0);
|
||||||
|
assertPermanentCount(playerA, sliver, 1);
|
||||||
|
assertPermanentCount(playerA, kruphix, 0);
|
||||||
|
assertTappedCount("Forest", true, 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ import java.util.UUID;
|
||||||
/**
|
/**
|
||||||
* @author nantuko
|
* @author nantuko
|
||||||
*/
|
*/
|
||||||
public class ConditionalMana extends Mana implements Serializable {
|
public class ConditionalMana extends Mana implements Serializable, Emptiable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Conditions that should be met (all or any depending on comparison scope)
|
* Conditions that should be met (all or any depending on comparison scope)
|
||||||
|
|
15
Mage/src/main/java/mage/Emptiable.java
Normal file
15
Mage/src/main/java/mage/Emptiable.java
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package mage;
|
||||||
|
|
||||||
|
import mage.constants.ManaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author TheElk801
|
||||||
|
*/
|
||||||
|
public interface Emptiable {
|
||||||
|
|
||||||
|
public void add(ManaType manaType, int amount);
|
||||||
|
|
||||||
|
public void clear(ManaType manaType);
|
||||||
|
|
||||||
|
public int get(final ManaType manaType);
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package mage.players;
|
package mage.players;
|
||||||
|
|
||||||
import mage.ConditionalMana;
|
import mage.ConditionalMana;
|
||||||
|
import mage.Emptiable;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.Mana;
|
import mage.Mana;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
@ -37,6 +38,7 @@ public class ManaPool implements Serializable {
|
||||||
private final List<ManaPoolItem> poolBookmark = new ArrayList<>(); // mana pool bookmark for rollback purposes
|
private final List<ManaPoolItem> poolBookmark = new ArrayList<>(); // mana pool bookmark for rollback purposes
|
||||||
|
|
||||||
private final Set<ManaType> doNotEmptyManaTypes = new HashSet<>();
|
private final Set<ManaType> doNotEmptyManaTypes = new HashSet<>();
|
||||||
|
private boolean manaBecomesColorless = false;
|
||||||
|
|
||||||
private static final class ConditionalManaInfo {
|
private static final class ConditionalManaInfo {
|
||||||
private final ManaType manaType;
|
private final ManaType manaType;
|
||||||
|
@ -73,6 +75,7 @@ public class ManaPool implements Serializable {
|
||||||
}
|
}
|
||||||
this.doNotEmptyManaTypes.addAll(pool.doNotEmptyManaTypes);
|
this.doNotEmptyManaTypes.addAll(pool.doNotEmptyManaTypes);
|
||||||
this.lastPaymentWasSnow = pool.lastPaymentWasSnow;
|
this.lastPaymentWasSnow = pool.lastPaymentWasSnow;
|
||||||
|
this.manaBecomesColorless = pool.manaBecomesColorless;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRed() {
|
public int getRed() {
|
||||||
|
@ -229,12 +232,21 @@ public class ManaPool implements Serializable {
|
||||||
|
|
||||||
public void clearEmptyManaPoolRules() {
|
public void clearEmptyManaPoolRules() {
|
||||||
doNotEmptyManaTypes.clear();
|
doNotEmptyManaTypes.clear();
|
||||||
|
this.manaBecomesColorless = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDoNotEmptyManaType(ManaType manaType) {
|
public void addDoNotEmptyManaType(ManaType manaType) {
|
||||||
doNotEmptyManaTypes.add(manaType);
|
doNotEmptyManaTypes.add(manaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setManaBecomesColorless(boolean manaBecomesColorless) {
|
||||||
|
this.manaBecomesColorless = manaBecomesColorless;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isManaBecomesColorless() {
|
||||||
|
return manaBecomesColorless;
|
||||||
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
manaItems.clear();
|
manaItems.clear();
|
||||||
}
|
}
|
||||||
|
@ -246,35 +258,15 @@ public class ManaPool implements Serializable {
|
||||||
ManaPoolItem item = it.next();
|
ManaPoolItem item = it.next();
|
||||||
ConditionalMana conditionalItem = item.getConditionalMana();
|
ConditionalMana conditionalItem = item.getConditionalMana();
|
||||||
for (ManaType manaType : ManaType.values()) {
|
for (ManaType manaType : ManaType.values()) {
|
||||||
if (!doNotEmptyManaTypes.contains(manaType)) {
|
if (doNotEmptyManaTypes.contains(manaType)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (item.get(manaType) > 0) {
|
if (item.get(manaType) > 0) {
|
||||||
if (item.getDuration() != Duration.EndOfTurn
|
total += emptyItem(item, item, game, manaType);
|
||||||
|| game.getPhase().getType() == TurnPhase.END) {
|
|
||||||
if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) {
|
|
||||||
int amount = item.get(manaType);
|
|
||||||
item.clear(manaType);
|
|
||||||
item.add(ManaType.COLORLESS, amount);
|
|
||||||
} else {
|
|
||||||
total += item.get(manaType);
|
|
||||||
item.clear(manaType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (conditionalItem != null) {
|
|
||||||
if (conditionalItem.get(manaType) > 0) {
|
|
||||||
if (item.getDuration() != Duration.EndOfTurn
|
|
||||||
|| game.getPhase().getType() == TurnPhase.END) {
|
|
||||||
if (game.replaceEvent(new GameEvent(GameEvent.EventType.EMPTY_MANA_POOL, playerId, null, playerId))) {
|
|
||||||
int amount = conditionalItem.get(manaType);
|
|
||||||
conditionalItem.clear(manaType);
|
|
||||||
conditionalItem.add(ManaType.COLORLESS, amount);
|
|
||||||
} else {
|
|
||||||
total += conditionalItem.get(manaType);
|
|
||||||
conditionalItem.clear(manaType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (conditionalItem != null
|
||||||
|
&& conditionalItem.get(manaType) > 0) {
|
||||||
|
total += emptyItem(item, conditionalItem, game, manaType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.count() == 0) {
|
if (item.count() == 0) {
|
||||||
|
@ -284,6 +276,22 @@ public class ManaPool implements Serializable {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int emptyItem(ManaPoolItem item, Emptiable toEmpty, Game game, ManaType manaType) {
|
||||||
|
if (item.getDuration() == Duration.EndOfTurn
|
||||||
|
&& game.getPhase().getType() != TurnPhase.END) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!manaBecomesColorless) {
|
||||||
|
int amount = toEmpty.get(manaType);
|
||||||
|
toEmpty.clear(manaType);
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
int amount = toEmpty.get(manaType);
|
||||||
|
toEmpty.clear(manaType);
|
||||||
|
toEmpty.add(ManaType.COLORLESS, amount);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Mana getMana() {
|
public Mana getMana() {
|
||||||
Mana m = new Mana();
|
Mana m = new Mana();
|
||||||
for (ManaPoolItem item : manaItems) {
|
for (ManaPoolItem item : manaItems) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.util.UUID;
|
||||||
import mage.ConditionalMana;
|
import mage.ConditionalMana;
|
||||||
import mage.MageObject;
|
import mage.MageObject;
|
||||||
import mage.Mana;
|
import mage.Mana;
|
||||||
|
import mage.Emptiable;
|
||||||
import mage.constants.Duration;
|
import mage.constants.Duration;
|
||||||
import mage.constants.ManaType;
|
import mage.constants.ManaType;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ import mage.constants.ManaType;
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
*/
|
*/
|
||||||
public class ManaPoolItem implements Serializable {
|
public class ManaPoolItem implements Serializable, Emptiable {
|
||||||
|
|
||||||
private int red = 0;
|
private int red = 0;
|
||||||
private int green = 0;
|
private int green = 0;
|
||||||
|
|
Loading…
Reference in a new issue