* Fixed a problem of available mana generation caused by order of usage of mana sourced with costs (example Coal Golem and then Dromar's Attendant - related to #6698).

This commit is contained in:
LevelX2 2020-07-30 23:37:04 +02:00
parent 8e929d4e9d
commit 06968ad921
4 changed files with 127 additions and 48 deletions

View file

@ -129,7 +129,7 @@ public class SasayaOrochiAscendantTest extends CardTestPlayerBase {
assertManaOptions("{R}{R}{R}{R}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{R}{R}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{R}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
assertManaOptions("{R}{G}{G}{G}{G}{G}{G}{G}{G}", manaOptions);
}
}

View file

@ -108,6 +108,7 @@ public class NonTappingManaAbilitiesTest extends CardTestPlayerBase {
assertManaOptions("{W}{B}{B}{B}", manaOptions);
assertManaOptions("{B}{B}{B}{B}", manaOptions);
}
@Test
public void TestCrystallineCrawler() {
setStrictChooseMode(true);
@ -126,5 +127,58 @@ public class NonTappingManaAbilitiesTest extends CardTestPlayerBase {
Assert.assertEquals("mana variations don't fit", 1, manaOptions.size());
assertManaOptions("{Any}{Any}", manaOptions);
}
@Test
public void TestCoalGolemAndDromarsAttendant() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// {1}, Sacrifice Dromar's Attendant: Add {W}{U}{B}.
addCard(Zone.BATTLEFIELD, playerA, "Dromar's Attendant", 1);
// {3}, Sacrifice Coal Golem: Add {R}{R}{R}.
addCard(Zone.BATTLEFIELD, playerA, "Coal Golem", 1);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 3, manaOptions.size());
assertManaOptions("{G}", manaOptions);
assertManaOptions("{W}{U}{B}", manaOptions);
assertManaOptions("{R}{R}{R}", manaOptions);
}
/**
* The order the mana abilities are checked during available mana calculation does matter.
* Because Coal Golem can not be used as long as Dromar's Attendant is not used yet to produce the 3 mana.
*/
@Test
public void TestCoalGolemAndDromarsAttendantOrder2() {
setStrictChooseMode(true);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
// {3}, Sacrifice Coal Golem: Add {R}{R}{R}.
addCard(Zone.BATTLEFIELD, playerA, "Coal Golem", 1);
// {1}, Sacrifice Dromar's Attendant: Add {W}{U}{B}.
addCard(Zone.BATTLEFIELD, playerA, "Dromar's Attendant", 1);
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
ManaOptions manaOptions = playerA.getAvailableManaTest(currentGame);
Assert.assertEquals("mana variations don't fit", 3, manaOptions.size());
assertManaOptions("{G}", manaOptions);
assertManaOptions("{W}{U}{B}", manaOptions);
assertManaOptions("{R}{R}{R}", manaOptions);
}
}

View file

