From 2393485320437719d1652bfa0de909dadb71dcdc Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 2 Feb 2021 19:05:42 +0400 Subject: [PATCH] * Mana increase effects - fixed that some infinite mana combos gives 0 mana on too much permanents/effects (example: Nyxbloom Ancient); --- .../src/mage/cards/m/ManaReflection.java | 13 +- .../src/mage/cards/n/NyxbloomAncient.java | 13 +- .../cards/continuous/CommandersCastTest.java | 10 +- .../TappedForManaFromMultipleEffects.java | 23 +++- Mage/src/main/java/mage/Mana.java | 126 +++++++++--------- .../mana/AddConditionalManaEffect.java | 10 +- Mage/src/main/java/mage/players/ManaPool.java | 3 +- .../main/java/mage/players/PlayerImpl.java | 2 +- Mage/src/main/java/mage/util/CardUtil.java | 38 ++++-- Mage/src/test/java/mage/ManaTest.java | 30 +++++ 10 files changed, 165 insertions(+), 103 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/ManaReflection.java b/Mage.Sets/src/mage/cards/m/ManaReflection.java index 0a7dbbf5fa..0c5d13a83f 100644 --- a/Mage.Sets/src/mage/cards/m/ManaReflection.java +++ b/Mage.Sets/src/mage/cards/m/ManaReflection.java @@ -10,6 +10,7 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ManaEvent; +import mage.util.CardUtil; import java.util.UUID; @@ -55,22 +56,22 @@ class ManaReflectionReplacementEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Mana mana = ((ManaEvent) event).getMana(); if (mana.getBlack() > 0) { - mana.set(ManaType.BLACK, mana.getBlack() * 2); + mana.set(ManaType.BLACK, CardUtil.multiplyWithOverflowCheck(mana.getBlack(), 2)); } if (mana.getBlue() > 0) { - mana.set(ManaType.BLUE, mana.getBlue() * 2); + mana.set(ManaType.BLUE, CardUtil.multiplyWithOverflowCheck(mana.getBlue(), 2)); } if (mana.getWhite() > 0) { - mana.set(ManaType.WHITE, mana.getWhite() * 2); + mana.set(ManaType.WHITE, CardUtil.multiplyWithOverflowCheck(mana.getWhite(), 2)); } if (mana.getGreen() > 0) { - mana.set(ManaType.GREEN, mana.getGreen() * 2); + mana.set(ManaType.GREEN, CardUtil.multiplyWithOverflowCheck(mana.getGreen(), 2)); } if (mana.getRed() > 0) { - mana.set(ManaType.RED, mana.getRed() * 2); + mana.set(ManaType.RED, CardUtil.multiplyWithOverflowCheck(mana.getRed(), 2)); } if (mana.getColorless() > 0) { - mana.set(ManaType.COLORLESS, mana.getColorless() * 2); + mana.set(ManaType.COLORLESS, CardUtil.multiplyWithOverflowCheck(mana.getColorless(), 2)); } return false; } diff --git a/Mage.Sets/src/mage/cards/n/NyxbloomAncient.java b/Mage.Sets/src/mage/cards/n/NyxbloomAncient.java index e2a81bc54d..e7ad29c6f3 100644 --- a/Mage.Sets/src/mage/cards/n/NyxbloomAncient.java +++ b/Mage.Sets/src/mage/cards/n/NyxbloomAncient.java @@ -12,6 +12,7 @@ import mage.constants.*; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ManaEvent; +import mage.util.CardUtil; import java.util.UUID; @@ -64,22 +65,22 @@ class NyxbloomAncientReplacementEffect extends ReplacementEffectImpl { public boolean replaceEvent(GameEvent event, Ability source, Game game) { Mana mana = ((ManaEvent) event).getMana(); if (mana.getBlack() > 0) { - mana.set(ManaType.BLACK, mana.getBlack() * 3); + mana.set(ManaType.BLACK, CardUtil.multiplyWithOverflowCheck(mana.getBlack(), 3)); } if (mana.getBlue() > 0) { - mana.set(ManaType.BLUE, mana.getBlue() * 3); + mana.set(ManaType.BLUE, CardUtil.multiplyWithOverflowCheck(mana.getBlue(), 3)); } if (mana.getWhite() > 0) { - mana.set(ManaType.WHITE, mana.getWhite() * 3); + mana.set(ManaType.WHITE, CardUtil.multiplyWithOverflowCheck(mana.getWhite(), 3)); } if (mana.getGreen() > 0) { - mana.set(ManaType.GREEN, mana.getGreen() * 3); + mana.set(ManaType.GREEN, CardUtil.multiplyWithOverflowCheck(mana.getGreen(), 3)); } if (mana.getRed() > 0) { - mana.set(ManaType.RED, mana.getRed() * 3); + mana.set(ManaType.RED, CardUtil.multiplyWithOverflowCheck(mana.getRed(), 3)); } if (mana.getColorless() > 0) { - mana.set(ManaType.COLORLESS, mana.getColorless() * 3); + mana.set(ManaType.COLORLESS, CardUtil.multiplyWithOverflowCheck(mana.getColorless(), 3)); } return false; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java index d243eaff27..2fce6cc1ae 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/CommandersCastTest.java @@ -383,10 +383,11 @@ public class CommandersCastTest extends CardTestCommander4Players { addCustomEffect_TargetDamage(playerA, 10); // kill creature // use case: - // cast adventure spell from command zone and keep it in exile (inc next command cost) - // cast card from exile (do not inc next command cost) - // return commander to command zone - // cast as adventure spell (with x1 command cost) + // cast adventure spell from command zone (inc command tax to 1x) + // return to command zone + // cast adventure spell from command zone (inc command tax to 2x) + // return to command zone + // cast as creature (with 2x command tax) // Curious Pair, creature, {1}{G}, 1/3 // 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 Treats to Share", true); - // play adventure spell, but keep it castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Treats to Share"); setChoice(playerA, "No"); // do not return commander diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TappedForManaFromMultipleEffects.java b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TappedForManaFromMultipleEffects.java index 91f3f82a04..c89d869854 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TappedForManaFromMultipleEffects.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/continuous/TappedForManaFromMultipleEffects.java @@ -12,7 +12,7 @@ import org.mage.test.serverside.base.CardTestPlayerBase; public class TappedForManaFromMultipleEffects extends CardTestPlayerBase { @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. addCard(Zone.HAND, playerA, "Nyxbloom Ancient"); // {4}{G}{G}{G} addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); @@ -36,7 +36,7 @@ public class TappedForManaFromMultipleEffects extends CardTestPlayerBase { } @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. addCard(Zone.HAND, playerA, "Nyxbloom Ancient", 1); // {4}{G}{G}{G} addCard(Zone.BATTLEFIELD, playerA, "Forest", 7); @@ -70,6 +70,25 @@ public class TappedForManaFromMultipleEffects extends CardTestPlayerBase { 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 public void test_ChromeMox_Direct() { // Imprint — When Chrome Mox enters the battlefield, you may exile a nonartifact, nonland card from your hand. diff --git a/Mage/src/main/java/mage/Mana.java b/Mage/src/main/java/mage/Mana.java index 21735720a0..8c73b6aef2 100644 --- a/Mage/src/main/java/mage/Mana.java +++ b/Mage/src/main/java/mage/Mana.java @@ -3,6 +3,7 @@ package mage; import mage.constants.ColoredManaSymbol; import mage.constants.ManaType; import mage.filter.FilterMana; +import mage.util.CardUtil; import mage.util.Copyable; import org.apache.log4j.Logger; @@ -10,6 +11,8 @@ import java.io.Serializable; 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 */ public class Mana implements Comparable, Serializable, Copyable { @@ -38,20 +41,20 @@ public class Mana implements Comparable, Serializable, Copyable { } protected void incrementAmount(ManaColor manaColor) { - this.amount += manaColor.amount; - this.snowAmount += manaColor.snowAmount; + this.amount = CardUtil.addWithOverflowCheck(this.amount, manaColor.amount); + this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, manaColor.snowAmount); } protected void incrementAmount(int amount, boolean snow) { - this.amount += amount; + this.amount = CardUtil.addWithOverflowCheck(this.amount, amount); if (snow) { - this.snowAmount += amount; + this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, amount); } } protected void removeAmount(ManaColor manaColor) { - this.amount -= manaColor.amount; - this.snowAmount -= manaColor.snowAmount; + this.amount = CardUtil.subtractWithOverflowCheck(this.amount, manaColor.amount); + this.snowAmount = CardUtil.subtractWithOverflowCheck(this.snowAmount, manaColor.snowAmount); } protected void clear() { @@ -64,11 +67,11 @@ public class Mana implements Comparable, Serializable, Copyable { return false; } if (manaColor.getSnowAmount() > 0) { - manaColor.snowAmount -= 1; - snowAmount += 1; + manaColor.snowAmount = CardUtil.subtractWithOverflowCheck(manaColor.snowAmount, 1); + this.snowAmount = CardUtil.addWithOverflowCheck(this.snowAmount, 1); } - manaColor.amount -= 1; - amount += 1; + manaColor.amount = CardUtil.subtractWithOverflowCheck(manaColor.amount, 1); + this.amount = CardUtil.addWithOverflowCheck(this.amount, 1); return true; } @@ -594,25 +597,25 @@ public class Mana implements Comparable, Serializable, Copyable { } int count = 0; if (filter.isWhite()) { - count += white.getAmount(); + count = CardUtil.addWithOverflowCheck(count, white.getAmount()); } if (filter.isBlue()) { - count += blue.getAmount(); + count = CardUtil.addWithOverflowCheck(count, blue.getAmount()); } if (filter.isBlack()) { - count += black.getAmount(); + count = CardUtil.addWithOverflowCheck(count, black.getAmount()); } if (filter.isRed()) { - count += red.getAmount(); + count = CardUtil.addWithOverflowCheck(count, red.getAmount()); } if (filter.isGreen()) { - count += green.getAmount(); + count = CardUtil.addWithOverflowCheck(count, green.getAmount()); } if (filter.isGeneric()) { - count += generic.getAmount(); + count = CardUtil.addWithOverflowCheck(count, generic.getAmount()); } if (filter.isColorless()) { - count += colorless.getAmount(); + count = CardUtil.addWithOverflowCheck(count, colorless.getAmount()); } return count; } @@ -640,29 +643,33 @@ public class Mana implements Comparable, Serializable, Copyable { public String toString() { StringBuilder sbMana = new StringBuilder(); 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) { - sbMana.append(Integer.toString(colorless.getAmount())).append("{C}"); + sbMana.append(colorless.getAmount()).append("{C}"); } if (white.getAmount() >= 20) { - sbMana.append(Integer.toString(white.getAmount())).append("{W}"); + sbMana.append(white.getAmount()).append("{W}"); } if (blue.getAmount() >= 20) { - sbMana.append(Integer.toString(blue.getAmount())).append("{U}"); + sbMana.append(blue.getAmount()).append("{U}"); } if (black.getAmount() >= 20) { - sbMana.append(Integer.toString(black.getAmount())).append("{B}"); + sbMana.append(black.getAmount()).append("{B}"); } if (red.getAmount() >= 20) { - sbMana.append(Integer.toString(red.getAmount())).append("{R}"); + sbMana.append(red.getAmount()).append("{R}"); } if (green.getAmount() >= 20) { - sbMana.append(Integer.toString(green.getAmount())).append("{G}"); + sbMana.append(green.getAmount()).append("{G}"); } 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++) { sbMana.append("{C}"); } @@ -684,6 +691,7 @@ public class Mana implements Comparable, Serializable, Copyable { for (int i = 0; i < any.getAmount() && any.getAmount() < 20; i++) { sbMana.append("{Any}"); } + return sbMana.toString(); } @@ -757,9 +765,7 @@ public class Mana implements Comparable, Serializable, Copyable { compare.generic.incrementAmount(compare.green); compare.generic.incrementAmount(compare.colorless); compare.generic.incrementAmount(compare.any); - if (compare.generic.getAmount() < 0) { - return false; - } + return compare.generic.getAmount() >= 0; } return true; } @@ -805,13 +811,13 @@ public class Mana implements Comparable, Serializable, Copyable { } if (compare.generic.getAmount() < 0) { int remaining = 0; - remaining += Math.min(0, compare.white.getAmount()); - remaining += Math.min(0, compare.blue.getAmount()); - remaining += Math.min(0, compare.black.getAmount()); - remaining += Math.min(0, compare.red.getAmount()); - remaining += Math.min(0, compare.green.getAmount()); - remaining += Math.min(0, compare.colorless.getAmount()); - remaining += Math.min(0, compare.any.getAmount()); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.white.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.blue.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.black.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.red.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.green.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.colorless.getAmount())); + remaining = CardUtil.addWithOverflowCheck(remaining, Math.min(0, compare.any.getAmount())); if (remaining > 0) { int diff = Math.min(remaining, Math.abs(compare.generic.getAmount())); compare.generic.incrementAmount(diff, false); @@ -1022,10 +1028,7 @@ public class Mana implements Comparable, Serializable, Copyable { return true; } else if (color.isRed() && red.getSnowAmount() > 0) { return true; - } else if (color.isGreen() && green.getSnowAmount() > 0) { - return true; - } - return false; + } else return color.isGreen() && green.getSnowAmount() > 0; } /** @@ -1036,7 +1039,7 @@ public class Mana implements Comparable, Serializable, Copyable { */ @Override 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, Serializable, Copyable { if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0) { return true; } - if (mana.generic.getAmount() > 0 && this.count() > 0) { - return true; - } - - return false; + return mana.generic.getAmount() > 0 && this.count() > 0; } public boolean containsAny(final Mana mana) { @@ -1098,11 +1097,7 @@ public class Mana implements Comparable, Serializable, Copyable { return true; } else if (mana.colorless.getAmount() > 0 && this.colorless.getAmount() > 0 && includeColorless) { return true; - } else if (mana.any.getAmount() > 0 && this.count() > 0) { - return true; - } - - return false; + } else return mana.any.getAmount() > 0 && this.count() > 0; } /** @@ -1153,7 +1148,7 @@ public class Mana implements Comparable, Serializable, Copyable { case GREEN: return green.getAmount(); case COLORLESS: - return generic.getAmount() + colorless.getAmount(); + return CardUtil.addWithOverflowCheck(generic.getAmount(), colorless.getAmount()); } return 0; } @@ -1161,6 +1156,9 @@ public class Mana implements Comparable, Serializable, Copyable { /** * Set the color of mana specified by the passed in {@link ManaType} to * {@code amount} . + *

+ * 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 amount the value to set the mana too @@ -1168,22 +1166,22 @@ public class Mana implements Comparable, Serializable, Copyable { public void set(final ManaType manaType, final int amount) { switch (manaType) { case WHITE: - setWhite(amount); + setWhite(notNegative(amount, "white")); break; case BLUE: - setBlue(amount); + setBlue(notNegative(amount, "blue")); break; case BLACK: - setBlack(amount); + setBlack(notNegative(amount, "black")); break; case RED: - setRed(amount); + setRed(notNegative(amount, "red")); break; case GREEN: - setGreen(amount); + setGreen(notNegative(amount, "green")); break; case COLORLESS: - setColorless(amount); + setColorless(notNegative(amount, "colorless")); break; default: throw new IllegalArgumentException("Unknown color: " + manaType); @@ -1248,7 +1246,7 @@ public class Mana implements Comparable, Serializable, Copyable { && this.green.getAmount() >= mana.green.getAmount() && this.colorless.getAmount() >= mana.colorless.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, Serializable, Copyable { moreMana = mana1; lessMana = mana2; } - int anyDiff = mana2.getAny() - mana1.getAny(); + int anyDiff = CardUtil.subtractWithOverflowCheck(mana2.getAny(), mana1.getAny()); if (lessMana.getWhite() > moreMana.getWhite()) { - anyDiff -= lessMana.getWhite() - moreMana.getWhite(); + anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getWhite(), moreMana.getWhite())); if (anyDiff < 0) { return null; } } if (lessMana.getRed() > moreMana.getRed()) { - anyDiff -= lessMana.getRed() - moreMana.getRed(); + anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getRed(), moreMana.getRed())); if (anyDiff < 0) { return null; } } if (lessMana.getGreen() > moreMana.getGreen()) { - anyDiff -= lessMana.getGreen() - moreMana.getGreen(); + anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getGreen(), moreMana.getGreen())); if (anyDiff < 0) { return null; } } if (lessMana.getBlue() > moreMana.getBlue()) { - anyDiff -= lessMana.getBlue() - moreMana.getBlue(); + anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getBlue(), moreMana.getBlue())); if (anyDiff < 0) { return null; } } if (lessMana.getBlack() > moreMana.getBlack()) { - anyDiff -= lessMana.getBlack() - moreMana.getBlack(); + anyDiff = CardUtil.subtractWithOverflowCheck(anyDiff, CardUtil.subtractWithOverflowCheck(lessMana.getBlack(), moreMana.getBlack())); if (anyDiff < 0) { return null; } diff --git a/Mage/src/main/java/mage/abilities/effects/mana/AddConditionalManaEffect.java b/Mage/src/main/java/mage/abilities/effects/mana/AddConditionalManaEffect.java index 2f52eca97f..b79d7c436b 100644 --- a/Mage/src/main/java/mage/abilities/effects/mana/AddConditionalManaEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/mana/AddConditionalManaEffect.java @@ -5,14 +5,16 @@ */ package mage.abilities.effects.mana; -import java.util.ArrayList; -import java.util.List; import mage.Mana; import mage.abilities.Ability; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.mana.builder.ConditionalManaBuilder; import mage.constants.ManaType; import mage.game.Game; +import mage.util.CardUtil; + +import java.util.ArrayList; +import java.util.List; /** * @author LevelX2 @@ -55,8 +57,8 @@ public class AddConditionalManaEffect extends ManaEffect { int amountAvailableMana = netAmount.calculate(game, source, this); if (amountAvailableMana > 0) { Mana calculatedMana = mana.copy(); - for(ManaType manaType: ManaType.getTrueManaTypes()) { - calculatedMana.set(manaType, calculatedMana.get(manaType) * amountAvailableMana); + for (ManaType manaType : ManaType.getTrueManaTypes()) { + calculatedMana.set(manaType, CardUtil.multiplyWithOverflowCheck(calculatedMana.get(manaType), amountAvailableMana)); } maxAvailableMana.add(manaBuilder.setMana(calculatedMana, source, game).build()); } diff --git a/Mage/src/main/java/mage/players/ManaPool.java b/Mage/src/main/java/mage/players/ManaPool.java index c2f2fd8da2..7b27e7df8b 100644 --- a/Mage/src/main/java/mage/players/ManaPool.java +++ b/Mage/src/main/java/mage/players/ManaPool.java @@ -16,6 +16,7 @@ import mage.game.events.GameEvent.EventType; import mage.game.events.ManaEvent; import mage.game.events.ManaPaidEvent; import mage.game.stack.Spell; +import mage.util.CardUtil; import java.io.Serializable; 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) { for (ConditionalMana mana : getConditionalMana()) { 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()); GameEvent event = new ManaPaidEvent(ability, mana.getManaProducerId(), mana.getFlag(), mana.getManaProducerOriginalId()); game.fireEvent(event); diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 8449bebba9..0cff215dfc 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -3244,7 +3244,7 @@ public abstract class PlayerImpl implements Player, Serializable { restVal = 0; availableLifeMana -= oldPayOption.get(manaType); } else { - restVal = oldPayOption.get(manaType) - availableLifeMana; + restVal = CardUtil.subtractWithOverflowCheck(oldPayOption.get(manaType), availableLifeMana); availableLifeMana = 0; } manaCopy.set(manaType, restVal); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index 3df0aeea71..eb8346df6a 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1,12 +1,6 @@ package mage.util; 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.Mana; import mage.abilities.Abilities; @@ -43,6 +37,13 @@ import mage.target.targetpointer.FixedTarget; import mage.util.functions.CopyTokenFunction; 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 */ @@ -75,9 +76,9 @@ public final class CardUtil { /** * calculates the maximal possible generic mana reduction for a given mana cost * - * @param mana mana costs that should be reduced - * @param maxPossibleReduction max possible generic mana reduction - * @param notLessThan the complete costs may not be reduced more than this CMC mana costs + * @param mana mana costs that should be reduced + * @param maxPossibleReduction max possible generic mana reduction + * @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) { int nonGeneric = mana.count() - mana.getGeneric(); @@ -85,12 +86,11 @@ public final class CardUtil { int actualPossibleGenericManaReduction = Math.max(0, mana.getGeneric() - notPossibleGenericReduction); if (actualPossibleGenericManaReduction > maxPossibleReduction) { actualPossibleGenericManaReduction = maxPossibleReduction; - } + } return actualPossibleGenericManaReduction; } - - - + + /** * Reduces ability cost to be paid. * @@ -656,6 +656,16 @@ public final class CardUtil { 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) { String title; 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. * Effects will be discarded/ignored on any card movements or blinks (after ZCC change) - * + *

* Affected to all card's parts * * @param game diff --git a/Mage/src/test/java/mage/ManaTest.java b/Mage/src/test/java/mage/ManaTest.java index 853ba45c9b..df388fcff5 100644 --- a/Mage/src/test/java/mage/ManaTest.java +++ b/Mage/src/test/java/mage/ManaTest.java @@ -656,4 +656,34 @@ public class ManaTest { assertEquals(0, mana.getGeneric()); 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()); + } }