refactor: combined announceX methods in one, improved X message and selection for AI (part of #10330)

This commit is contained in:
Oleg Agafonov 2025-05-16 19:34:57 +04:00
parent 6af198836b
commit 66db821437
37 changed files with 90 additions and 158 deletions

View file

@ -2,7 +2,6 @@ package mage.player.ai;
import mage.MageObject; import mage.MageObject;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
import mage.cards.Card; import mage.cards.Card;
import mage.cards.Cards; import mage.cards.Cards;
@ -250,20 +249,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 {
} }
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
if (isUnderMe(game)) { if (isUnderMe(game)) {
return super.announceXMana(min, max, message, game, ability); return super.announceX(min, max, message, game, source, isManaPay);
} else { } else {
return getControllingPlayer(game).announceXMana(min, max, message, game, ability); return getControllingPlayer(game).announceX(min, max, message, game, source, isManaPay);
}
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
if (isUnderMe(game)) {
return super.announceXCost(min, max, message, game, ability, variableCost);
} else {
return getControllingPlayer(game).announceXCost(min, max, message, game, ability, variableCost);
} }
} }

View file

@ -5,7 +5,6 @@ import mage.ConditionalMana;
import mage.MageObject; import mage.MageObject;
import mage.Mana; import mage.Mana;
import mage.abilities.*; import mage.abilities.*;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.*; import mage.abilities.costs.mana.*;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DamageTargetEffect; import mage.abilities.effects.common.DamageTargetEffect;
@ -2039,42 +2038,40 @@ public class ComputerPlayer extends PlayerImpl {
} }
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
// current logic - use max possible mana // fast calc on nothing to choose
if (min >= max) {
return min;
}
// TODO: add good/bad effects support // TODO: add good/bad effects support
// TODO: add simple game simulations like declare blocker? // TODO: add simple game simulations like declare blocker (need to find only workable payment)?
// TODO: remove random logic or make it more stable (e.g. use same value in same game cycle)
int numAvailable = getAvailableManaProducers(game).size() - ability.getManaCosts().manaValue(); // protection from too big values
if (numAvailable < 0) { int realMin = min;
numAvailable = 0; int realMax = max;
if (max == Integer.MAX_VALUE) {
realMax = Math.max(realMin, 10); // AI don't need huge values for X, cause can't use infinite combos
}
int xValue;
if (isManaPay) {
// as X mana payment - due available mana
xValue = Math.max(0, getAvailableManaProducers(game).size() - source.getManaCostsToPay().getUnpaid().manaValue());
} else { } else {
if (numAvailable < min) { // as X actions
numAvailable = min; xValue = RandomUtil.nextInt(realMax + 1);
}
if (numAvailable > max) {
numAvailable = max;
}
} }
return numAvailable;
}
@Override if (xValue > realMax) {
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) { xValue = realMax;
// current logic - use random non-zero value
// TODO: add good/bad effects support
// TODO: remove random logic
int value = RandomUtil.nextInt(CardUtil.overflowInc(max, 1));
if (value < min) {
value = min;
} }
if (value < max) { if (xValue < realMin) {
// do not use zero values xValue = realMin;
value++;
} }
return value;
return xValue;
} }
@Override @Override

View file

@ -1687,64 +1687,39 @@ public class HumanPlayer extends PlayerImpl {
} }
/** /**
* Gets the amount of mana the player want to spent for a x spell * Gets the amount of mana the player want to spend for an x spell
*
* @param min
* @param max
* @param message
* @param ability
* @param game
* @return
*/ */
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
if (!canCallFeedback(game)) { if (!canCallFeedback(game)) {
return min; return min;
} }
int xValue = 0; // fast calc on nothing to choose
while (canRespond()) { if (min >= max) {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, ability), min, max);
}
waitForResponse(game);
if (response.getInteger() != null) {
break;
}
// TODO: add response verify here
}
if (response.getInteger() != null) {
xValue = response.getInteger();
}
return xValue;
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
if (!canCallFeedback(game)) {
return min; return min;
} }
int xValue = 0; int xValue = min;
while (canRespond()) { while (canRespond()) {
prepareForResponse(game); prepareForResponse(game);
if (!isExecutingMacro()) { if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message, min, max); game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max);
} }
waitForResponse(game); waitForResponse(game);
if (response.getInteger() != null) { if (response.getInteger() == null) {
break; continue;
} }
xValue = response.getInteger();
if (xValue < min || xValue > max) {
continue;
}
break;
} }
if (response.getInteger() != null) {
xValue = response.getInteger();
}
return xValue; return xValue;
} }

