diff --git a/forge-game/src/main/java/forge/game/GlobalRuleChange.java b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
index d0381166b4b..2a02d696449 100644
--- a/forge-game/src/main/java/forge/game/GlobalRuleChange.java
+++ b/forge-game/src/main/java/forge/game/GlobalRuleChange.java
@@ -25,7 +25,6 @@ public enum GlobalRuleChange {
alwaysWither ("All damage is dealt as though its source had wither."),
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."),
manaBurn ("A player losing unspent mana causes that player to lose that much life."),
- manapoolsDontEmpty ("Mana pools don't empty as steps and phases end."),
noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
noLegendRule ("The legend rule doesn't apply."),
diff --git a/forge-game/src/main/java/forge/game/mana/ManaPool.java b/forge-game/src/main/java/forge/game/mana/ManaPool.java
index 2f890d8c5d1..84caa9796b5 100644
--- a/forge-game/src/main/java/forge/game/mana/ManaPool.java
+++ b/forge-game/src/main/java/forge/game/mana/ManaPool.java
@@ -6,21 +6,21 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package forge.game.mana;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import org.apache.commons.lang3.StringUtils;
@@ -28,24 +28,28 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
-import forge.card.MagicColor;
import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard;
+import forge.game.Game;
import forge.game.GlobalRuleChange;
+import forge.game.ability.AbilityKey;
import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
+import forge.game.replacement.ReplacementLayer;
+import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility;
+import forge.game.staticability.StaticAbilityUnspentMana;
import forge.game.zone.ZoneType;
/**
*
* ManaPool class.
*
- *
+ *
* @author Forge
* @version $Id$
*/
@@ -78,47 +82,55 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
/**
*
* willManaBeLostAtEndOfPhase.
- *
+ *
* @return - whether floating mana will be lost if the current phase ended right now
*
*/
public final boolean willManaBeLostAtEndOfPhase() {
- if (floatingMana.isEmpty() ||
- owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty) ||
- owner.hasKeyword("Convert unused mana to Colorless")) {
+ if (floatingMana.isEmpty()) {
+ return false;
+ }
+
+ final Map runParams = AbilityKey.mapFromAffected(owner);
+ if (!owner.getGame().getReplacementHandler().getReplacementList(ReplacementType.LoseMana, runParams, ReplacementLayer.Other).isEmpty()) {
return false;
}
int safeMana = 0;
- for (final byte c : MagicColor.WUBRG) {
- final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
- if (owner.hasKeyword(captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
- safeMana += getAmountOfColor(c);
- }
+ for (final byte c : StaticAbilityUnspentMana.getManaToKeep(owner)) {
+ safeMana += getAmountOfColor(c);
}
return totalMana() != safeMana; //won't lose floating mana if all mana is of colors that aren't going to be emptied
}
+ public final boolean hasBurn() {
+ final Game game = owner.getGame();
+ return game.getRules().hasManaBurn() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn);
+ }
+
public final List clearPool(boolean isEndOfPhase) {
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
- List cleared = new ArrayList<>();
+ List cleared = Lists.newArrayList();
if (floatingMana.isEmpty()) { return cleared; }
- if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) {
- return cleared;
- }
+ boolean convertToColorless = false;
- final boolean convertToColorless = owner.hasKeyword("Convert unused mana to Colorless");
+ final Map runParams = AbilityKey.mapFromAffected(owner);
+ switch (owner.getGame().getReplacementHandler().run(ReplacementType.LoseMana, runParams)) {
+ case NotReplaced:
+ break;
+ case Skipped:
+ return cleared;
+ default: // the only ones that does replace losing Mana are making it colorless instead
+ convertToColorless = true;
+ break;
+
+ }
final List keys = Lists.newArrayList(floatingMana.keySet());
if (isEndOfPhase) {
- for (final Byte c : Lists.newArrayList(keys)) {
- final String captName = StringUtils.capitalize(MagicColor.toLongString(c));
- if (owner.hasKeyword(captName + " mana doesn't empty from your mana pool as steps and phases end.")) {
- keys.remove(c);
- }
- }
+ keys.removeAll(StaticAbilityUnspentMana.getManaToKeep(owner));
}
if (convertToColorless) {
keys.remove(Byte.valueOf((byte)ManaAtom.COLORLESS));
@@ -126,29 +138,22 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
for (Byte b : keys) {
Collection cm = floatingMana.get(b);
+ final List pMana = Lists.newArrayList();
if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) {
- final List pMana = new ArrayList<>();
for (final Mana mana : cm) {
if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) {
pMana.add(mana);
}
}
- cm.removeAll(pMana);
- if (convertToColorless) {
- convertManaColor(b, (byte)ManaAtom.COLORLESS);
- cm.addAll(pMana);
- } else {
- cleared.addAll(cm);
- cm.clear();
- floatingMana.putAll(b, pMana);
- }
+ }
+ cm.removeAll(pMana);
+ if (convertToColorless) {
+ convertManaColor(b, (byte)ManaAtom.COLORLESS);
+ cm.addAll(pMana);
} else {
- if (convertToColorless) {
- convertManaColor(b, (byte)ManaAtom.COLORLESS);
- } else {
- cleared.addAll(cm);
- cm.clear();
- }
+ cleared.addAll(cm);
+ cm.clear();
+ floatingMana.putAll(b, pMana);
}
}
@@ -158,7 +163,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
}
private void convertManaColor(final byte originalColor, final byte toColor) {
- List convert = new ArrayList<>();
+ List convert = Lists.newArrayList();
Collection cm = floatingMana.get(originalColor);
for (Mana m : cm) {
convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility()));
@@ -249,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
return false;
}
- final List removeFloating = new ArrayList<>();
+ final List removeFloating = Lists.newArrayList();
boolean manaNotAccountedFor = false;
// loop over mana produced by mana ability
@@ -329,8 +334,10 @@ public class ManaPool extends ManaConversionMatrix implements Iterable {
return shard.canBePaidWithManaOfColor((byte)0);
}
+
@Override
public Iterator iterator() {
return floatingMana.values().iterator();
}
+
}
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index a6c73345283..82450d2a2ee 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -478,9 +478,7 @@ public class PhaseHandler implements java.io.Serializable {
for (Player p : game.getPlayers()) {
int burn = p.getManaPool().clearPool(true).size();
- boolean manaBurns = game.getRules().hasManaBurn() ||
- (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn));
- if (manaBurns) {
+ if (p.getManaPool().hasBurn()) {
p.loseLife(burn, false, true);
}
}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java b/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java
new file mode 100644
index 00000000000..8b888be7462
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceLoseMana.java
@@ -0,0 +1,52 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package forge.game.replacement;
+
+import java.util.Map;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.spellability.SpellAbility;
+
+public class ReplaceLoseMana extends ReplacementEffect {
+
+ public ReplaceLoseMana(Map map, Card host, boolean intrinsic) {
+ super(map, host, intrinsic);
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.replacement.ReplacementEffect#canReplace(java.util.HashMap)
+ */
+ @Override
+ public boolean canReplace(Map runParams) {
+ if (!matchesValidParam("ValidPlayer", runParams.get(AbilityKey.Affected))) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /* (non-Javadoc)
+ * @see forge.card.replacement.ReplacementEffect#setReplacingObjects(java.util.HashMap, forge.card.spellability.SpellAbility)
+ */
+ @Override
+ public void setReplacingObjects(Map runParams, SpellAbility sa) {
+ sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
index 10187567ef5..adb1cf36cdb 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementHandler.java
@@ -403,7 +403,6 @@ public class ReplacementHandler {
if ("True".equals(replacementEffect.getParam("Skip"))) {
return ReplacementResult.Skipped; // Event is skipped.
}
-
Player player = host.getController();
if (effectSA != null) {
@@ -422,6 +421,10 @@ public class ReplacementHandler {
}
}
+ if ("Replaced".equals(replacementEffect.getParam("ReplacementResult"))) {
+ return ReplacementResult.Replaced; // Event is replaced without SA.
+ }
+
// if the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) {
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
index b63f9377961..2c9b8cc3700 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplacementType.java
@@ -30,6 +30,7 @@ public enum ReplacementType {
GameLoss(ReplaceGameLoss.class),
Learn(ReplaceLearn.class),
LifeReduced(ReplaceLifeReduced.class),
+ LoseMana(ReplaceLoseMana.class),
Mill(ReplaceMill.class),
Moved(ReplaceMoved.class),
ProduceMana(ReplaceProduceMana.class),
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java
new file mode 100644
index 00000000000..0a9bbe12029
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityUnspentMana.java
@@ -0,0 +1,44 @@
+package forge.game.staticability;
+
+import java.util.Collection;
+import java.util.Set;
+
+import com.google.common.collect.Sets;
+
+import forge.card.MagicColor;
+import forge.card.mana.ManaAtom;
+import forge.game.Game;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.zone.ZoneType;
+
+public class StaticAbilityUnspentMana {
+
+ static String MODE = "UnspentMana";
+
+ public static Collection getManaToKeep(final Player player) {
+ final Game game = player.getGame();
+ Set result = Sets.newHashSet();
+ for (final Card ca : game.getCardsIn(ZoneType.STATIC_ABILITIES_SOURCE_ZONES)) {
+ for (final StaticAbility stAb : ca.getStaticAbilities()) {
+ if (!stAb.getParam("Mode").equals(MODE) || stAb.isSuppressed() || !stAb.checkConditions()) {
+ continue;
+ }
+ applyUnspentManaAbility(stAb, player, result);
+ }
+ }
+ return result;
+ }
+ public static void applyUnspentManaAbility(final StaticAbility stAb, final Player player, Set result) {
+ if (!stAb.matchesValidParam("ValidPlayer", player)) {
+ return;
+ }
+ if (!stAb.hasParam("ManaType")) {
+ for (byte b : ManaAtom.MANATYPES) {
+ result.add(b);
+ }
+ } else {
+ result.add(MagicColor.fromName(stAb.getParam("ManaType")));
+ }
+ }
+}
diff --git a/forge-gui/res/cardsfolder/h/horizon_stone.txt b/forge-gui/res/cardsfolder/h/horizon_stone.txt
index 8c29ce57165..ea8c3fe588b 100644
--- a/forge-gui/res/cardsfolder/h/horizon_stone.txt
+++ b/forge-gui/res/cardsfolder/h/horizon_stone.txt
@@ -1,5 +1,5 @@
Name:Horizon Stone
ManaCost:5
Types:Artifact
-S:Mode$ Continuous | Affected$ You | AddKeyword$ Convert unused mana to Colorless | Description$ If you would lose unspent mana, that mana becomes colorless instead.
+R:Event$ LoseMana | ValidPlayer$ You | ReplacementResult$ Replaced | Description$ If you would lose unspent mana, that mana becomes colorless instead.
Oracle:If you would lose unspent mana, that mana becomes colorless instead.
diff --git a/forge-gui/res/cardsfolder/k/kruphix_god_of_horizons.txt b/forge-gui/res/cardsfolder/k/kruphix_god_of_horizons.txt
index 00c42e9e931..373a414c37c 100644
--- a/forge-gui/res/cardsfolder/k/kruphix_god_of_horizons.txt
+++ b/forge-gui/res/cardsfolder/k/kruphix_god_of_horizons.txt
@@ -6,7 +6,7 @@ K:Indestructible
S:Mode$ Continuous | Affected$ Card.Self | RemoveType$ Creature | CheckSVar$ X | SVarCompare$ LT7 | Description$ As long as your devotion to green and blue is less than seven, CARDNAME isn't a creature.
SVar:X:Count$DevotionDual.Green.Blue
S:Mode$ Continuous | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
-S:Mode$ Continuous | Affected$ You | AddKeyword$ Convert unused mana to Colorless | Description$ If you would lose unspent mana, that mana becomes colorless instead.
+R:Event$ LoseMana | ValidPlayer$ You | ReplacementResult$ Replaced | Description$ If you would lose unspent mana, that mana becomes colorless instead.
SVar:BuffedBy:Permanent.Green,Permanent.Blue
SVar:Picture:http://www.wizards.com/global/images/magic/general/kruphix_god_of_horizons.jpg
Oracle:Indestructible\nAs long as your devotion to green and blue is less than seven, Kruphix isn't a creature.\nYou have no maximum hand size.\nIf you would lose unspent mana, that mana becomes colorless instead.
diff --git a/forge-gui/res/cardsfolder/l/leyline_tyrant.txt b/forge-gui/res/cardsfolder/l/leyline_tyrant.txt
index b6ea1b08fce..f303211ffd3 100755
--- a/forge-gui/res/cardsfolder/l/leyline_tyrant.txt
+++ b/forge-gui/res/cardsfolder/l/leyline_tyrant.txt
@@ -3,7 +3,7 @@ ManaCost:2 R R
Types:Creature Dragon
PT:4/4
K:Flying
-S:Mode$ Continuous | Affected$ You | AddKeyword$ Red mana doesn't empty from your mana pool as steps and phases end. | Description$ You don't lose unspent red mana as steps and phases end.
+S:Mode$ UnspentMana | ValidPlayer$ You | ManaType$ Red | Description$ You don't lose unspent red mana as steps and phases end.
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigChooseX | TriggerController$ TriggeredCardController | TriggerDescription$ When CARDNAME dies, you may pay any amount of {R}. When you do, it deals that much damage to any target.
SVar:TrigChooseX:DB$ ChooseNumber | Defined$ You | ChooseAnyNumber$ True | ListTitle$ any amount of red mana | SubAbility$ DBDamage
SVar:DBDamage:DB$ DealDamage | ValidTgts$ Creature,Player,Planeswalker | TgtPrompt$ Select any target to damage with CARDNAME | NumDmg$ X | UnlessCost$ X | UnlessXColor$ R | UnlessSwitched$ True | UnlessPayer$ You
diff --git a/forge-gui/res/cardsfolder/o/omnath_locus_of_mana.txt b/forge-gui/res/cardsfolder/o/omnath_locus_of_mana.txt
index 8696f7846d6..e5ebdb6a8eb 100644
--- a/forge-gui/res/cardsfolder/o/omnath_locus_of_mana.txt
+++ b/forge-gui/res/cardsfolder/o/omnath_locus_of_mana.txt
@@ -2,7 +2,7 @@ Name:Omnath, Locus of Mana
ManaCost:2 G
Types:Legendary Creature Elemental
PT:1/1
-S:Mode$ Continuous | Affected$ You | AddKeyword$ Green mana doesn't empty from your mana pool as steps and phases end. | Description$ You don't lose unspent green mana as steps and phases end.
+S:Mode$ UnspentMana | ValidPlayer$ You | ManaType$ Green | Description$ You don't lose unspent green mana as steps and phases end.
S:Mode$ Continuous | Affected$ Card.Self | AddPower$ X | AddToughness$ X | Description$ CARDNAME gets +1/+1 for each unspent green mana you have.
SVar:X:Count$ManaPool:green
AI:RemoveDeck:All
diff --git a/forge-gui/res/cardsfolder/u/upwelling.txt b/forge-gui/res/cardsfolder/u/upwelling.txt
index 2ef975ae615..4e152f6a070 100644
--- a/forge-gui/res/cardsfolder/u/upwelling.txt
+++ b/forge-gui/res/cardsfolder/u/upwelling.txt
@@ -1,8 +1,7 @@
Name:Upwelling
ManaCost:3 G
Types:Enchantment
-S:Mode$ Continuous | GlobalRule$ Mana pools don't empty as steps and phases end. | Description$ Players don't lose unspent mana as steps and phases end.
+S:Mode$ UnspentMana | Description$ Players don't lose unspent mana as steps and phases end.
SVar:NonStackingEffect:True
AI:RemoveDeck:Random
-SVar:Picture:http://www.wizards.com/global/images/magic/general/upwelling.jpg
Oracle:Players don't lose unspent mana as steps and phases end.
diff --git a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java
index e03303b52c7..868b6f5aab9 100644
--- a/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java
+++ b/forge-gui/src/main/java/forge/gamemodes/match/input/InputPassPriority.java
@@ -110,7 +110,7 @@ public class InputPassPriority extends InputSyncronizedBase {
public void run() {
Localizer localizer = Localizer.getInstance();
String message = localizer.getMessage("lblYouHaveManaFloatingInYourManaPoolCouldBeLostIfPassPriority");
- if (FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN)) {
+ if (player.getManaPool().hasBurn()) {
message += " " + localizer.getMessage("lblYouWillTakeManaBurnDamageEqualAmountFloatingManaLostThisWay");
}
if (getController().getGui().showConfirmDialog(message, localizer.getMessage("lblManaFloating"), localizer.getMessage("lblOk"), localizer.getMessage("lblCancel"))) {