mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-19 20:28:00 +00:00
Make it so X mana costs are paid using a pre-mana payment announcement
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -16734,7 +16734,6 @@ forge-gui/src/main/java/forge/match/input/InputPassPriority.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputPayMana.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputPayManaOfCostPayment.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputPayManaSimple.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputPayManaX.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputPlaybackControl.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputProliferate.java -text
|
||||
forge-gui/src/main/java/forge/match/input/InputProxy.java -text
|
||||
|
||||
@@ -31,6 +31,8 @@ import forge.util.maps.MapToAmount;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* ManaCostBeingPaid class.
|
||||
@@ -215,6 +217,20 @@ public class ManaCostBeingPaid {
|
||||
return unpaidShards.isEmpty();
|
||||
}
|
||||
|
||||
public final void setXManaCostPaid(final int xPaid, final String xColor) {
|
||||
int xCost = xPaid * cntX;
|
||||
cntX = 0;
|
||||
|
||||
ManaCostShard increaseShard;
|
||||
if (StringUtils.isEmpty(xColor)) {
|
||||
increaseShard = ManaCostShard.COLORLESS;
|
||||
}
|
||||
else {
|
||||
increaseShard = ManaCostShard.valueOf(MagicColor.fromName(xColor));
|
||||
}
|
||||
unpaidShards.add(increaseShard, xCost);
|
||||
}
|
||||
|
||||
public final void increaseColorlessMana(final int manaToAdd) {
|
||||
increaseShard(ManaCostShard.COLORLESS, manaToAdd);
|
||||
}
|
||||
|
||||
@@ -13,16 +13,15 @@ There will no longer be a black rectangle for "Commander effect" in the command
|
||||
The details previously available by hovering over that rectangle will now appear when hovering over the commander itself.
|
||||
The dialog for the commander replacement effect will now display the commander's name.
|
||||
|
||||
|
||||
- Momir Basic variant type -
|
||||
Momir Basic is now available as its own variant option on the Constructed screen
|
||||
For this format. each player will automatically be given a deck with 12 of each basic land and the Momir Vig avatar
|
||||
|
||||
|
||||
- Auto-pay support for X mana costs -
|
||||
When prompted to choose a value for X when paying a mana cost, the left button will now as display "Auto" until you manually click a mana source
|
||||
If you press Auto, you'll be prompted to pick a value for X, with it defaulting to the maximum value you can afford.
|
||||
If you press OK on that prompt, Forge will use AI logic to automatically pay whatever value for X you chose.
|
||||
|
||||
- Choose value for X mana costs -
|
||||
Now, when playing spells/abilities with X in its mana cost, you will now be prompted for a value for X prior to mana payment, ensuring the final mana cost is calculated properly from cost adjustment effects and allowing using the "Auto" button to pay the entire cost.
|
||||
This also applies to spells with Replicate and Multikicker to allow picking the Replicate or Multikicker amount prior to paying the final mana cost.
|
||||
|
||||
- Auto-targeting support -
|
||||
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
package forge.match.input;
|
||||
|
||||
import forge.ai.ComputerUtilMana;
|
||||
import forge.card.ColorSet;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostParser;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.mana.Mana;
|
||||
import forge.game.mana.ManaCostBeingPaid;
|
||||
import forge.game.spellability.SpellAbility;
|
||||
import forge.util.Evaluator;
|
||||
import forge.util.ITriggerEvent;
|
||||
import forge.util.ThreadUtil;
|
||||
import forge.util.gui.SGuiChoose;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class InputPayManaX extends InputPayMana {
|
||||
private static final long serialVersionUID = -6900234444347364050L;
|
||||
private int xPaid = 0;
|
||||
private ArrayList<Mana> xPaidByColor = new ArrayList<>();
|
||||
private byte colorsPaid;
|
||||
private final String xCostStr;
|
||||
private final ManaCost manaCostPerX;
|
||||
private final boolean xCanBe0;
|
||||
private boolean canceled = false;
|
||||
private Integer max;
|
||||
|
||||
public InputPayManaX(final SpellAbility sa0, final int amountX, final boolean xCanBe0) {
|
||||
super(sa0, sa0.getActivatingPlayer());
|
||||
xPaid = 0;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < amountX; i++) {
|
||||
builder.append("{X}");
|
||||
}
|
||||
xCostStr = builder.toString();
|
||||
|
||||
if (saPaidFor.hasParam("XColor")) {
|
||||
String xColor = saPaidFor.getParam("XColor");
|
||||
if (amountX == 1) {
|
||||
manaCostPerX = new ManaCost(new ManaCostParser(xColor));
|
||||
}
|
||||
else {
|
||||
List<String> list = new ArrayList<String>(amountX);
|
||||
for (int i = 0; i < amountX; i++) {
|
||||
list.add(xColor);
|
||||
}
|
||||
manaCostPerX = new ManaCost(new ManaCostParser(StringUtils.join(list, ' ')));
|
||||
}
|
||||
}
|
||||
else {
|
||||
manaCostPerX = ManaCost.get(amountX);
|
||||
}
|
||||
manaCost = new ManaCostBeingPaid(manaCostPerX);
|
||||
|
||||
this.xCanBe0 = xCanBe0;
|
||||
colorsPaid = saPaidFor.getHostCard().getColorsPaid(); // for effects like sunburst
|
||||
canPayManaCost = true; //flag as true always since it doesn't need to be calculated using AI logic
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.control.input.InputPayManaBase#isPaid()
|
||||
*/
|
||||
@Override
|
||||
public boolean isPaid() {
|
||||
//return !( xPaid == 0 && !costMana.canXbe0() || this.colorX.equals("") && !this.manaCost.toString().equals(strX) );
|
||||
// return !( xPaid == 0 && !costMana.canXbe0()) && !(this.colorX.equals("") && !this.manaCost.toString().equals(strX));
|
||||
return !canceled && (xPaid > 0 || xCanBe0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportAutoPay() {
|
||||
return xPaid == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage() {
|
||||
if (isFinished()) { return; }
|
||||
|
||||
updateMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getMessage() {
|
||||
StringBuilder msg = new StringBuilder("Pay " + xCostStr + ". X=" + xPaid + ".");
|
||||
if (xPaid > 0) {
|
||||
// Enable just cancel is full X value hasn't been paid for multiple X values
|
||||
// or X is 0, and x can't be 0
|
||||
ButtonUtil.update(isPaid(), true, true);
|
||||
}
|
||||
if (!xCanBe0) {
|
||||
msg.append("\nX Can't be 0.");
|
||||
}
|
||||
return msg.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onCardSelected(final Card card, final ITriggerEvent triggerEvent) {
|
||||
// don't allow here the cards that produce only wrong colors
|
||||
return activateManaAbility(card, this.manaCost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onManaAbilityPaid() {
|
||||
if (this.manaCost.isPaid()) {
|
||||
this.colorsPaid |= manaCost.getColorsPaid();
|
||||
this.manaCost = new ManaCostBeingPaid(manaCostPerX);
|
||||
this.xPaid++;
|
||||
this.xPaidByColor.add(saPaidFor.getPayingMana().get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onCancel() {
|
||||
// If you hit cancel, isPaid needs to return false
|
||||
this.canceled = true;
|
||||
this.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void onOk() {
|
||||
if (supportAutoPay()) {
|
||||
ThreadUtil.invokeInGameThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int min = xCanBe0 ? 0 : 1;
|
||||
if (max == null) {
|
||||
//use AI utility to determine maximum possible value for X if that hasn't been determined yet
|
||||
Evaluator<Integer> proc = new Evaluator<Integer>() {
|
||||
@Override
|
||||
public Integer evaluate() {
|
||||
ManaCostBeingPaid cost = new ManaCostBeingPaid(manaCostPerX);
|
||||
for (int i = 1; i < 100; i++) {
|
||||
if (!ComputerUtilMana.canPayManaCost(cost, saPaidFor, player)) {
|
||||
return i - 1;
|
||||
}
|
||||
cost.addManaCost(manaCostPerX);
|
||||
}
|
||||
return 99;
|
||||
}
|
||||
};
|
||||
runAsAi(proc);
|
||||
max = proc.getResult();
|
||||
}
|
||||
Integer value = SGuiChoose.getInteger("Choose a value for X", min, max, true);
|
||||
if (value != null) {
|
||||
xPaid = value;
|
||||
saPaidFor.getHostCard().setXManaCostPaid(xPaid);
|
||||
if (xPaid > 0) {
|
||||
final ManaCostBeingPaid cost = new ManaCostBeingPaid(manaCostPerX);
|
||||
for (int i = 1; i < xPaid; i++) {
|
||||
cost.addManaCost(manaCostPerX);
|
||||
}
|
||||
runAsAi(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ComputerUtilMana.payManaCost(cost, saPaidFor, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
stop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
done();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
final Card card = saPaidFor.getHostCard();
|
||||
card.setXManaCostPaid(this.xPaid);
|
||||
card.setXManaCostPaidByColor(this.xPaidByColor);
|
||||
card.setColorsPaid(this.colorsPaid);
|
||||
card.setSunburstValue(ColorSet.fromMask(this.colorsPaid).countColors());
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,7 @@ import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.card.MagicColor;
|
||||
import forge.card.mana.ManaCost;
|
||||
import forge.card.mana.ManaCostShard;
|
||||
import forge.game.Game;
|
||||
import forge.game.GameActionUtil;
|
||||
import forge.game.GameLogEntryType;
|
||||
@@ -27,7 +25,6 @@ import forge.game.zone.ZoneType;
|
||||
import forge.match.input.InputPayMana;
|
||||
import forge.match.input.InputPayManaOfCostPayment;
|
||||
import forge.match.input.InputPayManaSimple;
|
||||
import forge.match.input.InputPayManaX;
|
||||
import forge.match.input.InputSelectCardsFromList;
|
||||
import forge.util.Lang;
|
||||
import forge.util.gui.SGuiChoose;
|
||||
@@ -42,7 +39,6 @@ import java.util.Map;
|
||||
public class HumanPlay {
|
||||
|
||||
public HumanPlay() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,14 +690,12 @@ public class HumanPlay {
|
||||
final Card source = ability.getHostCard();
|
||||
ManaCostBeingPaid toPay = new ManaCostBeingPaid(realCost, mc.getRestiction());
|
||||
|
||||
boolean xWasBilled = false;
|
||||
String xInCard = source.getSVar("X");
|
||||
if (mc.getAmountOfX() > 0 && !"Count$xPaid".equals(xInCard)) { // announce X will overwrite whatever was in card script
|
||||
// this currently only works for things about Targeted object
|
||||
int xCost = AbilityUtils.calculateAmount(source, "X", ability) * mc.getAmountOfX();
|
||||
byte xColor = MagicColor.fromName(ability.hasParam("XColor") ? ability.getParam("XColor") : "1");
|
||||
toPay.increaseShard(ManaCostShard.valueOf(xColor), xCost);
|
||||
xWasBilled = true;
|
||||
int xPaid = AbilityUtils.calculateAmount(source, "X", ability);
|
||||
toPay.setXManaCostPaid(xPaid, ability.getParam("XColor"));
|
||||
source.setXManaCostPaid(xPaid);
|
||||
}
|
||||
|
||||
int timesMultikicked = source.getKickerMagnitude();
|
||||
@@ -740,19 +734,6 @@ public class HumanPlay {
|
||||
source.setColorsPaid(toPay.getColorsPaid());
|
||||
source.setSunburstValue(toPay.getSunburst());
|
||||
}
|
||||
if (mc.getAmountOfX() > 0) {
|
||||
if (!ability.isAnnouncing("X") && !xWasBilled) {
|
||||
source.setXManaCostPaid(0);
|
||||
inpPayment = new InputPayManaX(ability, mc.getAmountOfX(), mc.canXbe0());
|
||||
inpPayment.showAndWait();
|
||||
if (!inpPayment.isPaid()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
int x = AbilityUtils.calculateAmount(source, "X", ability);
|
||||
source.setXManaCostPaid(x);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle convoke and offerings
|
||||
if (ability.isOffering() && ability.getSacrificedAsOffering() != null) {
|
||||
|
||||
@@ -196,19 +196,21 @@ public class HumanPlaySpellAbility {
|
||||
private boolean announceValuesLikeX() {
|
||||
if (ability.isCopied()) { return true; } //don't re-announce for spell copies
|
||||
|
||||
boolean needX = true;
|
||||
boolean allowZero = !ability.hasParam("XCantBe0");
|
||||
CostPartMana manaCost = ability.getPayCosts().getCostMana();
|
||||
PlayerController controller = ability.getActivatingPlayer().getController();
|
||||
Card card = ability.getHostCard();
|
||||
|
||||
// Announcing Requirements like Choosing X or Multikicker
|
||||
// SA Params as comma delimited list
|
||||
String announce = ability.getParam("Announce");
|
||||
if (announce != null) {
|
||||
PlayerController controller = ability.getActivatingPlayer().getController();
|
||||
Card card = ability.getHostCard();
|
||||
boolean allowZero = !ability.hasParam("XCantBe0");
|
||||
CostPartMana manaCost = ability.getPayCosts().getCostMana();
|
||||
|
||||
for (String aVar : announce.split(",")) {
|
||||
String varName = aVar.trim();
|
||||
|
||||
boolean isX = "X".equalsIgnoreCase(varName);
|
||||
if (isX) { needX = false; }
|
||||
|
||||
Integer value = controller.announceRequirements(ability, varName, allowZero && (!isX || manaCost == null || manaCost.canXbe0()));
|
||||
if (value == null) {
|
||||
@@ -224,6 +226,16 @@ public class HumanPlaySpellAbility {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needX && manaCost != null && manaCost.getAmountOfX() > 0) {
|
||||
Integer value = controller.announceRequirements(ability, "X", allowZero && manaCost.canXbe0());
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ability.setSVar("X", value.toString());
|
||||
card.setSVar("X", value.toString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user