mirror of
https://github.com/correl/mage.git
synced 2024-11-15 03:00:16 +00:00
* Monohybrid mana cost improves:
* fixed wrong manually pay by mana pool (it pays generic cost instead colored part of monohybrid); * fixed not working cost reduction effects (now monohybrid cost will be reduced correctly with some limitation, see #6130);
This commit is contained in:
parent
13ad86cb21
commit
b5acf64772
9 changed files with 589 additions and 78 deletions
|
@ -1389,10 +1389,12 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
public boolean playMana(Ability ability, ManaCost unpaid, String promptText, Game game) {
|
||||
payManaMode = true;
|
||||
currentUnpaidMana = unpaid;
|
||||
boolean result = playManaHandling(ability, unpaid, game);
|
||||
try {
|
||||
return playManaHandling(ability, unpaid, game);
|
||||
} finally {
|
||||
currentUnpaidMana = null;
|
||||
payManaMode = false;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean playManaHandling(Ability ability, ManaCost unpaid, final Game game) {
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package org.mage.test.cards.cost.modification;
|
||||
|
||||
import mage.abilities.costs.mana.ManaCost;
|
||||
import mage.abilities.costs.mana.ManaCosts;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.util.CardUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class CostReduceTest extends CardTestPlayerBase {
|
||||
|
||||
private void testReduce(String sourceCost, int reduceAmount, String needReducedCost) {
|
||||
// load mana by sctrict mode (e.g. for real mana usage in param)
|
||||
ManaCosts<ManaCost> source = new ManaCostsImpl<>();
|
||||
source.load(sourceCost, true);
|
||||
ManaCosts<ManaCost> need = new ManaCostsImpl<>();
|
||||
need.load(needReducedCost, true);
|
||||
ManaCosts<ManaCost> reduced = CardUtil.reduceCost(source, reduceAmount);
|
||||
|
||||
if (!reduced.getText().equals(need.getText())) {
|
||||
Assert.fail(sourceCost + " after reduction by " + reduceAmount + " must be " + need.getText() + ", but get " + reduced.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Monohybrid() {
|
||||
// extra test to ensure about mono hybrid test code
|
||||
ManaCosts<ManaCost> testCost = new ManaCostsImpl<>();
|
||||
testCost.load("{1/R}");
|
||||
Assert.assertEquals("normal mono hybrid always 2 generics", "{2/R}", testCost.getText());
|
||||
testCost = new ManaCostsImpl<>();
|
||||
testCost.load("{1/R}", true);
|
||||
Assert.assertEquals("test mono hybrid have variant generic", "{1/R}", testCost.getText());
|
||||
testReduce("{5/R}", 0, "{5/R}"); // ensure that mono hybrid in test mode
|
||||
|
||||
// DECREASE COST
|
||||
|
||||
// colorless is not reduce
|
||||
testReduce("{C}", 1, "{C}");
|
||||
testReduce("{C}{G}", 1, "{C}{G}");
|
||||
|
||||
// 0 generic, decrease cost by 1
|
||||
testReduce("", 1, "");
|
||||
testReduce("{R}", 1, "{R}");
|
||||
testReduce("{R}{G}", 1, "{R}{G}");
|
||||
|
||||
// 1 generic, decrease cost by 1
|
||||
testReduce("{1}", 1, "");
|
||||
testReduce("{R}{1}", 1, "{R}");
|
||||
testReduce("{1}{R}", 1, "{R}");
|
||||
testReduce("{1}{R}{G}", 1, "{R}{G}");
|
||||
testReduce("{R}{1}{G}", 1, "{R}{G}");
|
||||
testReduce("{R}{G}{1}", 1, "{R}{G}");
|
||||
|
||||
// 2 generics, decrease cost by 1
|
||||
testReduce("{2}", 1, "{1}");
|
||||
testReduce("{R}{2}", 1, "{R}{1}");
|
||||
testReduce("{2}{R}", 1, "{1}{R}");
|
||||
testReduce("{2}{R}{G}", 1, "{1}{R}{G}");
|
||||
testReduce("{R}{2}{G}", 1, "{R}{1}{G}");
|
||||
testReduce("{R}{G}{2}", 1, "{R}{G}{1}");
|
||||
|
||||
// 3 generics, decrease cost by 2
|
||||
testReduce("{2}", 2, "");
|
||||
testReduce("{3}", 2, "{1}");
|
||||
testReduce("{R}{3}", 2, "{R}{1}");
|
||||
testReduce("{3}{R}", 2, "{1}{R}");
|
||||
testReduce("{3}{R}{G}", 2, "{1}{R}{G}");
|
||||
testReduce("{R}{3}{G}", 2, "{R}{1}{G}");
|
||||
testReduce("{R}{G}{3}", 2, "{R}{G}{1}");
|
||||
|
||||
// INCREASE COST
|
||||
|
||||
// colorless, increase cost by 1
|
||||
testReduce("{C}", -1, "{C}{1}");
|
||||
testReduce("{C}{G}", -1, "{C}{G}{1}");
|
||||
|
||||
// 0 generic, increase cost by 1
|
||||
testReduce("", -1, "{1}");
|
||||
testReduce("{R}", -1, "{R}{1}");
|
||||
testReduce("{R}{G}", -1, "{R}{G}{1}");
|
||||
|
||||
// 1 generic, increase cost by 1
|
||||
testReduce("{1}", -1, "{2}");
|
||||
testReduce("{R}{1}", -1, "{R}{2}");
|
||||
testReduce("{1}{R}", -1, "{2}{R}");
|
||||
testReduce("{1}{R}{G}", -1, "{2}{R}{G}");
|
||||
testReduce("{R}{1}{G}", -1, "{R}{2}{G}");
|
||||
testReduce("{R}{G}{1}", -1, "{R}{G}{2}");
|
||||
|
||||
// 2 generics, increase cost by 1
|
||||
testReduce("{2}", -1, "{3}");
|
||||
testReduce("{R}{2}", -1, "{R}{3}");
|
||||
testReduce("{2}{R}", -1, "{3}{R}");
|
||||
testReduce("{2}{R}{G}", -1, "{3}{R}{G}");
|
||||
testReduce("{R}{2}{G}", -1, "{R}{3}{G}");
|
||||
testReduce("{R}{G}{2}", -1, "{R}{G}{3}");
|
||||
|
||||
// 3 generics, increase cost by 2
|
||||
testReduce("{3}", -2, "{5}");
|
||||
testReduce("{R}{3}", -2, "{R}{5}");
|
||||
testReduce("{3}{R}", -2, "{5}{R}");
|
||||
testReduce("{3}{R}{G}", -2, "{5}{R}{G}");
|
||||
testReduce("{R}{3}{G}", -2, "{R}{5}{G}");
|
||||
testReduce("{R}{G}{3}", -2, "{R}{G}{5}");
|
||||
|
||||
// HYBRID
|
||||
// from Reaper King
|
||||
// If an effect reduces the cost to cast a spell by an amount of generic mana, it applies to a monocolored hybrid
|
||||
// spell only if you’ve chosen a method of paying for it that includes generic mana.
|
||||
// (2008-05-01)
|
||||
|
||||
// MONO HYBRID
|
||||
// 1. Mono hybrid always 2 generic mana like 2/R
|
||||
// 2. Generic must have priority over hybrid
|
||||
|
||||
// no generic, normal amount
|
||||
// mono hybrid, decrease cost by 1
|
||||
testReduce("{2/R}", 1, "{1/R}");
|
||||
testReduce("{2/R}{2/G}", 1, "{1/R}{2/G}"); // TODO: add or/or reduction? (see https://github.com/magefree/mage/issues/6130 )
|
||||
// mono hybrid, increase cost by 1
|
||||
testReduce("{2/R}", -1, "{2/R}{1}");
|
||||
testReduce("{2/R}{2/G}", -1, "{2/R}{2/G}{1}");
|
||||
|
||||
// generic, normal amount
|
||||
// mono hybrid + 1 generic, decrease cost by 1
|
||||
testReduce("{2/R}{1}", 1, "{2/R}");
|
||||
testReduce("{2/R}{2/G}{1}", 1, "{2/R}{2/G}");
|
||||
// mono hybrid + 1 generic, increase cost by 1
|
||||
testReduce("{2/R}{1}", -1, "{2/R}{2}");
|
||||
testReduce("{2/R}{2/G}{1}", -1, "{2/R}{2/G}{2}");
|
||||
|
||||
// generic, too much generic
|
||||
// mono hybrid + 2 generic, decrease cost by 1
|
||||
testReduce("{2/R}{2}", 1, "{2/R}{1}");
|
||||
testReduce("{2/R}{2/G}{2}", 1, "{2/R}{2/G}{1}");
|
||||
// mono hybrid + 2 generic, increase cost by 1
|
||||
testReduce("{2/R}{2}", -1, "{2/R}{3}");
|
||||
testReduce("{2/R}{2/G}{2}", -1, "{2/R}{2/G}{3}");
|
||||
|
||||
// generic, too much reduce
|
||||
// mono hybrid + 1 generic, decrease cost by 2
|
||||
testReduce("{2/R}{1}", 2, "{1/R}");
|
||||
testReduce("{2/R}{2/G}{1}", 2, "{1/R}{2/G}"); // TODO: add or/or reduction? (see https://github.com/magefree/mage/issues/6130 )
|
||||
// mono hybrid + 1 generic, increase cost by 2
|
||||
testReduce("{2/R}{1}", -2, "{2/R}{3}");
|
||||
testReduce("{2/R}{2/G}{1}", -2, "{2/R}{2/G}{3}");
|
||||
|
||||
// EXTRA
|
||||
// TODO: add or/or reduction? (see https://github.com/magefree/mage/issues/6130 )
|
||||
testReduce("{2}{2/R}{2/G}", 3, "{1/R}{2/G}");
|
||||
testReduce("{2}{2/R}{2/G}", 4, "{0/R}{2/G}");
|
||||
testReduce("{2}{2/R}{2/G}", 5, "{0/R}{1/G}");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.mage.test.cards.cost.modification;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class MonohybridCostReduceTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_CostReduction_First() {
|
||||
// monohybrid supports with some limitation -- it reduce first hybrid cost, see https://github.com/magefree/mage/issues/6130
|
||||
|
||||
// Artifact spells you cast cost {1} less to cast.
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Etherium Sculptor");
|
||||
//
|
||||
// Reaper King
|
||||
addCard(Zone.HAND, playerA, "Reaper King"); // {2/W}{2/U}{2/B}{2/R}{2/G}
|
||||
// Add {C}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Blinkmoth Nexus", 2 * 5 - 1); // one less to test cost reduction
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reaper King");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package org.mage.test.cards.cost.modification;
|
||||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class MonohybridManaPayTest extends CardTestPlayerBase {
|
||||
|
||||
@Test
|
||||
public void test_PaySimpleMana_Manually() {
|
||||
// simulate user click on mana pool icons
|
||||
disableManaAutoPayment(playerA);
|
||||
|
||||
addCard(Zone.HAND, playerA, "Balduvian Bears");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
|
||||
// fill mana pool
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}");
|
||||
// cast spell
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Balduvian Bears");
|
||||
// unlock mana order in pool
|
||||
setChoice(playerA, "Green");
|
||||
setChoice(playerA, "Black");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Balduvian Bears", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PayMonohybridMana_ColorPart_SameUnlockOrder() {
|
||||
// simulate user click on mana pool icons
|
||||
// mono hybrid can be paid by color or {2}
|
||||
disableManaAutoPayment(playerA);
|
||||
|
||||
// Reaper King
|
||||
addCard(Zone.HAND, playerA, "Reaper King"); // {2/W}{2/U}{2/B}{2/R}{2/G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
|
||||
// fill mana pool
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
|
||||
// cast spell
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reaper King");
|
||||
// unlock mana order in pool (SAME ORDER AS PAYMENT - {W}{U}{B}{R}{G})
|
||||
setChoice(playerA, "White");
|
||||
setChoice(playerA, "Blue");
|
||||
setChoice(playerA, "Black");
|
||||
setChoice(playerA, "Red");
|
||||
setChoice(playerA, "Green");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reaper King", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PayMonohybridMana_ColorPart_AnyUnlockOrder() {
|
||||
// simulate user click on mana pool icons
|
||||
// mono hybrid can be paid by color or {2}
|
||||
disableManaAutoPayment(playerA);
|
||||
|
||||
// Reaper King
|
||||
addCard(Zone.HAND, playerA, "Reaper King"); // {2/W}{2/U}{2/B}{2/R}{2/G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
|
||||
// fill mana pool
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}");
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
|
||||
// cast spell
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reaper King");
|
||||
// unlock mana order in pool (DIFFERENT ORDER AS PAYMENT - {R}{G}{W}{U}{B})
|
||||
setChoice(playerA, "Red");
|
||||
setChoice(playerA, "Green");
|
||||
setChoice(playerA, "White");
|
||||
setChoice(playerA, "Blue");
|
||||
setChoice(playerA, "Black");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reaper King", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_PayMonohybridMana_GenericPart_AnyUnlockOrder() {
|
||||
// simulate user click on mana pool icons
|
||||
// mono hybrid must be payed as color first
|
||||
disableManaAutoPayment(playerA); // must pay by mana unlock command (like human clicks on mana pool icons)
|
||||
|
||||
// Reaper King
|
||||
addCard(Zone.HAND, playerA, "Reaper King"); // {2/W}{2/U}{2/B}{2/R}{2/G}
|
||||
//addCard(Zone.BATTLEFIELD, playerA, "Plains", 1); -- white mana paid by {2}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Island", 1 + 2 + 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
|
||||
//addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1); -- red mana paid by {2}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
|
||||
|
||||
// fill mana pool
|
||||
//activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {W}"); -- paid by {2}
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {U}", 5); // pay for {U}, 2/W and 2/R
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}");
|
||||
//activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {R}"); -- paid by {2}
|
||||
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {G}");
|
||||
// cast spell
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Reaper King");
|
||||
// unlock mana order in pool (ANY ORDER)
|
||||
setChoice(playerA, "Black");
|
||||
setChoice(playerA, "Green");
|
||||
setChoice(playerA, "Blue", 5); // unlocks and pays for U, 2/W and 2/R // TODO: add support to pay one by one, not all
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertPermanentCount(playerA, "Reaper King", 1);
|
||||
}
|
||||
}
|
|
@ -29,7 +29,15 @@ public interface ManaCosts<T extends ManaCost> extends List<T>, ManaCost {
|
|||
*/
|
||||
void setX(int xValue, int xPay);
|
||||
|
||||
void load(String mana);
|
||||
default void load(String mana) {
|
||||
load(mana, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mana mana in strinct like "{2}{R}" or "{2/W}"
|
||||
* @param extractMonoHybridGenericValue for tests only, extract generic mana value from mono hybrid string
|
||||
*/
|
||||
void load(String mana, boolean extractMonoHybridGenericValue);
|
||||
|
||||
List<String> getSymbols();
|
||||
|
||||
|
|
|
@ -17,9 +17,11 @@ import mage.game.Game;
|
|||
import mage.players.ManaPool;
|
||||
import mage.players.Player;
|
||||
import mage.target.Targets;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.ManaUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @param <T>
|
||||
|
@ -30,7 +32,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
protected final UUID id;
|
||||
protected String text = null;
|
||||
|
||||
private static Map<String, ManaCosts> costs = new HashMap<>();
|
||||
private static Map<String, ManaCosts> costsCache = new ConcurrentHashMap<>(); // must be thread safe, can't use nulls
|
||||
|
||||
public ManaCostsImpl() {
|
||||
this.id = UUID.randomUUID();
|
||||
|
@ -208,17 +210,13 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
/* find which color mana is in the cost and set it in the temp Phyrexian cost */
|
||||
if (phyrexianColors.isWhite() && mana.getWhite() > 0) {
|
||||
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.W);
|
||||
}
|
||||
else if (phyrexianColors.isBlue() && mana.getBlue() > 0) {
|
||||
} else if (phyrexianColors.isBlue() && mana.getBlue() > 0) {
|
||||
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.U);
|
||||
}
|
||||
else if (phyrexianColors.isBlack() && mana.getBlack() > 0) {
|
||||
} else if (phyrexianColors.isBlack() && mana.getBlack() > 0) {
|
||||
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.B);
|
||||
}
|
||||
else if (phyrexianColors.isRed() && mana.getRed() > 0) {
|
||||
} else if (phyrexianColors.isRed() && mana.getRed() > 0) {
|
||||
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.R);
|
||||
}
|
||||
else if (phyrexianColors.isGreen() && mana.getGreen() > 0) {
|
||||
} else if (phyrexianColors.isGreen() && mana.getGreen() > 0) {
|
||||
tempPhyrexianCost = new PhyrexianManaCost(ColoredManaSymbol.G);
|
||||
}
|
||||
|
||||
|
@ -297,18 +295,36 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
public void setPayment(Mana mana) {
|
||||
}
|
||||
|
||||
private boolean canPayColoredManaFromPool(ManaType needColor, ManaCost cost, ManaType canUseManaType, ManaPool pool) {
|
||||
if (canUseManaType == null || canUseManaType.equals(needColor)) {
|
||||
return cost.containsColor(CardUtil.manaTypeToColoredManaSymbol(needColor))
|
||||
&& (pool.getColoredAmount(needColor) > 0 || pool.ConditionalManaHasManaType(needColor));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
|
||||
boolean wasUnlockedManaType = (pool.getUnlockedManaType() != null);
|
||||
if (!pool.isAutoPayment() && !wasUnlockedManaType) {
|
||||
// if auto payment is inactive and no mana type was clicked manually - do nothing
|
||||
// try to assign mana from pool to payment in priority order (color first)
|
||||
|
||||
// auto-payment allows to use any mana type, if not then only unlocked can be used (mana type that were clicked in mana pool)
|
||||
ManaType canUseManaType;
|
||||
if (pool.isAutoPayment()) {
|
||||
canUseManaType = null; // can use any type
|
||||
} else {
|
||||
canUseManaType = pool.getUnlockedManaType();
|
||||
if (canUseManaType == null) {
|
||||
// auto payment is inactive and no mana type was clicked manually - do nothing
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ManaCosts referenceCosts = null;
|
||||
if (pool.isForcedToPay()) {
|
||||
referenceCosts = this.copy();
|
||||
}
|
||||
// attempt to pay colorless costs (not generic) mana costs first
|
||||
|
||||
// colorless costs (not generic)
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof ColorlessManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -317,7 +333,8 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
}
|
||||
//attempt to pay colored costs first
|
||||
|
||||
// colored
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof ColoredManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -327,6 +344,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
|
||||
// hybrid
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof HybridManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -336,15 +354,15 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
|
||||
// Mono Hybrid mana costs
|
||||
// First try only to pay colored mana or conditional colored mana with the pool
|
||||
// monohybrid
|
||||
// try to pay colored part
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof MonoHybridManaCost) {
|
||||
if (((cost.containsColor(ColoredManaSymbol.W)) && (pool.getWhite() > 0 || pool.ConditionalManaHasManaType(ManaType.WHITE)))
|
||||
|| ((cost.containsColor(ColoredManaSymbol.B)) && (pool.getBlack() > 0 || pool.ConditionalManaHasManaType(ManaType.BLACK)))
|
||||
|| ((cost.containsColor(ColoredManaSymbol.R)) && (pool.getRed() > 0 || pool.ConditionalManaHasManaType(ManaType.RED)))
|
||||
|| ((cost.containsColor(ColoredManaSymbol.G)) && (pool.getGreen() > 0 || pool.ConditionalManaHasManaType(ManaType.GREEN)))
|
||||
|| ((cost.containsColor(ColoredManaSymbol.U)) && (pool.getBlue() > 0) || pool.ConditionalManaHasManaType(ManaType.BLUE))) {
|
||||
if (canPayColoredManaFromPool(ManaType.WHITE, cost, canUseManaType, pool)
|
||||
|| canPayColoredManaFromPool(ManaType.BLACK, cost, canUseManaType, pool)
|
||||
|| canPayColoredManaFromPool(ManaType.RED, cost, canUseManaType, pool)
|
||||
|| canPayColoredManaFromPool(ManaType.GREEN, cost, canUseManaType, pool)
|
||||
|| canPayColoredManaFromPool(ManaType.BLUE, cost, canUseManaType, pool)) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
if (pool.isEmpty() && pool.getConditionalMana().isEmpty()) {
|
||||
return;
|
||||
|
@ -352,7 +370,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
}
|
||||
// if colored didn't fit pay colorless with the mana
|
||||
// try to pay generic part
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof MonoHybridManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -362,6 +380,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
|
||||
// snow
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof SnowManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -371,6 +390,7 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
|
||||
// generic
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof GenericManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
|
@ -380,14 +400,16 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
|
||||
// variable (generic)
|
||||
for (ManaCost cost : this) {
|
||||
if (!cost.isPaid() && cost instanceof VariableManaCost) {
|
||||
cost.assignPayment(game, ability, pool, costToPay);
|
||||
}
|
||||
}
|
||||
|
||||
// stop using mana of the clicked mana type
|
||||
pool.lockManaType();
|
||||
if (!wasUnlockedManaType) {
|
||||
if (canUseManaType == null) {
|
||||
handleForcedToPayOnlyForCurrentPayment(game, pool, referenceCosts);
|
||||
}
|
||||
}
|
||||
|
@ -420,10 +442,10 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void load(String mana) {
|
||||
public final void load(String mana, boolean extractMonoHybridGenericValue) {
|
||||
this.clear();
|
||||
if (costs.containsKey(mana)) {
|
||||
ManaCosts<ManaCost> savedCosts = costs.get(mana);
|
||||
if (!extractMonoHybridGenericValue && mana != null && costsCache.containsKey(mana)) {
|
||||
ManaCosts<ManaCost> savedCosts = costsCache.get(mana);
|
||||
for (ManaCost cost : savedCosts) {
|
||||
this.add(cost.copy());
|
||||
}
|
||||
|
@ -455,7 +477,14 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
this.add(new VariableManaCost(modifierForX));
|
||||
} //TODO: handle multiple {X} and/or {Y} symbols
|
||||
} else if (Character.isDigit(symbol.charAt(0))) {
|
||||
this.add(new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2))));
|
||||
MonoHybridManaCost cost;
|
||||
if (extractMonoHybridGenericValue) {
|
||||
// for tests only, no usage in real game
|
||||
cost = new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2)), Integer.parseInt(symbol.substring(0, 1)));
|
||||
} else {
|
||||
cost = new MonoHybridManaCost(ColoredManaSymbol.lookup(symbol.charAt(2)));
|
||||
}
|
||||
this.add(cost);
|
||||
} else if (symbol.contains("P")) {
|
||||
this.add(new PhyrexianManaCost(ColoredManaSymbol.lookup(symbol.charAt(0))));
|
||||
} else {
|
||||
|
@ -463,7 +492,9 @@ public class ManaCostsImpl<T extends ManaCost> extends ArrayList<T> implements M
|
|||
}
|
||||
}
|
||||
}
|
||||
costs.put(mana, this.copy());
|
||||
if (!extractMonoHybridGenericValue) {
|
||||
costsCache.put(mana, this.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,46 +12,53 @@ import java.util.List;
|
|||
|
||||
public class MonoHybridManaCost extends ManaCostImpl {
|
||||
|
||||
private final ColoredManaSymbol mana;
|
||||
private int mana2 = 2;
|
||||
private final ColoredManaSymbol manaColor;
|
||||
private int manaGeneric;
|
||||
|
||||
public MonoHybridManaCost(ColoredManaSymbol mana) {
|
||||
this.mana = mana;
|
||||
this.cost = new Mana(mana);
|
||||
this.cost.add(Mana.GenericMana(2));
|
||||
addColoredOption(mana);
|
||||
options.add(Mana.GenericMana(2));
|
||||
public MonoHybridManaCost(ColoredManaSymbol manaColor) {
|
||||
this(manaColor, 2);
|
||||
}
|
||||
|
||||
public MonoHybridManaCost(ColoredManaSymbol manaColor, int genericAmount) {
|
||||
this.manaColor = manaColor;
|
||||
this.manaGeneric = genericAmount;
|
||||
this.cost = new Mana(manaColor);
|
||||
this.cost.add(Mana.GenericMana(genericAmount));
|
||||
addColoredOption(manaColor);
|
||||
options.add(Mana.GenericMana(genericAmount));
|
||||
}
|
||||
|
||||
public MonoHybridManaCost(MonoHybridManaCost manaCost) {
|
||||
super(manaCost);
|
||||
this.mana = manaCost.mana;
|
||||
this.mana2 = manaCost.mana2;
|
||||
this.manaColor = manaCost.manaColor;
|
||||
this.manaGeneric = manaCost.manaGeneric;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int convertedManaCost() {
|
||||
return 2;
|
||||
// from wiki: A card with monocolored hybrid mana symbols in its mana cost has a converted mana cost equal to
|
||||
// the highest possible cost it could be played for. Its converted mana cost never changes.
|
||||
return Math.max(manaGeneric, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPaid() {
|
||||
if (paid || isColoredPaid(this.mana)) {
|
||||
if (paid || isColoredPaid(this.manaColor)) {
|
||||
return true;
|
||||
}
|
||||
return isColorlessPaid(this.mana2);
|
||||
return isColorlessPaid(this.manaGeneric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
|
||||
if (!assignColored(ability, game, pool, mana, costToPay)) {
|
||||
assignGeneric(ability, game, pool, mana2, null, costToPay);
|
||||
if (!assignColored(ability, game, pool, manaColor, costToPay)) {
|
||||
assignGeneric(ability, game, pool, manaGeneric, null, costToPay);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return "{2/" + mana.toString() + '}';
|
||||
return "{" + manaGeneric + "/" + manaColor.toString() + '}';
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,7 +68,7 @@ public class MonoHybridManaCost extends ManaCostImpl {
|
|||
|
||||
@Override
|
||||
public boolean testPay(Mana testMana) {
|
||||
switch (mana) {
|
||||
switch (manaColor) {
|
||||
case B:
|
||||
if (testMana.getBlack() > 0) {
|
||||
return true;
|
||||
|
@ -93,18 +100,18 @@ public class MonoHybridManaCost extends ManaCostImpl {
|
|||
|
||||
@Override
|
||||
public boolean containsColor(ColoredManaSymbol coloredManaSymbol) {
|
||||
return mana == coloredManaSymbol;
|
||||
return manaColor == coloredManaSymbol;
|
||||
}
|
||||
|
||||
public ColoredManaSymbol getManaColor() {
|
||||
return mana;
|
||||
return manaColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Mana> getManaOptions() {
|
||||
List<Mana> manaList = new ArrayList<>();
|
||||
manaList.add(new Mana(mana));
|
||||
manaList.add(Mana.GenericMana(2));
|
||||
manaList.add(new Mana(manaColor));
|
||||
manaList.add(Mana.GenericMana(manaGeneric));
|
||||
return manaList;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -456,4 +456,23 @@ public class ManaPool implements Serializable {
|
|||
manaItems.addAll(itemsCopy);
|
||||
}
|
||||
}
|
||||
|
||||
public int getColoredAmount(ManaType manaType) {
|
||||
switch (manaType) {
|
||||
case BLACK:
|
||||
return getBlack();
|
||||
case BLUE:
|
||||
return getBlue();
|
||||
case GREEN:
|
||||
return getGreen();
|
||||
case RED:
|
||||
return getRed();
|
||||
case WHITE:
|
||||
return getWhite();
|
||||
case GENERIC:
|
||||
case COLORLESS:
|
||||
default:
|
||||
throw new IllegalArgumentException("Wrong mana type " + manaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,13 +7,16 @@ import mage.abilities.SpellAbility;
|
|||
import mage.abilities.costs.VariableCost;
|
||||
import mage.abilities.costs.mana.*;
|
||||
import mage.cards.Card;
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import mage.constants.EmptyNames;
|
||||
import mage.constants.ManaType;
|
||||
import mage.filter.Filter;
|
||||
import mage.game.CardState;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.game.permanent.token.Token;
|
||||
import mage.util.functions.CopyTokenFunction;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
@ -90,34 +93,118 @@ public final class CardUtil {
|
|||
}
|
||||
|
||||
private static ManaCosts<ManaCost> adjustCost(ManaCosts<ManaCost> manaCosts, int reduceCount) {
|
||||
int restToReduce = reduceCount;
|
||||
ManaCosts<ManaCost> adjustedCost = new ManaCostsImpl<>();
|
||||
boolean updated = false;
|
||||
|
||||
// nothing to change
|
||||
if (reduceCount == 0) {
|
||||
for (ManaCost manaCost : manaCosts) {
|
||||
adjustedCost.add(manaCost.copy());
|
||||
}
|
||||
return adjustedCost;
|
||||
}
|
||||
|
||||
// remove or save cost
|
||||
if (reduceCount > 0) {
|
||||
int restToReduce = reduceCount;
|
||||
|
||||
// first run - priority single option costs (generic)
|
||||
for (ManaCost manaCost : manaCosts) {
|
||||
if (manaCost instanceof SnowManaCost) {
|
||||
adjustedCost.add(manaCost);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (manaCost.getOptions().size() == 0) {
|
||||
adjustedCost.add(manaCost);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore monohybrid and other multi-option mana (for potential support)
|
||||
if (manaCost.getOptions().size() > 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// generic mana reduce
|
||||
Mana mana = manaCost.getOptions().get(0);
|
||||
int colorless = mana != null ? mana.getGeneric() : 0;
|
||||
if (restToReduce != 0 && colorless > 0) {
|
||||
if ((colorless - restToReduce) > 0) {
|
||||
// partly reduce
|
||||
int newColorless = colorless - restToReduce;
|
||||
adjustedCost.add(new GenericManaCost(newColorless));
|
||||
restToReduce = 0;
|
||||
} else {
|
||||
// full reduce - ignore cost
|
||||
restToReduce -= colorless;
|
||||
}
|
||||
updated = true;
|
||||
} else {
|
||||
adjustedCost.add(manaCost);
|
||||
// nothing to reduce
|
||||
adjustedCost.add(manaCost.copy());
|
||||
}
|
||||
}
|
||||
|
||||
// for increasing spell cost effects
|
||||
if (!updated && reduceCount < 0) {
|
||||
// second run - priority for multi option costs (monohybrid)
|
||||
//
|
||||
// from Reaper King:
|
||||
// If an effect reduces the cost to cast a spell by an amount of generic mana, it applies to a monocolored hybrid
|
||||
// spell only if you’ve chosen a method of paying for it that includes generic mana.
|
||||
// (2008-05-01)
|
||||
// TODO: xmage don't use announce for hybrid mana (instead it uses auto-pay), so that's workaround uses first hybrid to reduce (see https://github.com/magefree/mage/issues/6130 )
|
||||
for (ManaCost manaCost : manaCosts) {
|
||||
if (manaCost.getOptions().size() <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (manaCost instanceof MonoHybridManaCost) {
|
||||
// current implemention supports only 1 hybrid cost per object
|
||||
MonoHybridManaCost mono = (MonoHybridManaCost) manaCost;
|
||||
int colorless = mono.getOptions().get(1).getGeneric();
|
||||
if (restToReduce != 0 && colorless > 0) {
|
||||
if ((colorless - restToReduce) > 0) {
|
||||
// partly reduce
|
||||
int newColorless = colorless - restToReduce;
|
||||
adjustedCost.add(new MonoHybridManaCost(mono.getManaColor(), newColorless));
|
||||
restToReduce = 0;
|
||||
} else {
|
||||
// full reduce
|
||||
adjustedCost.add(new MonoHybridManaCost(mono.getManaColor(), 0));
|
||||
restToReduce -= colorless;
|
||||
}
|
||||
} else {
|
||||
// nothing to reduce
|
||||
adjustedCost.add(mono.copy());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// unsupported multi-option mana types for reduce (like HybridManaCost)
|
||||
adjustedCost.add(manaCost.copy());
|
||||
}
|
||||
}
|
||||
|
||||
// increase cost (add to first generic or add new)
|
||||
if (reduceCount < 0) {
|
||||
Assert.assertEquals("must be empty", 0, adjustedCost.size());
|
||||
boolean added = false;
|
||||
for (ManaCost manaCost : manaCosts) {
|
||||
if (reduceCount != 0 && manaCost instanceof GenericManaCost) {
|
||||
// add increase cost to existing generic
|
||||
GenericManaCost gen = (GenericManaCost) manaCost;
|
||||
adjustedCost.add(new GenericManaCost(gen.getOptions().get(0).getGeneric() + -reduceCount));
|
||||
reduceCount = 0;
|
||||
added = true;
|
||||
} else {
|
||||
// non-generic mana
|
||||
adjustedCost.add(manaCost.copy());
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
// add increase cost as new
|
||||
adjustedCost.add(new GenericManaCost(-reduceCount));
|
||||
}
|
||||
}
|
||||
|
||||
// cost modifying effects requiring snow mana unnecessarily (fixes #6000)
|
||||
Filter filter = manaCosts.stream()
|
||||
.filter(manaCost -> !(manaCost instanceof SnowManaCost))
|
||||
.map(ManaCost::getSourceFilter)
|
||||
|
@ -642,4 +729,23 @@ public final class CardUtil {
|
|||
res.addAll(mana2);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ColoredManaSymbol manaTypeToColoredManaSymbol(ManaType manaType) {
|
||||
switch (manaType) {
|
||||
case BLACK:
|
||||
return ColoredManaSymbol.B;
|
||||
case BLUE:
|
||||
return ColoredManaSymbol.U;
|
||||
case GREEN:
|
||||
return ColoredManaSymbol.G;
|
||||
case RED:
|
||||
return ColoredManaSymbol.R;
|
||||
case WHITE:
|
||||
return ColoredManaSymbol.W;
|
||||
case GENERIC:
|
||||
case COLORLESS:
|
||||
default:
|
||||
throw new IllegalArgumentException("Wrong mana type " + manaType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue