foul-magics/Mage/src/main/java/mage/abilities/costs/mana/VariableManaCost.java
Oleg Agafonov bae3089abb Reworked cost adjuster logic for better support of X and cost modification effects:
Improves:
* refactor: split CostAdjuster logic in multiple parts - prepare X, prepare cost, increase cost, reduce cost;
* refactor: improved VariableManaCost to support min/max values, playable and AI calculations, test framework;
* refactor: improved EarlyTargetCost to support mana costs too (related to #13023);
* refactor: migrated some cards with CostAdjuster and X to EarlyTargetCost (Knollspine Invocation, etc - related to #13023);
* refactor: added shared code for "As an additional cost to cast this spell, discard X creature cards";
* refactor: added shared code for "X is the converted mana cost of the exiled card";
* tests: added dozens tests with cost adjusters;

Bug fixes:
* game: fixed that some cards with CostAdjuster ignore min/max limits for X (allow to choose any X, example: Scorched Earth, Open The Way);
* game: fixed that some cards ask to announce already defined X values (example: Bargaining Table);
* game: fixed that some cards with CostAdjuster do not support combo with other cost modification effects;
* game, gui: fixed missing game logs about predefined X values;
* game, gui: fixed wrong X icon for predefined X values;

Test framework:
* test framework: added X min/max check for wrong values;
* test framework: added X min/max info in miss X value announce;
* test framework: added check to find duplicated effect bugs (see assertNoDuplicatedEffects);

Cards:
* Open The Way - fixed that it allow to choose any X without limits (close #12810);
* Unbound Flourishing - improved combo support for activated abilities with predefined X mana costs like Bargaining Table;
2025-04-08 22:39:10 +04:00

210 lines
5.6 KiB
Java

package mage.abilities.costs.mana;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.MinMaxVariableCost;
import mage.abilities.costs.VariableCostType;
import mage.abilities.mana.ManaOptions;
import mage.constants.ColoredManaSymbol;
import mage.filter.FilterMana;
import mage.game.Game;
import mage.players.ManaPool;
import mage.util.CardUtil;
/**
* @author BetaSteward_at_googlemail.com, JayDi85
*/
public class VariableManaCost extends ManaCostImpl implements MinMaxVariableCost {
// variable mana cost usage on 2019-06-20:
// 1. as X value in spell/ability cast (announce X, set VariableManaCost as paid and add generic mana to pay instead)
// 2. as X value in direct pay (X already announced, cost is unpaid, need direct pay)
protected VariableCostType costType;
protected int xInstancesCount; // number of {X} instances in cost like {X} or {X}{X}
protected int xValue = 0; // final X value after announce and replace events
protected int xPay = 0; // final/total need pay after announce and replace events (example: {X}{X}, X=3, xPay = 6)
protected boolean wasAnnounced = false; // X was announced by player or auto-defined by CostAdjuster
protected FilterMana filter; // mana filter that can be used for that cost
protected int minX = 0;
protected int maxX = Integer.MAX_VALUE;
public VariableManaCost(VariableCostType costType) {
this(costType, 1);
}
public VariableManaCost(VariableCostType costType, int xInstancesCount) {
this.costType = costType;
this.xInstancesCount = xInstancesCount;
this.cost = new Mana();
options.add(new Mana());
}
protected VariableManaCost(final VariableManaCost manaCost) {
super(manaCost);
this.costType = manaCost.costType;
this.xInstancesCount = manaCost.xInstancesCount;
this.xValue = manaCost.xValue;
this.xPay = manaCost.xPay;
this.wasAnnounced = manaCost.wasAnnounced;
if (manaCost.filter != null) {
this.filter = manaCost.filter.copy();
}
this.minX = manaCost.minX;
this.maxX = manaCost.maxX;
}
@Override
public int manaValue() {
return 0;
}
@Override
public ManaOptions getOptions(boolean canPayLifeCost) {
ManaOptions res = new ManaOptions();
// limit mana options for better performance
CardUtil.distributeValues(10, getMinX(), getMaxX()).forEach(value -> {
res.add(Mana.GenericMana(value));
});
return res;
}
@Override
public void assignPayment(Game game, Ability ability, ManaPool pool, Cost costToPay) {
// X mana cost always pays as generic mana
this.assignGeneric(ability, game, pool, xPay, filter, costToPay);
}
@Override
public String getText() {
if (xInstancesCount > 1) {
StringBuilder symbol = new StringBuilder(xInstancesCount);
for (int i = 0; i < xInstancesCount; i++) {
symbol.append("{X}");
}
return symbol.toString();
} else {
return "{X}";
}
}
@Override
public boolean isPaid() {
if (!wasAnnounced) return false;
if (paid) return true;
return this.isColorlessPaid(xPay);
}
public boolean wasAnnounced() {
return this.wasAnnounced;
}
@Override
public VariableManaCost getUnpaid() {
return this;
}
@Override
public int getAmount() {
// must return X value
return this.xValue;
}
@Override
public void setAmount(int xValue, int xPay, boolean isPayed) {
// xPay is total pay value (X * instances)
this.xValue = xValue;
this.xPay = xPay;
if (isPayed) {
payment.setGeneric(xPay);
}
this.wasAnnounced = true;
}
@Override
public boolean testPay(Mana testMana) {
return true; // TODO: need rework to generic mana style?
}
@Override
public VariableManaCost copy() {
return new VariableManaCost(this);
}
public int getXInstancesCount() {
return this.xInstancesCount;
}
@Override
public int getMinX() {
return minX;
}
@Override
public void setMinX(int minX) {
this.minX = minX;
}
@Override
public int getMaxX() {
return maxX;
}
@Override
public void setMaxX(int maxX) {
this.maxX = maxX;
}
@Override
public boolean containsColor(ColoredManaSymbol coloredManaSymbol) {
return false;
}
@Override
public int announceXValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public Cost getFixedCostsFromAnnouncedValue(int xValue) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public String getActionText() {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public int getMinValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported.");
}
@Override
public int getMaxValue(Ability source, Game game) {
throw new UnsupportedOperationException("Not supported.");
}
public FilterMana getFilter() {
return filter;
}
public void setFilter(FilterMana filter) {
this.filter = filter;
}
@Override
public VariableCostType getCostType() {
return this.costType;
}
@Override
public void setCostType(VariableCostType costType) {
this.costType = costType;
}
}