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."),
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."),

View File

@@ -17,10 +17,10 @@
*/
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,17 +28,21 @@ 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;
/**
@@ -83,42 +87,50 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
* </p>
*/
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<AbilityKey, Object> 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.")) {
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 List<Mana> clearPool(boolean isEndOfPhase) {
// isEndOfPhase parameter: true = end of phase, false = mana drain effect
List<Mana> cleared = new ArrayList<>();
if (floatingMana.isEmpty()) { return cleared; }
if (isEndOfPhase && owner.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manapoolsDontEmpty)) {
return cleared;
public final boolean hasBurn() {
final Game game = owner.getGame();
return game.getRules().hasManaBurn() || game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn);
}
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());
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,13 +138,14 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
for (Byte b : keys) {
Collection<Mana> cm = floatingMana.get(b);
final List<Mana> pMana = Lists.newArrayList();
if (isEndOfPhase && !owner.getGame().getPhaseHandler().is(PhaseType.CLEANUP)) {
final List<Mana> 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);
@@ -142,14 +155,6 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
cm.clear();
floatingMana.putAll(b, pMana);
}
} else {
if (convertToColorless) {
convertManaColor(b, (byte)ManaAtom.COLORLESS);
} else {
cleared.addAll(cm);
cm.clear();
}
}
}
owner.updateManaForView();
@@ -158,7 +163,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
}
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);
for (Mana m : cm) {
convert.add(new Mana(toColor, m.getSourceCard(), m.getManaAbility()));
@@ -249,7 +254,7 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return false;
}
final List<Mana> removeFloating = new ArrayList<>();
final List<Mana> removeFloating = Lists.newArrayList();
boolean manaNotAccountedFor = false;
// loop over mana produced by mana ability
@@ -329,8 +334,10 @@ public class ManaPool extends ManaConversionMatrix implements Iterable<Mana> {
return shard.canBePaidWithManaOfColor((byte)0);
}
@Override
public Iterator<Mana> iterator() {
return floatingMana.values().iterator();
}
}

View File

@@ -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);
}
}

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"))) {
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)) {

View File

@@ -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),

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
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.

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.
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.

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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"))) {