mirror of
https://github.com/correl/mage.git
synced 2024-12-25 03:00:15 +00:00
[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:
parent
042aa61ad4
commit
600cac6fc7
35 changed files with 1209 additions and 232 deletions
|
@ -0,0 +1,103 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.2" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JInternalFrameFormInfo">
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="2"/>
|
||||
</SyntheticProperties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="12" pref="12" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="header" alignment="1" max="32767" attributes="0"/>
|
||||
<Component id="counterText" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="184" max="-2" attributes="0"/>
|
||||
<Component id="chooseButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="172" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="header" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="counterText" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="jScrollPane1" pref="276" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chooseButton" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="chooseButton">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Choose"/>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="chooseButtonActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="header">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Header"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="counterText">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="Counter"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="jPanel1">
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="413" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<EmptySpace min="0" pref="273" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
|
@ -0,0 +1,215 @@
|
|||
package mage.client.dialog;
|
||||
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import org.mage.card.arcane.ManaSymbols;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author weirddan455
|
||||
*/
|
||||
public class PickMultiNumberDialog extends MageDialog {
|
||||
|
||||
private List<JLabel> labelList = null;
|
||||
private List<JSpinner> spinnerList = null;
|
||||
|
||||
public PickMultiNumberDialog() {
|
||||
initComponents();
|
||||
this.setModal(true);
|
||||
}
|
||||
|
||||
public void showDialog(List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
this.header.setText((String) options.get("header"));
|
||||
this.header.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
this.setTitle((String) options.get("title"));
|
||||
|
||||
if (labelList != null) {
|
||||
for (JLabel label : labelList) {
|
||||
jPanel1.remove(label);
|
||||
}
|
||||
}
|
||||
if (spinnerList != null) {
|
||||
for (JSpinner spinner : spinnerList) {
|
||||
jPanel1.remove(spinner);
|
||||
}
|
||||
}
|
||||
int size = messages.size();
|
||||
labelList = new ArrayList<>(size);
|
||||
spinnerList = new ArrayList<>(size);
|
||||
jPanel1.setLayout(new GridBagLayout());
|
||||
GridBagConstraints labelC = new GridBagConstraints();
|
||||
GridBagConstraints spinnerC = new GridBagConstraints();
|
||||
for (int i = 0; i < size; i++) {
|
||||
JLabel label = new JLabel();
|
||||
|
||||
// mana mode
|
||||
String manaText = null;
|
||||
String input = messages.get(i);
|
||||
switch (input) {
|
||||
case "W":
|
||||
manaText = ColoredManaSymbol.W.getColorHtmlName();
|
||||
break;
|
||||
case "U":
|
||||
manaText = ColoredManaSymbol.U.getColorHtmlName();
|
||||
break;
|
||||
case "B":
|
||||
manaText = ColoredManaSymbol.B.getColorHtmlName();
|
||||
break;
|
||||
case "R":
|
||||
manaText = ColoredManaSymbol.R.getColorHtmlName();
|
||||
break;
|
||||
case "G":
|
||||
manaText = ColoredManaSymbol.G.getColorHtmlName();
|
||||
break;
|
||||
}
|
||||
if (manaText != null) {
|
||||
label.setText("<html>" + manaText);
|
||||
Image image = ManaSymbols.getSizedManaSymbol(input);
|
||||
if (image != null) {
|
||||
label.setIcon(new ImageIcon(image));
|
||||
}
|
||||
} else {
|
||||
// text mode
|
||||
label.setText("<html>" + input);
|
||||
}
|
||||
|
||||
labelC.weightx = 0.5;
|
||||
labelC.gridx = 0;
|
||||
labelC.gridy = i;
|
||||
jPanel1.add(label, labelC);
|
||||
labelList.add(label);
|
||||
|
||||
JSpinner spinner = new JSpinner();
|
||||
spinner.setModel(new SpinnerNumberModel(0, 0, max, 1));
|
||||
spinnerC.weightx = 0.5;
|
||||
spinnerC.gridx = 1;
|
||||
spinnerC.gridy = i;
|
||||
spinnerC.ipadx = 20;
|
||||
spinner.addChangeListener(e -> {
|
||||
updateControls(min, max);
|
||||
});
|
||||
jPanel1.add(spinner, spinnerC);
|
||||
spinnerList.add(spinner);
|
||||
}
|
||||
this.counterText.setText("0 out of 0");
|
||||
this.counterText.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
updateControls(min, max);
|
||||
|
||||
this.pack();
|
||||
this.makeWindowCentered();
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
private void updateControls(int min, int max) {
|
||||
int totalChosenAmount = 0;
|
||||
for (JSpinner jSpinner : spinnerList) {
|
||||
totalChosenAmount += ((Number) jSpinner.getValue()).intValue();
|
||||
}
|
||||
counterText.setText(totalChosenAmount + " out of " + max);
|
||||
chooseButton.setEnabled(totalChosenAmount >= min && totalChosenAmount <= max);
|
||||
}
|
||||
|
||||
public String getMultiAmount() {
|
||||
return spinnerList
|
||||
.stream()
|
||||
.map(spinner -> ((Number) spinner.getValue()).intValue())
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
chooseButton = new javax.swing.JButton();
|
||||
header = new javax.swing.JLabel();
|
||||
counterText = new javax.swing.JLabel();
|
||||
jScrollPane1 = new javax.swing.JScrollPane();
|
||||
jPanel1 = new javax.swing.JPanel();
|
||||
|
||||
chooseButton.setText("Choose");
|
||||
chooseButton.setEnabled(false);
|
||||
chooseButton.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
chooseButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
header.setText("Header");
|
||||
|
||||
counterText.setText("Counter");
|
||||
|
||||
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
|
||||
jPanel1.setLayout(jPanel1Layout);
|
||||
jPanel1Layout.setHorizontalGroup(
|
||||
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 413, Short.MAX_VALUE)
|
||||
);
|
||||
jPanel1Layout.setVerticalGroup(
|
||||
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGap(0, 273, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
jScrollPane1.setViewportView(jPanel1);
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(12, 12, 12)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(header, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(counterText, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(184, 184, 184)
|
||||
.addComponent(chooseButton)
|
||||
.addGap(0, 172, Short.MAX_VALUE))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(jScrollPane1)))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(header)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(counterText)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 276, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chooseButton)
|
||||
.addContainerGap())
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void chooseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chooseButtonActionPerformed
|
||||
this.hideDialog();
|
||||
}//GEN-LAST:event_chooseButtonActionPerformed
|
||||
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton chooseButton;
|
||||
private javax.swing.JLabel counterText;
|
||||
private javax.swing.JLabel header;
|
||||
private javax.swing.JPanel jPanel1;
|
||||
private javax.swing.JScrollPane jScrollPane1;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
|
@ -86,6 +86,7 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
GamePane gamePane;
|
||||
private ReplayTask replayTask;
|
||||
private final PickNumberDialog pickNumber;
|
||||
private final PickMultiNumberDialog pickMultiNumber;
|
||||
private JLayeredPane jLayeredPane;
|
||||
private String chosenHandKey = "You";
|
||||
private boolean smallMode = false;
|
||||
|
@ -134,6 +135,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
pickNumber = new PickNumberDialog();
|
||||
MageFrame.getDesktop().add(pickNumber, JLayeredPane.MODAL_LAYER);
|
||||
|
||||
pickMultiNumber = new PickMultiNumberDialog();
|
||||
MageFrame.getDesktop().add(pickMultiNumber, JLayeredPane.MODAL_LAYER);
|
||||
|
||||
this.feedbackPanel.setConnectedChatPanel(this.userChatPanel);
|
||||
|
||||
// Override layout (I can't edit generated code)
|
||||
|
@ -238,6 +242,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
if (pickNumber != null) {
|
||||
pickNumber.removeDialog();
|
||||
}
|
||||
if (pickMultiNumber != null) {
|
||||
pickMultiNumber.removeDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog exileDialog : exiles.values()) {
|
||||
exileDialog.cleanUp();
|
||||
exileDialog.removeDialog();
|
||||
|
@ -1617,6 +1624,11 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
public void getMultiAmount(List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
pickMultiNumber.showDialog(messages, min, max, options);
|
||||
SessionHandler.sendPlayerString(gameId, pickMultiNumber.getMultiAmount());
|
||||
}
|
||||
|
||||
public void getChoice(Choice choice, UUID objectId) {
|
||||
hideAll();
|
||||
// TODO: remember last choices and search incremental for same events?
|
||||
|
|
|
@ -297,6 +297,18 @@ public class CallbackClientImpl implements CallbackClient {
|
|||
break;
|
||||
}
|
||||
|
||||
case GAME_GET_MULTI_AMOUNT: {
|
||||
GameClientMessage message = (GameClientMessage) callback.getData();
|
||||
|
||||
GamePanel panel = MageFrame.getGame(callback.getObjectId());
|
||||
if (panel != null) {
|
||||
appendJsonEvent("GAME_GET_MULTI_AMOUNT", callback.getObjectId(), message);
|
||||
|
||||
panel.getMultiAmount(message.getMessages(), message.getMin(), message.getMax(), message.getOptions());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GAME_UPDATE: {
|
||||
GamePanel panel = MageFrame.getGame(callback.getObjectId());
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ public enum ClientCallbackMethod {
|
|||
GAME_PLAY_MANA("gamePlayMana"),
|
||||
GAME_PLAY_XMANA("gamePlayXMana"),
|
||||
GAME_GET_AMOUNT("gameSelectAmount"),
|
||||
GAME_GET_MULTI_AMOUNT("gameSelectMultiAmount"),
|
||||
DRAFT_INIT("draftInit"),
|
||||
DRAFT_PICK("draftPick"),
|
||||
DRAFT_UPDATE("draftUpdate");
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package mage.view;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
@ -42,6 +43,8 @@ public class GameClientMessage implements Serializable {
|
|||
private Map<String, Serializable> options;
|
||||
@Expose
|
||||
private Choice choice;
|
||||
@Expose
|
||||
private List<String> messages;
|
||||
|
||||
public GameClientMessage(GameView gameView) {
|
||||
this.gameView = gameView;
|
||||
|
@ -93,6 +96,13 @@ public class GameClientMessage implements Serializable {
|
|||
this.message = name;
|
||||
}
|
||||
|
||||
public GameClientMessage(List<String> messages, int min, int max, Map<String, Serializable> options) {
|
||||
this.messages = messages;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public GameClientMessage(Choice choice) {
|
||||
this.choice = choice;
|
||||
}
|
||||
|
@ -145,6 +155,10 @@ public class GameClientMessage implements Serializable {
|
|||
return choice;
|
||||
}
|
||||
|
||||
public List<String> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.excludeFieldsWithoutExposeAnnotation()
|
||||
|
|
|
@ -2028,6 +2028,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
// TODO: add AI support with outcome and replace random with min/max
|
||||
public int getAmount(int min, int max, String message, Game game) {
|
||||
log.debug("getAmount");
|
||||
if (message.startsWith("Assign damage to ")) {
|
||||
|
@ -2039,6 +2040,29 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
|||
return min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||
log.debug("getMultiAmount");
|
||||
|
||||
int needCount = messages.size();
|
||||
List<Integer> defaultList = MultiAmountType.prepareDefaltValues(needCount, min, max);
|
||||
if (needCount == 0) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
// BAD effect
|
||||
// default list uses minimum possible values, so return it on bad effect
|
||||
// TODO: need something for damage target and mana logic here, current version is useless but better than random
|
||||
if (!outcome.isGood()) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
// GOOD effect
|
||||
// values must be stable, so AI must able to simulate it and choose correct actions
|
||||
// fill max values as much as possible
|
||||
return MultiAmountType.prepareMaxValues(needCount, min, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
|
||||
//TODO: improve this
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.apache.log4j.Logger;
|
|||
import java.awt.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -668,7 +669,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
options.put("choosable", (Serializable) choosable);
|
||||
}
|
||||
|
||||
// if nothing to choose then show window (user must see non selectable items and click on any of them)
|
||||
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
|
||||
if (required && choosable.isEmpty()) {
|
||||
required = false;
|
||||
}
|
||||
|
@ -743,7 +744,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
options.put("choosable", (Serializable) choosable);
|
||||
}
|
||||
|
||||
// if nothing to choose then show window (user must see non selectable items and click on any of them)
|
||||
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
|
||||
if (required && choosable.isEmpty()) {
|
||||
required = false;
|
||||
}
|
||||
|
@ -781,6 +782,7 @@ public class HumanPlayer extends PlayerImpl {
|
|||
@Override
|
||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||
// choose amount
|
||||
// human can choose or un-choose MULTIPLE targets at once
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -790,55 +792,106 @@ public class HumanPlayer extends PlayerImpl {
|
|||
abilityControllerId = target.getAbilityController();
|
||||
}
|
||||
|
||||
int amountTotal = target.getAmountTotal(game, source);
|
||||
|
||||
// Two steps logic:
|
||||
// 1. Select targets
|
||||
// 2. Distribute amount between selected targets
|
||||
|
||||
// 1. Select targets
|
||||
while (canRespond()) {
|
||||
Set<UUID> possibleTargets = target.possibleTargets(source == null ? null : source.getSourceId(), abilityControllerId, game);
|
||||
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
|
||||
if (possibleTargets.isEmpty()
|
||||
|| target.getTargets().size() >= target.getNumberOfTargets()) {
|
||||
|| target.getSize() >= target.getNumberOfTargets()) {
|
||||
required = false;
|
||||
}
|
||||
|
||||
// selected
|
||||
Map<String, Serializable> options = getOptions(target, null);
|
||||
java.util.List<UUID> chosen = target.getTargets();
|
||||
options.put("chosen", (Serializable) chosen);
|
||||
// selectable
|
||||
java.util.List<UUID> choosable = new ArrayList<>();
|
||||
for (UUID targetId : possibleTargets) {
|
||||
if (target.canTarget(abilityControllerId, targetId, source, game)) {
|
||||
choosable.add(targetId);
|
||||
}
|
||||
}
|
||||
if (!choosable.isEmpty()) {
|
||||
options.put("choosable", (Serializable) choosable);
|
||||
}
|
||||
|
||||
// if nothing to choose then show dialog (user must see non selectable items and click on any of them)
|
||||
if (required && choosable.isEmpty()) {
|
||||
required = false;
|
||||
}
|
||||
|
||||
updateGameStatePriority("chooseTargetAmount", game);
|
||||
prepareForResponse(game);
|
||||
if (!isExecutingMacro()) {
|
||||
String selectedNames = target.getTargetedName(game);
|
||||
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()
|
||||
+ "<br> Amount remaining: " + target.getAmountRemaining()
|
||||
+ (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames),
|
||||
getRelatedObjectName(source, game)),
|
||||
possibleTargets,
|
||||
required,
|
||||
getOptions(target, null));
|
||||
// target amount uses for damage only, if you see another use case then message must be changed here and on getMultiAmount call
|
||||
String message = String.format("Select targets to distribute %d damage (selected %d)", amountTotal, target.getTargets().size());
|
||||
game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargets, required, options);
|
||||
}
|
||||
waitForResponse(game);
|
||||
|
||||
UUID responseId = getFixedResponseUUID(game);
|
||||
if (responseId != null) {
|
||||
if (target.canTarget(abilityControllerId, responseId, source, game)) {
|
||||
UUID targetId = responseId;
|
||||
MageObject targetObject = game.getObject(targetId);
|
||||
|
||||
boolean removeMode = target.getTargets().contains(targetId)
|
||||
&& chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : " target") + "?", "",
|
||||
"Remove from selected", "Add extra amount (remaining " + target.getAmountRemaining() + ")", source, game);
|
||||
|
||||
if (removeMode) {
|
||||
target.remove(targetId);
|
||||
} else {
|
||||
if (target.getAmountRemaining() > 0) {
|
||||
int amountSelected = getAmount(1, target.getAmountRemaining(), "Select amount", game);
|
||||
target.addTarget(targetId, amountSelected, source, game);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
if (target.contains(responseId)) {
|
||||
// unselect
|
||||
target.remove(responseId);
|
||||
} else if (possibleTargets.contains(responseId) && target.canTarget(abilityControllerId, responseId, source, game)) {
|
||||
// select
|
||||
target.addTarget(responseId, source, game);
|
||||
}
|
||||
} else if (!required) {
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// no targets to choose or disconnected
|
||||
List<UUID> targets = target.getTargets();
|
||||
if (targets.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Distribute amount between selected targets
|
||||
|
||||
// prepare targets list with p/t or life stats (cause that's dialog used for damage distribute)
|
||||
List<String> targetNames = new ArrayList<>();
|
||||
for (UUID targetId : targets) {
|
||||
MageObject targetObject = game.getObject(targetId);
|
||||
if (targetObject != null) {
|
||||
targetNames.add(String.format("%s, P/T: %d/%d",
|
||||
targetObject.getIdName(),
|
||||
targetObject.getPower().getValue(),
|
||||
targetObject.getToughness().getValue()
|
||||
));
|
||||
} else {
|
||||
Player player = game.getPlayer(targetId);
|
||||
if (player != null) {
|
||||
targetNames.add(String.format("%s, life: %d", player.getName(), player.getLife()));
|
||||
} else {
|
||||
targetNames.add("ERROR, unknown target " + targetId.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ask and assign new amount
|
||||
List<Integer> targetValues = getMultiAmount(outcome, targetNames, 1, amountTotal, MultiAmountType.DAMAGE, game);
|
||||
for (int i = 0; i < targetValues.size(); i++) {
|
||||
int newAmount = targetValues.get(i);
|
||||
UUID targetId = targets.get(i);
|
||||
if (newAmount <= 0) {
|
||||
// remove target
|
||||
target.remove(targetId);
|
||||
} else {
|
||||
// set amount
|
||||
target.setTargetAmount(targetId, newAmount, source, game);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1880,6 +1933,52 @@ public class HumanPlayer extends PlayerImpl {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||
int needCount = messages.size();
|
||||
List<Integer> defaultList = MultiAmountType.prepareDefaltValues(needCount, min, max);
|
||||
if (needCount == 0) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
if (gameInCheckPlayableState(game)) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
List<Integer> answer = null;
|
||||
while (canRespond()) {
|
||||
updateGameStatePriority("getMultiAmount", game);
|
||||
prepareForResponse(game);
|
||||
if (!isExecutingMacro()) {
|
||||
Map<String, Serializable> options = new HashMap<>(2);
|
||||
options.put("title", type.getTitle());
|
||||
options.put("header", type.getHeader());
|
||||
game.fireGetMultiAmountEvent(playerId, messages, min, max, options);
|
||||
}
|
||||
waitForResponse(game);
|
||||
|
||||
// waiting correct values only
|
||||
if (response.getString() != null) {
|
||||
answer = MultiAmountType.parseAnswer(response.getString(), needCount, min, max, false);
|
||||
if (MultiAmountType.isGoodValues(answer, needCount, min, max)) {
|
||||
break;
|
||||
} else {
|
||||
// it's not normal: can be cheater or a wrong GUI checks
|
||||
answer = null;
|
||||
logger.error(String.format("GUI return wrong MultiAmountType values: %d %d %d - %s", needCount, min, max, response.getString()));
|
||||
game.informPlayer(this, "Error, you must enter correct values.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (answer != null) {
|
||||
return answer;
|
||||
} else {
|
||||
// something wrong, e.g. player disconnected
|
||||
return defaultList;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sideboard(Match match, Deck deck) {
|
||||
match.fireSideboardEvent(playerId, deck);
|
||||
|
|
|
@ -219,6 +219,9 @@ public class GameController implements GameCallback {
|
|||
case AMOUNT:
|
||||
amount(event.getPlayerId(), event.getMessage(), event.getMin(), event.getMax());
|
||||
break;
|
||||
case MULTI_AMOUNT:
|
||||
multiAmount(event.getPlayerId(), event.getMessages(), event.getMin(), event.getMax(), event.getOptions());
|
||||
break;
|
||||
case PERSONAL_MESSAGE:
|
||||
informPersonal(event.getPlayerId(), event.getMessage());
|
||||
break;
|
||||
|
@ -844,6 +847,10 @@ public class GameController implements GameCallback {
|
|||
perform(playerId, playerId1 -> getGameSession(playerId1).getAmount(message, min, max));
|
||||
}
|
||||
|
||||
private synchronized void multiAmount(UUID playerId, final List<String> messages, final int min, final int max, final Map<String, Serializable> options) throws MageException {
|
||||
perform(playerId, playerId1 -> getGameSession(playerId1).getMultiAmount(messages, min, max, options));
|
||||
}
|
||||
|
||||
private void informOthers(UUID playerId) throws MageException {
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (game.getStep() != null) {
|
||||
|
|
|
@ -114,6 +114,13 @@ public class GameSessionPlayer extends GameSessionWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
public void getMultiAmount(final List<String> messages, final int min, final int max, final Map<String, Serializable> options) {
|
||||
if (!killed) {
|
||||
userManager.getUser(userId).ifPresent(user
|
||||
-> user.fireCallback(new ClientCallback(ClientCallbackMethod.GAME_GET_MULTI_AMOUNT, game.getId(), new GameClientMessage(messages, min, max, options))));
|
||||
}
|
||||
}
|
||||
|
||||
public void endGameInfo(Table table) {
|
||||
if (!killed) {
|
||||
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.END_GAME_INFO, game.getId(), getGameEndView(playerId, table))));
|
||||
|
|
|
@ -17,7 +17,6 @@ public final class Boulderfall extends CardImpl {
|
|||
public Boulderfall(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{6}{R}{R}");
|
||||
|
||||
|
||||
// Boulderfall deals 5 damage divided as you choose among any number of target creatures and/or players.
|
||||
this.getSpellAbility().addEffect(new DamageMultiEffect(5));
|
||||
this.getSpellAbility().addTarget(new TargetAnyTargetAmount(5));
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
|
||||
package mage.cards.b;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import mage.Mana;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||
import mage.abilities.effects.Effect;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana;
|
||||
import mage.abilities.effects.mana.AddManaInAnyCombinationEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.choices.Choice;
|
||||
import mage.choices.ChoiceImpl;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.ColoredManaSymbol;
|
||||
import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
|
||||
/**
|
||||
|
@ -34,7 +24,10 @@ public final class BurntOffering extends CardImpl {
|
|||
//As an additional cost to cast Burnt Offering, sacrifice a creature.
|
||||
this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT)));
|
||||
//Add an amount of {B} and/or {R} equal to the sacrificed creature's converted mana cost.
|
||||
this.getSpellAbility().addEffect(new BurntOfferingEffect());
|
||||
SacrificeCostConvertedMana xValue = new SacrificeCostConvertedMana("creature");
|
||||
this.getSpellAbility().addEffect(new AddManaInAnyCombinationEffect(
|
||||
xValue, xValue, ColoredManaSymbol.B, ColoredManaSymbol.R
|
||||
));
|
||||
}
|
||||
|
||||
private BurntOffering(final BurntOffering card) {
|
||||
|
@ -46,77 +39,3 @@ public final class BurntOffering extends CardImpl {
|
|||
return new BurntOffering(this);
|
||||
}
|
||||
}
|
||||
|
||||
class BurntOfferingEffect extends OneShotEffect {
|
||||
|
||||
public BurntOfferingEffect() {
|
||||
super(Outcome.PutManaInPool);
|
||||
this.staticText = "Add X mana in any combination of {B} and/or {R},"
|
||||
+ " where X is the sacrificed creature's converted mana cost";
|
||||
}
|
||||
|
||||
public BurntOfferingEffect(final BurntOfferingEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player player = game.getPlayer(source.getControllerId());
|
||||
if (player != null) {
|
||||
Choice manaChoice = new ChoiceImpl();
|
||||
Set<String> choices = new LinkedHashSet<>();
|
||||
choices.add("Red");
|
||||
choices.add("Black");
|
||||
manaChoice.setChoices(choices);
|
||||
manaChoice.setMessage("Select color of mana to add");
|
||||
|
||||
int xValue = getCost(source);
|
||||
|
||||
for (int i = 0; i < xValue; i++) {
|
||||
Mana mana = new Mana();
|
||||
if (!player.choose(Outcome.Benefit, manaChoice, game)) {
|
||||
return false;
|
||||
}
|
||||
if (manaChoice.getChoice() == null) { //Can happen if player leaves game
|
||||
return false;
|
||||
}
|
||||
switch (manaChoice.getChoice()) {
|
||||
case "Red":
|
||||
mana.increaseRed();
|
||||
break;
|
||||
case "Black":
|
||||
mana.increaseBlack();
|
||||
break;
|
||||
}
|
||||
player.getManaPool().addMana(mana, game, source);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Effect copy() {
|
||||
return new BurntOfferingEffect(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine the CMC of the sacrificed creature.
|
||||
*
|
||||
* @param sourceAbility
|
||||
* @return
|
||||
*/
|
||||
private int getCost(Ability sourceAbility) {
|
||||
for (Cost cost : sourceAbility.getCosts()) {
|
||||
if (cost instanceof SacrificeTargetCost) {
|
||||
SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost;
|
||||
int totalCMC = 0;
|
||||
for (Permanent permanent : sacrificeCost.getPermanents()) {
|
||||
totalCMC += permanent.getConvertedManaCost();
|
||||
}
|
||||
return totalCMC;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ public final class SelvalaHeartOfTheWilds extends CardImpl {
|
|||
}
|
||||
|
||||
private static final String rule = "Whenever another creature enters the battlefield, its controller may draw a card if its power is greater than each other creature's power.";
|
||||
private static final String rule2 = "Add X mana in any combination of colors, where X is the greatest power among creatures you control.";
|
||||
|
||||
public SelvalaHeartOfTheWilds(UUID ownerId, CardSetInfo setInfo) {
|
||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}");
|
||||
|
@ -50,8 +49,8 @@ public final class SelvalaHeartOfTheWilds extends CardImpl {
|
|||
|
||||
// {G}, {T}: Add X mana in any combination of colors, where X is the greatest power among creatures you control.
|
||||
ManaEffect manaEffect = new AddManaInAnyCombinationEffect(
|
||||
GreatestPowerAmongControlledCreaturesValue.instance, GreatestPowerAmongControlledCreaturesValue.instance, rule2,
|
||||
ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G);
|
||||
GreatestPowerAmongControlledCreaturesValue.instance, GreatestPowerAmongControlledCreaturesValue.instance,
|
||||
ColoredManaSymbol.W, ColoredManaSymbol.U, ColoredManaSymbol.B, ColoredManaSymbol.R, ColoredManaSymbol.G);
|
||||
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, manaEffect, new ManaCostsImpl("{G}"));
|
||||
ability.addCost(new TapSourceCost());
|
||||
this.addAbility(ability);
|
||||
|
|
|
@ -3,6 +3,7 @@ package org.mage.test.cards.single.dka;
|
|||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
/**
|
||||
|
@ -20,8 +21,11 @@ public class AltarOfTheLostTest extends CardTestPlayerBase {
|
|||
// Flashback {1}{B}
|
||||
addCard(Zone.GRAVEYARD, playerA, "Lingering Souls");
|
||||
|
||||
setChoice(playerA, "Black");
|
||||
setChoice(playerA, "Black");
|
||||
// Add 2 black mana (mana choice in WUBRG order)
|
||||
setChoice(playerA, "X=0");
|
||||
setChoice(playerA, "X=0");
|
||||
setChoice(playerA, "X=2");
|
||||
setChoice(playerA, TestPlayer.CHOICE_SKIP);
|
||||
|
||||
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {1}{B}");
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
|
@ -38,8 +42,10 @@ public class AltarOfTheLostTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerA, "Altar of the Lost");
|
||||
addCard(Zone.HAND, playerA, "Lingering Souls");
|
||||
|
||||
setChoice(playerA, "Black");
|
||||
setChoice(playerA, "Black");
|
||||
setChoice(playerA, "X=0");
|
||||
setChoice(playerA, "X=0");
|
||||
setChoice(playerA, "X=2");
|
||||
setChoice(playerA, TestPlayer.CHOICE_SKIP);
|
||||
|
||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lingering Souls");
|
||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
package org.mage.test.cards.targets;
|
||||
|
||||
import mage.constants.MultiAmountType;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.player.TestPlayer;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBaseWithAIHelps;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author JayDi85
|
||||
*/
|
||||
|
||||
public class TargetMultiAmountTest extends CardTestPlayerBaseWithAIHelps {
|
||||
|
||||
@Test
|
||||
public void test_DefaultValues() {
|
||||
// default values must be assigned from first to last by minimum values
|
||||
assertDefaultValues("", 0, 0, 0);
|
||||
//
|
||||
assertDefaultValues("0", 1, 0, 0);
|
||||
assertDefaultValues("0 0", 2, 0, 0);
|
||||
assertDefaultValues("0 0 0", 3, 0, 0);
|
||||
//
|
||||
assertDefaultValues("1", 1, 1, 1);
|
||||
assertDefaultValues("1 0", 2, 1, 1);
|
||||
assertDefaultValues("1 0 0", 3, 1, 1);
|
||||
//
|
||||
assertDefaultValues("1", 1, 1, 2);
|
||||
assertDefaultValues("1 0", 2, 1, 2);
|
||||
assertDefaultValues("1 0 0", 3, 1, 2);
|
||||
//
|
||||
assertDefaultValues("2", 1, 2, 2);
|
||||
assertDefaultValues("2 0", 2, 2, 2);
|
||||
assertDefaultValues("2 0 0", 3, 2, 2);
|
||||
//
|
||||
assertDefaultValues("2", 1, 2, 10);
|
||||
assertDefaultValues("2 0", 2, 2, 10);
|
||||
assertDefaultValues("2 0 0", 3, 2, 10);
|
||||
//
|
||||
// performance test
|
||||
assertDefaultValues("2 0 0", 3, 2, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void assertDefaultValues(String need, int count, int min, int max) {
|
||||
List<Integer> defaultValues = MultiAmountType.prepareDefaltValues(count, min, max);
|
||||
String current = defaultValues
|
||||
.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(" "));
|
||||
Assert.assertEquals("default values", need, current);
|
||||
Assert.assertTrue("default values must be good", MultiAmountType.isGoodValues(defaultValues, count, min, max));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_MaxValues() {
|
||||
// max possible values must be assigned from first to last by max possible values
|
||||
assertMaxValues("", 0, 0, 0);
|
||||
//
|
||||
assertMaxValues("0", 1, 0, 0);
|
||||
assertMaxValues("0 0", 2, 0, 0);
|
||||
assertMaxValues("0 0 0", 3, 0, 0);
|
||||
//
|
||||
assertMaxValues("1", 1, 1, 1);
|
||||
assertMaxValues("1 0", 2, 1, 1);
|
||||
assertMaxValues("1 0 0", 3, 1, 1);
|
||||
//
|
||||
assertMaxValues("2", 1, 1, 2);
|
||||
assertMaxValues("1 1", 2, 1, 2);
|
||||
assertMaxValues("1 1 0", 3, 1, 2);
|
||||
//
|
||||
assertMaxValues("2", 1, 2, 2);
|
||||
assertMaxValues("1 1", 2, 2, 2);
|
||||
assertMaxValues("1 1 0", 3, 2, 2);
|
||||
//
|
||||
assertMaxValues("10", 1, 2, 10);
|
||||
assertMaxValues("5 5", 2, 2, 10);
|
||||
assertMaxValues("4 3 3", 3, 2, 10);
|
||||
//
|
||||
assertMaxValues("1 1 1 1 1 0 0 0 0 0", 10, 2, 5);
|
||||
//
|
||||
// performance test
|
||||
assertMaxValues(String.valueOf(Integer.MAX_VALUE), 1, 2, Integer.MAX_VALUE);
|
||||
int part = Integer.MAX_VALUE / 3;
|
||||
String need = String.format("%d %d %d", part + 1, part, part);
|
||||
assertMaxValues(need, 3, 2, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
private void assertMaxValues(String need, int count, int min, int max) {
|
||||
List<Integer> maxValues = MultiAmountType.prepareMaxValues(count, min, max);
|
||||
String current = maxValues
|
||||
.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(" "));
|
||||
Assert.assertEquals("max values", need, current);
|
||||
Assert.assertTrue("max values must be good", MultiAmountType.isGoodValues(maxValues, count, min, max));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_GoodValues() {
|
||||
// good values are checking in test_DefaultValues, it's an additional
|
||||
List<Integer> list = MultiAmountType.prepareDefaltValues(3, 0, 0);
|
||||
|
||||
// count (0, 0, 0)
|
||||
Assert.assertFalse("count", MultiAmountType.isGoodValues(list, 0, 0, 0));
|
||||
Assert.assertFalse("count", MultiAmountType.isGoodValues(list, 1, 0, 0));
|
||||
Assert.assertFalse("count", MultiAmountType.isGoodValues(list, 2, 0, 0));
|
||||
Assert.assertTrue("count", MultiAmountType.isGoodValues(list, 3, 0, 0));
|
||||
Assert.assertFalse("count", MultiAmountType.isGoodValues(list, 4, 0, 0));
|
||||
|
||||
// min (0, 1, 1)
|
||||
list.set(0, 0);
|
||||
list.set(1, 1);
|
||||
list.set(2, 1);
|
||||
Assert.assertTrue("min", MultiAmountType.isGoodValues(list, 3, 0, 100));
|
||||
Assert.assertTrue("min", MultiAmountType.isGoodValues(list, 3, 1, 100));
|
||||
Assert.assertTrue("min", MultiAmountType.isGoodValues(list, 3, 2, 100));
|
||||
Assert.assertFalse("min", MultiAmountType.isGoodValues(list, 3, 3, 100));
|
||||
Assert.assertFalse("min", MultiAmountType.isGoodValues(list, 3, 4, 100));
|
||||
|
||||
// max (0, 1, 1)
|
||||
list.set(0, 0);
|
||||
list.set(1, 1);
|
||||
list.set(2, 1);
|
||||
Assert.assertFalse("max", MultiAmountType.isGoodValues(list, 3, 0, 0));
|
||||
Assert.assertFalse("max", MultiAmountType.isGoodValues(list, 3, 0, 1));
|
||||
Assert.assertTrue("max", MultiAmountType.isGoodValues(list, 3, 0, 2));
|
||||
Assert.assertTrue("max", MultiAmountType.isGoodValues(list, 3, 0, 3));
|
||||
Assert.assertTrue("max", MultiAmountType.isGoodValues(list, 3, 0, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Parse() {
|
||||
// parse must use correct values on good data or default values on broken data
|
||||
|
||||
// simple parse without data check
|
||||
assertParse("", 3, 1, 3, "", false);
|
||||
assertParse("1", 3, 1, 3, "1", false);
|
||||
assertParse("0 0 0", 3, 1, 3, "0 0 0", false);
|
||||
assertParse("1 0 3", 3, 1, 3, "1 0 3", false);
|
||||
assertParse("0 5 0 6", 3, 1, 3, "1,text 5 4. 6", false);
|
||||
|
||||
// parse with data check - good data
|
||||
assertParse("1 0 2", 3, 0, 3, "1 0 2", true);
|
||||
|
||||
// parse with data check - broken data (must return defalt - 1 0 0)
|
||||
assertParse("1 0 0", 3, 1, 3, "", true);
|
||||
assertParse("1 0 0", 3, 1, 3, "1", true);
|
||||
assertParse("1 0 0", 3, 1, 3, "0 0 0", true);
|
||||
assertParse("1 0 0", 3, 1, 3, "1 0 3", true);
|
||||
assertParse("1 0 0", 3, 1, 3, "1,text 4.", true);
|
||||
}
|
||||
|
||||
private void assertParse(String need, int count, int min, int max, String answerToParse, Boolean returnDefaultOnError) {
|
||||
List<Integer> parsedValues = MultiAmountType.parseAnswer(answerToParse, count, min, max, returnDefaultOnError);
|
||||
String current = parsedValues
|
||||
.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(" "));
|
||||
Assert.assertEquals("parsed values", need, current);
|
||||
if (returnDefaultOnError) {
|
||||
Assert.assertTrue("parsed values must be good", MultiAmountType.isGoodValues(parsedValues, count, min, max));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Mana_Manamorphose_Manual() {
|
||||
removeAllCardsFromHand(playerA);
|
||||
|
||||
// Add two mana in any combination of colors.
|
||||
// Draw a card.
|
||||
addCard(Zone.HAND, playerA, "Manamorphose", 2); // {1}{R/G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2 * 2);
|
||||
|
||||
// cast and select {B}{B}
|
||||
// one type of choices: wubrg order
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Manamorphose");
|
||||
setChoiceAmount(playerA, 0); // W
|
||||
setChoiceAmount(playerA, 0); // U
|
||||
setChoiceAmount(playerA, 2); // B
|
||||
setChoice(playerA, TestPlayer.CHOICE_SKIP); // skip RG
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkManaPool("after first cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "B", 2);
|
||||
|
||||
// cast and select {R}{G}
|
||||
// another type of choices
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Manamorphose");
|
||||
setChoiceAmount(playerA, 0, 0, 0, 1, 1);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
checkManaPool("after second cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "R", 1);
|
||||
checkManaPool("after second cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "G", 1);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Mana_Manamorphose_AI() {
|
||||
removeAllCardsFromHand(playerA);
|
||||
|
||||
// Add two mana in any combination of colors.
|
||||
// Draw a card.
|
||||
addCard(Zone.HAND, playerA, "Manamorphose", 1); // {1}{R/G}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
|
||||
|
||||
// cast, but AI must select first manas (WU)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Manamorphose");
|
||||
aiPlayPriority(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkManaPool("after ai cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "W", 1);
|
||||
checkManaPool("after ai cast", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "U", 1);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Damage_Boulderfall_Manual() {
|
||||
// Boulderfall deals 5 damage divided as you choose among any number of target creatures and/or players.
|
||||
addCard(Zone.HAND, playerA, "Boulderfall", 1); // {6}{R}{R}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Kitesail Corsair@bear", 3); // 2/1
|
||||
|
||||
// distribute 4x + 1x damage (kill two creatures)
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boulderfall");
|
||||
addTargetAmount(playerA, "@bear.1", 4);
|
||||
addTargetAmount(playerA, "@bear.2", 1);
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "@bear.1", 0);
|
||||
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "@bear.2", 0);
|
||||
checkPermanentCount("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "@bear.3", 1);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_Damage_Boulderfall_AI() {
|
||||
// AI don't use multi amount dialogs like human (it's just one target amount choose/simulation)
|
||||
|
||||
// Boulderfall deals 5 damage divided as you choose among any number of target creatures and/or players.
|
||||
addCard(Zone.HAND, playerA, "Boulderfall", 1); // {6}{R}{R}
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 8);
|
||||
//
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Kitesail Corsair", 6); // 2/1
|
||||
|
||||
// play card and distribute damage by game simulations for best score (kills 5x creatures)
|
||||
aiPlayStep(1, PhaseStep.PRECOMBAT_MAIN, playerA);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertAllCommandsUsed();
|
||||
|
||||
assertGraveyardCount(playerB, "Kitesail Corsair", 5);
|
||||
}
|
||||
}
|
|
@ -2667,6 +2667,51 @@ public class TestPlayer implements Player {
|
|||
return computerPlayer.getAmount(min, max, message, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||
assertAliasSupportInChoices(false);
|
||||
|
||||
int needCount = messages.size();
|
||||
List<Integer> defaultList = MultiAmountType.prepareDefaltValues(needCount, min, max);
|
||||
if (needCount == 0) {
|
||||
return defaultList;
|
||||
}
|
||||
|
||||
List<Integer> answer = new ArrayList<>(defaultList);
|
||||
if (!choices.isEmpty()) {
|
||||
// must fill all possible choices or skip it
|
||||
for (int i = 0; i < messages.size(); i++) {
|
||||
if (!choices.isEmpty()) {
|
||||
// normal choice
|
||||
if (choices.get(0).startsWith("X=")) {
|
||||
answer.set(i, Integer.parseInt(choices.get(0).substring(2)));
|
||||
choices.remove(0);
|
||||
continue;
|
||||
}
|
||||
// skip
|
||||
if (choices.get(0).equals(CHOICE_SKIP)) {
|
||||
choices.remove(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert.fail(String.format("Missing choice in multi amount: %s (pos %d - %s)", type.getHeader(), i + 1, messages.get(i)));
|
||||
}
|
||||
|
||||
// extra check
|
||||
if (!MultiAmountType.isGoodValues(answer, needCount, min, max)) {
|
||||
Assert.fail("Wrong choices in multi amount: " + answer
|
||||
.stream()
|
||||
.map(String::valueOf)
|
||||
.collect(Collectors.joining(",")));
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
this.chooseStrictModeFailed("choice", game, "Multi amount: " + type.getHeader());
|
||||
return computerPlayer.getMultiAmount(outcome, messages, min, max, type, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAbility(Ability ability) {
|
||||
computerPlayer.addAbility(ability);
|
||||
|
@ -3873,7 +3918,7 @@ public class TestPlayer implements Player {
|
|||
|
||||
Assert.assertNotEquals("chooseTargetAmount needs non zero amount remaining", 0, target.getAmountRemaining());
|
||||
|
||||
assertAliasSupportInTargets(false);
|
||||
assertAliasSupportInTargets(true);
|
||||
if (!targets.isEmpty()) {
|
||||
|
||||
// skip targets
|
||||
|
@ -3894,6 +3939,8 @@ public class TestPlayer implements Player {
|
|||
String targetName = choiceSettings[0];
|
||||
int targetAmount = Integer.parseInt(choiceSettings[1].substring("X=".length()));
|
||||
|
||||
checkTargetDefinitionMarksSupport(target, targetName, "=");
|
||||
|
||||
// player target support
|
||||
if (targetName.startsWith("targetPlayer=")) {
|
||||
targetName = targetName.substring(targetName.indexOf("targetPlayer=") + "targetPlayer=".length());
|
||||
|
@ -3905,10 +3952,21 @@ public class TestPlayer implements Player {
|
|||
|
||||
if (target.getAmountRemaining() > 0) {
|
||||
for (UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) {
|
||||
boolean foundTarget = false;
|
||||
|
||||
// permanent
|
||||
MageObject objectPermanent = game.getObject(possibleTarget);
|
||||
if (objectPermanent != null && hasObjectTargetNameOrAlias(objectPermanent, targetName)) {
|
||||
foundTarget = true;
|
||||
}
|
||||
|
||||
// player
|
||||
Player objectPlayer = game.getPlayer(possibleTarget);
|
||||
String objectName = objectPermanent != null ? objectPermanent.getName() : objectPlayer.getName();
|
||||
if (objectName.equals(targetName)) {
|
||||
if (!foundTarget && objectPlayer != null && objectPlayer.getName().equals(targetName)) {
|
||||
foundTarget = true;
|
||||
}
|
||||
|
||||
if (foundTarget) {
|
||||
if (!target.getTargets().contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) {
|
||||
// can select
|
||||
target.addTarget(possibleTarget, targetAmount, source, game);
|
||||
|
|
|
@ -1908,6 +1908,20 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup amount choices.
|
||||
*
|
||||
* Multi amount choices uses WUBRG order (so use 1,2,3,4,5 values list)
|
||||
*
|
||||
* @param player
|
||||
* @param amountList
|
||||
*/
|
||||
public void setChoiceAmount(TestPlayer player, int... amountList) {
|
||||
for (int amount : amountList) {
|
||||
setChoice(player, "X=" + amount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the modes for modal spells
|
||||
*
|
||||
|
|
|
@ -974,6 +974,11 @@ public class PlayerStub 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) {
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
108
Mage/src/main/java/mage/constants/MultiAmountType.java
Normal file
108
Mage/src/main/java/mage/constants/MultiAmountType.java
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -147,4 +147,8 @@ public interface Target extends Serializable {
|
|||
String getChooseHint();
|
||||
|
||||
void setEventReporting(boolean shouldReport);
|
||||
|
||||
int getSize();
|
||||
|
||||
boolean contains(UUID targetId);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue