Manapool: remove use of player keywords, use static and replacement effect

This commit is contained in:
Hans Mackowiak
2021-11-28 11:03:03 +01:00
parent 1b59d25046
commit a62491adf2
13 changed files with 158 additions and 55 deletions

View File

@@ -25,7 +25,6 @@ public enum GlobalRuleChange {
alwaysWither ("All damage is dealt as though its source had wither."), alwaysWither ("All damage is dealt as though its source had wither."),
attackerChoosesBlockers ("The attacking player chooses how each creature blocks each combat."), 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."), 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."), noCreatureETBTriggers ("Creatures entering the battlefield don't cause abilities to trigger."),
noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."), noCreatureDyingTriggers ("Creatures dying don't cause abilities to trigger."),
noLegendRule ("The legend rule doesn't apply."), noLegendRule ("The legend rule doesn't apply."),

View File

@@ -17,10 +17,10 @@
*/ */
package forge.game.mana; package forge.game.mana;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -28,17 +28,21 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import forge.card.MagicColor;
import forge.card.mana.ManaAtom; import forge.card.mana.ManaAtom;
import forge.card.mana.ManaCostShard; import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GlobalRuleChange; import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityKey;
import forge.game.event.EventValueChangeType; import forge.game.event.EventValueChangeType;
import forge.game.event.GameEventManaPool; import forge.game.event.GameEventManaPool;
import forge.game.event.GameEventZone; import forge.game.event.GameEventZone;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.replacement.ReplacementLayer;
import forge.game.replacement.ReplacementType;
import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilityManaPart;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.staticability.StaticAbilityUnspentMana;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
/** /**
@@ -83,42 +87,50 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
* </p> * </p>
*/ */
public final boolean willManaBeLostAtEndOfPhase() { public final boolean willManaBeLostAtEndOfPhase() {
if (floatingMana.isEmpty() || if (floatingMana.isEmpty()) {
owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty) || return false;
owner.hasKeyword("Convert unused mana to Colorless")) { }
final Map<AbilityKey, Object> runParams = AbilityKey.mapFromAffected(owner);
if (!owner.getGame().getReplacementHandler().getReplacementList(ReplacementType.LoseMana, runParams, ReplacementLayer.Other).isEmpty()) {
return false; return false;
} }
int safeMana = 0; int safeMana = 0;
for (final byte c : MagicColor.WUBRG) { for (final byte c : StaticAbilityUnspentMana.getManaToKeep(owner)) {
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); safeMana += getAmountOfColor(c);
} }
}
return totalMana() != safeMana; //won't lose floating mana if all mana is of colors that aren't going to be emptied return totalMana() != safeMana; //won't lose floating mana if all mana is of colors that aren't going to be emptied
} }
public final List<Mana> clearPool(boolean isEndOfPhase) { public final boolean hasBurn() {
// isEndOfPhase parameter: true = end of phase, false = mana drain effect final Game game = owner.getGame();
List<Mana> cleared = new ArrayList<>(); return game.getRules().hasManaBurn() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn);
if (floatingMana.isEmpty()) { return cleared; }
if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) {
return cleared;
} }
final boolean convertToColorless = owner.hasKeyword("Convert unused mana to Colorless"); public final List<Mana> clearPool(boolean isEndOfPhase) {
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
List<Mana> cleared = Lists.newArrayList();
if (floatingMana.isEmpty()) { return cleared; }
boolean convertToColorless = false;
final Map<AbilityKey, Object> 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<Byte> keys = Lists.newArrayList(floatingMana.keySet()); final List<Byte> keys = Lists.newArrayList(floatingMana.keySet());
if (isEndOfPhase) { if (isEndOfPhase) {
for (final Byte c : Lists.newArrayList(keys)) { keys.removeAll(StaticAbilityUnspentMana.getManaToKeep(owner));
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);
}
}
} }
if (convertToColorless) { if (convertToColorless) {
keys.remove(Byte.valueOf((byte)ManaAtom.COLORLESS)); keys.remove(Byte.valueOf((byte)ManaAtom.COLORLESS));
@@ -126,13 +138,14 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
for (Byte b : keys) { for (Byte b : keys) {
Collection<Mana> cm = floatingMana.get(b); Collection<Mana> cm = floatingMana.get(b);
final List<Mana> pMana = Lists.newArrayList();
if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) { if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) {
final List<Mana> pMana = new ArrayList<>();
for (final Mana mana : cm) { for (final Mana mana : cm) {
if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) { if (mana.getManaAbility()!= null && mana.getManaAbility().isPersistentMana()) {
pMana.add(mana); pMana.add(mana);
} }
} }
}
cm.removeAll(pMana); cm.removeAll(pMana);
if (convertToColorless) { if (convertToColorless) {
convertManaColor(b, (byte)ManaAtom.COLORLESS); convertManaColor(b, (byte)ManaAtom.COLORLESS);
@@ -142,14 +155,6 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
cm.clear(); cm.clear();
floatingMana.putAll(b, pMana); floatingMana.putAll(b, pMana);
} }
} else {
if (convertToColorless) {
convertManaColor(b, (byte)ManaAtom.COLORLESS);
} else {
cleared.addAll(cm);
cm.clear();
}
}
} }
owner.updateManaForView(); owner.updateManaForView();
@@ -158,7 +163,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
} }
private void convertManaColor(final byte originalColor, final byte toColor) { private void convertManaColor(final byte originalColor, final byte toColor) {
List<Mana> convert = new ArrayList<>(); List<Mana> convert = Lists.newArrayList();
Collection<Mana> cm = floatingMana.get(originalColor); Collection<Mana> cm = floatingMana.get(originalColor);
for (Mana m : cm) { for (Mana m : cm) {
convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility())); convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility()));
@@ -249,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return false; return false;
} }
final List<Mana> removeFloating = new ArrayList<>(); final List<Mana> removeFloating = Lists.newArrayList();
boolean manaNotAccountedFor = false; boolean manaNotAccountedFor = false;
// loop over mana produced by mana ability // loop over mana produced by mana ability
@@ -329,8 +334,10 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return shard.canBePaidWithManaOfColor((byte)0); return shard.canBePaidWithManaOfColor((byte)0);
} }
@Override @Override
public Iterator<Mana> iterator() { public Iterator<Mana> iterator() {
return floatingMana.values().iterator(); return floatingMana.values().iterator();
} }
} }