View file

@ -163,7 +163,7 @@ class AzorTheLawbringerAttacksEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{U}"); ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{U}");
if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, you gain X life and draw X cards.", source, game)) { if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, you gain X life and draw X cards.", source, game)) {
int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (gain life and draw cards)", game, source,true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
controller.resetStoredBookmark(game); // otherwise you can undo the payment controller.resetStoredBookmark(game); // otherwise you can undo the payment

View file

@ -86,7 +86,7 @@ class DepalaPilotExemplarEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller != null) {
ManaCosts<ManaCost> cost = new ManaCostsImpl<>("{X}"); ManaCosts<ManaCost> cost = new ManaCostsImpl<>("{X}");
int xValue = controller.announceXMana(0, Integer.MAX_VALUE, "Choose the amount of mana to pay", game, source); int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to reveal)", game, source, true);
cost.add(new GenericManaCost(xValue)); cost.add(new GenericManaCost(xValue));
if (cost.pay(source, game, source, source.getControllerId(), false) && xValue > 0) { if (cost.pay(source, game, source, source.getControllerId(), false) && xValue > 0) {
new RevealLibraryPutIntoHandEffect(xValue, filter, Zone.LIBRARY, false).apply(game, source); new RevealLibraryPutIntoHandEffect(xValue, filter, Zone.LIBRARY, false).apply(game, source);

View file

@ -80,7 +80,7 @@ class ElendaAndAzorEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{B}"); ManaCosts cost = new ManaCostsImpl<>("{X}{W}{U}{B}");
if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, draw X cards.", source, game)) { if (controller.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, draw X cards.", source, game)) {
int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to draw)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
controller.resetStoredBookmark(game); // otherwise you can undo the payment controller.resetStoredBookmark(game); // otherwise you can undo the payment

View file

