mirror of
https://github.com/correl/mage.git
synced 2025-01-12 19:25:44 +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;
|
GamePane gamePane;
|
||||||
private ReplayTask replayTask;
|
private ReplayTask replayTask;
|
||||||
private final PickNumberDialog pickNumber;
|
private final PickNumberDialog pickNumber;
|
||||||
|
private final PickMultiNumberDialog pickMultiNumber;
|
||||||
private JLayeredPane jLayeredPane;
|
private JLayeredPane jLayeredPane;
|
||||||
private String chosenHandKey = "You";
|
private String chosenHandKey = "You";
|
||||||
private boolean smallMode = false;
|
private boolean smallMode = false;
|
||||||
|
@ -134,6 +135,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
||||||
pickNumber = new PickNumberDialog();
|
pickNumber = new PickNumberDialog();
|
||||||
MageFrame.getDesktop().add(pickNumber, JLayeredPane.MODAL_LAYER);
|
MageFrame.getDesktop().add(pickNumber, JLayeredPane.MODAL_LAYER);
|
||||||
|
|
||||||
|
pickMultiNumber = new PickMultiNumberDialog();
|
||||||
|
MageFrame.getDesktop().add(pickMultiNumber, JLayeredPane.MODAL_LAYER);
|
||||||
|
|
||||||
this.feedbackPanel.setConnectedChatPanel(this.userChatPanel);
|
this.feedbackPanel.setConnectedChatPanel(this.userChatPanel);
|
||||||
|
|
||||||
// Override layout (I can't edit generated code)
|
// Override layout (I can't edit generated code)
|
||||||
|
@ -238,6 +242,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
||||||
if (pickNumber != null) {
|
if (pickNumber != null) {
|
||||||
pickNumber.removeDialog();
|
pickNumber.removeDialog();
|
||||||
}
|
}
|
||||||
|
if (pickMultiNumber != null) {
|
||||||
|
pickMultiNumber.removeDialog();
|
||||||
|
}
|
||||||
for (CardInfoWindowDialog exileDialog : exiles.values()) {
|
for (CardInfoWindowDialog exileDialog : exiles.values()) {
|
||||||
exileDialog.cleanUp();
|
exileDialog.cleanUp();
|
||||||
exileDialog.removeDialog();
|
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) {
|
public void getChoice(Choice choice, UUID objectId) {
|
||||||
hideAll();
|
hideAll();
|
||||||
// TODO: remember last choices and search incremental for same events?
|
// TODO: remember last choices and search incremental for same events?
|
||||||
|
|
|
@ -297,6 +297,18 @@ public class CallbackClientImpl implements CallbackClient {
|
||||||
break;
|
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: {
|
case GAME_UPDATE: {
|
||||||
GamePanel panel = MageFrame.getGame(callback.getObjectId());
|
GamePanel panel = MageFrame.getGame(callback.getObjectId());
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ public enum ClientCallbackMethod {
|
||||||
GAME_PLAY_MANA("gamePlayMana"),
|
GAME_PLAY_MANA("gamePlayMana"),
|
||||||
GAME_PLAY_XMANA("gamePlayXMana"),
|
GAME_PLAY_XMANA("gamePlayXMana"),
|
||||||
GAME_GET_AMOUNT("gameSelectAmount"),
|
GAME_GET_AMOUNT("gameSelectAmount"),
|
||||||
|
GAME_GET_MULTI_AMOUNT("gameSelectMultiAmount"),
|
||||||
DRAFT_INIT("draftInit"),
|
DRAFT_INIT("draftInit"),
|
||||||
DRAFT_PICK("draftPick"),
|
DRAFT_PICK("draftPick"),
|
||||||
DRAFT_UPDATE("draftUpdate");
|
DRAFT_UPDATE("draftUpdate");
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package mage.view;
|
package mage.view;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -42,6 +43,8 @@ public class GameClientMessage implements Serializable {
|
||||||
private Map<String, Serializable> options;
|
private Map<String, Serializable> options;
|
||||||
@Expose
|
@Expose
|
||||||
private Choice choice;
|
private Choice choice;
|
||||||
|
@Expose
|
||||||
|
private List<String> messages;
|
||||||
|
|
||||||
public GameClientMessage(GameView gameView) {
|
public GameClientMessage(GameView gameView) {
|
||||||
this.gameView = gameView;
|
this.gameView = gameView;
|
||||||
|
@ -93,6 +96,13 @@ public class GameClientMessage implements Serializable {
|
||||||
this.message = name;
|
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) {
|
public GameClientMessage(Choice choice) {
|
||||||
this.choice = choice;
|
this.choice = choice;
|
||||||
}
|
}
|
||||||
|
@ -145,6 +155,10 @@ public class GameClientMessage implements Serializable {
|
||||||
return choice;
|
return choice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
public String toJson() {
|
public String toJson() {
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.excludeFieldsWithoutExposeAnnotation()
|
.excludeFieldsWithoutExposeAnnotation()
|
||||||
|
|
|
@ -2028,6 +2028,7 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
// TODO: add AI support with outcome and replace random with min/max
|
||||||
public int getAmount(int min, int max, String message, Game game) {
|
public int getAmount(int min, int max, String message, Game game) {
|
||||||
log.debug("getAmount");
|
log.debug("getAmount");
|
||||||
if (message.startsWith("Assign damage to ")) {
|
if (message.startsWith("Assign damage to ")) {
|
||||||
|
@ -2039,6 +2040,29 @@ public class ComputerPlayer extends PlayerImpl implements Player {
|
||||||
return min;
|
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
|
@Override
|
||||||
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
|
public UUID chooseAttackerOrder(List<Permanent> attackers, Game game) {
|
||||||
//TODO: improve this
|
//TODO: improve this
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.apache.log4j.Logger;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -668,7 +669,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("choosable", (Serializable) choosable);
|
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()) {
|
if (required && choosable.isEmpty()) {
|
||||||
required = false;
|
required = false;
|
||||||
}
|
}
|
||||||
|
@ -743,7 +744,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("choosable", (Serializable) choosable);
|
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()) {
|
if (required && choosable.isEmpty()) {
|
||||||
required = false;
|
required = false;
|
||||||
}
|
}
|
||||||
|
@ -781,6 +782,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||||
// choose amount
|
// choose amount
|
||||||
|
// human can choose or un-choose MULTIPLE targets at once
|
||||||
if (gameInCheckPlayableState(game)) {
|
if (gameInCheckPlayableState(game)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -790,55 +792,106 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
abilityControllerId = target.getAbilityController();
|
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()) {
|
while (canRespond()) {
|
||||||
Set<UUID> possibleTargets = target.possibleTargets(source == null ? null : source.getSourceId(), abilityControllerId, game);
|
Set<UUID> possibleTargets = target.possibleTargets(source == null ? null : source.getSourceId(), abilityControllerId, game);
|
||||||
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
|
boolean required = target.isRequired(source != null ? source.getSourceId() : null, game);
|
||||||
if (possibleTargets.isEmpty()
|
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;
|
required = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGameStatePriority("chooseTargetAmount", game);
|
updateGameStatePriority("chooseTargetAmount", game);
|
||||||
prepareForResponse(game);
|
prepareForResponse(game);
|
||||||
if (!isExecutingMacro()) {
|
if (!isExecutingMacro()) {
|
||||||
String selectedNames = target.getTargetedName(game);
|
// target amount uses for damage only, if you see another use case then message must be changed here and on getMultiAmount call
|
||||||
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()
|
String message = String.format("Select targets to distribute %d damage (selected %d)", amountTotal, target.getTargets().size());
|
||||||
+ "<br> Amount remaining: " + target.getAmountRemaining()
|
game.fireSelectTargetEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), possibleTargets, required, options);
|
||||||
+ (selectedNames.isEmpty() ? "" : ", selected: " + selectedNames),
|
|
||||||
getRelatedObjectName(source, game)),
|
|
||||||
possibleTargets,
|
|
||||||
required,
|
|
||||||
getOptions(target, null));
|
|
||||||
}
|
}
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
|
|
||||||
UUID responseId = getFixedResponseUUID(game);
|
UUID responseId = getFixedResponseUUID(game);
|
||||||
if (responseId != null) {
|
if (responseId != null) {
|
||||||
if (target.canTarget(abilityControllerId, responseId, source, game)) {
|
if (target.contains(responseId)) {
|
||||||
UUID targetId = responseId;
|
// unselect
|
||||||
MageObject targetObject = game.getObject(targetId);
|
target.remove(responseId);
|
||||||
|
} else if (possibleTargets.contains(responseId) && target.canTarget(abilityControllerId, responseId, source, game)) {
|
||||||
boolean removeMode = target.getTargets().contains(targetId)
|
// select
|
||||||
&& chooseUse(outcome, "What do you want to do with " + (targetObject != null ? targetObject.getLogName() : " target") + "?", "",
|
target.addTarget(responseId, source, game);
|
||||||
"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;
|
|
||||||
}
|
}
|
||||||
} else if (!required) {
|
} 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
|
@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
|
@Override
|
||||||
public void sideboard(Match match, Deck deck) {
|
public void sideboard(Match match, Deck deck) {
|
||||||
match.fireSideboardEvent(playerId, deck);
|
match.fireSideboardEvent(playerId, deck);
|
||||||
|
|
|
@ -219,6 +219,9 @@ public class GameController implements GameCallback {
|
||||||
case AMOUNT:
|
case AMOUNT:
|
||||||
amount(event.getPlayerId(), event.getMessage(), event.getMin(), event.getMax());
|
amount(event.getPlayerId(), event.getMessage(), event.getMin(), event.getMax());
|
||||||
break;
|
break;
|
||||||
|
case MULTI_AMOUNT:
|
||||||
|
multiAmount(event.getPlayerId(), event.getMessages(), event.getMin(), event.getMax(), event.getOptions());
|
||||||
|
break;
|
||||||
case PERSONAL_MESSAGE:
|
case PERSONAL_MESSAGE:
|
||||||
informPersonal(event.getPlayerId(), event.getMessage());
|
informPersonal(event.getPlayerId(), event.getMessage());
|
||||||
break;
|
break;
|
||||||
|
@ -844,6 +847,10 @@ public class GameController implements GameCallback {
|
||||||
perform(playerId, playerId1 -> getGameSession(playerId1).getAmount(message, min, max));
|
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 {
|
private void informOthers(UUID playerId) throws MageException {
|
||||||
StringBuilder message = new StringBuilder();
|
StringBuilder message = new StringBuilder();
|
||||||
if (game.getStep() != null) {
|
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) {
|
public void endGameInfo(Table table) {
|
||||||
if (!killed) {
|
if (!killed) {
|
||||||
userManager.getUser(userId).ifPresent(user -> user.fireCallback(new ClientCallback(ClientCallbackMethod.END_GAME_INFO, game.getId(), getGameEndView(playerId, table))));
|
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) {
|
public Boulderfall(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId,setInfo,new CardType[]{CardType.INSTANT},"{6}{R}{R}");
|
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.
|
// 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().addEffect(new DamageMultiEffect(5));
|
||||||
this.getSpellAbility().addTarget(new TargetAnyTargetAmount(5));
|
this.getSpellAbility().addTarget(new TargetAnyTargetAmount(5));
|
||||||
|
|
|
@ -1,25 +1,15 @@
|
||||||
|
|
||||||
package mage.cards.b;
|
package mage.cards.b;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
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.costs.common.SacrificeTargetCost;
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.dynamicvalue.common.SacrificeCostConvertedMana;
|
||||||
import mage.abilities.effects.OneShotEffect;
|
import mage.abilities.effects.mana.AddManaInAnyCombinationEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.choices.Choice;
|
|
||||||
import mage.choices.ChoiceImpl;
|
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.ColoredManaSymbol;
|
||||||
import static mage.filter.StaticFilters.FILTER_CONTROLLED_CREATURE_SHORT_TEXT;
|
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;
|
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.
|
//As an additional cost to cast Burnt Offering, sacrifice a creature.
|
||||||
this.getSpellAbility().addCost(new SacrificeTargetCost(new TargetControlledCreaturePermanent(FILTER_CONTROLLED_CREATURE_SHORT_TEXT)));
|
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.
|
//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) {
|
private BurntOffering(final BurntOffering card) {
|
||||||
|
@ -46,77 +39,3 @@ public final class BurntOffering extends CardImpl {
|
||||||
return new BurntOffering(this);
|
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 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) {
|
public SelvalaHeartOfTheWilds(UUID ownerId, CardSetInfo setInfo) {
|
||||||
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{G}");
|
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.
|
// {G}, {T}: Add X mana in any combination of colors, where X is the greatest power among creatures you control.
|
||||||
ManaEffect manaEffect = new AddManaInAnyCombinationEffect(
|
ManaEffect manaEffect = new AddManaInAnyCombinationEffect(
|
||||||
GreatestPowerAmongControlledCreaturesValue.instance, GreatestPowerAmongControlledCreaturesValue.instance, rule2,
|
GreatestPowerAmongControlledCreaturesValue.instance, GreatestPowerAmongControlledCreaturesValue.instance,
|
||||||
ColoredManaSymbol.B, ColoredManaSymbol.U, ColoredManaSymbol.R, ColoredManaSymbol.W, ColoredManaSymbol.G);
|
ColoredManaSymbol.W, ColoredManaSymbol.U, ColoredManaSymbol.B, ColoredManaSymbol.R, ColoredManaSymbol.G);
|
||||||
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, manaEffect, new ManaCostsImpl("{G}"));
|
Ability ability = new SimpleManaAbility(Zone.BATTLEFIELD, manaEffect, new ManaCostsImpl("{G}"));
|
||||||
ability.addCost(new TapSourceCost());
|
ability.addCost(new TapSourceCost());
|
||||||
this.addAbility(ability);
|
this.addAbility(ability);
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.mage.test.cards.single.dka;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mage.test.player.TestPlayer;
|
||||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,8 +21,11 @@ public class AltarOfTheLostTest extends CardTestPlayerBase {
|
||||||
// Flashback {1}{B}
|
// Flashback {1}{B}
|
||||||
addCard(Zone.GRAVEYARD, playerA, "Lingering Souls");
|
addCard(Zone.GRAVEYARD, playerA, "Lingering Souls");
|
||||||
|
|
||||||
setChoice(playerA, "Black");
|
// Add 2 black mana (mana choice in WUBRG order)
|
||||||
setChoice(playerA, "Black");
|
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}");
|
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Flashback {1}{B}");
|
||||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
||||||
|
@ -38,8 +42,10 @@ public class AltarOfTheLostTest extends CardTestPlayerBase {
|
||||||
addCard(Zone.BATTLEFIELD, playerA, "Altar of the Lost");
|
addCard(Zone.BATTLEFIELD, playerA, "Altar of the Lost");
|
||||||
addCard(Zone.HAND, playerA, "Lingering Souls");
|
addCard(Zone.HAND, playerA, "Lingering Souls");
|
||||||
|
|
||||||
setChoice(playerA, "Black");
|
setChoice(playerA, "X=0");
|
||||||
setChoice(playerA, "Black");
|
setChoice(playerA, "X=0");
|
||||||
|
setChoice(playerA, "X=2");
|
||||||
|
setChoice(playerA, TestPlayer.CHOICE_SKIP);
|
||||||
|
|
||||||
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lingering Souls");
|
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Lingering Souls");
|
||||||
setStopAt(3, PhaseStep.BEGIN_COMBAT);
|
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);
|
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
|
@Override
|
||||||
public void addAbility(Ability ability) {
|
public void addAbility(Ability ability) {
|
||||||
computerPlayer.addAbility(ability);
|
computerPlayer.addAbility(ability);
|
||||||
|
@ -3873,7 +3918,7 @@ public class TestPlayer implements Player {
|
||||||
|
|
||||||
Assert.assertNotEquals("chooseTargetAmount needs non zero amount remaining", 0, target.getAmountRemaining());
|
Assert.assertNotEquals("chooseTargetAmount needs non zero amount remaining", 0, target.getAmountRemaining());
|
||||||
|
|
||||||
assertAliasSupportInTargets(false);
|
assertAliasSupportInTargets(true);
|
||||||
if (!targets.isEmpty()) {
|
if (!targets.isEmpty()) {
|
||||||
|
|
||||||
// skip targets
|
// skip targets
|
||||||
|
@ -3894,6 +3939,8 @@ public class TestPlayer implements Player {
|
||||||
String targetName = choiceSettings[0];
|
String targetName = choiceSettings[0];
|
||||||
int targetAmount = Integer.parseInt(choiceSettings[1].substring("X=".length()));
|
int targetAmount = Integer.parseInt(choiceSettings[1].substring("X=".length()));
|
||||||
|
|
||||||
|
checkTargetDefinitionMarksSupport(target, targetName, "=");
|
||||||
|
|
||||||
// player target support
|
// player target support
|
||||||
if (targetName.startsWith("targetPlayer=")) {
|
if (targetName.startsWith("targetPlayer=")) {
|
||||||
targetName = targetName.substring(targetName.indexOf("targetPlayer=") + "targetPlayer=".length());
|
targetName = targetName.substring(targetName.indexOf("targetPlayer=") + "targetPlayer=".length());
|
||||||
|
@ -3905,10 +3952,21 @@ public class TestPlayer implements Player {
|
||||||
|
|
||||||
if (target.getAmountRemaining() > 0) {
|
if (target.getAmountRemaining() > 0) {
|
||||||
for (UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) {
|
for (UUID possibleTarget : target.possibleTargets(source.getSourceId(), source.getControllerId(), game)) {
|
||||||
|
boolean foundTarget = false;
|
||||||
|
|
||||||
|
// permanent
|
||||||
MageObject objectPermanent = game.getObject(possibleTarget);
|
MageObject objectPermanent = game.getObject(possibleTarget);
|
||||||
|
if (objectPermanent != null && hasObjectTargetNameOrAlias(objectPermanent, targetName)) {
|
||||||
|
foundTarget = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// player
|
||||||
Player objectPlayer = game.getPlayer(possibleTarget);
|
Player objectPlayer = game.getPlayer(possibleTarget);
|
||||||
String objectName = objectPermanent != null ? objectPermanent.getName() : objectPlayer.getName();
|
if (!foundTarget && objectPlayer != null && objectPlayer.getName().equals(targetName)) {
|
||||||
if (objectName.equals(targetName)) {
|
foundTarget = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundTarget) {
|
||||||
if (!target.getTargets().contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) {
|
if (!target.getTargets().contains(possibleTarget) && target.canTarget(possibleTarget, source, game)) {
|
||||||
// can select
|
// can select
|
||||||
target.addTarget(possibleTarget, targetAmount, source, game);
|
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
|
* Set the modes for modal spells
|
||||||
*
|
*
|
||||||
|
|
|
@ -974,6 +974,11 @@ public class PlayerStub implements Player {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sideboard(Match match, Deck deck) {
|
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.dynamicvalue.common.StaticValue;
|
||||||
import mage.abilities.mana.builder.ConditionalManaBuilder;
|
import mage.abilities.mana.builder.ConditionalManaBuilder;
|
||||||
import mage.choices.ChoiceColor;
|
import mage.choices.ChoiceColor;
|
||||||
|
import mage.constants.MultiAmountType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
@ -87,29 +88,26 @@ public class AddConditionalManaOfAnyColorEffect extends ManaEffect {
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
Player controller = game.getPlayer(source.getControllerId());
|
Player controller = game.getPlayer(source.getControllerId());
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
ConditionalMana mana = null;
|
|
||||||
int value = amount.calculate(game, source, this);
|
int value = amount.calculate(game, source, this);
|
||||||
ChoiceColor choice = new ChoiceColor(true);
|
if (value > 0) {
|
||||||
for (int i = 0; i < value; i++) {
|
if (oneChoice || value == 1) {
|
||||||
if (choice.getChoice() == null) {
|
ChoiceColor choice = new ChoiceColor(true);
|
||||||
controller.choose(outcome, choice, game);
|
controller.choose(outcome, choice, game);
|
||||||
}
|
if (choice.getChoice() == null) {
|
||||||
if (choice.getChoice() == null) {
|
return 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));
|
|
||||||
}
|
}
|
||||||
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();
|
return new Mana();
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package mage.abilities.effects.mana;
|
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.Mana;
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.dynamicvalue.DynamicValue;
|
import mage.abilities.dynamicvalue.DynamicValue;
|
||||||
|
@ -13,10 +7,14 @@ import mage.abilities.dynamicvalue.common.StaticValue;
|
||||||
import mage.abilities.mana.ManaOptions;
|
import mage.abilities.mana.ManaOptions;
|
||||||
import mage.constants.ColoredManaSymbol;
|
import mage.constants.ColoredManaSymbol;
|
||||||
import mage.constants.ManaType;
|
import mage.constants.ManaType;
|
||||||
|
import mage.constants.MultiAmountType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
|
@ -27,7 +25,13 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
|
||||||
private final DynamicValue netAmount;
|
private final DynamicValue netAmount;
|
||||||
|
|
||||||
public AddManaInAnyCombinationEffect(int amount) {
|
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) {
|
public AddManaInAnyCombinationEffect(int amount, ColoredManaSymbol... coloredManaSymbols) {
|
||||||
|
@ -106,26 +110,20 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
|
||||||
public Mana produceMana(Game game, Ability source) {
|
public Mana produceMana(Game game, Ability source) {
|
||||||
Player player = game.getPlayer(source.getControllerId());
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
|
int size = manaSymbols.size();
|
||||||
Mana mana = new Mana();
|
Mana mana = new Mana();
|
||||||
int amountOfManaLeft = amount.calculate(game, source, this);
|
List<String> manaStrings = new ArrayList<>(size);
|
||||||
int maxAmount = amountOfManaLeft;
|
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
|
||||||
|
manaStrings.add(coloredManaSymbol.toString());
|
||||||
while (amountOfManaLeft > 0 && player.canRespond()) {
|
}
|
||||||
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
|
List<Integer> manaList = player.getMultiAmount(this.outcome, manaStrings, 0, amount.calculate(game, source, this), MultiAmountType.MANA, game);
|
||||||
int number = player.getAmount(0, amountOfManaLeft, "Distribute mana by color (" + mana.count()
|
for (int i = 0; i < size; i++) {
|
||||||
+ " of " + maxAmount + " done). How many <b>" + coloredManaSymbol.getColorHtmlName() + "</b> mana to add (enter 0 to pass to next color)?", game);
|
ColoredManaSymbol coloredManaSymbol = manaSymbols.get(i);
|
||||||
if (number > 0) {
|
int amount = manaList.get(i);
|
||||||
for (int i = 0; i < number; i++) {
|
for (int j = 0; j < amount; j++) {
|
||||||
mana.add(new Mana(coloredManaSymbol));
|
mana.add(new Mana(coloredManaSymbol));
|
||||||
}
|
|
||||||
amountOfManaLeft -= number;
|
|
||||||
}
|
|
||||||
if (amountOfManaLeft == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mana;
|
return mana;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -134,7 +132,7 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
|
||||||
@Override
|
@Override
|
||||||
public Set<ManaType> getProducableManaTypes(Game game, Ability source) {
|
public Set<ManaType> getProducableManaTypes(Game game, Ability source) {
|
||||||
Set<ManaType> manaTypes = new HashSet<>();
|
Set<ManaType> manaTypes = new HashSet<>();
|
||||||
for(ColoredManaSymbol coloredManaSymbol: manaSymbols) {
|
for (ColoredManaSymbol coloredManaSymbol : manaSymbols) {
|
||||||
if (coloredManaSymbol.equals(ColoredManaSymbol.B)) {
|
if (coloredManaSymbol.equals(ColoredManaSymbol.B)) {
|
||||||
manaTypes.add(ManaType.BLACK);
|
manaTypes.add(ManaType.BLACK);
|
||||||
}
|
}
|
||||||
|
@ -156,7 +154,8 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
|
||||||
|
|
||||||
private String setText() {
|
private String setText() {
|
||||||
StringBuilder sb = new StringBuilder("Add ");
|
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 ");
|
sb.append(" mana in any combination of ");
|
||||||
if (manaSymbols.size() == 5) {
|
if (manaSymbols.size() == 5) {
|
||||||
sb.append("colors");
|
sb.append("colors");
|
||||||
|
@ -170,6 +169,10 @@ public class AddManaInAnyCombinationEffect extends ManaEffect {
|
||||||
sb.append('{').append(coloredManaSymbol.toString()).append('}');
|
sb.append('{').append(coloredManaSymbol.toString()).append('}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (amountString.equals("X")) {
|
||||||
|
sb.append(", where X is ");
|
||||||
|
sb.append(amount.getMessage());
|
||||||
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import mage.abilities.Ability;
|
||||||
import mage.abilities.Mode;
|
import mage.abilities.Mode;
|
||||||
import mage.abilities.dynamicvalue.DynamicValue;
|
import mage.abilities.dynamicvalue.DynamicValue;
|
||||||
import mage.choices.ChoiceColor;
|
import mage.choices.ChoiceColor;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.MultiAmountType;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
|
|
||||||
|
@ -143,18 +143,23 @@ public class DynamicManaEffect extends ManaEffect {
|
||||||
computedMana.setColorless(count);
|
computedMana.setColorless(count);
|
||||||
} else if (baseMana.getAny() > 0) {
|
} else if (baseMana.getAny() > 0) {
|
||||||
Player controller = game.getPlayer(source.getControllerId());
|
Player controller = game.getPlayer(source.getControllerId());
|
||||||
if (controller != null) {
|
if (controller != null && count > 0) {
|
||||||
ChoiceColor choiceColor = new ChoiceColor(true);
|
if (oneChoice || count == 1) {
|
||||||
for (int i = 0; i < count; i++) {
|
ChoiceColor choice = new ChoiceColor(true);
|
||||||
if (!choiceColor.isChosen()) {
|
controller.choose(outcome, choice, game);
|
||||||
if (!controller.choose(Outcome.Benefit, choiceColor, game)) {
|
if (choice.getChoice() == null) {
|
||||||
return computedMana;
|
return computedMana;
|
||||||
}
|
|
||||||
}
|
|
||||||
choiceColor.increaseMana(computedMana);
|
|
||||||
if (!oneChoice) {
|
|
||||||
choiceColor.clearChoice();
|
|
||||||
}
|
}
|
||||||
|
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 {
|
} else {
|
||||||
|
|
|
@ -56,6 +56,9 @@ public class Choices extends ArrayList<Choice> {
|
||||||
public boolean choose(Game game, Ability source) {
|
public boolean choose(Game game, Ability source) {
|
||||||
if (this.size() > 0) {
|
if (this.size() > 0) {
|
||||||
Player player = game.getPlayer(source.getControllerId());
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
while (!isChosen()) {
|
while (!isChosen()) {
|
||||||
Choice choice = this.getUnchosen().get(0);
|
Choice choice = this.getUnchosen().get(0);
|
||||||
if (!player.choose(outcome, choice, game)) {
|
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 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 fireChoosePileEvent(UUID playerId, String message, List<? extends Card> pile1, List<? extends Card> pile2);
|
||||||
|
|
||||||
void fireInformEvent(String message);
|
void fireInformEvent(String message);
|
||||||
|
|
|
@ -2515,6 +2515,14 @@ public abstract class GameImpl implements Game, Serializable {
|
||||||
playerQueryEventSource.amount(playerId, message, min, max);
|
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
|
@Override
|
||||||
public void fireChooseChoiceEvent(UUID playerId, Choice choice) {
|
public void fireChooseChoiceEvent(UUID playerId, Choice choice) {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
|
|
|
@ -35,6 +35,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
||||||
PLAY_MANA,
|
PLAY_MANA,
|
||||||
PLAY_X_MANA,
|
PLAY_X_MANA,
|
||||||
AMOUNT,
|
AMOUNT,
|
||||||
|
MULTI_AMOUNT,
|
||||||
PICK_CARD,
|
PICK_CARD,
|
||||||
CONSTRUCT,
|
CONSTRUCT,
|
||||||
CHOOSE_PILE,
|
CHOOSE_PILE,
|
||||||
|
@ -58,8 +59,11 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
||||||
private List<? extends Card> pile1;
|
private List<? extends Card> pile1;
|
||||||
private List<? extends Card> pile2;
|
private List<? extends Card> pile2;
|
||||||
private Choice choice;
|
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);
|
super(playerId);
|
||||||
this.queryType = queryType;
|
this.queryType = queryType;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
@ -77,6 +81,7 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
||||||
this.options = options;
|
this.options = options;
|
||||||
}
|
}
|
||||||
this.options.put("queryType", queryType);
|
this.options.put("queryType", queryType);
|
||||||
|
this.messages = messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayerQueryEvent(UUID playerId, String message, List<Card> booster, QueryType queryType, int time) {
|
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());
|
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) {
|
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 = new HashSet<>();
|
||||||
nameAsSet.add(objectName);
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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;
|
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));
|
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) {
|
public void chooseChoice(UUID playerId, Choice choice) {
|
||||||
dispatcher.fireEvent(PlayerQueryEvent.chooseChoiceEvent(playerId, 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);
|
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 sideboard(Match match, Deck deck);
|
||||||
|
|
||||||
void construct(Tournament tournament, Deck deck);
|
void construct(Tournament tournament, Deck deck);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import mage.cards.Card;
|
||||||
import mage.cards.Cards;
|
import mage.cards.Cards;
|
||||||
import mage.cards.decks.Deck;
|
import mage.cards.decks.Deck;
|
||||||
import mage.choices.Choice;
|
import mage.choices.Choice;
|
||||||
|
import mage.constants.MultiAmountType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.RangeOfInfluence;
|
import mage.constants.RangeOfInfluence;
|
||||||
import mage.filter.FilterMana;
|
import mage.filter.FilterMana;
|
||||||
|
@ -205,6 +206,11 @@ public class StubPlayer extends PlayerImpl implements Player {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Integer> getMultiAmount(Outcome outcome, List<String> messages, int min, int max, MultiAmountType type, Game game) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sideboard(Match match, Deck deck) {
|
public void sideboard(Match match, Deck deck) {
|
||||||
|
|
||||||
|
|
|
@ -147,4 +147,8 @@ public interface Target extends Serializable {
|
||||||
String getChooseHint();
|
String getChooseHint();
|
||||||
|
|
||||||
void setEventReporting(boolean shouldReport);
|
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.abilities.dynamicvalue.common.StaticValue;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
|
import mage.players.Player;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -59,7 +60,7 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
public void clearChosen() {
|
public void clearChosen() {
|
||||||
super.clearChosen();
|
super.clearChosen();
|
||||||
amountWasSet = false;
|
amountWasSet = false;
|
||||||
// remainingAmount = amount;
|
// remainingAmount = amount; // auto-calced on target remove
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmountDefinition(DynamicValue amount) {
|
public void setAmountDefinition(DynamicValue amount) {
|
||||||
|
@ -71,6 +72,9 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
amountWasSet = true;
|
amountWasSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getAmountTotal(Game game, Ability source) {
|
||||||
|
return amount.calculate(game, source, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
|
public void addTarget(UUID id, int amount, Ability source, Game game, boolean skipEvent) {
|
||||||
|
@ -92,17 +96,25 @@ public abstract class TargetAmount extends TargetImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||||
|
Player player = game.getPlayer(playerId);
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!amountWasSet) {
|
if (!amountWasSet) {
|
||||||
setAmount(source, game);
|
setAmount(source, game);
|
||||||
}
|
}
|
||||||
chosen = isChosen();
|
chosen = isChosen();
|
||||||
while (remainingAmount > 0) {
|
while (remainingAmount > 0) {
|
||||||
|
if (!player.canRespond()) {
|
||||||
|
return chosen;
|
||||||
|
}
|
||||||
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
if (!getTargetController(game, playerId).chooseTargetAmount(outcome, this, source, game)) {
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = isChosen();
|
chosen = isChosen();
|
||||||
}
|
}
|
||||||
return chosen = true;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
@Override
|
||||||
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Game game) {
|
public boolean choose(Outcome outcome, UUID playerId, UUID sourceId, Game game) {
|
||||||
Player player = game.getPlayer(playerId);
|
Player targetController = getTargetController(game, playerId);
|
||||||
if (player == null) {
|
if (targetController == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!isChosen() && !doneChosing()) {
|
chosen = targets.size() >= getNumberOfTargets();
|
||||||
if (!player.canRespond()) {
|
do {
|
||||||
return chosen = targets.size() >= getNumberOfTargets();
|
if (!targetController.canRespond()) {
|
||||||
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= getNumberOfTargets();
|
if (!targetController.choose(outcome, this, sourceId, game)) {
|
||||||
if (!player.choose(outcome, this, sourceId, game)) {
|
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= getNumberOfTargets();
|
chosen = targets.size() >= getNumberOfTargets();
|
||||||
}
|
} while (!isChosen() && !doneChosing());
|
||||||
return chosen = true;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
public boolean chooseTarget(Outcome outcome, UUID playerId, Ability source, Game game) {
|
||||||
Player player = game.getPlayer(playerId);
|
Player targetController = getTargetController(game, playerId);
|
||||||
if (player == null) {
|
if (targetController == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<UUID> possibleTargets = new ArrayList<>(possibleTargets(source.getSourceId(), playerId, game));
|
List<UUID> possibleTargets = new ArrayList<>(possibleTargets(source.getSourceId(), playerId, game));
|
||||||
while (!isChosen() && !doneChosing()) {
|
|
||||||
if (!player.canRespond()) {
|
chosen = targets.size() >= getNumberOfTargets();
|
||||||
return chosen = targets.size() >= getNumberOfTargets();
|
do {
|
||||||
|
if (!targetController.canRespond()) {
|
||||||
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= getNumberOfTargets();
|
|
||||||
if (isRandom()) {
|
if (isRandom()) {
|
||||||
if (!possibleTargets.isEmpty()) {
|
if (possibleTargets.isEmpty()) {
|
||||||
int index = RandomUtil.nextInt(possibleTargets.size());
|
|
||||||
this.addTarget(possibleTargets.get(index), source, game);
|
|
||||||
possibleTargets.remove(index);
|
|
||||||
} else {
|
|
||||||
return chosen;
|
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;
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= getNumberOfTargets();
|
chosen = targets.size() >= getNumberOfTargets();
|
||||||
}
|
} while (!isChosen() && !doneChosing());
|
||||||
return chosen = true;
|
|
||||||
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -574,4 +583,14 @@ public abstract class TargetImpl implements Target {
|
||||||
public void setEventReporting(boolean shouldReport) {
|
public void setEventReporting(boolean shouldReport) {
|
||||||
this.shouldReportEvents = 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.constants.Outcome;
|
||||||
import mage.game.Game;
|
import mage.game.Game;
|
||||||
import mage.game.events.GameEvent;
|
import mage.game.events.GameEvent;
|
||||||
|
import mage.players.Player;
|
||||||
import mage.target.targetpointer.*;
|
import mage.target.targetpointer.*;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ public class Targets extends ArrayList<Target> {
|
||||||
if (!canChoose(source.getSourceId(), playerId, game)) {
|
if (!canChoose(source.getSourceId(), playerId, game)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//int state = game.bookmarkState();
|
//int state = game.bookmarkState();
|
||||||
while (!isChosen()) {
|
while (!isChosen()) {
|
||||||
Target target = this.getUnchosen().get(0);
|
Target target = this.getUnchosen().get(0);
|
||||||
|
|
|
@ -72,21 +72,19 @@ public class TargetCardInLibrary extends TargetCard {
|
||||||
}
|
}
|
||||||
cards.sort(Comparator.comparing(MageObject::getName));
|
cards.sort(Comparator.comparing(MageObject::getName));
|
||||||
Cards cardsId = new CardsImpl();
|
Cards cardsId = new CardsImpl();
|
||||||
cards.forEach((card) -> {
|
cards.forEach(cardsId::add);
|
||||||
cardsId.add(card);
|
|
||||||
});
|
|
||||||
|
|
||||||
while (!isChosen() && !doneChosing()) {
|
chosen = targets.size() >= getMinNumberOfTargets();
|
||||||
|
do {
|
||||||
if (!player.canRespond()) {
|
if (!player.canRespond()) {
|
||||||
return chosen = targets.size() >= minNumberOfTargets;
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= minNumberOfTargets;
|
|
||||||
if (!player.chooseTarget(outcome, cardsId, this, null, game)) {
|
if (!player.chooseTarget(outcome, cardsId, this, null, game)) {
|
||||||
return chosen;
|
return chosen;
|
||||||
}
|
}
|
||||||
chosen = targets.size() >= minNumberOfTargets;
|
chosen = targets.size() >= getMinNumberOfTargets();
|
||||||
}
|
} while (!isChosen() && !doneChosing());
|
||||||
return chosen = true;
|
return chosen;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1271,6 +1271,16 @@ public final class CardUtil {
|
||||||
return res;
|
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)
|
* Find mapping from original to copied card (e.g. map original left side with copied left side)
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue