* Mana increase effects - fixed that some infinite mana combos gives 0 mana on too much permanents/effects (example: Nyxbloom Ancient);

This commit is contained in:
Oleg Agafonov 2021-02-02 19:05:42 +04:00
parent cda79866ad
commit 2393485320
10 changed files with 165 additions and 103 deletions

View file

@ -10,6 +10,7 @@ import mage.constants.*;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ManaEvent; import mage.game.events.ManaEvent;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -55,22 +56,22 @@ class ManaReflectionReplacementEffect extends ReplacementEffectImpl {
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Mana mana = ((ManaEvent) event).getMana(); Mana mana = ((ManaEvent) event).getMana();
if (mana.getBlack() > 0) { if (mana.getBlack() > 0) {
mana.set(ManaType.BLACK, mana.getBlack() * 2); mana.set(ManaType.BLACK, CardUtil.multiplyWithOverflowCheck(mana.getBlack(), 2));
} }
if (mana.getBlue() > 0) { if (mana.getBlue() > 0) {
mana.set(ManaType.BLUE, mana.getBlue() * 2); mana.set(ManaType.BLUE, CardUtil.multiplyWithOverflowCheck(mana.getBlue(), 2));
} }
if (mana.getWhite() > 0) { if (mana.getWhite() > 0) {
mana.set(ManaType.WHITE, mana.getWhite() * 2); mana.set(ManaType.WHITE, CardUtil.multiplyWithOverflowCheck(mana.getWhite(), 2));
} }
if (mana.getGreen() > 0) { if (mana.getGreen() > 0) {
mana.set(ManaType.GREEN, mana.getGreen() * 2); mana.set(ManaType.GREEN, CardUtil.multiplyWithOverflowCheck(mana.getGreen(), 2));
} }
if (mana.getRed() > 0) { if (mana.getRed() > 0) {
mana.set(ManaType.RED, mana.getRed() * 2); mana.set(ManaType.RED, CardUtil.multiplyWithOverflowCheck(mana.getRed(), 2));
} }
if (mana.getColorless() > 0) { if (mana.getColorless() > 0) {
mana.set(ManaType.COLORLESS, mana.getColorless() * 2); mana.set(ManaType.COLORLESS, CardUtil.multiplyWithOverflowCheck(mana.getColorless(), 2));
} }
return false; return false;
} }

View file

@ -12,6 +12,7 @@ import mage.constants.*;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.ManaEvent; import mage.game.events.ManaEvent;
import mage.util.CardUtil;
import java.util.UUID; import java.util.UUID;
@ -64,22 +65,22 @@ class NyxbloomAncientReplacementEffect extends ReplacementEffectImpl {
public boolean replaceEvent(GameEvent event, Ability source, Game game) { public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Mana mana = ((ManaEvent) event).getMana(); Mana mana = ((ManaEvent) event).getMana();
if (mana.getBlack() > 0) { if (mana.getBlack() > 0) {
mana.set(ManaType.BLACK, mana.getBlack() * 3); mana.set(ManaType.BLACK, CardUtil.multiplyWithOverflowCheck(mana.getBlack(), 3));
} }
if (mana.getBlue() > 0) { if (mana.getBlue() > 0) {
mana.set(ManaType.BLUE, mana.getBlue() * 3); mana.set(ManaType.BLUE, CardUtil.multiplyWithOverflowCheck(mana.getBlue(), 3));
} }
if (mana.getWhite() > 0) { if (mana.getWhite() > 0) {
mana.set(ManaType.WHITE, mana.getWhite() * 3); mana.set(ManaType.WHITE, CardUtil.multiplyWithOverflowCheck(mana.getWhite(), 3));
} }
if (mana.getGreen() > 0) { if (mana.getGreen() > 0) {
mana.set(ManaType.GREEN, mana.getGreen() * 3); mana.set(ManaType.GREEN, CardUtil.multiplyWithOverflowCheck(mana.getGreen(), 3));
} }
if (mana.getRed() > 0) { if (mana.getRed() > 0) {
mana.set(ManaType.RED, mana.getRed() * 3); mana.set(ManaType.RED, CardUtil.multiplyWithOverflowCheck(mana.getRed(), 3));
} }
if (mana.getColorless() > 0) { if (mana.getColorless() > 0) {
mana.set(ManaType.COLORLESS, mana.getColorless() * 3); mana.set(ManaType.COLORLESS, CardUtil.multiplyWithOverflowCheck(mana.getColorless(), 3));
} }
return false; return false;
} }

View file

@ -383,10 +383,11 @@ public class CommandersCastTest extends CardTestCommander4Players {
addCustomEffect_TargetDamage(playerA, 10); // kill creature addCustomEffect_TargetDamage(playerA, 10); // kill creature
// use case: // use case:
// cast adventure spell from command zone and keep it in exile (inc next command cost) // cast adventure spell from command zone (inc command tax to 1x)
// cast card from exile (do not inc next command cost) // return to command zone
// return commander to command zone // cast adventure spell from command zone (inc command tax to 2x)
// cast as adventure spell (with x1 command cost) // return to command zone
// cast as creature (with 2x command tax)
// Curious Pair, creature, {1}{G}, 1/3 // Curious Pair, creature, {1}{G}, 1/3
// Treats to Share, sorcery, {G} // Treats to Share, sorcery, {G}
@ -418,7 +419,6 @@ public class CommandersCastTest extends CardTestCommander4Players {
checkPlayableAbility("after mana add", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Curious Pair", false); checkPlayableAbility("after mana add", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Curious Pair", false);
checkPlayableAbility("after mana add", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Treats to Share", true); checkPlayableAbility("after mana add", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Treats to Share", true);
// play adventure spell, but keep it // play adventure spell, but keep it
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share");
setChoice(playerA, "No"); // do not return commander setChoice(playerA, "No"); // do not return commander

View file

@ -12,7 +12,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
public class TappedForManaFromMultipleEffects extends CardTestPlayerBase { public class TappedForManaFromMultipleEffects extends CardTestPlayerBase {
@Test @Test
public void test_One() { public void test_NyxbloomAncient_One() {
// If you tap a permanent for mana, it produces three times as much of that mana instead. // If you tap a permanent for mana, it produces three times as much of that mana instead.
addCard(Zone.HAND, playerA, "Nyxbloom Ancient"); // {4}{G}{G}{G} addCard(Zone.HAND, playerA, "Nyxbloom Ancient"); // {4}{G}{G}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); addCard(Zone.BATTLEFIELD, playerA, "Forest", 7);
@ -36,7 +36,7 @@ public class TappedForManaFromMultipleEffects extends CardTestPlayerBase {
} }
@Test @Test
public void test_Two() { public void test_NyxbloomAncient_Two() {
// If you tap a permanent for mana, it produces three times as much of that mana instead. // If you tap a permanent for mana, it produces three times as much of that mana instead.
addCard(Zone.HAND, playerA, "Nyxbloom Ancient", 1); // {4}{G}{G}{G} addCard(Zone.HAND, playerA, "Nyxbloom Ancient", 1); // {4}{G}{G}{G}
addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); addCard(Zone.BATTLEFIELD, playerA, "Forest", 7);
@ -70,6 +70,25 @@ public class TappedForManaFromMultipleEffects extends CardTestPlayerBase {
assertPermanentCount(playerA, "Chlorophant", 1); assertPermanentCount(playerA, "Chlorophant", 1);
} }
@Test
public void test_NyxbloomAncient_IntegerOverflow() {
// If you tap a permanent for mana, it produces three times as much of that mana instead.
int permanentsCount = 30;
addCard(Zone.BATTLEFIELD, playerA, "Nyxbloom Ancient", permanentsCount);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// total mana = 3^count, so must be Integer overflow protection (no zero or negative values)
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
checkManaPool("max mana", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "G", Integer.MAX_VALUE);
setChoice(playerA, "Nyxbloom Ancient", permanentsCount - 1); // choose replacement effects order
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
}
@Test @Test
public void test_ChromeMox_Direct() { public void test_ChromeMox_Direct() {
// Imprint When Chrome Mox enters the battlefield, you may exile a nonartifact, nonland card from your hand. // Imprint When Chrome Mox enters the battlefield, you may exile a nonartifact, nonland card from your hand.

View file

@ -3,6 +3,7 @@ package mage;
import mage.constants.ColoredManaSymbol; import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType; import mage.constants.ManaType;
import mage.filter.FilterMana; import mage.filter.FilterMana;
import mage.util.CardUtil;
import mage.util.Copyable; import mage.util.Copyable;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
@ -10,6 +11,8 @@ import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
/** /**
* WARNING, all mana operations must use overflow check, see usage of CardUtil.addWithOverflowCheck and same methods
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
*/ */
public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> { public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
@ -38,20 +41,20 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
} }
protected void incrementAmount(ManaColor manaColor) { protected void incrementAmount(ManaColor manaColor) {
this.amount += manaColor.amount; this.amount = CardUtil.addWithOverflowCheck(this.amount, manaColor.amount);
this.snowAmount += manaColor.snowAmount; this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, manaColor.snowAmount);
} }
protected void incrementAmount(int amount, boolean snow) { protected void incrementAmount(int amount, boolean snow) {
this.amount += amount; this.amount = CardUtil.addWithOverflowCheck(this.amount, amount);
if (snow) { if (snow) {
this.snowAmount += amount; this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, amount);
} }
} }
protected void removeAmount(ManaColor manaColor) { protected void removeAmount(ManaColor manaColor) {
this.amount -= manaColor.amount; this.amount = CardUtil.subtractWithOverflowCheck(this.amount, manaColor.amount);
this.snowAmount -= manaColor.snowAmount; this.snowAmount = CardUtil.subtractWithOverflowCheck(this.snowAmount, manaColor.snowAmount);
} }
protected void clear() { protected void clear() {
@ -64,11 +67,11 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
return false; return false;
} }
if (manaColor.getSnowAmount() > 0) { if (manaColor.getSnowAmount() > 0) {
manaColor.snowAmount -= 1; manaColor.snowAmount = CardUtil.subtractWithOverflowCheck(manaColor.snowAmount, 1);
snowAmount += 1; this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, 1);
} }
manaColor.amount -= 1; manaColor.amount = CardUtil.subtractWithOverflowCheck(manaColor.amount, 1);
amount += 1; this.amount = CardUtil.addWithOverflowCheck(this.amount, 1);
return true; return true;
} }
@ -594,25 +597,25 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
} }
int count = 0; int count = 0;
if (filter.isWhite()) { if (filter.isWhite()) {
count += white.getAmount(); count = CardUtil.addWithOverflowCheck(count, white.getAmount());
} }
if (filter.isBlue()) { if (filter.isBlue()) {
count += blue.getAmount(); count = CardUtil.addWithOverflowCheck(count, blue.getAmount());
} }
if (filter.isBlack()) { if (filter.isBlack()) {
count += black.getAmount(); count = CardUtil.addWithOverflowCheck(count, black.getAmount());
} }
if (filter.isRed()) { if (filter.isRed()) {
count += red.getAmount(); count = CardUtil.addWithOverflowCheck(count, red.getAmount());
} }
if (filter.isGreen()) { if (filter.isGreen()) {
count += green.getAmount(); count = CardUtil.addWithOverflowCheck(count, green.getAmount());
} }
if (filter.isGeneric()) { if (filter.isGeneric()) {
count += generic.getAmount(); count = CardUtil.addWithOverflowCheck(count, generic.getAmount());
} }
if (filter.isColorless()) { if (filter.isColorless()) {
count += colorless.getAmount(); count = CardUtil.addWithOverflowCheck(count, colorless.getAmount());
} }
return count; return count;
} }
@ -640,29 +643,33 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
public String toString() { public String toString() {
StringBuilder sbMana = new StringBuilder(); StringBuilder sbMana = new StringBuilder();
if (generic.getAmount() > 0) { if (generic.getAmount() > 0) {
sbMana.append('{').append(Integer.toString(generic.getAmount())).append('}'); sbMana.append('{').append(generic.getAmount()).append('}');
} }
// too many mana - replace by single icon
if (colorless.getAmount() >= 20) { if (colorless.getAmount() >= 20) {
sbMana.append(Integer.toString(colorless.getAmount())).append("{C}"); sbMana.append(colorless.getAmount()).append("{C}");
} }
if (white.getAmount() >= 20) { if (white.getAmount() >= 20) {
sbMana.append(Integer.toString(white.getAmount())).append("{W}"); sbMana.append(white.getAmount()).append("{W}");
} }
if (blue.getAmount() >= 20) { if (blue.getAmount() >= 20) {
sbMana.append(Integer.toString(blue.getAmount())).append("{U}"); sbMana.append(blue.getAmount()).append("{U}");
} }
if (black.getAmount() >= 20) { if (black.getAmount() >= 20) {
sbMana.append(Integer.toString(black.getAmount())).append("{B}"); sbMana.append(black.getAmount()).append("{B}");
} }
if (red.getAmount() >= 20) { if (red.getAmount() >= 20) {
sbMana.append(Integer.toString(red.getAmount())).append("{R}"); sbMana.append(red.getAmount()).append("{R}");
} }
if (green.getAmount() >= 20) { if (green.getAmount() >= 20) {
sbMana.append(Integer.toString(green.getAmount())).append("{G}"); sbMana.append(green.getAmount()).append("{G}");
} }
if (any.getAmount() >= 20) { if (any.getAmount() >= 20) {
sbMana.append(Integer.toString(any.getAmount())).append("{Any}"); sbMana.append(any.getAmount()).append("{Any}");
} }
// normal mana
for (int i = 0; i < colorless.getAmount() && colorless.getAmount() < 20; i++) { for (int i = 0; i < colorless.getAmount() && colorless.getAmount() < 20; i++) {
sbMana.append("{C}"); sbMana.append("{C}");
} }
@ -684,6 +691,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
for (int i = 0; i < any.getAmount() && any.getAmount() < 20; i++) { for (int i = 0; i < any.getAmount() && any.getAmount() < 20; i++) {
sbMana.append("{Any}"); sbMana.append("{Any}");
} }
return sbMana.toString(); return sbMana.toString();
} }
@ -757,9 +765,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
compare.generic.incrementAmount(compare.green); compare.generic.incrementAmount(compare.green);
compare.generic.incrementAmount(compare.colorless); compare.generic.incrementAmount(compare.colorless);
compare.generic.incrementAmount(compare.any); compare.generic.incrementAmount(compare.any);
if (compare.generic.getAmount() < 0) { return compare.generic.getAmount() >= 0;
return false;
}
} }
return true; return true;
} }
@ -805,13 +811,13 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
} }
if (compare.generic.getAmount() < 0) { if (compare.generic.getAmount() < 0) {
int remaining = 0; int remaining = 0;
remaining += Math.min(0, compare.white.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.white.getAmount()));
remaining += Math.min(0, compare.blue.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.blue.getAmount()));
remaining += Math.min(0, compare.black.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.black.getAmount()));
remaining += Math.min(0, compare.red.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.red.getAmount()));
remaining += Math.min(0, compare.green.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.green.getAmount()));
remaining += Math.min(0, compare.colorless.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.colorless.getAmount()));
remaining += Math.min(0, compare.any.getAmount()); remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.any.getAmount()));
if (remaining > 0) { if (remaining > 0) {
int diff = Math.min(remaining, Math.abs(compare.generic.getAmount())); int diff = Math.min(remaining, Math.abs(compare.generic.getAmount()));
compare.generic.incrementAmount(diff, false); compare.generic.incrementAmount(diff, false);
@ -1022,10 +1028,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
return true; return true;
} else if (color.isRed() && red.getSnowAmount() > 0) { } else if (color.isRed() && red.getSnowAmount() > 0) {
return true; return true;
} else if (color.isGreen() && green.getSnowAmount() > 0) { } else return color.isGreen() && green.getSnowAmount() > 0;
return true;
}
return false;
} }
/** /**
@ -1036,7 +1039,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
*/ */
@Override @Override
public int compareTo(final Mana o) { public int compareTo(final Mana o) {
return this.count() - o.count(); return Integer.compare(this.count(), o.count());
} }
/** /**
@ -1065,11 +1068,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0) { if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0) {
return true; return true;
} }
if (mana.generic.getAmount() > 0 && this.count() > 0) { return mana.generic.getAmount() > 0 && this.count() > 0;
return true;
}
return false;
} }
public boolean containsAny(final Mana mana) { public boolean containsAny(final Mana mana) {
@ -1098,11 +1097,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
return true; return true;
} else if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0 && includeColorless) { } else if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0 && includeColorless) {
return true; return true;
} else if (mana.any.getAmount() > 0 && this.count() > 0) { } else return mana.any.getAmount() > 0 && this.count() > 0;
return true;
}
return false;
} }
/** /**
@ -1153,7 +1148,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
case GREEN: case GREEN:
return green.getAmount(); return green.getAmount();
case COLORLESS: case COLORLESS:
return generic.getAmount() + colorless.getAmount(); return CardUtil.addWithOverflowCheck(generic.getAmount(), colorless.getAmount());
} }
return 0; return 0;
} }
@ -1161,6 +1156,9 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
/** /**
* Set the color of mana specified by the passed in {@link ManaType} to * Set the color of mana specified by the passed in {@link ManaType} to
* {@code amount} . * {@code amount} .
* <p>
* WARNING, you must check amount for overflow values, see CardUtil.multiplyWithOverflowCheck
* and other CardUtil.xxxWithOverflowCheck math methods
* *
* @param manaType the color of the mana to set * @param manaType the color of the mana to set
* @param amount the value to set the mana too * @param amount the value to set the mana too
@ -1168,22 +1166,22 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
public void set(final ManaType manaType, final int amount) { public void set(final ManaType manaType, final int amount) {
switch (manaType) { switch (manaType) {
case WHITE: case WHITE:
setWhite(amount); setWhite(notNegative(amount, "white"));
break; break;
case BLUE: case BLUE:
setBlue(amount); setBlue(notNegative(amount, "blue"));
break; break;
case BLACK: case BLACK:
setBlack(amount); setBlack(notNegative(amount, "black"));
break; break;
case RED: case RED:
setRed(amount); setRed(notNegative(amount, "red"));
break; break;
case GREEN: case GREEN:
setGreen(amount); setGreen(notNegative(amount, "green"));
break; break;
case COLORLESS: case COLORLESS:
setColorless(amount); setColorless(notNegative(amount, "colorless"));
break; break;
default: default:
throw new IllegalArgumentException("Unknown color: " + manaType); throw new IllegalArgumentException("Unknown color: " + manaType);
@ -1248,7 +1246,7 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
&& this.green.getAmount() >= mana.green.getAmount() && this.green.getAmount() >= mana.green.getAmount()
&& this.colorless.getAmount() >= mana.colorless.getAmount() && this.colorless.getAmount() >= mana.colorless.getAmount()
&& (this.generic.getAmount() >= mana.generic.getAmount() && (this.generic.getAmount() >= mana.generic.getAmount()
|| this.countColored() + this.colorless.getAmount() >= mana.count()); || CardUtil.addWithOverflowCheck(this.countColored(), this.colorless.getAmount()) >= mana.count());
} }
@ -1284,33 +1282,33 @@ public class Mana implements Comparable<Mana>, Serializable, Copyable<Mana> {
moreMana = mana1; moreMana = mana1;
lessMana = mana2; lessMana = mana2;
} }
int anyDiff = mana2.getAny() - mana1.getAny(); int anyDiff = CardUtil.subtractWithOverflowCheck(mana2.getAny(), mana1.getAny());
if (lessMana.getWhite() > moreMana.getWhite()) { if (lessMana.getWhite() > moreMana.getWhite()) {
anyDiff -= lessMana.getWhite() - moreMana.getWhite(); anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getWhite(), moreMana.getWhite()));
if (anyDiff < 0) { if (anyDiff < 0) {
return null; return null;
} }
} }
if (lessMana.getRed() > moreMana.getRed()) { if (lessMana.getRed() > moreMana.getRed()) {
anyDiff -= lessMana.getRed() - moreMana.getRed(); anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getRed(), moreMana.getRed()));
if (anyDiff < 0) { if (anyDiff < 0) {
return null; return null;
} }
} }
if (lessMana.getGreen() > moreMana.getGreen()) { if (lessMana.getGreen() > moreMana.getGreen()) {
anyDiff -= lessMana.getGreen() - moreMana.getGreen(); anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getGreen(), moreMana.getGreen()));
if (anyDiff < 0) { if (anyDiff < 0) {
return null; return null;
} }
} }
if (lessMana.getBlue() > moreMana.getBlue()) { if (lessMana.getBlue() > moreMana.getBlue()) {
anyDiff -= lessMana.getBlue() - moreMana.getBlue(); anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getBlue(), moreMana.getBlue()));
if (anyDiff < 0) { if (anyDiff < 0) {
return null; return null;
} }
} }
if (lessMana.getBlack() > moreMana.getBlack()) { if (lessMana.getBlack() > moreMana.getBlack()) {
anyDiff -= lessMana.getBlack() - moreMana.getBlack(); anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getBlack(), moreMana.getBlack()));
if (anyDiff < 0) { if (anyDiff < 0) {
return null; return null;
} }

View file

@ -5,14 +5,16 @@
*/ */
package mage.abilities.effects.mana; package mage.abilities.effects.mana;
import java.util.ArrayList;
import java.util.List;
import mage.Mana; import mage.Mana;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.mana.builder.ConditionalManaBuilder; import mage.abilities.mana.builder.ConditionalManaBuilder;
import mage.constants.ManaType; import mage.constants.ManaType;
import mage.game.Game; import mage.game.Game;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.List;
/** /**
* @author LevelX2 * @author LevelX2
@ -55,8 +57,8 @@ public class AddConditionalManaEffect extends ManaEffect {
int amountAvailableMana = netAmount.calculate(game, source, this); int amountAvailableMana = netAmount.calculate(game, source, this);
if (amountAvailableMana > 0) { if (amountAvailableMana > 0) {
Mana calculatedMana = mana.copy(); Mana calculatedMana = mana.copy();
for(ManaType manaType: ManaType.getTrueManaTypes()) { for (ManaType manaType : ManaType.getTrueManaTypes()) {
calculatedMana.set(manaType, calculatedMana.get(manaType) * amountAvailableMana); calculatedMana.set(manaType, CardUtil.multiplyWithOverflowCheck(calculatedMana.get(manaType), amountAvailableMana));
} }
maxAvailableMana.add(manaBuilder.setMana(calculatedMana, source, game).build()); maxAvailableMana.add(manaBuilder.setMana(calculatedMana, source, game).build());
} }

View file

@ -16,6 +16,7 @@ import mage.game.events.GameEvent.EventType;
import mage.game.events.ManaEvent; import mage.game.events.ManaEvent;
import mage.game.events.ManaPaidEvent; import mage.game.events.ManaPaidEvent;
import mage.game.stack.Spell; import mage.game.stack.Spell;
import mage.util.CardUtil;
import java.io.Serializable; import java.io.Serializable;
import java.util.*; import java.util.*;
@ -387,7 +388,7 @@ public class ManaPool implements Serializable {
private void removeConditional(ConditionalManaInfo manaInfo, Ability ability, Game game, Cost costToPay, Mana usedManaToPay) { private void removeConditional(ConditionalManaInfo manaInfo, Ability ability, Game game, Cost costToPay, Mana usedManaToPay) {
for (ConditionalMana mana : getConditionalMana()) { for (ConditionalMana mana : getConditionalMana()) {
if (mana.get(manaInfo.manaType) > 0 && mana.apply(ability, game, mana.getManaProducerId(), costToPay)) { if (mana.get(manaInfo.manaType) > 0 && mana.apply(ability, game, mana.getManaProducerId(), costToPay)) {
mana.set(manaInfo.manaType, mana.get(manaInfo.manaType) - 1); mana.set(manaInfo.manaType, CardUtil.subtractWithOverflowCheck(mana.get(manaInfo.manaType), 1));
usedManaToPay.increase(manaInfo.manaType, manaInfo.sourceObject.isSnow()); usedManaToPay.increase(manaInfo.manaType, manaInfo.sourceObject.isSnow());
GameEvent event = new ManaPaidEvent(ability, mana.getManaProducerId(), mana.getFlag(), mana.getManaProducerOriginalId()); GameEvent event = new ManaPaidEvent(ability, mana.getManaProducerId(), mana.getFlag(), mana.getManaProducerOriginalId());
game.fireEvent(event); game.fireEvent(event);

View file

@ -3244,7 +3244,7 @@ public abstract class PlayerImpl implements Player, Serializable {
restVal = 0; restVal = 0;
availableLifeMana -= oldPayOption.get(manaType); availableLifeMana -= oldPayOption.get(manaType);
} else { } else {
restVal = oldPayOption.get(manaType) - availableLifeMana; restVal = CardUtil.subtractWithOverflowCheck(oldPayOption.get(manaType), availableLifeMana);
availableLifeMana = 0; availableLifeMana = 0;
} }
manaCopy.set(manaType, restVal); manaCopy.set(manaType, restVal);

View file

@ -1,12 +1,6 @@
package mage.util; package mage.util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.abilities.Abilities; import mage.abilities.Abilities;
@ -43,6 +37,13 @@ import mage.target.targetpointer.FixedTarget;
import mage.util.functions.CopyTokenFunction; import mage.util.functions.CopyTokenFunction;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/** /**
* @author nantuko * @author nantuko
*/ */
@ -75,9 +76,9 @@ public final class CardUtil {
/** /**
* calculates the maximal possible generic mana reduction for a given mana cost * calculates the maximal possible generic mana reduction for a given mana cost
* *
* @param mana mana costs that should be reduced * @param mana mana costs that should be reduced
* @param maxPossibleReduction max possible generic mana reduction * @param maxPossibleReduction max possible generic mana reduction
* @param notLessThan the complete costs may not be reduced more than this CMC mana costs * @param notLessThan the complete costs may not be reduced more than this CMC mana costs
*/ */
public static int calculateActualPossibleGenericManaReduction(Mana mana, int maxPossibleReduction, int notLessThan) { public static int calculateActualPossibleGenericManaReduction(Mana mana, int maxPossibleReduction, int notLessThan) {
int nonGeneric = mana.count() - mana.getGeneric(); int nonGeneric = mana.count() - mana.getGeneric();
@ -90,7 +91,6 @@ public final class CardUtil {
} }
/** /**
* Reduces ability cost to be paid. * Reduces ability cost to be paid.
* *
@ -656,6 +656,16 @@ public final class CardUtil {
return base - decrement; return base - decrement;
} }
public static int multiplyWithOverflowCheck(int base, int multiply) {
long result = ((long) base) * multiply;
if (result > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else if (result < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return base * multiply;
}
public static String createObjectRealtedWindowTitle(Ability source, Game game, String textSuffix) { public static String createObjectRealtedWindowTitle(Ability source, Game game, String textSuffix) {
String title; String title;
if (source != null) { if (source != null) {
@ -1032,7 +1042,7 @@ public final class CardUtil {
/** /**
* Add effects to game that allows to play/cast card from current zone and spend mana as any type for it. * Add effects to game that allows to play/cast card from current zone and spend mana as any type for it.
* Effects will be discarded/ignored on any card movements or blinks (after ZCC change) * Effects will be discarded/ignored on any card movements or blinks (after ZCC change)
* * <p>
* Affected to all card's parts * Affected to all card's parts
* *
* @param game * @param game

View file

@ -656,4 +656,34 @@ public class ManaTest {
assertEquals(0, mana.getGeneric()); assertEquals(0, mana.getGeneric());
assertEquals(0, mana.getAny()); assertEquals(0, mana.getAny());
} }
@Test
public void shouldNotOverflow() {
// given
Mana mana = new Mana();
// when
mana.setRed(Integer.MAX_VALUE);
mana.increaseRed();
mana.setGreen(Integer.MAX_VALUE);
mana.increaseGreen();
mana.setBlue(Integer.MAX_VALUE);
mana.increaseBlue();
mana.setWhite(Integer.MAX_VALUE);
mana.increaseWhite();
mana.setBlack(Integer.MAX_VALUE);
mana.increaseBlack();
mana.setGeneric(Integer.MAX_VALUE);
mana.increaseGeneric();
mana.setAny(Integer.MAX_VALUE);
// then
assertEquals(Integer.MAX_VALUE, mana.getRed());
assertEquals(Integer.MAX_VALUE, mana.getGreen());
assertEquals(Integer.MAX_VALUE, mana.getBlue());
assertEquals(Integer.MAX_VALUE, mana.getWhite());
assertEquals(Integer.MAX_VALUE, mana.getBlack());
assertEquals(Integer.MAX_VALUE, mana.getGeneric());
assertEquals(Integer.MAX_VALUE, mana.getAny());
}
} }