View File

@@ -478,9 +478,7 @@ public class PhaseHandler implements java.io.Serializable {
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {
int burn = p.getManaPool().clearPool(true).size(); int burn = p.getManaPool().clearPool(true).size();
boolean manaBurns = game.getRules().hasManaBurn() || if (p.getManaPool().hasBurn()) {
(game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn));
if (manaBurns) {
p.loseLife(burn, false, true); p.loseLife(burn, false, true);
} }
} }

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<String, String> 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<AbilityKey, Object> 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<AbilityKey, Object> runParams, SpellAbility sa) {
sa.setReplacingObject(AbilityKey.Player, runParams.get(AbilityKey.Affected));
}
}

View File

@@ -403,7 +403,6 @@ public class ReplacementHandler {
if ("True".equals(replacementEffect.getParam("Skip"))) { if ("True".equals(replacementEffect.getParam("Skip"))) {
return ReplacementResult.Skipped; // Event is skipped. return ReplacementResult.Skipped; // Event is skipped.
} }
Player player = host.getController(); Player player = host.getController();
if (effectSA != null) { 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 the spellability is a replace effect then its some new logic
// if ReplacementResult is set in run params use that instead // if ReplacementResult is set in run params use that instead
if (runParams.containsKey(AbilityKey.ReplacementResult)) { if (runParams.containsKey(AbilityKey.ReplacementResult)) {

View File

@@ -30,6 +30,7 @@ public enum ReplacementType {
GameLoss(ReplaceGameLoss.class), GameLoss(ReplaceGameLoss.class),
Learn(ReplaceLearn.class), Learn(ReplaceLearn.class),
LifeReduced(ReplaceLifeReduced.class), LifeReduced(ReplaceLifeReduced.class),
LoseMana(ReplaceLoseMana.class),
Mill(ReplaceMill.class), Mill(ReplaceMill.class),
Moved(ReplaceMoved.class), Moved(ReplaceMoved.class),
ProduceMana(ReplaceProduceMana.class), ProduceMana(ReplaceProduceMana.class),

View File

@@ -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<Byte> getManaToKeep(final Player player) {
final Game game = player.getGame();
Set<Byte> 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<Byte> 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")));
}
}
}

View File

@@ -1,5 +1,5 @@
Name:Horizon Stone Name:Horizon Stone
ManaCost:5 ManaCost:5
Types:Artifact 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. Oracle:If you would lose unspent mana, that mana becomes colorless instead.

View File

@@ -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. 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 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 | 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:BuffedBy:Permanent.Green,Permanent.Blue
SVar:Picture:http://www.wizards.com/global/images/magic/general/kruphix_god_of_horizons.jpg 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. 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.

View File

@@ -3,7 +3,7 @@ ManaCost:2 R R
Types:Creature Dragon Types:Creature Dragon
PT:4/4 PT:4/4
K:Flying 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. 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: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 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

View File

@@ -2,7 +2,7 @@ Name:Omnath, Locus of Mana
ManaCost:2 G ManaCost:2 G
Types:Legendary Creature Elemental Types:Legendary Creature Elemental
PT:1/1 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. 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 SVar:X:Count$ManaPool:green
AI:RemoveDeck:All AI:RemoveDeck:All

View File

@@ -1,8 +1,7 @@
Name:Upwelling Name:Upwelling
ManaCost:3 G ManaCost:3 G
Types:Enchantment 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 SVar:NonStackingEffect:True
AI:RemoveDeck:Random 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. Oracle:Players don't lose unspent mana as steps and phases end.

View File

@@ -110,7 +110,7 @@ public class InputPassPriority extends InputSyncronizedBase {
public void run() { public void run() {
Localizer localizer = Localizer.getInstance(); Localizer localizer = Localizer.getInstance();
String message = localizer.getMessage("lblYouHaveManaFloatingInYourManaPoolCouldBeLostIfPassPriority"); String message = localizer.getMessage("lblYouHaveManaFloatingInYourManaPoolCouldBeLostIfPassPriority");
if (FModel.getPreferences().getPrefBoolean(FPref.UI_MANABURN)) { if (player.getManaPool().hasBurn()) {
message += " " + localizer.getMessage("lblYouWillTakeManaBurnDamageEqualAmountFloatingManaLostThisWay"); message += " " + localizer.getMessage("lblYouWillTakeManaBurnDamageEqualAmountFloatingManaLostThisWay");
} }
if (getController().getGui().showConfirmDialog(message, localizer.getMessage("lblManaFloating"), localizer.getMessage("lblOk"), localizer.getMessage("lblCancel"))) { if (getController().getGui().showConfirmDialog(message, localizer.getMessage("lblManaFloating"), localizer.getMessage("lblOk"), localizer.getMessage("lblCancel"))) {