[READY FOR REVIEW] Implement a "multi-amount" dialog (#7528)

* Implemented chooseTargetAmount and new GUI dialog (distribute damage, distribute mana)
* Added tests and AI support;
* Test framework: added aliases support in TargetAmount dialogs;

Co-authored-by: Oleg Agafonov <jaydi85@gmail.com>
This commit is contained in:
Daniel Bomar 2021-04-17 05:28:01 -05:00 committed by GitHub
parent 042aa61ad4
commit 600cac6fc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1209 additions and 232 deletions

View file

@ -9,6 +9,7 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.mana.builder.ConditionalManaBuilder;
import mage.choices.ChoiceColor;
import mage.constants.MultiAmountType;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
@ -87,29 +88,26 @@ public class AddConditionalManaOfAnyColorEffect extends ManaEffect {
if (game != null) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
ConditionalMana mana = null;
int value = amount.calculate(game, source, this);
ChoiceColor choice = new ChoiceColor(true);
for (int i = 0; i < value; i++) {
if (choice.getChoice() == null) {
if (value > 0) {
if (oneChoice || value == 1) {
ChoiceColor choice = new ChoiceColor(true);
controller.choose(outcome, choice, game);
}
if (choice.getChoice() == null) {
return null;
}
if (oneChoice) {
mana = new ConditionalMana(manaBuilder.setMana(choice.getMana(value), source, game).build());
break;
} else {
if (mana == null) {
mana = new ConditionalMana(manaBuilder.setMana(choice.getMana(1), source, game).build());
} else {
mana.add(choice.getMana(1));
if (choice.getChoice() == null) {
return null;
}
choice.clearChoice();
return new ConditionalMana(manaBuilder.setMana(choice.getMana(value), source, game).build());
}
List<String> manaStrings = new ArrayList<>(5);
manaStrings.add("W");
manaStrings.add("U");
manaStrings.add("B");
manaStrings.add("R");
manaStrings.add("G");
List<Integer> choices = controller.getMultiAmount(this.outcome, manaStrings, 0, value, MultiAmountType.MANA, game);
Mana mana = new Mana(choices.get(0), choices.get(1), choices.get(2), choices.get(3), choices.get(4), 0, 0, 0);
return new ConditionalMana(manaBuilder.setMana(mana, source, game).build());
}
return mana;
}
}
return new Mana();

View file

@ -1,11 +1,5 @@
package mage.abilities.effects.mana;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
@ -13,10 +7,14 @@ import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.mana.ManaOptions;
import mage.constants.ColoredManaSymbol;
import mage.constants.ManaType;
import mage.constants.MultiAmountType;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.*;
import java.util.stream.Collectors;
/**
* @author LevelX2
*/
@ -27,7 +25,13 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
private final DynamicValue netAmount;
public AddManaInAnyCombinationEffect(int amount) {
this(StaticValue.get(amount), StaticValue.get(amount), ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G);
this(StaticValue.get(amount), StaticValue.get(amount),
ColoredManaSymbol.W,
ColoredManaSymbol.U,
ColoredManaSymbol.B,
ColoredManaSymbol.R,
ColoredManaSymbol.G
);
}
public AddManaInAnyCombinationEffect(int amount, ColoredManaSymbol... coloredManaSymbols) {
@ -106,26 +110,20 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
public Mana produceMana(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player != null) {
int size = manaSymbols.size();
Mana mana = new Mana();
int amountOfManaLeft = amount.calculate(game, source, this);
int maxAmount = amountOfManaLeft;
while (amountOfManaLeft > 0 && player.canRespond()) {
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
int number = player.getAmount(0, amountOfManaLeft, "Distribute mana by color (" + mana.count()
+ " of " + maxAmount + " done). How many <b>" + coloredManaSymbol.getColorHtmlName() + "</b> mana to add (enter 0 to pass to next color)?", game);
if (number > 0) {
for (int i = 0; i < number; i++) {
mana.add(new Mana(coloredManaSymbol));
}
amountOfManaLeft -= number;
}
if (amountOfManaLeft == 0) {
break;
}
List<String> manaStrings = new ArrayList<>(size);
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
manaStrings.add(coloredManaSymbol.toString());
}
List<Integer> manaList = player.getMultiAmount(this.outcome, manaStrings, 0, amount.calculate(game, source, this), MultiAmountType.MANA, game);
for (int i = 0; i < size; i++) {
ColoredManaSymbol coloredManaSymbol = manaSymbols.get(i);
int amount = manaList.get(i);
for (int j = 0; j < amount; j++) {
mana.add(new Mana(coloredManaSymbol));
}
}
return mana;
}
return null;
@ -134,7 +132,7 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
@Override
public Set<ManaType> getProducableManaTypes(Game game, Ability source) {
Set<ManaType> manaTypes = new HashSet<>();
for(ColoredManaSymbol coloredManaSymbol: manaSymbols) {
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
if (coloredManaSymbol.equals(ColoredManaSymbol.B)) {
manaTypes.add(ManaType.BLACK);
}
@ -156,7 +154,8 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
private String setText() {
StringBuilder sb = new StringBuilder("Add ");
sb.append(CardUtil.numberToText(amount.toString()));
String amountString = CardUtil.numberToText(amount.toString());
sb.append(amountString);
sb.append(" mana in any combination of ");
if (manaSymbols.size() == 5) {
sb.append("colors");
@ -170,6 +169,10 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
sb.append('{').append(coloredManaSymbol.toString()).append('}');
}
}
if (amountString.equals("X")) {
sb.append(", where X is ");
sb.append(amount.getMessage());
}
return sb.toString();
}
}

View file

@ -5,7 +5,7 @@ import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.choices.ChoiceColor;
import mage.constants.Outcome;
import mage.constants.MultiAmountType;
import mage.game.Game;
import mage.players.Player;
@ -143,18 +143,23 @@ public class DynamicManaEffect extends ManaEffect {
computedMana.setColorless(count);
} else if (baseMana.getAny() > 0) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
ChoiceColor choiceColor = new ChoiceColor(true);
for (int i = 0; i < count; i++) {
if (!choiceColor.isChosen()) {
if (!controller.choose(Outcome.Benefit, choiceColor, game)) {
return computedMana;
}
}
choiceColor.increaseMana(computedMana);
if (!oneChoice) {
choiceColor.clearChoice();
if (controller != null && count > 0) {
if (oneChoice || count == 1) {
ChoiceColor choice = new ChoiceColor(true);
controller.choose(outcome, choice, game);
if (choice.getChoice() == null) {
return computedMana;
}
computedMana.add(choice.getMana(count));
} else {
List<String> manaStrings = new ArrayList<>(5);
manaStrings.add("W");
manaStrings.add("U");
manaStrings.add("B");
manaStrings.add("R");
manaStrings.add("G");
List<Integer> choices = controller.getMultiAmount(this.outcome, manaStrings, 0, count, MultiAmountType.MANA, game);
computedMana.add(new Mana(choices.get(0), choices.get(1), choices.get(2), choices.get(3), choices.get(4), 0, 0, 0));
}
}
} else {

View file

@ -56,6 +56,9 @@ public class Choices extends ArrayList<Choice> {
public boolean choose(Game game, Ability source) {
if (this.size() > 0) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
while (!isChosen()) {
Choice choice = this.getUnchosen().get(0);
if (!player.choose(outcome, choice, game)) {

View file

@ -0,0 +1,108 @@
package mage.constants;
import com.google.common.collect.Iterables;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.IntStream;
public enum MultiAmountType {
MANA("Add mana", "Distribute mana among colors"),
DAMAGE("Assign damage", "Assign damage among targets");
private final String title;
private final String header;
MultiAmountType(String title, String header) {
this.title = title;
this.header = header;
}
public String getTitle() {
return title;
}
public String getHeader() {
return header;
}
public static List<Integer> prepareDefaltValues(int count, int min, int max) {
// default values must be assigned from first to last by minimum values
List<Integer> res = new ArrayList<>();
if (count == 0) {
return res;
}
// fill list
IntStream.range(0, count).forEach(i -> res.add(0));
// fill values
if (min > 0) {
res.set(0, min);
}
return res;
}
public static List<Integer> prepareMaxValues(int count, int min, int max) {
// fill max values as much as possible
List<Integer> res = new ArrayList<>();
if (count == 0) {
return res;
}
// fill list
int startingValue = max / count;
IntStream.range(0, count).forEach(i -> res.add(startingValue));
// fill values
// from first to last until complete
List<Integer> resIndexes = new ArrayList<>(res.size());
IntStream.range(0, res.size()).forEach(resIndexes::add);
// infinite iterator (no needs with starting values use, but can be used later for different logic)
Iterator<Integer> resIterator = Iterables.cycle(resIndexes).iterator();
int valueInc = 1;
int valueTotal = startingValue * count;
while (valueTotal < max) {
int currentIndex = resIterator.next();
int newValue = CardUtil.overflowInc(res.get(currentIndex), valueInc);
res.set(currentIndex, newValue);
valueTotal += valueInc;
}
return res;
}
public static boolean isGoodValues(List<Integer> values, int count, int min, int max) {
if (values.size() != count) {
return false;
}
int currentSum = values.stream().mapToInt(i -> i).sum();
return currentSum >= min && currentSum <= max;
}
public static List<Integer> parseAnswer(String answerToParse, int count, int min, int max, boolean returnDefaultOnError) {
List<Integer> res = new ArrayList<>();
// parse
String normalValue = answerToParse.trim();
if (!normalValue.isEmpty()) {
Arrays.stream(normalValue.split(" ")).forEach(valueStr -> {
res.add(CardUtil.parseIntWithDefault(valueStr, 0));
});
}
// data check
if (returnDefaultOnError && !isGoodValues(res, count, min, max)) {
// on broken data - return default
return prepareDefaltValues(count, min, max);
}
return res;
}
}

View file

@ -277,6 +277,8 @@ public interface Game extends MageItem, Serializable {
void fireGetAmountEvent(UUID playerId, String message, int min, int max);
void fireGetMultiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options);
void fireChoosePileEvent(UUID playerId, String message, List<? extends Card> pile1, List<? extends Card> pile2);
void fireInformEvent(String message);

View file

@ -2515,6 +2515,14 @@ public abstract class GameImpl implements Game, Serializable {
playerQueryEventSource.amount(playerId, message, min, max);
}
@Override
public void fireGetMultiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
if (simulation) {
return;
}
playerQueryEventSource.multiAmount(playerId, messages, min, max, options);
}
@Override
public void fireChooseChoiceEvent(UUID playerId, Choice choice) {
if (simulation) {

View file

@ -35,6 +35,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
PLAY_MANA,
PLAY_X_MANA,
AMOUNT,
MULTI_AMOUNT,
PICK_CARD,
CONSTRUCT,
CHOOSE_PILE,
@ -58,8 +59,11 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
private List<? extends Card> pile1;
private List<? extends Card> pile2;
private Choice choice;
private List<String> messages;
private PlayerQueryEvent(UUID playerId, String message, List<? extends Ability> abilities, Set<String> choices, Set<UUID> targets, Cards cards, QueryType queryType, int min, int max, boolean required, Map<String, Serializable> options) {
private PlayerQueryEvent(UUID playerId, String message, List<? extends Ability> abilities, Set<String> choices,
Set<UUID> targets, Cards cards, QueryType queryType, int min, int max, boolean required,
Map<String, Serializable> options, List<String> messages) {
super(playerId);
this.queryType = queryType;
this.message = message;
@ -77,6 +81,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
this.options = options;
}
this.options.put("queryType", queryType);
this.messages = messages;
}
private PlayerQueryEvent(UUID playerId, String message, List<Card> booster, QueryType queryType, int time) {
@ -143,7 +148,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
}
options.put("originalId", source.getOriginalId());
}
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.ASK, 0, 0, false, options);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.ASK, 0, 0, false, options, null);
}
public static PlayerQueryEvent chooseAbilityEvent(UUID playerId, String message, String objectName, List<? extends ActivatedAbility> choices) {
@ -152,7 +157,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
nameAsSet = new HashSet<>();
nameAsSet.add(objectName);
}
return new PlayerQueryEvent(playerId, message, choices, nameAsSet, null, null, QueryType.CHOOSE_ABILITY, 0, 0, false, null);
return new PlayerQueryEvent(playerId, message, choices, nameAsSet, null, null, QueryType.CHOOSE_ABILITY, 0, 0, false, null, null);
}
public static PlayerQueryEvent choosePileEvent(UUID playerId, String message, List<? extends Card> pile1, List<? extends Card> pile2) {
@ -168,19 +173,19 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
}
public static PlayerQueryEvent targetEvent(UUID playerId, String message, Set<UUID> targets, boolean required) {
return new PlayerQueryEvent(playerId, message, null, null, targets, null, QueryType.PICK_TARGET, 0, 0, required, null);
return new PlayerQueryEvent(playerId, message, null, null, targets, null, QueryType.PICK_TARGET, 0, 0, required, null, null);
}
public static PlayerQueryEvent targetEvent(UUID playerId, String message, Set<UUID> targets, boolean required, Map<String, Serializable> options) {
return new PlayerQueryEvent(playerId, message, null, null, targets, null, QueryType.PICK_TARGET, 0, 0, required, options);
return new PlayerQueryEvent(playerId, message, null, null, targets, null, QueryType.PICK_TARGET, 0, 0, required, options, null);
}
public static PlayerQueryEvent targetEvent(UUID playerId, String message, Cards cards, boolean required, Map<String, Serializable> options) {
return new PlayerQueryEvent(playerId, message, null, null, null, cards, QueryType.PICK_TARGET, 0, 0, required, options);
return new PlayerQueryEvent(playerId, message, null, null, null, cards, QueryType.PICK_TARGET, 0, 0, required, options, null);
}
public static PlayerQueryEvent targetEvent(UUID playerId, String message, List<TriggeredAbility> abilities) {
return new PlayerQueryEvent(playerId, message, abilities, null, null, null, QueryType.PICK_ABILITY, 0, 0, true, null);
return new PlayerQueryEvent(playerId, message, abilities, null, null, null, QueryType.PICK_ABILITY, 0, 0, true, null, null);
}
public static PlayerQueryEvent targetEvent(UUID playerId, String message, List<Permanent> perms, boolean required) {
@ -188,23 +193,27 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
}
public static PlayerQueryEvent selectEvent(UUID playerId, String message) {
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false, null);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false, null, null);
}
public static PlayerQueryEvent selectEvent(UUID playerId, String message, Map<String, Serializable> options) {
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false, options);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false, options, null);
}
public static PlayerQueryEvent playManaEvent(UUID playerId, String message, Map<String, Serializable> options) {
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.PLAY_MANA, 0, 0, false, options);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.PLAY_MANA, 0, 0, false, options, null);
}
public static PlayerQueryEvent playXManaEvent(UUID playerId, String message) {
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.PLAY_X_MANA, 0, 0, false, null);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.PLAY_X_MANA, 0, 0, false, null, null);
}
public static PlayerQueryEvent amountEvent(UUID playerId, String message, int min, int max) {
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.AMOUNT, min, max, false, null);
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.AMOUNT, min, max, false, null, null);
}
public static PlayerQueryEvent multiAmountEvent(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
return new PlayerQueryEvent(playerId, null, null, null, null, null, QueryType.MULTI_AMOUNT, min, max, false, options, messages);
}
public static PlayerQueryEvent pickCard(UUID playerId, String message, List<Card> booster, int time) {
@ -287,4 +296,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
return choice;
}
public List<String> getMessages() {
return messages;
}
}

View file

@ -84,6 +84,10 @@ public class PlayerQueryEventSource implements EventSource<PlayerQueryEvent>, Se
dispatcher.fireEvent(PlayerQueryEvent.amountEvent(playerId, message, min, max));
}
public void multiAmount(UUID playerId, List<String> messages, int min, int max, Map<String, Serializable> options) {
dispatcher.fireEvent(PlayerQueryEvent.multiAmountEvent(playerId, messages, min, max, options));
}
public void chooseChoice(UUID playerId, Choice choice) {
dispatcher.fireEvent(PlayerQueryEvent.chooseChoiceEvent(playerId, choice));
}

View file

@ -708,6 +708,19 @@ public interface Player extends MageItem, Copyable<Player> {
int getAmount(int min, int max, String message, Game game);
/**
* Player distributes amount among multiple options
*
* @param outcome AI hint
* @param messages List of options to distribute amount among
* @param min Minimum value per option
* @param max Total amount to be distributed
* @param type MultiAmountType enum to set dialog options such as title and header
* @param game Game
* @return List of integers with size equal to messages.size(). The sum of the integers is equal to max.
*/
List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game);
void sideboard(Match match, Deck deck);
void construct(Tournament tournament, Deck deck);

View file

@ -11,6 +11,7 @@ import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.decks.Deck;
import mage.choices.Choice;
import mage.constants.MultiAmountType;
import mage.constants.Outcome;
import mage.constants.RangeOfInfluence;
import mage.filter.FilterMana;
@ -205,6 +206,11 @@ public class StubPlayer extends PlayerImpl implements Player {
return 0;
}
@Override
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
return null;
}
@Override
public void sideboard(Match match, Deck deck) {

View file

@ -147,4 +147,8 @@ public interface Target extends Serializable {
String getChooseHint();
void setEventReporting(boolean shouldReport);
int getSize();
boolean contains(UUID targetId);
}

View file

@ -5,6 +5,7 @@ import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.constants.Outcome;
import mage.game.Game;
import mage.players.Player;
import java.util.*;
import java.util.stream.Collectors;
@ -59,7 +60,7 @@ public abstract class TargetAmount extends TargetImpl {
public void clearChosen() {
super.clearChosen();
amountWasSet = false;
// remainingAmount = amount;
// remainingAmount = amount; // auto-calced on target remove
}
public void setAmountDefinition(DynamicValue amount) {
@ -71,6 +72,9 @@ public abstract class TargetAmount extends TargetImpl {
amountWasSet = true;
}
public int getAmountTotal(Game game, Ability source) {
return amount.calculate(game, source, null);
}
@Override
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
@ -92,17 +96,25 @@ public abstract class TargetAmount extends TargetImpl {
@Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
Player player = game.getPlayer(playerId);
if (player == null) {
return false;
}
if (!amountWasSet) {
setAmount(source, game);
}
chosen = isChosen();
while (remainingAmount > 0) {
if (!player.canRespond()) {
return chosen;
}
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
return chosen;
}
chosen = isChosen();
}
return chosen = true;
return chosen;
}
@Override
@ -163,4 +175,12 @@ public abstract class TargetAmount extends TargetImpl {
}
}
}
public void setTargetAmount(UUID targetId, int amount, Ability source, Game game) {
if (!amountWasSet) {
setAmount(source, game);
}
remainingAmount -= (amount - this.getTargetAmount(targetId));
this.setTargetAmount(targetId, amount, game);
}
}

View file

@ -278,51 +278,60 @@ public abstract class TargetImpl implements Target {
@Override
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Game game) {
Player player = game.getPlayer(playerId);
if (player == null) {
Player targetController = getTargetController(game, playerId);
if (targetController == null) {
return false;
}
while (!isChosen() && !doneChosing()) {
if (!player.canRespond()) {
return chosen = targets.size() >= getNumberOfTargets();
chosen = targets.size() >= getNumberOfTargets();
do {
if (!targetController.canRespond()) {
return chosen;
}
chosen = targets.size() >= getNumberOfTargets();
if (!player.choose(outcome, this, sourceId, game)) {
if (!targetController.choose(outcome, this, sourceId, game)) {
return chosen;
}
chosen = targets.size() >= getNumberOfTargets();
}
return chosen = true;
} while (!isChosen() && !doneChosing());
return chosen;
}
@Override
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
Player player = game.getPlayer(playerId);
if (player == null) {
Player targetController = getTargetController(game, playerId);
if (targetController == null) {
return false;
}
List<UUID> possibleTargets = new ArrayList<>(possibleTargets(source.getSourceId(), playerId, game));
while (!isChosen() && !doneChosing()) {
if (!player.canRespond()) {
return chosen = targets.size() >= getNumberOfTargets();
chosen = targets.size() >= getNumberOfTargets();
do {
if (!targetController.canRespond()) {
return chosen;
}
chosen = targets.size() >= getNumberOfTargets();
if (isRandom()) {
if (!possibleTargets.isEmpty()) {
int index = RandomUtil.nextInt(possibleTargets.size());
this.addTarget(possibleTargets.get(index), source, game);
possibleTargets.remove(index);
} else {
if (possibleTargets.isEmpty()) {
return chosen;
}
} else if (!getTargetController(game, playerId).chooseTarget(outcome, this, source, game)) {
// find valid target
while (!possibleTargets.isEmpty()) {
int index = RandomUtil.nextInt(possibleTargets.size());
if (this.canTarget(playerId, possibleTargets.get(index), source, game)) {
this.addTarget(possibleTargets.get(index), source, game);
possibleTargets.remove(index);
break;
} else {
possibleTargets.remove(index);
}
}
} else if (!targetController.chooseTarget(outcome, this, source, game)) {
return chosen;
}
chosen = targets.size() >= getNumberOfTargets();
}
return chosen = true;
} while (!isChosen() && !doneChosing());
return chosen;
}
@Override
@ -574,4 +583,14 @@ public abstract class TargetImpl implements Target {
public void setEventReporting(boolean shouldReport) {
this.shouldReportEvents = shouldReport;
}
@Override
public int getSize() {
return targets.size();
}
@Override
public boolean contains(UUID targetId) {
return targets.containsKey(targetId);
}
}

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.constants.Outcome;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.target.targetpointer.*;
import org.apache.log4j.Logger;
@ -65,6 +66,7 @@ public class Targets extends ArrayList<Target> {
if (!canChoose(source.getSourceId(), playerId, game)) {
return false;
}
//int state = game.bookmarkState();
while (!isChosen()) {
Target target = this.getUnchosen().get(0);

View file

@ -72,21 +72,19 @@ public class TargetCardInLibrary extends TargetCard {
}
cards.sort(Comparator.comparing(MageObject::getName));
Cards cardsId = new CardsImpl();
cards.forEach((card) -> {
cardsId.add(card);
});
cards.forEach(cardsId::add);
while (!isChosen() && !doneChosing()) {
chosen = targets.size() >= getMinNumberOfTargets();
do {
if (!player.canRespond()) {
return chosen = targets.size() >= minNumberOfTargets;
return chosen;
}
chosen = targets.size() >= minNumberOfTargets;
if (!player.chooseTarget(outcome, cardsId, this, null, game)) {
return chosen;
}
chosen = targets.size() >= minNumberOfTargets;
}
return chosen = true;
chosen = targets.size() >= getMinNumberOfTargets();
} while (!isChosen() && !doneChosing());
return chosen;
}
@Override

View file

@ -1271,6 +1271,16 @@ public final class CardUtil {
return res;
}
public static int parseIntWithDefault(String value, int defaultValue) {
int res;
try {
res = Integer.parseInt(value);
} catch(NumberFormatException ex) {
res = defaultValue;
}
return res;
}
/**
* Find mapping from original to copied card (e.g. map original left side with copied left side)
*