* Server: fixed that too much permanents or mana sources on battlefield can crush or slow down the server (#6938);

This commit is contained in:
Oleg Agafonov 2020-08-04 05:36:43 +04:00
parent de110a92dd
commit 3430013f8d
2 changed files with 61 additions and 16 deletions

View file

@ -343,4 +343,28 @@ public class ConvokeTest extends CardTestPlayerBaseWithAIHelps {
assertPermanentCount(playerA, "Hogaak, Arisen Necropolis", 1); assertPermanentCount(playerA, "Hogaak, Arisen Necropolis", 1);
} }
@Test
public void test_Mana_MemoryOverflow() {
// possible bug: convoke mana calculation can overflow server's memory (too much mana options from too much permanents)
// https://github.com/magefree/mage/issues/6938
// Create X 1/1 white Soldier creature tokens with lifelink.
// Convoke (Your creatures can help cast this spell. Each creature you tap while casting this spell pays for {1} or one mana of that creatures color.)
addCard(Zone.HAND, playerA, "March of the Multitudes", 1); // {X}{G}{W}{W}
addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears", 500);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "March of the Multitudes");
setChoice(playerA, "X=1");
addTarget(playerA, "Grizzly Bears"); // convoke pay
setStrictChooseMode(true);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Soldier", 1);
}
} }

View file

@ -1,30 +1,27 @@
package mage.abilities.mana; package mage.abilities.mana;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import mage.Mana; import mage.Mana;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.common.TapSourceCost;
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.players.Player; import mage.players.Player;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** /**
*
* @author BetaSteward_at_googlemail.com * @author BetaSteward_at_googlemail.com
* * <p>
* this class is used to build a list of all possible mana combinations it can * this class is used to build a list of all possible mana combinations it can
* be used to find all the ways to pay a mana cost or all the different mana * be used to find all the ways to pay a mana cost or all the different mana
* combinations available to a player * combinations available to a player
* * <p>
* TODO: Conditional Mana is not supported yet. The mana adding removes the * TODO: Conditional Mana is not supported yet. The mana adding removes the
* condition of conditional mana * condition of conditional mana
*
*/ */
public class ManaOptions extends ArrayList<Mana> { public class ManaOptions extends ArrayList<Mana> {
@ -87,6 +84,8 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} }
forceManaDeduplication();
} }
private void addManaVariation(List<Mana> netManas, ActivatedManaAbilityImpl ability, Game game) { private void addManaVariation(List<Mana> netManas, ActivatedManaAbilityImpl ability, Game game) {
@ -102,6 +101,8 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} }
forceManaDeduplication();
} }
private static List<List<Mana>> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) { private static List<List<Mana>> getSimulatedTriggeredManaFromPlayer(Game game, Ability ability) {
@ -189,9 +190,9 @@ public class ManaOptions extends ArrayList<Mana> {
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
if (!subtractCostAddMana(manaCosts, triggeredManaVariation, ability.getCosts().isEmpty(), startingMana)) { if (!subtractCostAddMana(manaCosts, triggeredManaVariation, ability.getCosts().isEmpty(), startingMana)) {
// the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option // the starting mana includes mana parts that the increased mana does not include, so add starting mana also as an option
add(prevMana); add(prevMana);
} }
wasUsable = true; wasUsable = true;
} else { } else {
// mana costs can't be paid so keep starting mana // mana costs can't be paid so keep starting mana
add(prevMana); add(prevMana);
@ -251,10 +252,13 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} }
if (this.size() > 30 || replaces > 30) { if (this.size() > 30 || replaces > 30) {
logger.trace("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString()); logger.trace("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString());
logger.trace("Abilities: " + abilities.toString()); logger.trace("Abilities: " + abilities.toString());
} }
forceManaDeduplication();
return wasUsable; return wasUsable;
} }
@ -302,6 +306,8 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} }
forceManaDeduplication();
} }
public void addMana(Mana addMana) { public void addMana(Mana addMana) {
@ -311,6 +317,8 @@ public class ManaOptions extends ArrayList<Mana> {
for (Mana mana : this) { for (Mana mana : this) {
mana.add(addMana); mana.add(addMana);
} }
forceManaDeduplication();
} }
public void addMana(ManaOptions options) { public void addMana(ManaOptions options) {
@ -335,6 +343,17 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
} }
forceManaDeduplication();
}
private void forceManaDeduplication() {
// memory overflow protection - force de-duplication on too much mana sources
// bug example: https://github.com/magefree/mage/issues/6938
// use it after new mana adding
if (this.size() > 1000) {
this.removeDuplicated();
}
} }
public ManaOptions copy() { public ManaOptions copy() {
@ -408,14 +427,16 @@ public class ManaOptions extends ArrayList<Mana> {
} }
} }
forceManaDeduplication();
return oldManaWasReplaced; return oldManaWasReplaced;
} }
/** /**
* * @param number of generic mana
* @param number of generic mana
* @param manaAvailable * @param manaAvailable
* @return * @return
*/ */
public static List<Mana> getPossiblePayCombinations(int number, Mana manaAvailable) { public static List<Mana> getPossiblePayCombinations(int number, Mana manaAvailable) {
List<Mana> payCombinations = new ArrayList<>(); List<Mana> payCombinations = new ArrayList<>();