GUI, game: added source info in "choose number/amount" dialogs, added auto-choose for single possible value (part of #13638);

This commit is contained in:
Oleg Agafonov 2025-05-17 21:18:45 +04:00
parent 06242496d7
commit e320bf241c
83 changed files with 142 additions and 106 deletions

View file

@ -448,7 +448,7 @@ public final class SystemUtil {
cardName = cardChoice.getChoice();
// amount
int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", game);
int cardAmount = feedbackPlayer.getAmount(1, 100, "How many [" + cardName + "] to add?", null, game);
if (cardAmount == 0) {
break;
}

View file

@ -276,11 +276,11 @@ public class ComputerPlayerControllableProxy extends ComputerPlayer7 {
}
@Override
public int getAmount(int min, int max, String message, Game game) {
public int getAmount(int min, int max, String message, Ability source, Game game) {
if (isUnderMe(game)) {
return super.getAmount(min, max, message, game);
return super.getAmount(min, max, message, source, game);
} else {
return getControllingPlayer(game).getAmount(min, max, message, game);
return getControllingPlayer(game).getAmount(min, max, message, source, game);
}
}

View file

@ -2368,9 +2368,15 @@ public class ComputerPlayer extends PlayerImpl {
@Override
// TODO: add AI support with outcome and replace random with min/max
public int getAmount(int min, int max, String message, Game game) {
public int getAmount(int min, int max, String message, Ability source, Game game) {
log.debug("getAmount");
if (min < max && min == 0) {
// fast calc on nothing to choose
if (min >= max) {
return min;
}
if (min == 0) {
return RandomUtil.nextInt(CardUtil.overflowInc(max, 1));
}
return min;

View file

@ -375,11 +375,11 @@ public final class SimulatedPlayerMCTS extends MCTSPlayer {
}
@Override
public int getAmount(int min, int max, String message, Game game) {
public int getAmount(int min, int max, String message, Ability source, Game game) {
if (this.isHuman()) {
return RandomUtil.nextInt(max - min) + min;
return RandomUtil.nextInt(max - min + 1) + min;
}
return super.getAmount(min, max, message, game);
return super.getAmount(min, max, message, source, game);
}
}

View file

@ -2158,28 +2158,37 @@ public class HumanPlayer extends PlayerImpl {
}
@Override
public int getAmount(int min, int max, String message, Game game) {
public int getAmount(int min, int max, String message, Ability source, Game game) {
if (!canCallFeedback(game)) {
return min;
}
// fast calc on nothing to choose
if (min >= max) {
return min;
}
int xValue = min;
while (canRespond()) {
prepareForResponse(game);
if (!isExecutingMacro()) {
game.fireGetAmountEvent(playerId, message, min, max);
game.fireGetAmountEvent(playerId, message + CardUtil.getSourceLogName(game, source), min, max);
}
waitForResponse(game);
if (response.getInteger() != null) {
break;
if (response.getInteger() == null) {
continue;
}
xValue = response.getInteger();
if (xValue < min || xValue > max) {
continue;
}
break;
}
if (response.getInteger() != null) {
return response.getInteger();
} else {
return 0;
}
return xValue;
}
@Override

View file

@ -119,7 +119,7 @@ class AetherRefineryTokenEffect extends OneShotEffect {
return true;
}
int numberToPay = controller.getAmount(1, totalEnergy,
"Pay one or more {E}", game);
"Pay one or more {E}", source, game);
Cost cost = new PayEnergyCost(numberToPay);
if (cost.pay(source, game, source, source.getControllerId(), true)) {

View file

@ -68,7 +68,7 @@ class AetherSpikeEffect extends OneShotEffect {
}
int numberToPay = controller.getAmount(
0, controller.getCountersCount(CounterType.ENERGY),
"How many {E} do you want to pay?", game
"How many {E} do you want to pay?", source, game
);
Cost cost = new PayEnergyCost(numberToPay);
int numberPaid = 0;

View file

@ -89,7 +89,7 @@ class AetherbornMarauderEffect extends OneShotEffect {
int numberOfCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1);
int numberToMove = 1;
if (numberOfCounters > 1) {
numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", game);
numberToMove = controller.getAmount(0, numberOfCounters, "Choose how many +1/+1 counters to move", source, game);
}
if (numberToMove > 0) {
fromPermanent.removeCounters(CounterType.P1P1.createInstance(numberToMove), source, game);

View file

@ -87,7 +87,7 @@ class MoveCounterFromTargetToTargetEffect extends OneShotEffect {
}
int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1);
if (amountCounters > 0) {
int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", game);
int amountToMove = controller.getAmount(0, amountCounters, "Choose how many counters to move", source, game);
if (amountToMove > 0) {
fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game);
toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game);

View file

@ -58,7 +58,7 @@ class ByInvitationOnlyEffect extends OneShotEffect {
return false;
}
int number = player.getAmount(
0, 13, "Choose a number between 0 and 13", game
0, 13, "Choose a number between 0 and 13", source, game
);
return new SacrificeAllEffect(
number, StaticFilters.FILTER_PERMANENT_CREATURE

View file

@ -166,7 +166,7 @@ class CemeteryDesecratorRemoveCountersEffect extends OneShotEffect {
remainingCounters -= numCounters;
int min = Math.max(0, countersLeftToRemove - remainingCounters);
int max = Math.min(countersLeftToRemove, numCounters);
int toRemove = controller.getAmount(min, max, counterName + " counters to remove", game);
int toRemove = controller.getAmount(min, max, counterName + " counters to remove", source, game);
// Sanity check in case of GUI bugs/disconnects
toRemove = Math.max(toRemove, min);
toRemove = Math.min(toRemove, max);

View file

@ -74,7 +74,7 @@ class ChoiceOfDamnationsEffect extends OneShotEffect {
amount = Math.min(numberPermanents, safeLifeToLost);
} else {
// Human must choose
amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", game);
amount = targetPlayer.getAmount(0, Integer.MAX_VALUE, "Chooses a number", source, game);
}
Player controller = game.getPlayer(source.getControllerId());

View file

@ -104,7 +104,7 @@ class ClockworkAvianEffect extends OneShotEffect {
return false;
}
int toAdd = player.getAmount(
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game
);
return toAdd > 0 && permanent.addCounters(
CounterType.P1P0.createInstance(toAdd), source.getControllerId(),

View file

@ -101,7 +101,7 @@ class ClockworkBeastEffect extends OneShotEffect {
return false;
}
int toAdd = player.getAmount(
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game
);
return toAdd > 0 && permanent.addCounters(
CounterType.P1P0.createInstance(toAdd), source.getControllerId(),

View file

@ -109,7 +109,7 @@ class ClockworkSteedEffect extends OneShotEffect {
return false;
}
int toAdd = player.getAmount(
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game
);
return toAdd > 0 && permanent.addCounters(
CounterType.P1P0.createInstance(toAdd), source.getControllerId(),

View file

@ -113,7 +113,7 @@ class ClockworkSwarmEffect extends OneShotEffect {
return false;
}
int toAdd = player.getAmount(
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), game
0, maxCounters, "Choose how many +1/+0 counters to put on " + permanent.getName(), source, game
);
return toAdd > 0 && permanent.addCounters(
CounterType.P1P0.createInstance(toAdd), source.getControllerId(),

View file

@ -80,7 +80,7 @@ class CullingRitualEffect extends OneShotEffect {
}
int black = player.getAmount(
0, counter, counter + " permanents were destroyed, " +
"choose the amount of black mana to produce (the rest will be green)", game
"choose the amount of black mana to produce (the rest will be green)", source, game
);
Mana mana = new Mana(ManaType.BLACK, black);
if (black < counter) {

View file

@ -67,7 +67,7 @@ class DieYoungEffect extends OneShotEffect {
if (controller != null) {
new GetEnergyCountersControllerEffect(2).apply(game, source);
int max = controller.getCountersCount(CounterType.ENERGY);
int numberToPayed = controller.getAmount(0, max, "How many energy counters do you like to pay? (maximum = " + max + ')', game);
int numberToPayed = controller.getAmount(0, max, "How many energy counters do you like to pay? (maximum = " + max + ')', source, game);
if (numberToPayed > 0) {
Cost cost = new PayEnergyCost(numberToPayed);
if (cost.pay(source, game, source, source.getControllerId(), true)) {

View file

@ -57,7 +57,7 @@ class DiminishingReturnsEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
int amount = player.getAmount(0, 7, "How many cards to draw (up to 7)?", game);
int amount = player.getAmount(0, 7, "How many cards to draw (up to 7)?", source, game);
player.drawCards(amount, source, game);
}
}

View file

@ -114,7 +114,7 @@ class EsperTerraEffect extends OneShotEffect {
Optional.ofNullable(source.getControllerId())
.map(game::getPlayer)
.map(player -> player.getAmount(
0, 3, "Choose how many lore counters to put on " + token.getIdName(), game
0, 3, "Choose how many lore counters to put on " + token.getIdName(), source, game
))
.filter(amount -> amount > 0)
.ifPresent(amount -> token.addCounters(CounterType.LORE.createInstance(amount), source, game));

View file

@ -62,7 +62,7 @@ class ExpelTheInterlopersEffect extends OneShotEffect {
}
// Choose a number between 0 and 10.
int number = player.getAmount(0, 10, "Choose a number between 0 and 10", game);
int number = player.getAmount(0, 10, "Choose a number between 0 and 10", source, game);
game.informPlayers(player.getLogName() + " has chosen the number " + number + "." + CardUtil.getSourceLogName(game, source));
// Destroy all creatures with power greater than or equal to the chosen number.

View file

@ -72,8 +72,8 @@ class ExpertLevelSafeEffect extends OneShotEffect {
return false;
}
int controllerChoice = controller.getAmount(1, 3, "Choose a number", game);
int opponentChoice = opponent.getAmount(1, 3, "Choose a number", game);
int controllerChoice = controller.getAmount(1, 3, "Choose a number", source, game);
int opponentChoice = opponent.getAmount(1, 3, "Choose a number", source, game);
game.informPlayers(controller.getLogName() + " chose " + controllerChoice);
game.informPlayers(opponent.getLogName() + " chose " + opponentChoice);

View file

@ -64,7 +64,7 @@ class FadeAwayEffect extends OneShotEffect {
int payAmount = 0;
boolean paid = false;
while (player.canRespond() && !paid) {
payAmount = player.getAmount(0, creaturesNumber, message, game);
payAmount = player.getAmount(0, creaturesNumber, message, source, game);
ManaCostsImpl cost = new ManaCostsImpl<>();
cost.add(new GenericManaCost(payAmount));
cost.clearPaid();

View file

@ -98,7 +98,7 @@ class FatalLoreEffect extends OneShotEffect {
if (player == null) {
return false;
}
int toDraw = player.getAmount(0, 3, "Choose how many cards to draw", game);
int toDraw = player.getAmount(0, 3, "Choose how many cards to draw", source, game);
return player.drawCards(toDraw, source, game) > 0;
}
}

View file

@ -59,7 +59,7 @@ class FluxEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards?", game);
int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards?", source, game);
player.discard(numToDiscard, false, false, source, game);
player.drawCards(numToDiscard, source, game);
}

View file

@ -107,7 +107,7 @@ class ForgottenAncientEffect extends OneShotEffect {
break;
}
int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", game);
int amountToMove = controller.getAmount(0, numCounters, "Choose how many counters to move (" + numCounters + " counters remaining.)", source, game);
if (amountToMove == 0) {
break;
}

View file

@ -63,7 +63,7 @@ class GalvanicDischargeEffect extends OneShotEffect {
return false;
}
new GetEnergyCountersControllerEffect(3).apply(game, source);
int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", game);
int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", source, game);
if (numberToPay <= 0) {
return true;
}

View file

@ -104,7 +104,7 @@ class GlissaSunslayerEffect extends OneShotEffect {
permanent.removeCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
permanent.removeCounters(counterName, amount, source, game);

View file

@ -68,7 +68,7 @@ class GoblinGameEffect extends OneShotEffect {
.collect(Collectors.toList());
for (Player player : players) {
// TODO: consider changing 1000 to another cap, or even Integer.MAX_VALUE if the Volcano Hellion binary wraparound gets addressed (although hiding over two billions of items would be rather difficult IRL)
numberChosen.put(player.getId(), player.getAmount(1, 1000, "Choose a number of objects to hide.", game));
numberChosen.put(player.getId(), player.getAmount(1, 1000, "Choose a number of objects to hide.", source, game));
}
// get lowest number

View file

@ -62,7 +62,7 @@ class HarnessedLightningEffect extends OneShotEffect {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
new GetEnergyCountersControllerEffect(3).apply(game, source);
int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", game);
int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY), "How many {E} do you like to pay?", source, game);
if (numberToPay > 0) {
Cost cost = new PayEnergyCost(numberToPay);
if (cost.pay(source, game, source, source.getControllerId(), true)) {

View file

@ -90,7 +90,7 @@ class HeartlessActEffect extends OneShotEffect {
permanent.removeCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
permanent.removeCounters(counterName, amount, source, game);

View file

@ -84,7 +84,7 @@ class HexParasiteEffect extends OneShotEffect {
permanent.removeCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
permanent.removeCounters(counterName, amount, source, game);

View file

@ -67,7 +67,7 @@ class HolochessEffect extends OneShotEffect {
if (player == null || opponent == null) {
return false;
}
int chosenNumber = player.getAmount(0, 3, "Choose a number between 0 and 3", game);
int chosenNumber = player.getAmount(0, 3, "Choose a number between 0 and 3", source, game);
List<Permanent> creaturesControlledByOpponent = game.getBattlefield().getActivePermanents(
StaticFilters.FILTER_PERMANENT_CREATURES, opponent.getId(), game);
if (chosenNumber < creaturesControlledByOpponent.size()) {

View file

@ -89,7 +89,7 @@ class IllicitAuctionEffect extends GainControlTargetEffect {
newBid = Math.max(creatureValue % 2, computerLife - 100);
} else {
if (currentPlayer.canRespond()) {
newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", game);
newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", source, game);
}
}
if (newBid > highBid) {

View file

@ -88,7 +88,7 @@ class LocalizedDestructionEffect extends OneShotEffect {
}
int numberToPay = controller.getAmount(1, totalEnergy,
"Pay one or more {E}", game);
"Pay one or more {E}", source, game);
Cost cost = new PayEnergyCost(numberToPay);

View file

@ -79,7 +79,7 @@ class MagesContestEffect extends OneShotEffect {
}
} else if (currentPlayer.chooseUse(Outcome.Benefit, winner.getLogName() + " has bet " + highBid + " life. Top the bid?", source, game)) {
// Human choose
newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", game);
newBid = currentPlayer.getAmount(highBid + 1, Integer.MAX_VALUE, "Choose bid", source, game);
}
if (newBid > highBid) {
highBid = newBid;

View file

@ -81,7 +81,7 @@ class MenacingOgreEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
number = player.getAmount(0, 1000, message, game);
number = player.getAmount(0, 1000, message, source, game);
numberChosen.put(player, number);
}
}

View file

@ -87,7 +87,7 @@ class MinionOfTheWastesEffect extends ReplacementEffectImpl {
if (creature == null || controller == null) {
return false;
}
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game);
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game);
Cost cost = new PayLifeCost(payAmount);
if (!cost.pay(source, game, source, source.getControllerId(), true)) {
return false;

View file

@ -111,7 +111,7 @@ class NamelessRaceEffect extends ReplacementEffectImpl {
int permanentsInPlay = new PermanentsOnBattlefieldCount(filter).calculate(game, source, null);
int cardsInGraveyards = new CardsInAllGraveyardsCount(filter2).calculate(game, source, null);
int maxAmount = Math.min(permanentsInPlay + cardsInGraveyards, controller.getLife());
int payAmount = controller.getAmount(0, maxAmount, "Pay up to " + maxAmount + " life", game);
int payAmount = controller.getAmount(0, maxAmount, "Pay up to " + maxAmount + " life", source, game);
Cost cost = new PayLifeCost(payAmount);
if (!cost.pay(source, game, source, source.getControllerId(), true)) {
return false;

View file

@ -81,7 +81,7 @@ class NecrodominanceEffect extends OneShotEffect {
if (controller == null) {
return false;
}
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game);
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game);
Cost cost = new PayLifeCost(payAmount);
if (!cost.pay(source, game, source, source.getControllerId(), true)) {
return false;

View file

@ -88,7 +88,7 @@ class NightshadeAssassinEffect extends OneShotEffect {
FilterCard filter = new FilterCard();
filter.add(new ColorPredicate(ObjectColor.BLACK));
int blackCards = controller.getHand().count(filter, source.getControllerId(), source, game);
int cardsToReveal = controller.getAmount(0, blackCards, "Reveal how many black cards?", game);
int cardsToReveal = controller.getAmount(0, blackCards, "Reveal how many black cards?", source, game);
game.informPlayers(controller.getLogName() + " chooses to reveal " + cardsToReveal + " black cards.");
if (cardsToReveal > 0) {
TargetCardInHand target = new TargetCardInHand(cardsToReveal, cardsToReveal, filter);

View file

@ -60,7 +60,7 @@ class PainsRewardEffect extends OneShotEffect {
playerList.setCurrent(controller.getId());
Player winner = game.getPlayer(controller.getId());
int highBid = chooseLifeAmountToBid(controller, -1, game); // -1 for start with 0 min big
int highBid = chooseLifeAmountToBid(controller, -1, source, game); // -1 for start with 0 min big
game.informPlayers(winner.getLogName() + " has bet " + highBid + " lifes");
Player currentPlayer = playerList.getNextInRange(controller, game);
@ -72,7 +72,7 @@ class PainsRewardEffect extends OneShotEffect {
Outcome aiOutcome = (highBid + 1 <= safeLifeToLost) ? Outcome.Benefit : Outcome.Detriment;
if (currentPlayer.chooseUse(aiOutcome, text, source, game)) {
int newBid = chooseLifeAmountToBid(currentPlayer, highBid, game);
int newBid = chooseLifeAmountToBid(currentPlayer, highBid, source, game);
if (newBid > highBid) {
highBid = newBid;
winner = currentPlayer;
@ -90,14 +90,14 @@ class PainsRewardEffect extends OneShotEffect {
return false;
}
private int chooseLifeAmountToBid(Player player, int currentBig, Game game) {
private int chooseLifeAmountToBid(Player player, int currentBig, Ability source, Game game) {
int newBid;
if (player.isComputer()) {
// AI choose
newBid = currentBig + 1;
} else {
// Human choose
newBid = player.getAmount(currentBig + 1, Integer.MAX_VALUE, "Choose amount of life to bid", game);
newBid = player.getAmount(currentBig + 1, Integer.MAX_VALUE, "Choose amount of life to bid", source, game);
}
return newBid;
}

View file

@ -74,7 +74,7 @@ class PhyrexianProcessorPayLifeEffect extends OneShotEffect {
if (controller == null || permanent == null) {
return false;
}
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", game);
int payAmount = controller.getAmount(0, controller.getLife(), "Pay any amount of life", source, game);
Cost cost = new PayLifeCost(payAmount);
if (!cost.pay(source, game, source, source.getControllerId(), true)) {
return false;

View file

@ -83,7 +83,7 @@ class PiaNalaarChiefMechanicEffect extends OneShotEffect {
return false;
}
int energyToPay = controller.getAmount(1, controller.getCountersCount(CounterType.ENERGY),
"Pay 1 or more {E}", game);
"Pay 1 or more {E}", source, game);
if (energyToPay == 0) {
return true;
}

View file

@ -78,7 +78,7 @@ class PlagueOfVerminEffect extends OneShotEffect {
currentLifePaid = 0;
totalPaidLife = 0;
if (currentPlayer.chooseUse(Outcome.AIDontUseIt, "Pay life?", source, game)) {
totalPaidLife = currentPlayer.getAmount(0, controller.getLife(), "Pay how many life?", game);
totalPaidLife = currentPlayer.getAmount(0, controller.getLife(), "Pay how many life?", source, game);
if (totalPaidLife > 0) {
currentPlayer.loseLife(totalPaidLife, game, source, false);
if (payLife.get(currentPlayer.getId()) == null) {

View file

@ -88,7 +88,7 @@ class PriceOfBetrayalEffect extends OneShotEffect {
permanent.removeCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
permanent.removeCounters(counterName, amount, source, game);
@ -114,7 +114,7 @@ class PriceOfBetrayalEffect extends OneShotEffect {
player.loseCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(player.getCountersCount(counterName), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(player.getCountersCount(counterName), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
player.loseCounters(counterName, amount, source, game);

View file

@ -86,7 +86,7 @@ class RampagingAetherhoodEffect extends OneShotEffect {
int totalEnergy = controller.getCountersCount(CounterType.ENERGY);
if (totalEnergy > 0) {
if (controller.chooseUse(Outcome.Benefit, "Pay one or more {E}? Put that many +1/+1 counters on this creature", source, game)) {
int energyToPay = controller.getAmount(1, totalEnergy, "Pay one or more {E}", game);
int energyToPay = controller.getAmount(1, totalEnergy, "Pay one or more {E}", source, game);
Cost cost = new PayEnergyCost(energyToPay);
if (cost.pay(source, game, source, controller.getId(), true)) {
new AddCountersSourceEffect(CounterType.P1P1.createInstance(energyToPay), true).apply(game, source);

View file

@ -72,7 +72,7 @@ class RenderInertEffect extends OneShotEffect {
permanent.removeCounters(counterName, 1, source, game);
removed++;
} else {
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", game);
int amount = controller.getAmount(1, Math.min(permanent.getCounters(game).get(counterName).getCount(), toRemove - removed), "How many?", source, game);
if (amount > 0) {
removed += amount;
permanent.removeCounters(counterName, amount, source, game);

View file

@ -56,7 +56,7 @@ class RitesOfInitiationEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards at random?", game);
int numToDiscard = player.getAmount(0, player.getHand().size(), "Discard how many cards at random?", source, game);
player.discard(numToDiscard, true, false, source, game);
game.addEffect(new BoostControlledEffect(numToDiscard, 0, Duration.EndOfTurn), source);
return true;

View file

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

View file

@ -78,7 +78,7 @@ class ScroungingBandarEffect extends OneShotEffect {
if (fromPermanent != null && toPermanent != null) {
int amountCounters = fromPermanent.getCounters(game).getCount(CounterType.P1P1);
if (amountCounters > 0) {
int amountToMove = controller.getAmount(0, amountCounters, "How many counters do you want to move?", game);
int amountToMove = controller.getAmount(0, amountCounters, "How many counters do you want to move?", source, game);
if (amountToMove > 0) {
fromPermanent.removeCounters(CounterType.P1P1.createInstance(amountToMove), source, game);
toPermanent.addCounters(CounterType.P1P1.createInstance(amountToMove), source.getControllerId(), source, game);

View file

@ -64,7 +64,7 @@ class ScryingGlassEffect extends OneShotEffect {
int amount = 0;
if (controller != null
&& targetOpponent != null) {
amount = controller.getAmount(1, Integer.MAX_VALUE, "Choose a number", game);
amount = controller.getAmount(1, Integer.MAX_VALUE, "Choose a number", source, game);
controller.choose(Outcome.Discard, color, game);
FilterCard filter = new FilterCard();
filter.add(new ColorPredicate(color.getColor()));

View file

@ -101,7 +101,7 @@ class ShahOfNaarIsleEffect extends OneShotEffect {
for (UUID playerId : game.getOpponents(controller.getId())) {
Player opponent = game.getPlayer(playerId);
if (opponent != null) {
int number = opponent.getAmount(0, 3, "Draw how many cards?", game);
int number = opponent.getAmount(0, 3, "Draw how many cards?", source, game);
opponent.drawCards(number, source, game);
}
}

View file

@ -67,7 +67,7 @@ class SixyBeastEffect extends OneShotEffect {
Permanent permanent = game.getPermanentEntering(source.getSourceId());
Player controller = game.getPlayer(source.getControllerId());
if (permanent != null && controller != null) {
int counterAmount = controller.getAmount(0, 6, "Secretly put up to six counters on " + permanent.getName(), game);
int counterAmount = controller.getAmount(0, 6, "Secretly put up to six counters on " + permanent.getName(), source, game);
permanent.addCounters(CounterType.P1P1.createInstance(counterAmount), source.getControllerId(), source, game);
Player opponent = null;
Set<UUID> opponents = game.getOpponents(source.getControllerId());
@ -82,7 +82,7 @@ class SixyBeastEffect extends OneShotEffect {
}
}
if (opponent != null) {
int guessedAmount = opponent.getAmount(0, 6, "Guess the number of counters on " + permanent.getName(), game);
int guessedAmount = opponent.getAmount(0, 6, "Guess the number of counters on " + permanent.getName(), source, game);
game.informPlayers(opponent.getLogName() + " guessed " + guessedAmount + " as the number of counters on " + permanent.getLogName());
if (counterAmount == guessedAmount) {
permanent.sacrifice(source, game);

View file

@ -120,7 +120,7 @@ class SlipperyBogbonderEffect extends OneShotEffect {
int num = player.getAmount(
0, entry.getValue().getCount(),
"Choose how many " + entry.getKey()
+ " counters to remove from " + permanent.getLogName(), game
+ " counters to remove from " + permanent.getLogName(), source, game
);
counterMap.computeIfAbsent(
permanent.getId(), x -> new HashMap<>()

View file

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

View file

@ -114,6 +114,7 @@ class SuppressionRayTargetEffect extends OneShotEffect {
int numberToPay = controller.getAmount(
0, maxEnergy,
"How many {E} do you like to pay? (" + tappedThisWay.size() + " creature(s) were tapped)",
source,
game
);
if (numberToPay == 0) {

View file

@ -109,7 +109,7 @@ class TalionTheKindlyLordEffect extends OneShotEffect {
if (controller == null) {
return true;
}
int numberChoice = controller.getAmount(1, 10, "Choose a number.", game);
int numberChoice = controller.getAmount(1, 10, "Choose a number.", source, game);
game.getState().setValue("chosenNumber_" + source.getSourceId()
+ '_' + source.getSourceObjectZoneChangeCounter(), numberChoice);
Permanent permanent = game.getPermanentEntering(source.getSourceId());

View file

@ -57,7 +57,7 @@ class TemporaryTruceEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", game);
int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", source, game);
player.drawCards(cardsToDraw, source, game);
player.gainLife((2 - cardsToDraw) * 2, game, source);
}

View file

@ -83,7 +83,7 @@ class TerritorialAetherkiteEffect extends OneShotEffect {
}
new GetEnergyCountersControllerEffect(2).apply(game, source);
int energyToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY),
"Pay any amount of {E}", game);
"Pay any amount of {E}", source, game);
if (energyToPay == 0) {
return true;
}

View file

@ -103,7 +103,7 @@ class TetravusCreateTokensEffect extends OneShotEffect {
if (countersToRemove == 0) {
return false;
}
countersToRemove = player.getAmount(0, countersToRemove, "Choose an amount of counters to remove", game);
countersToRemove = player.getAmount(0, countersToRemove, "Choose an amount of counters to remove", source, game);
Cost cost = new RemoveCountersSourceCost(CounterType.P1P1.createInstance(countersToRemove));
if (cost.pay(source, game, source, source.getControllerId(), true)) {
CreateTokenEffect effect = new CreateTokenEffect(new TetraviteToken(), countersToRemove);

View file

@ -127,7 +127,7 @@ class ThornmantleStrikerEffect extends OneShotEffect {
remainingCounters -= numCounters;
int min = Math.max(0, countersLeftToRemove - remainingCounters);
int max = Math.min(countersLeftToRemove, numCounters);
int toRemove = controller.getAmount(min, max, counterName + " counters to remove", game);
int toRemove = controller.getAmount(min, max, counterName + " counters to remove", source, game);
// Sanity check in case of GUI bugs/disconnects
toRemove = Math.max(toRemove, min);
toRemove = Math.min(toRemove, max);

View file

@ -59,11 +59,11 @@ class TradeSecretsEffect extends OneShotEffect {
if (controller != null
&& targetOpponent != null) {
new DrawCardTargetEffect(2).apply(game, source);//The drawcard method would not work immediately
int amountOfCardsToDraw = controller.getAmount(0, 4, message2, game);
int amountOfCardsToDraw = controller.getAmount(0, 4, message2, source, game);
new DrawCardSourceControllerEffect(amountOfCardsToDraw).apply(game, source);
while (targetOpponent.chooseUse(Outcome.AIDontUseIt, message, source, game)) {
new DrawCardTargetEffect(2).apply(game, source);
amountOfCardsToDraw = controller.getAmount(0, 4, message2, game);
amountOfCardsToDraw = controller.getAmount(0, 4, message2, source, game);
new DrawCardSourceControllerEffect(amountOfCardsToDraw).apply(game, source);
}
return true;

View file

@ -57,7 +57,7 @@ class TruceEffect extends OneShotEffect {
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
Player player = game.getPlayer(playerId);
if (player != null) {
int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", game);
int cardsToDraw = player.getAmount(0, 2, "Draw how many cards?", source, game);
player.drawCards(cardsToDraw, source, game);
player.gainLife((2 - cardsToDraw) * 2, game, source);
}

View file

@ -92,7 +92,7 @@ class Vault112SadisticSimulationChapterEffect extends OneShotEffect {
}
int numberToPay = controller.getAmount(
0, controller.getCountersCount(CounterType.ENERGY),
"How many {E} do you like to pay?", game
"How many {E} do you like to pay?", source, game
);
if (numberToPay <= 0) {
return true;

View file

@ -79,7 +79,7 @@ class VizkopaConfessorEffect extends OneShotEffect {
Player targetPlayer = game.getPlayer(source.getFirstTarget());
Card sourceCard = game.getCard(source.getSourceId());
if (controller != null && targetPlayer != null && sourceCard != null) {
int payLife = controller.getAmount(0, controller.getLife(),"Pay how many life?", game);
int payLife = controller.getAmount(0, controller.getLife(),"Pay how many life?", source, game);
if (payLife > 0) {
controller.loseLife(payLife, game, source, false);
game.informPlayers(sourceCard.getName() + ": " + controller.getLogName() + " pays " + payLife + " life");

View file

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

View file

@ -82,7 +82,7 @@ class VolcanoHellionEffect extends OneShotEffect {
}
} else {
//Human choose
amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", game);
amount = controller.getAmount(0, Integer.MAX_VALUE, "Choose the amount of damage to deliver to you and a target creature. The damage can't be prevented.", source, game);
}
if (amount > 0) {

View file

@ -63,7 +63,7 @@ class WheelOfMisfortuneEffect extends OneShotEffect {
if (player == null) {
continue;
}
playerMap.put(playerId, player.getAmount(0, 1000, "Choose a number", game));
playerMap.put(playerId, player.getAmount(0, 1000, "Choose a number", source, game));
}
for (Map.Entry<UUID, Integer> entry : playerMap.entrySet()) {
Player player = game.getPlayer(entry.getKey());

View file

@ -67,7 +67,7 @@ class WheelOfPotentialEffect extends OneShotEffect {
}
int numberToPay = controller.getAmount(
0, controller.getCountersCount(CounterType.ENERGY),
"How many {E} do you want to pay?", game
"How many {E} do you want to pay?", source, game
);
Cost cost = new PayEnergyCost(numberToPay);
int numberPaid = 0;

View file

@ -68,7 +68,7 @@ class WrathOfTheSkiesEffect extends OneShotEffect {
}
int numberToPay = controller.getAmount(0, controller.getCountersCount(CounterType.ENERGY),
"Pay any amount of {E}", game);
"Pay any amount of {E}", source, game);
Cost cost = new PayEnergyCost(numberToPay);
if (cost.pay(source, game, source, source.getControllerId(), true)) {
game.getBattlefield()

View file

@ -81,7 +81,7 @@ class XenagosManaEffect extends OneShotEffect {
}
Mana mana = new Mana();
int redCount = player.getAmount(0, x, "How much <b>RED</b> mana add to pool? (available: " + x + ", another mana goes to <b>GREEN</b>)?", game);
int redCount = player.getAmount(0, x, "How much <b>RED</b> mana add to pool? (available: " + x + ", another mana goes to <b>GREEN</b>)?", source, game);
int greenCount = Math.max(0, x - redCount);
mana.setRed(redCount);
mana.setGreen(greenCount);

View file

@ -69,7 +69,7 @@ class YusriFortunesFlameEffect extends OneShotEffect {
if (player == null) {
return false;
}
int flips = player.getAmount(1, 5, "Choose a number between 1 and 5", game);
int flips = player.getAmount(1, 5, "Choose a number between 1 and 5", source, game);
int wins = 0;
int losses = 0;
for (int i = 0; i < flips; i++) {

View file

@ -35,7 +35,7 @@ public class WillbreakerTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerB, "Silvercoat Lion");
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{B}, Remove", "Silvercoat Lion");
setChoice(playerA, "X=0");
//setChoice(playerA, "X=0"); // auto-choose X=0
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();

View file

@ -29,7 +29,7 @@ public class HELIOSOneTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
addCard(Zone.BATTLEFIELD, playerA, "Memnite");
setChoice(playerA, "X=0");
//setChoice(playerA, "X=0"); // auto-choose X=0
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}");
addTarget(playerA, "Memnite");
@ -52,7 +52,10 @@ public class HELIOSOneTest extends CardTestPlayerBase {
// TODO: So the test suite let's you activate the ability (as it does not go to adjust targets to check them.)
// But X=0 is not a valid choice once targets are checked (no nonland card with that MV in play).
setChoice(playerA, "X=0");
checkPlayableAbility("Pay X {E} ability is playable, but can't be activated",
1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}", true);
showAvailableAbilities("hmm", 1, PhaseStep.PRECOMBAT_MAIN, playerA);
//setChoice(playerA, "X=0"); // auto-choose X=0
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}");
addTarget(playerA, "Elite Vanguard"); // not a valid target for X=0 energy payment
@ -65,7 +68,7 @@ public class HELIOSOneTest extends CardTestPlayerBase {
} catch (AssertionError e) {
Assert.assertTrue(
"X=0 is not a valid choice. Error message:\n" + e.getMessage(),
e.getMessage().contains("Message: Announce the value for {X}")
e.getMessage().contains("Can't find ability to activate command: {3}")
);
}
}

View file

@ -2917,6 +2917,12 @@ public class TestPlayer implements Player {
@Override
public int announceX(int min, int max, String message, Game game, Ability source, boolean isManaPay) {
assertAliasSupportInChoices(false);
// fast calc on nothing to choose
if (min >= max) {
return min;
}
if (!choices.isEmpty()) {
if (choices.get(0).startsWith("X=")) {
int xValue = Integer.parseInt(choices.get(0).substring(2));
@ -2949,8 +2955,14 @@ public class TestPlayer implements Player {
}
@Override
public int getAmount(int min, int max, String message, Game game) {
public int getAmount(int min, int max, String message, Ability source, Game game) {
assertAliasSupportInChoices(false);
// fast calc on nothing to choose
if (min >= max) {
return min;
}
if (!choices.isEmpty()) {
if (choices.get(0).startsWith("X=")) {
int xValue = Integer.parseInt(choices.get(0).substring(2));
@ -2960,7 +2972,7 @@ public class TestPlayer implements Player {
}
this.chooseStrictModeFailed("choice", game, message);
return computerPlayer.getAmount(min, max, message, game);
return computerPlayer.getAmount(min, max, message, source, game);
}
@Override

View file

@ -204,7 +204,7 @@ class SagaLoreCountersEffect extends OneShotEffect {
}
int counters = player.getAmount(
1, maxChapter.getNumber(),
"Choose the number of lore counters to enter with", game
"Choose the number of lore counters to enter with", source, game
);
return permanent.addCounters(CounterType.LORE.createInstance(counters), source, game);
}

View file

@ -136,7 +136,8 @@ public class RemoveCounterCost extends CostImpl {
int numberOfCountersSelected = 1;
if (countersLeft > 1 && countersOnPermanent > 1) {
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
"Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment", game);
"Choose how many counters (" + counterName + ") to remove from " + targetObject.getLogName() + " as payment",
source, game);
}
targetObject.removeCounters(counterName, numberOfCountersSelected, source, game);
countersRemoved += numberOfCountersSelected;

View file

@ -40,7 +40,7 @@ public class ExileCardsFromHandAdjuster implements CostAdjuster {
// real - need to choose
// TODO: need early target cost instead dialog here
int toExile = cardCount == 0 ? 0 : player.getAmount(
0, cardCount, "Choose how many " + filter.getMessage() + " to exile", game
0, cardCount, "Choose how many " + filter.getMessage() + " to exile", ability, game
);
reduceCount = 2 * toExile;
if (toExile > 0) {

View file

@ -64,7 +64,7 @@ public class DrawCardTargetEffect extends OneShotEffect {
&& player.canRespond()) {
int cardsToDraw = amount.calculate(game, source, this);
if (upTo) {
cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", game);
cardsToDraw = player.getAmount(0, cardsToDraw, "Draw how many cards?", source, game);
}
if (!optional
|| player.chooseUse(outcome, "Use draw effect?", source, game)) {

View file

@ -382,7 +382,7 @@ public class CombatGroup implements Serializable, Copyable<CombatGroup> {
break;
}
int damageAssigned = 0;
damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), game);
damageAssigned = player.getAmount(0, damage, "Assign damage to " + defendingCreature.getName(), null, game);
assigned.put(defendingCreature.getId(), damageAssigned);
damage -= damageAssigned;
}

View file

@ -763,7 +763,11 @@ public interface Player extends MageItem, Copyable<Player> {
void selectBlockers(Ability source, Game game, UUID defendingPlayerId);
int getAmount(int min, int max, String message, Game game);
/**
*
* @param source can be null for system actions like define damage
*/
int getAmount(int min, int max, String message, Ability source, Game game);
/**
* Player distributes amount among multiple options

View file

@ -186,8 +186,8 @@ public class StubPlayer extends PlayerImpl {
}
@Override
public int getAmount(int min, int max, String message, Game game) {
return 0;
public int getAmount(int min, int max, String message, Ability source, Game game) {
return min;
}
@Override