@ -68,7 +68,7 @@ class FlameblastDragonEffect extends OneShotEffect {
ManaCosts cost = new ManaCostsImpl<>("{X}{R}"); ManaCosts cost = new ManaCostsImpl<>("{X}{R}");
if (player != null) { if (player != null) {
if (player.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, Flameblast Dragon deals X damage to any target", source, game)) { if (player.chooseUse(Outcome.Damage, "Pay " + cost.getText() + "? If you do, Flameblast Dragon deals X damage to any target", source, game)) {
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to damage)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
Permanent permanent = game.getPermanent(source.getFirstTarget()); Permanent permanent = game.getPermanent(source.getFirstTarget());

View file

@ -78,7 +78,7 @@ class HaloForagerPayEffect extends OneShotEffect {
if (player == null || !player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) { if (player == null || !player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) {
return false; return false;
} }
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to free cast)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -70,7 +70,7 @@ class HeroOfLeinaTowerEffect extends OneShotEffect {
Player you = game.getPlayer(source.getControllerId()); Player you = game.getPlayer(source.getControllerId());
ManaCosts cost = new ManaCostsImpl<>("{X}"); ManaCosts cost = new ManaCostsImpl<>("{X}");
if (you != null && you.chooseUse(Outcome.BoostCreature, "Do you want to to pay {X}?", source, game)) { if (you != null && you.chooseUse(Outcome.BoostCreature, "Do you want to to pay {X}?", source, game)) {
int costX = you.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = you.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add +1/+1 counters)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
Permanent sourcePermanent = game.getPermanent(source.getSourceId()); Permanent sourcePermanent = game.getPermanent(source.getSourceId());

View file

@ -88,7 +88,7 @@ class IncineratorOfTheGuiltyEffect extends OneShotEffect {
return false; return false;
} }
int xValue = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for X", game, source); int xValue = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (collect evidence)", game, source, false);
CollectEvidenceCost cost = new CollectEvidenceCost(xValue); CollectEvidenceCost cost = new CollectEvidenceCost(xValue);
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -85,7 +85,7 @@ class IsarethTheAwakenerCreateReflexiveTriggerEffect extends OneShotEffect {
|| !player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { || !player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) {
return false; return false;
} }
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to return due mana value)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -111,10 +111,8 @@ class LeylineTyrantDamageEffect extends OneShotEffect {
if (player == null) { if (player == null) {
return false; return false;
} }
int costX = player.announceXMana( // TODO: add some AI hints by min/max values
0, Integer.MAX_VALUE, int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source, true);
"Announce the value for {X}", game, source
);
String manaString; String manaString;
if (costX == 0) { if (costX == 0) {
manaString = "{0}"; manaString = "{0}";

View file

@ -81,7 +81,8 @@ class NumaJoragaChieftainEffect extends OneShotEffect {
if (!player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { if (!player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) {
return false; return false;
} }
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); // TODO: add some AI hints by min/max values
int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to distribute counters)", game, source,true);
cost.add(new GenericManaCost(2 * costX)); cost.add(new GenericManaCost(2 * costX));
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -80,7 +80,7 @@ class PowerLeakEffect extends OneShotEffect {
String message = "Pay {X} to prevent X damage from " + permanent.getLogName() + "?"; String message = "Pay {X} to prevent X damage from " + permanent.getLogName() + "?";
int xValue = 0; int xValue = 0;
if (player.chooseUse(Outcome.Neutral, message, source, game)) { if (player.chooseUse(Outcome.Neutral, message, source, game)) {
xValue = player.announceXMana(0, Integer.MAX_VALUE, "Choose the amount of mana to pay", game, source); xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to prevent damage)", game, source, true);
cost.add(new GenericManaCost(xValue)); cost.add(new GenericManaCost(xValue));
if (cost.pay(source, game, source, player.getId(), false, null)) { if (cost.pay(source, game, source, player.getId(), false, null)) {
game.informPlayers(player.getLogName() + " paid {" + xValue + "} for " + permanent.getLogName()); game.informPlayers(player.getLogName() + " paid {" + xValue + "} for " + permanent.getLogName());

View file

@ -116,7 +116,7 @@ class RemnantOfTheRisingStarEffect extends OneShotEffect {
)) { )) {
return false; return false;
} }
int xValue = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int xValue = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add counters)", game, source, true);
cost.add(new GenericManaCost(xValue)); cost.add(new GenericManaCost(xValue));
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -19,7 +19,6 @@ import mage.constants.CardType;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.constants.TargetController; import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterPermanent; import mage.filter.FilterPermanent;
import mage.filter.predicate.Predicates; import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.ColorPredicate; import mage.filter.predicate.mageobject.ColorPredicate;
@ -87,7 +86,7 @@ class RiseOfTheHobgoblinsEffect extends OneShotEffect {
Player you = game.getPlayer(source.getControllerId()); Player you = game.getPlayer(source.getControllerId());
ManaCosts<ManaCost> cost = new ManaCostsImpl<>("{X}"); ManaCosts<ManaCost> cost = new ManaCostsImpl<>("{X}");
if (you != null && you.chooseUse(Outcome.Neutral, "Do you want to to pay {X}?", source, game)) { if (you != null && you.chooseUse(Outcome.Neutral, "Do you want to to pay {X}?", source, game)) {
int costX = you.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = you.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to add counters)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
Token token = new GoblinSoldierToken(); Token token = new GoblinSoldierToken();

View file

@ -80,7 +80,7 @@ class RoseRoomTreasurerEffect extends OneShotEffect {
if (!player.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) { if (!player.chooseUse(Outcome.BoostCreature, "Pay {X}?", source, game)) {
return false; return false;
} }
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to damage)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (!cost.pay(source, game, source, source.getControllerId(), false)) { if (!cost.pay(source, game, source, source.getControllerId(), false)) {
return false; return false;

View file

@ -62,7 +62,7 @@ class ChooseNumberEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId()); Player controller = game.getPlayer(source.getControllerId());
if (controller != null) { if (controller != null) {
int numberChoice = controller.announceXMana(0, Integer.MAX_VALUE, "Choose a number.", game, source); int numberChoice = controller.getAmount(0, Integer.MAX_VALUE, "Choose a number (mana cost to restrict)", game);
game.getState().setValue(source.getSourceId().toString(), numberChoice); game.getState().setValue(source.getSourceId().toString(), numberChoice);
Permanent permanent = game.getPermanentEntering(source.getSourceId()); Permanent permanent = game.getPermanentEntering(source.getSourceId());

View file

@ -18,7 +18,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.abilities.effects.common.continuous.BoostTargetEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.constants.ColoredManaSymbol;
import mage.constants.Duration; import mage.constants.Duration;
import mage.constants.Outcome; import mage.constants.Outcome;
import mage.game.Game; import mage.game.Game;
@ -80,7 +79,7 @@ class SquealingDevilEffect extends OneShotEffect {
ManaCosts cost = new ManaCostsImpl<>("{X}"); ManaCosts cost = new ManaCostsImpl<>("{X}");
if (player != null) { if (player != null) {
if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) { if (player.chooseUse(Outcome.BoostCreature, "Pay " + cost.getText() + "?", source, game)) {
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to boost)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
Permanent permanent = game.getPermanent(source.getFirstTarget()); Permanent permanent = game.getPermanent(source.getFirstTarget());

View file

@ -53,7 +53,7 @@ class SqueesRevengeEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) { public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId()); Player player = game.getPlayer(source.getControllerId());
if(player != null) { if(player != null) {
int number = player.announceXMana(0, Integer.MAX_VALUE, "Choose how many times to flip a coin", game, source); int number = player.getAmount(0, Integer.MAX_VALUE, "Choose how many times to flip a coin", game);
game.informPlayers(player.getLogName() + " chooses " + number + '.'); game.informPlayers(player.getLogName() + " chooses " + number + '.');
for(int i = 0; i < number; i++) { for(int i = 0; i < number; i++) {
if(!player.flipCoin(source, game, true)) { if(!player.flipCoin(source, game, true)) {

View file

@ -78,7 +78,7 @@ class TilonallisSummonerEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
ManaCosts cost = new ManaCostsImpl<>("{X}{R}"); ManaCosts cost = new ManaCostsImpl<>("{X}{R}");
if (controller.chooseUse(outcome, "Pay " + cost.getText() + "? If you do, you create X 1/1 red Elemental creature tokens that are tapped and attacking.", source, game)) { if (controller.chooseUse(outcome, "Pay " + cost.getText() + "? If you do, you create X 1/1 red Elemental creature tokens that are tapped and attacking.", source, game)) {
int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay for tokens)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (cost.pay(source, game, source, source.getControllerId(), false, null)) { if (cost.pay(source, game, source, source.getControllerId(), false, null)) {
controller.resetStoredBookmark(game); // otherwise you can undo the payment controller.resetStoredBookmark(game); // otherwise you can undo the payment

View file

@ -53,7 +53,7 @@ class VigilForTheLostEffect extends OneShotEffect {
if (controller == null) { if (controller == null) {
return false; return false;
} }
int costX = controller.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = controller.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay to gain life)", game, source, true);
if (new GenericManaCost(costX).pay(source, game, source, source.getControllerId(), false, null)) { if (new GenericManaCost(costX).pay(source, game, source, source.getControllerId(), false, null)) {
controller.gainLife(costX, game, source); controller.gainLife(costX, game, source);
return true; return true;

View file

@ -64,7 +64,7 @@ class VoidEffect extends OneShotEffect {
return false; return false;
} }
int number = controller.announceXMana(0, Integer.MAX_VALUE, this.staticText, game, source); int number = controller.getAmount(0, Integer.MAX_VALUE, "Choose a number (mana cost to destroy)", game);
game.informPlayers(controller.getLogName() + " chooses " + number + '.'); game.informPlayers(controller.getLogName() + " chooses " + number + '.');
for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) { for (Permanent permanent : game.getBattlefield().getActivePermanents(source.getControllerId(), game)) {

View file

@ -59,7 +59,7 @@ class WellOfLostDreamsEffect extends OneShotEffect {
if (controller != null) { if (controller != null) {
int amount = SavedGainedLifeValue.MANY.calculate(game, source, this); int amount = SavedGainedLifeValue.MANY.calculate(game, source, this);
if (amount > 0) { if (amount > 0) {
int xValue = controller.announceXMana(0, amount, "Announce X Value", game, source); int xValue = controller.announceX(0, amount, "Announce the value for {X} (pay for draw cards)", game, source, true);
if (xValue > 0) { if (xValue > 0) {
if (new GenericManaCost(xValue).pay(source, game, source, controller.getId(), false)) { if (new GenericManaCost(xValue).pay(source, game, source, controller.getId(), false)) {
game.informPlayers(controller.getLogName() + " payed {" + xValue + '}'); game.informPlayers(controller.getLogName() + " payed {" + xValue + '}');

View file

@ -94,7 +94,7 @@ class WildbornPreserverCreateReflexiveTriggerEffect extends OneShotEffect {
if (!player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) { if (!player.chooseUse(outcome, "Pay " + cost.getText() + "?", source, game)) {
return false; return false;
} }
int costX = player.announceXMana(0, Integer.MAX_VALUE, "Announce the value for {X}", game, source); int costX = player.announceX(0, Integer.MAX_VALUE, "Announce the value for {X} (pay for counters)", game, source, true);
cost.add(new GenericManaCost(costX)); cost.add(new GenericManaCost(costX));
if (!cost.pay(source, game, source, source.getControllerId(), false, null)) { if (!cost.pay(source, game, source, source.getControllerId(), false, null)) {
return false; return false;

View file

@ -23,7 +23,7 @@ public class ProteanHydraTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Protean Hydra"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Protean Hydra");
setStrictChooseMode(false); // test AI use max for X setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();

View file

@ -169,6 +169,7 @@ public class SoulBurnTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Burn", "Craw Wurm"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Soul Burn", "Craw Wurm");
setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test (few examples)
setStopAt(1, PhaseStep.BEGIN_COMBAT); setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute(); execute();
assertPermanentCount(playerB, "Craw Wurm", 1); assertPermanentCount(playerB, "Craw Wurm", 1);

View file

@ -56,6 +56,7 @@ public class IncreasingCardsTest extends CardTestPlayerBase {
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Increasing Confusion"); castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Increasing Confusion");
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {X}{U}"); activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {X}{U}");
setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test
setStopAt(3, PhaseStep.BEGIN_COMBAT); setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute(); execute();

View file

@ -65,7 +65,7 @@ public class HELIOSOneTest extends CardTestPlayerBase {
} catch (AssertionError e) { } catch (AssertionError e) {
Assert.assertTrue( Assert.assertTrue(
"X=0 is not a valid choice. Error message:\n" + e.getMessage(), "X=0 is not a valid choice. Error message:\n" + e.getMessage(),
e.getMessage().contains("Message: Announce the number of {E} to pay") e.getMessage().contains("Message: Announce the value for {X}")
); );
} }
} }

View file

@ -29,6 +29,7 @@ public class PostMortemLungeTest extends CardTestPlayerBase {
attack(1, playerA, "Elite Vanguard"); attack(1, playerA, "Elite Vanguard");
setStrictChooseMode(false); // TODO: good test for AI's announceX - duplicate it as AI test
setStopAt(1, PhaseStep.CLEANUP); setStopAt(1, PhaseStep.CLEANUP);
execute(); execute();

View file

@ -6,7 +6,6 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs; import mage.abilities.costs.Costs;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.effects.common.InfoEffect; import mage.abilities.effects.common.InfoEffect;
@ -2916,39 +2915,20 @@ public class TestPlayer implements Player {
} }
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
assertAliasSupportInChoices(false);
if (!choices.isEmpty()) {
for (String choice : new ArrayList<>(choices)) {
if (choice.startsWith("X=")) {
int xValue = Integer.parseInt(choice.substring(2));
assertXMinMaxValue(game, ability, xValue, min, max);
choices.remove(choice);
return xValue;
}
}
}
this.chooseStrictModeFailed("choice", game, getInfo(ability, game)
+ "\nMessage: " + message + prepareXMaxInfo(min, max));
return computerPlayer.announceXMana(min, max, message, game, ability);
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variablCost) {
assertAliasSupportInChoices(false); assertAliasSupportInChoices(false);
if (!choices.isEmpty()) { if (!choices.isEmpty()) {
if (choices.get(0).startsWith("X=")) { if (choices.get(0).startsWith("X=")) {
int xValue = Integer.parseInt(choices.get(0).substring(2)); int xValue = Integer.parseInt(choices.get(0).substring(2));
assertXMinMaxValue(game, ability, xValue, min, max); assertXMinMaxValue(game, source, xValue, min, max);
choices.remove(0); choices.remove(0);
return xValue; return xValue;
} }
} }
this.chooseStrictModeFailed("choice", game, getInfo(ability, game) this.chooseStrictModeFailed("choice", game, getInfo(source, game)
+ "\nMessage: " + message + prepareXMaxInfo(min, max)); + "\nMessage: " + message + prepareXMaxInfo(min, max));
return computerPlayer.announceXCost(min, max, message, game, ability, null); return computerPlayer.announceX(min, max, message, game, source, isManaPay);
} }
private String prepareXMaxInfo(int min, int max) { private String prepareXMaxInfo(int min, int max) {

View file

@ -787,8 +787,8 @@ public abstract class AbilityImpl implements Ability {
xValue = variableManaCost.getAmount(); xValue = variableManaCost.getAmount();
} else { } else {
// announce by player // announce by player
xValue = controller.announceXMana(variableManaCost.getMinX(), variableManaCost.getMaxX(), xValue = controller.announceX(variableManaCost.getMinX(), variableManaCost.getMaxX(),
"Announce the value for " + variableManaCost.getText(), game, this); "Announce the value for " + variableManaCost.getText(), game, this, true);
} }
int amountMana = xValue * variableManaCost.getXInstancesCount(); int amountMana = xValue * variableManaCost.getXInstancesCount();

View file

@ -158,8 +158,8 @@ public abstract class VariableCostImpl implements Cost, VariableCost {
if (controller != null if (controller != null
&& (source instanceof ManaAbility && (source instanceof ManaAbility
|| stackObject != null)) { || stackObject != null)) {
xValue = controller.announceXCost(getMinValue(source, game), getMaxValue(source, game), xValue = controller.announceX(getMinValue(source, game), getMaxValue(source, game),
"Announce the number of " + actionText, game, source, this); "Announce the value for {X} (" + actionText + ")", game, source, false);
} }
return xValue; return xValue;
} }

View file

@ -173,8 +173,8 @@ class AssistEffect extends OneShotEffect {
// AI can't assist other players, maybe for teammates only (but tests must work as normal) // AI can't assist other players, maybe for teammates only (but tests must work as normal)
int amountToPay = 0; int amountToPay = 0;
if (!targetPlayer.isComputer()) { if (!targetPlayer.isComputer()) {
amountToPay = targetPlayer.announceXMana(0, unpaid.getMana().getGeneric(), amountToPay = targetPlayer.announceX(0, unpaid.getMana().getGeneric(),
"How much mana to pay as assist for " + controller.getName() + "?", game, source); "How much mana to pay as assist for " + controller.getName() + "?", game, source, true);
} }
if (amountToPay > 0) { if (amountToPay > 0) {

View file

@ -5,7 +5,6 @@ import mage.abilities.*;
import mage.abilities.costs.AlternativeSourceCosts; import mage.abilities.costs.AlternativeSourceCosts;
import mage.abilities.costs.Cost; import mage.abilities.costs.Cost;
import mage.abilities.costs.Costs; import mage.abilities.costs.Costs;
import mage.abilities.costs.VariableCost;
import mage.abilities.costs.mana.ManaCost; import mage.abilities.costs.mana.ManaCost;
import mage.abilities.costs.mana.ManaCosts; import mage.abilities.costs.mana.ManaCosts;
import mage.abilities.mana.ManaOptions; import mage.abilities.mana.ManaOptions;
@ -748,16 +747,12 @@ public interface Player extends MageItem, Copyable<Player> {
boolean shuffleCardsToLibrary(Card card, Game game, Ability source); boolean shuffleCardsToLibrary(Card card, Game game, Ability source);
/** /**
* Set the value for X mana spells and abilities * Set the value for X in spells and abilities
* @param isManaPay helper param for better AI logic
*/ */
int announceXMana(int min, int max, String message, Game game, Ability ability); int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay);
/** // TODO: rework to use pair's list of effect + ability instead string's map
* Set the value for non mana X costs
*/
int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost);
// TODO: rework to use pair's list of effect + ability instead string's map
int chooseReplacementEffect(Map<String, String> effectsMap, Map<String, MageObject> objectsMap, Game game); int chooseReplacementEffect(Map<String, String> effectsMap, Map<String, MageObject> objectsMap, Game game);
TriggeredAbility chooseTriggeredAbility(List<TriggeredAbility> abilities, Game game); TriggeredAbility chooseTriggeredAbility(List<TriggeredAbility> abilities, Game game);

View file

@ -156,13 +156,8 @@ public class StubPlayer extends PlayerImpl {
} }
@Override @Override
public int announceXMana(int min, int max, String message, Game game, Ability ability) { public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
return 0; return min;
}
@Override
public int announceXCost(int min, int max, String message, Game game, Ability ability, VariableCost variableCost) {
return 0;
} }
@Override @Override

View file

@ -710,7 +710,7 @@ public final class ManaUtil {
int bookmark = game.bookmarkState(); int bookmark = game.bookmarkState();
player.resetStoredBookmark(game); player.resetStoredBookmark(game);
wantToPay = player.announceXMana(0, maxValue, "Choose how much mana to pay", game, source); wantToPay = player.announceX(0, maxValue, "Choose how much mana to pay", game, source, true);
if (wantToPay > 0) { if (wantToPay > 0) {
Cost cost = ManaUtil.createManaCost(wantToPay, payAsX); Cost cost = ManaUtil.createManaCost(wantToPay, payAsX);
payed = cost.pay(source, game, source, player.getId(), false, null); payed = cost.pay(source, game, source, player.getId(), false, null);