@ -146,7 +146,16 @@ public class ManaOptions extends ArrayList<Mana> {
return false;
}
public void addManaWithCost(List<ActivatedManaAbilityImpl> abilities, Game game) {
/**
* This adds the mana the abilities can produce to the possible mana
* variabtion.
*
* @param abilities
* @param game
* @return false if the costs could not be paid
*/
public boolean addManaWithCost(List<ActivatedManaAbilityImpl> abilities, Game game) {
boolean wasUsable = false;
int replaces = 0;
if (isEmpty()) {
this.add(new Mana()); // needed if this is the first available mana, otherwise looping over existing options woold not loop
@ -185,8 +194,15 @@ public class ManaOptions extends ArrayList<Mana> {
for (Mana triggeredManaVariation : getTriggeredManaVariations(game, ability, netMana)) {
for (Mana prevMana : copy) {
Mana startingMana = prevMana.copy();
if (!subtractCostAddMana(ability.getManaCosts().getMana(), 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
Mana manaCosts = ability.getManaCosts().getMana();
if (startingMana.includesMana(manaCosts)) { // can pay the mana costs to use the ability
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
add(prevMana);
}
wasUsable = true;
} else {
// mana costs can't be paid so keep starting mana
add(prevMana);
}
}
@ -248,6 +264,7 @@ public class ManaOptions extends ArrayList<Mana> {
logger.trace("ManaOptionsCosts " + this.size() + " Ign:" + replaces + " => " + this.toString());
logger.trace("Abilities: " + abilities.toString());
}
return wasUsable;
}
private List<Mana> getTriggeredManaVariations(Game game, Ability ability, Mana baseMana) {
@ -353,54 +370,52 @@ public class ManaOptions extends ArrayList<Mana> {
repeatable = true; // only replace to any with mana costs only will be repeated if able
}
Mana prevMana = currentMana.copy();
if (currentMana.includesMana(cost)) { // cost can be paid
// generic mana costs can be paid with different colored mana, can lead to different color combinations
if (cost.getGeneric() > 0 && cost.getGeneric() > (currentMana.getGeneric() + currentMana.getColorless())) {
for (Mana payCombination : getPossiblePayCombinations(cost.getGeneric(), currentMana)) {
Mana currentManaCopy = currentMana.copy();
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
boolean newCombinations = false;
// generic mana costs can be paid with different colored mana, can lead to different color combinations
if (cost.getGeneric() > 0 && cost.getGeneric() > (currentMana.getGeneric() + currentMana.getColorless())) {
for (Mana payCombination : getPossiblePayCombinations(cost.getGeneric(), currentMana)) {
Mana currentManaCopy = currentMana.copy();
while (currentManaCopy.includesMana(payCombination)) { // loop for multiple usage if possible
boolean newCombinations = false;
Mana newMana = currentManaCopy.copy();
newMana.subtract(payCombination);
newMana.add(manaToAdd);
// Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!isExistingManaCombination(newMana)) {
this.add(newMana); // add the new combination
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
currentManaCopy = newMana.copy();
Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!oldManaWasReplaced && newMana.equals(moreValuable)) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return
}
}
if (!newCombinations || !repeatable) {
break;
Mana newMana = currentManaCopy.copy();
newMana.subtract(payCombination);
newMana.add(manaToAdd);
// Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!isExistingManaCombination(newMana)) {
this.add(newMana); // add the new combination
newCombinations = true; // repeat the while as long there are new combinations and usage is repeatable
currentManaCopy = newMana.copy();
Mana moreValuable = Mana.getMoreValuableMana(currentMana, newMana);
if (!oldManaWasReplaced && newMana.equals(moreValuable)) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one, so no need to add it after return
}
}
}
} else {
while (currentMana.includesMana(cost)) { // loop for multiple usage if possible
currentMana.subtractCost(cost);
currentMana.add(manaToAdd);
if (!repeatable) {
break; // Stop adding multiple usages of the ability
}
}
// Don't use mana that only reduce the available mana
if (prevMana.contains(currentMana) && prevMana.count() > currentMana.count()) {
currentMana.setToMana(prevMana);
}
Mana moreValuable = Mana.getMoreValuableMana(prevMana, currentMana);
if (!prevMana.equals(moreValuable)) {
this.add(currentMana);
if (moreValuable != null) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one
if (!newCombinations || !repeatable) {
break;
}
}
}
} else {
while (currentMana.includesMana(cost)) { // loop for multiple usage if possible
currentMana.subtractCost(cost);
currentMana.add(manaToAdd);
if (!repeatable) {
break; // Stop adding multiple usages of the ability
}
}
// Don't use mana that only reduce the available mana
if (prevMana.contains(currentMana) && prevMana.count() > currentMana.count()) {
currentMana.setToMana(prevMana);
}
Mana moreValuable = Mana.getMoreValuableMana(prevMana, currentMana);
if (!prevMana.equals(moreValuable)) {
this.add(currentMana);
if (moreValuable != null) {
oldManaWasReplaced = true; // the new mana includes all possibilities of the old one
}
}
}
return oldManaWasReplaced;
}

View file

@ -2936,9 +2936,19 @@ public abstract class PlayerImpl implements Player, Serializable {
for (Abilities<ActivatedManaAbilityImpl> manaAbilities : sourceWithoutManaCosts) {
availableMana.addMana(manaAbilities, game);
}
for (Abilities<ActivatedManaAbilityImpl> manaAbilities : sourceWithCosts) {
availableMana.removeDuplicated();
availableMana.addManaWithCost(manaAbilities, game);
boolean anAbilityWasUsed = true;
while (anAbilityWasUsed && !sourceWithCosts.isEmpty()) {
anAbilityWasUsed = false;
for (Iterator<Abilities<ActivatedManaAbilityImpl>> iterator = sourceWithCosts.iterator(); iterator.hasNext();) {
Abilities<ActivatedManaAbilityImpl> manaAbilities = iterator.next();
boolean used = availableMana.addManaWithCost(manaAbilities, game);
if (used) {
iterator.remove();
availableMana.removeDuplicated();
anAbilityWasUsed = true;
}
}
}
// remove duplicated variants (see ManaOptionsTest for info - when that rises)