mirror of
https://github.com/correl/mage.git
synced 2024-11-28 11:09:54 +00:00
* Added button popup menu to be able to automatically answer ability related "Yes" / "No" requests (related to #328).
This commit is contained in:
parent
faac815ed2
commit
758f56792e
17 changed files with 555 additions and 184 deletions
|
@ -47,7 +47,9 @@ import mage.client.components.MageTextArea;
|
||||||
import mage.client.dialog.MageDialog;
|
import mage.client.dialog.MageDialog;
|
||||||
import mage.client.util.audio.AudioManager;
|
import mage.client.util.audio.AudioManager;
|
||||||
import mage.client.util.gui.ArrowBuilder;
|
import mage.client.util.gui.ArrowBuilder;
|
||||||
import mage.constants.Constants;
|
import static mage.constants.Constants.Option.ORIGINAL_ID;
|
||||||
|
import static mage.constants.Constants.Option.SECOND_MESSAGE;
|
||||||
|
import static mage.constants.Constants.Option.SPECIAL_BUTTON;
|
||||||
import mage.constants.PlayerAction;
|
import mage.constants.PlayerAction;
|
||||||
import mage.remote.Session;
|
import mage.remote.Session;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
@ -85,6 +87,7 @@ public class FeedbackPanel extends javax.swing.JPanel {
|
||||||
public void init(UUID gameId) {
|
public void init(UUID gameId) {
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
session = MageFrame.getSession();
|
session = MageFrame.getSession();
|
||||||
|
helper.init(gameId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getFeedback(FeedbackMode mode, String message, boolean special, Map<String, Serializable> options, int messageId) {
|
public void getFeedback(FeedbackMode mode, String message, boolean special, Map<String, Serializable> options, int messageId) {
|
||||||
|
@ -95,61 +98,42 @@ public class FeedbackPanel extends javax.swing.JPanel {
|
||||||
}
|
}
|
||||||
this.lastMessageId = messageId;
|
this.lastMessageId = messageId;
|
||||||
}
|
}
|
||||||
|
this.helper.setBasicMessage(message);
|
||||||
this.lblMessage.setText(message);
|
this.helper.setOriginalId(null); // reference to the feedback causing ability
|
||||||
this.helper.setMessage(message);
|
String lblText = addAdditionalText(message, options);
|
||||||
|
this.helper.setTextArea(lblText);
|
||||||
|
this.lblMessage.setText(lblText);
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
switch (this.mode) {
|
switch (this.mode) {
|
||||||
case INFORM:
|
case INFORM:
|
||||||
this.btnLeft.setVisible(false);
|
setButtonState("", "", mode);
|
||||||
this.btnRight.setVisible(false);
|
|
||||||
this.helper.setState("", false, "", false);
|
|
||||||
break;
|
break;
|
||||||
case QUESTION:
|
case QUESTION:
|
||||||
this.btnLeft.setVisible(true);
|
setButtonState("Yes", "No", mode);
|
||||||
this.btnLeft.setText("Yes");
|
if (options != null && options.containsKey(ORIGINAL_ID)) {
|
||||||
this.btnRight.setVisible(true);
|
this.helper.setOriginalId((UUID) options.get(ORIGINAL_ID));
|
||||||
this.btnRight.setText("No");
|
}
|
||||||
this.helper.setState("Yes", true, "No", true);
|
|
||||||
break;
|
break;
|
||||||
case CONFIRM:
|
case CONFIRM:
|
||||||
this.btnLeft.setVisible(true);
|
setButtonState("OK", "Cancel", mode);
|
||||||
this.btnLeft.setText("OK");
|
|
||||||
this.btnRight.setVisible(true);
|
|
||||||
this.btnRight.setText("Cancel");
|
|
||||||
this.helper.setState("Ok", true, "Cancel", true);
|
|
||||||
break;
|
break;
|
||||||
case CANCEL:
|
case CANCEL:
|
||||||
this.btnLeft.setVisible(false);
|
setButtonState("", "Cancel", mode);
|
||||||
this.btnRight.setVisible(true);
|
|
||||||
this.btnRight.setText("Cancel");
|
|
||||||
this.helper.setState("", false, "Cancel", true);
|
|
||||||
this.helper.setUndoEnabled(false);
|
this.helper.setUndoEnabled(false);
|
||||||
break;
|
break;
|
||||||
case SELECT:
|
case SELECT:
|
||||||
this.btnLeft.setVisible(false);
|
setButtonState("", "Done", mode);
|
||||||
this.btnRight.setVisible(true);
|
|
||||||
this.btnRight.setText("Done");
|
|
||||||
this.helper.setState("", false, "Done", true);
|
|
||||||
break;
|
break;
|
||||||
case END:
|
case END:
|
||||||
this.btnLeft.setVisible(false);
|
setButtonState("", "Close game", mode);
|
||||||
this.btnRight.setVisible(true);
|
|
||||||
this.btnRight.setText("Close game");
|
|
||||||
this.helper.setState("", false, "Close game", true);
|
|
||||||
ArrowBuilder.getBuilder().removeAllArrows(gameId);
|
ArrowBuilder.getBuilder().removeAllArrows(gameId);
|
||||||
endWithTimeout();
|
endWithTimeout();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (options != null && options.containsKey(Constants.Option.SPECIAL_BUTTON)) {
|
if (options != null && options.containsKey(SPECIAL_BUTTON)) {
|
||||||
String specialText = (String) options.get(Constants.Option.SPECIAL_BUTTON);
|
this.setSpecial((String) options.get(SPECIAL_BUTTON), true);
|
||||||
this.btnSpecial.setVisible(true);
|
|
||||||
this.btnSpecial.setText(specialText);
|
|
||||||
this.helper.setSpecial(specialText, true);
|
|
||||||
} else {
|
} else {
|
||||||
this.btnSpecial.setVisible(special);
|
this.setSpecial("Special", special);
|
||||||
this.btnSpecial.setText("Special");
|
|
||||||
this.helper.setSpecial("Special", special);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestFocusIfPossible();
|
requestFocusIfPossible();
|
||||||
|
@ -162,6 +146,32 @@ public class FeedbackPanel extends javax.swing.JPanel {
|
||||||
this.helper.setVisible(true);
|
this.helper.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setButtonState(String leftText, String rightText, FeedbackMode mode) {
|
||||||
|
btnLeft.setVisible(!leftText.isEmpty());
|
||||||
|
btnLeft.setText(leftText);
|
||||||
|
btnRight.setVisible(!rightText.isEmpty());
|
||||||
|
btnRight.setText(rightText);
|
||||||
|
this.helper.setState(leftText, !leftText.isEmpty(), rightText, !rightText.isEmpty(), mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String addAdditionalText(String message, Map<String, Serializable> options) {
|
||||||
|
if (options != null && options.containsKey(SECOND_MESSAGE)) {
|
||||||
|
return message + getSmallText((String) options.get(SECOND_MESSAGE));
|
||||||
|
} else {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getSmallText(String text) {
|
||||||
|
return "<div style='font-size:11pt'>" + text + "</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSpecial(String text, boolean visible) {
|
||||||
|
this.btnSpecial.setText(text);
|
||||||
|
this.btnSpecial.setVisible(visible);
|
||||||
|
this.helper.setSpecial(text, visible);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close game window by pressing OK button after 8 seconds
|
* Close game window by pressing OK button after 8 seconds
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -28,17 +28,34 @@
|
||||||
package mage.client.game;
|
package mage.client.game;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.GridBagLayout;
|
import java.awt.GridBagLayout;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.util.UUID;
|
||||||
import javax.swing.BoxLayout;
|
import javax.swing.BoxLayout;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.ToolTipManager;
|
import javax.swing.ToolTipManager;
|
||||||
import javax.swing.UIManager;
|
import javax.swing.UIManager;
|
||||||
|
import mage.client.MageFrame;
|
||||||
import mage.client.components.MageTextArea;
|
import mage.client.components.MageTextArea;
|
||||||
|
import mage.client.game.FeedbackPanel.FeedbackMode;
|
||||||
|
import static mage.client.game.FeedbackPanel.FeedbackMode.QUESTION;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_ID_NO;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_ID_YES;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_TEXT_NO;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_TEXT_YES;
|
||||||
|
import mage.remote.Session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Panel with buttons that copy the state of feedback panel.
|
* Panel with buttons that copy the state of feedback panel.
|
||||||
|
@ -64,12 +81,34 @@ public class HelperPanel extends JPanel {
|
||||||
private final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
|
private final int defaultDismissTimeout = ToolTipManager.sharedInstance().getDismissDelay();
|
||||||
private final Object tooltipBackground = UIManager.get("info");
|
private final Object tooltipBackground = UIManager.get("info");
|
||||||
|
|
||||||
|
private static final String CMD_AUTO_ANSWER_ID_YES = "cmdAutoAnswerIdYes";
|
||||||
|
private static final String CMD_AUTO_ANSWER_ID_NO = "cmdAutoAnswerIdNo";
|
||||||
|
private static final String CMD_AUTO_ANSWER_NAME_YES = "cmdAutoAnswerNameYes";
|
||||||
|
private static final String CMD_AUTO_ANSWER_NAME_NO = "cmdAutoAnswerNameNo";
|
||||||
|
private static final String CMD_AUTO_ANSWER_RESET_ALL = "cmdAutoAnswerResetAll";
|
||||||
|
|
||||||
|
// popup menu for set automatic answers
|
||||||
|
private JPopupMenu popupMenuAskYes;
|
||||||
|
private JPopupMenu popupMenuAskNo;
|
||||||
|
|
||||||
|
// originalId of feedback causing ability
|
||||||
|
private UUID originalId;
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
private UUID gameId;
|
||||||
|
private Session session;
|
||||||
|
|
||||||
public HelperPanel() {
|
public HelperPanel() {
|
||||||
initComponents();
|
initComponents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
public void init(UUID gameId) {
|
||||||
|
this.gameId = gameId;
|
||||||
|
session = MageFrame.getSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
initPopupMenuTriggerOrder();
|
||||||
setBackground(new Color(0, 0, 0, 100));
|
setBackground(new Color(0, 0, 0, 100));
|
||||||
//setLayout(new GridBagLayout());
|
//setLayout(new GridBagLayout());
|
||||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||||
|
@ -108,71 +147,49 @@ public class HelperPanel extends JPanel {
|
||||||
btnUndo.setVisible(false);
|
btnUndo.setVisible(false);
|
||||||
container.add(btnUndo);
|
container.add(btnUndo);
|
||||||
|
|
||||||
btnLeft.addActionListener(new java.awt.event.ActionListener() {
|
MouseListener checkPopupAdapter = new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mousePressed(MouseEvent me) {
|
||||||
|
checkPopupMenu(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(MouseEvent me) {
|
||||||
|
checkPopupMenu(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
btnLeft.addMouseListener(checkPopupAdapter);
|
||||||
|
btnLeft.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
if (linkLeft != null) {
|
if (linkLeft != null) {
|
||||||
{
|
clickButton(linkLeft);
|
||||||
Thread worker = new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setState("", false, "", false);
|
|
||||||
setSpecial("", false);
|
|
||||||
linkLeft.doClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
worker.start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
btnRight.addActionListener(new java.awt.event.ActionListener() {
|
btnRight.addMouseListener(checkPopupAdapter);
|
||||||
|
btnRight.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
if (linkRight != null) {
|
if (linkRight != null) {
|
||||||
Thread worker = new Thread() {
|
clickButton(linkRight);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setState("", false, "", false);
|
|
||||||
setSpecial("", false);
|
|
||||||
linkRight.doClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
worker.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
btnSpecial.addActionListener(new java.awt.event.ActionListener() {
|
btnSpecial.addActionListener(new ActionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||||
if (linkSpecial != null) {
|
if (linkSpecial != null) {
|
||||||
{
|
{
|
||||||
Thread worker = new Thread() {
|
// if (evt.getActionCommand().equals("automatic")) {
|
||||||
@Override
|
// showPopupMenu(evt);
|
||||||
public void run() {
|
// } else {
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
clickButton(linkSpecial);
|
||||||
@Override
|
// }
|
||||||
public void run() {
|
|
||||||
setState("", false, "", false);
|
|
||||||
setSpecial("", false);
|
|
||||||
linkSpecial.doClick();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
worker.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +217,7 @@ public class HelperPanel extends JPanel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// sets a darker background and higher simiss time fpr tooltip in the feedback / helper panel
|
// sets a darker background and higher simiss time fur tooltip in the feedback / helper panel
|
||||||
textArea.addMouseListener(new MouseAdapter() {
|
textArea.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -217,17 +234,52 @@ public class HelperPanel extends JPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setState(String txtLeft, boolean leftVisible, String txtRight, boolean rightVisible) {
|
private void checkPopupMenu(MouseEvent me) {
|
||||||
|
if (me.isPopupTrigger()
|
||||||
|
&& originalId != null) { // only Yes/No requests from abilities can be automated
|
||||||
|
JButton source = (JButton) me.getSource();
|
||||||
|
if (source.getActionCommand().startsWith(QUESTION.toString())) {
|
||||||
|
showPopupMenu(me.getComponent(), source.getActionCommand());
|
||||||
|
me.consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clickButton(final JButton button) {
|
||||||
|
Thread worker = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
setState("", false, "", false, null);
|
||||||
|
setSpecial("", false);
|
||||||
|
button.doClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(String txtLeft, boolean leftVisible, String txtRight, boolean rightVisible, FeedbackMode mode) {
|
||||||
this.btnLeft.setVisible(leftVisible);
|
this.btnLeft.setVisible(leftVisible);
|
||||||
if (!txtLeft.isEmpty()) {
|
if (!txtLeft.isEmpty()) {
|
||||||
this.btnLeft.setText(txtLeft);
|
this.btnLeft.setText(txtLeft);
|
||||||
|
if (mode != null) {
|
||||||
|
this.btnLeft.setActionCommand(mode.toString() + txtLeft);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.btnRight.setVisible(rightVisible);
|
this.btnRight.setVisible(rightVisible);
|
||||||
if (!txtRight.isEmpty()) {
|
if (!txtRight.isEmpty()) {
|
||||||
this.btnRight.setText(txtRight);
|
this.btnRight.setText(txtRight);
|
||||||
|
if (mode != null) {
|
||||||
|
this.btnRight.setActionCommand(mode.toString() + txtRight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void setSpecial(String txtSpecial, boolean specialVisible) {
|
public void setSpecial(String txtSpecial, boolean specialVisible) {
|
||||||
this.btnSpecial.setVisible(specialVisible);
|
this.btnSpecial.setVisible(specialVisible);
|
||||||
this.btnSpecial.setText(txtSpecial);
|
this.btnSpecial.setText(txtSpecial);
|
||||||
|
@ -251,25 +303,116 @@ public class HelperPanel extends JPanel {
|
||||||
this.linkUndo = undo;
|
this.linkUndo = undo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMessage(String message) {
|
public void setOriginalId(UUID originalId) {
|
||||||
// if (message.startsWith("Use alternative cost")) {
|
this.originalId = originalId;
|
||||||
// message = "Use alternative cost?";
|
|
||||||
// } else if (message.contains("Use ")) {
|
|
||||||
// if (message.length() < this.getWidth() / 10) {
|
|
||||||
// message = getSmallText(message);
|
|
||||||
// } else {
|
|
||||||
// message = "Use ability?" + getSmallText(message.substring(0, this.getWidth() / 10));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
textArea.setText(message, this.getWidth());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getSmallText(String text) {
|
public void setBasicMessage(String message) {
|
||||||
return "<div style='font-size:11pt'>" + text + "</div>";
|
this.message = message;
|
||||||
|
this.textArea.setText(message, this.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextArea(String message) {
|
||||||
|
this.textArea.setText(message, this.getWidth());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestFocus() {
|
public void requestFocus() {
|
||||||
this.btnRight.requestFocus();
|
this.btnRight.requestFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initPopupMenuTriggerOrder() {
|
||||||
|
|
||||||
|
ActionListener actionListener = new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
handleAutoAnswerPopupMenuEvent(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
popupMenuAskYes = new JPopupMenu();
|
||||||
|
popupMenuAskNo = new JPopupMenu();
|
||||||
|
|
||||||
|
// String tooltipText = "";
|
||||||
|
JMenuItem menuItem;
|
||||||
|
menuItem = new JMenuItem("Always Yes for the same text and ability");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_ID_YES);
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
menuItem.setToolTipText("<HTML>If the same question from the same ability would<br/>be asked again, it's automatically answered with <b>Yes</b>.");
|
||||||
|
popupMenuAskYes.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Always No for the same text and ability");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_ID_NO);
|
||||||
|
menuItem.setToolTipText("<HTML>If the same question from the same ability would<br/>be asked again, it's automatically answered with <b>No</b>.");
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
popupMenuAskNo.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Always Yes for the same text");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_NAME_YES);
|
||||||
|
menuItem.setToolTipText("<HTML>If the same question would be asked again (regardless from which source),<br/> it's automatically answered with <b>Yes</b>.");
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
popupMenuAskYes.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Always No for the same text");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_NAME_NO);
|
||||||
|
menuItem.setToolTipText("<HTML>If the same question would be asked again (regardless from which source),<br/> it's automatically answered with <b>No</b>.");
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
popupMenuAskNo.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Delete all automatic Yes/No settings");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_RESET_ALL);
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
popupMenuAskYes.add(menuItem);
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Delete all automatic Yes/No settings");
|
||||||
|
menuItem.setActionCommand(CMD_AUTO_ANSWER_RESET_ALL);
|
||||||
|
menuItem.addActionListener(actionListener);
|
||||||
|
popupMenuAskNo.add(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleAutoAnswerPopupMenuEvent(ActionEvent e) {
|
||||||
|
switch (e.getActionCommand()) {
|
||||||
|
case CMD_AUTO_ANSWER_ID_YES:
|
||||||
|
session.sendPlayerAction(REQUEST_AUTO_ANSWER_ID_YES, gameId, originalId.toString() + "#" + message);
|
||||||
|
clickButton(btnLeft);
|
||||||
|
break;
|
||||||
|
case CMD_AUTO_ANSWER_ID_NO:
|
||||||
|
session.sendPlayerAction(REQUEST_AUTO_ANSWER_ID_NO, gameId, originalId.toString() + "#" + message);
|
||||||
|
clickButton(btnRight);
|
||||||
|
break;
|
||||||
|
case CMD_AUTO_ANSWER_NAME_YES:
|
||||||
|
session.sendPlayerAction(REQUEST_AUTO_ANSWER_TEXT_YES, gameId, message);
|
||||||
|
clickButton(btnLeft);
|
||||||
|
break;
|
||||||
|
case CMD_AUTO_ANSWER_NAME_NO:
|
||||||
|
session.sendPlayerAction(REQUEST_AUTO_ANSWER_TEXT_NO, gameId, message);
|
||||||
|
clickButton(btnRight);
|
||||||
|
break;
|
||||||
|
case CMD_AUTO_ANSWER_RESET_ALL:
|
||||||
|
session.sendPlayerAction(REQUEST_AUTO_ANSWER_RESET_ALL, gameId, null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPopupMenu(Component callingComponent, String actionCommand) {
|
||||||
|
// Get the location of the point 'on the screen'
|
||||||
|
Point p = callingComponent.getLocationOnScreen();
|
||||||
|
// Show the JPopupMenu via program
|
||||||
|
// Parameter desc
|
||||||
|
// ----------------
|
||||||
|
// this - represents current frame
|
||||||
|
// 0,0 is the co ordinate where the popup
|
||||||
|
// is shown
|
||||||
|
JPopupMenu menu;
|
||||||
|
if (actionCommand.endsWith("Yes")) {
|
||||||
|
menu = popupMenuAskYes;
|
||||||
|
} else {
|
||||||
|
menu = popupMenuAskNo;
|
||||||
|
}
|
||||||
|
menu.show(this, 0, 0);
|
||||||
|
|
||||||
|
// Now set the location of the JPopupMenu
|
||||||
|
// This location is relative to the screen
|
||||||
|
menu.setLocation(p.x, p.y + callingComponent.getHeight());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -290,6 +290,18 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
menuItem = new JMenuItem("Use requests - reset automatic answers");
|
||||||
|
menuItem.setMnemonic(KeyEvent.VK_T);
|
||||||
|
menuItem.setToolTipText("Deletes all defined automatic answers for Yes/No usage requests.");
|
||||||
|
automaticConfirmsMenu.add(menuItem);
|
||||||
|
// Reset the replacement effcts that were auto selected for the game
|
||||||
|
menuItem.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
gamePanel.getSession().sendPlayerAction(PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL, gameId, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
JMenu handCardsMenu = new JMenu("Cards on hand");
|
JMenu handCardsMenu = new JMenu("Cards on hand");
|
||||||
handCardsMenu.setMnemonic(KeyEvent.VK_H);
|
handCardsMenu.setMnemonic(KeyEvent.VK_H);
|
||||||
popupMenu.add(handCardsMenu);
|
popupMenu.add(handCardsMenu);
|
||||||
|
|
|
@ -83,6 +83,10 @@ public final class Constants {
|
||||||
|
|
||||||
public static final String POSSIBLE_ATTACKERS = "possibleAttackers";
|
public static final String POSSIBLE_ATTACKERS = "possibleAttackers";
|
||||||
public static final String SPECIAL_BUTTON = "specialButton";
|
public static final String SPECIAL_BUTTON = "specialButton";
|
||||||
|
// used to control automatic answers of optional effects
|
||||||
|
public static final String ORIGINAL_ID = "originalId";
|
||||||
|
public static final String SECOND_MESSAGE = "secondMessage";
|
||||||
|
public static final String HINT_TEXT = "hintText";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ import mage.constants.ManaType;
|
||||||
import mage.constants.Outcome;
|
import mage.constants.Outcome;
|
||||||
import mage.constants.PhaseStep;
|
import mage.constants.PhaseStep;
|
||||||
import mage.constants.PlayerAction;
|
import mage.constants.PlayerAction;
|
||||||
|
import static mage.constants.PlayerAction.REQUEST_AUTO_ANSWER_RESET_ALL;
|
||||||
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
import static mage.constants.PlayerAction.TRIGGER_AUTO_ORDER_RESET_ALL;
|
||||||
import mage.constants.RangeOfInfluence;
|
import mage.constants.RangeOfInfluence;
|
||||||
import mage.constants.Zone;
|
import mage.constants.Zone;
|
||||||
|
@ -89,6 +90,7 @@ import mage.target.common.TargetCreatureOrPlayer;
|
||||||
import mage.target.common.TargetDefender;
|
import mage.target.common.TargetDefender;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
import mage.util.ManaUtil;
|
import mage.util.ManaUtil;
|
||||||
|
import mage.util.MessageToClient;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -115,6 +117,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
protected Set<String> triggerAutoOrderNameFirst = new HashSet<>();
|
protected Set<String> triggerAutoOrderNameFirst = new HashSet<>();
|
||||||
protected Set<String> triggerAutoOrderNameLast = new HashSet<>();
|
protected Set<String> triggerAutoOrderNameLast = new HashSet<>();
|
||||||
|
|
||||||
|
protected Map<String, Boolean> requestAutoAnswerId = new HashMap<>();
|
||||||
|
protected Map<String, Boolean> requestAutoAnswerText = new HashMap<>();
|
||||||
|
|
||||||
public HumanPlayer(String name, RangeOfInfluence range, int skill) {
|
public HumanPlayer(String name, RangeOfInfluence range, int skill) {
|
||||||
super(name, range);
|
super(name, range);
|
||||||
replacementEffectChoice = new ChoiceImpl(true);
|
replacementEffectChoice = new ChoiceImpl(true);
|
||||||
|
@ -173,10 +178,9 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
public boolean chooseMulligan(Game game) {
|
public boolean chooseMulligan(Game game) {
|
||||||
updateGameStatePriority("chooseMulligan", game);
|
updateGameStatePriority("chooseMulligan", game);
|
||||||
int nextHandSize = game.mulliganDownTo(playerId);
|
int nextHandSize = game.mulliganDownTo(playerId);
|
||||||
game.fireAskPlayerEvent(playerId, new StringBuilder("Mulligan ")
|
game.fireAskPlayerEvent(playerId, new MessageToClient("Mulligan "
|
||||||
.append(getHand().size() > nextHandSize ? "down to " : "for free, draw ")
|
+ (getHand().size() > nextHandSize ? "down to " : "for free, draw ")
|
||||||
.append(nextHandSize)
|
+ nextHandSize + (nextHandSize == 1 ? " card?" : " cards?")), null);
|
||||||
.append(nextHandSize == 1 ? " card?" : " cards?").toString());
|
|
||||||
waitForBooleanResponse(game);
|
waitForBooleanResponse(game);
|
||||||
if (!abort) {
|
if (!abort) {
|
||||||
return response.getBoolean();
|
return response.getBoolean();
|
||||||
|
@ -186,8 +190,19 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) {
|
public boolean chooseUse(Outcome outcome, String message, Ability source, Game game) {
|
||||||
|
if (source != null) {
|
||||||
|
Boolean answer = requestAutoAnswerId.get(source.getOriginalId() + "#" + message);
|
||||||
|
if (answer != null) {
|
||||||
|
return answer;
|
||||||
|
} else {
|
||||||
|
answer = requestAutoAnswerText.get(message);
|
||||||
|
if (answer != null) {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
updateGameStatePriority("chooseUse", game);
|
updateGameStatePriority("chooseUse", game);
|
||||||
game.fireAskPlayerEvent(playerId, addSecondLineWithObjectName(message, source == null ? null : source.getSourceId(), game));
|
game.fireAskPlayerEvent(playerId, new MessageToClient(message, getRelatedObjectName(source, game)), source);
|
||||||
waitForBooleanResponse(game);
|
waitForBooleanResponse(game);
|
||||||
if (!abort) {
|
if (!abort) {
|
||||||
return response.getBoolean();
|
return response.getBoolean();
|
||||||
|
@ -195,6 +210,21 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getRelatedObjectName(Ability source, Game game) {
|
||||||
|
if (source != null) {
|
||||||
|
return getRelatedObjectName(source.getSourceId(), game);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getRelatedObjectName(UUID sourceId, Game game) {
|
||||||
|
MageObject mageObject = game.getObject(sourceId);
|
||||||
|
if (mageObject != null) {
|
||||||
|
return mageObject.getLogName();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private String addSecondLineWithObjectName(String message, UUID sourceId, Game game) {
|
private String addSecondLineWithObjectName(String message, UUID sourceId, Game game) {
|
||||||
if (sourceId != null) {
|
if (sourceId != null) {
|
||||||
MageObject mageObject = game.getPermanent(sourceId);
|
MageObject mageObject = game.getPermanent(sourceId);
|
||||||
|
@ -304,7 +334,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
List<UUID> chosen = target.getTargets();
|
List<UUID> chosen = target.getTargets();
|
||||||
options.put("chosen", (Serializable) chosen);
|
options.put("chosen", (Serializable) chosen);
|
||||||
|
|
||||||
game.fireSelectTargetEvent(getId(), addSecondLineWithObjectName(target.getMessage(), sourceId, game), targetIds, required, getOptions(target, options));
|
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(sourceId, game)), targetIds, required, getOptions(target, options));
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
if (response.getUUID() != null) {
|
if (response.getUUID() != null) {
|
||||||
if (!targetIds.contains(response.getUUID())) {
|
if (!targetIds.contains(response.getUUID())) {
|
||||||
|
@ -370,7 +400,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
required = false;
|
required = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
game.fireSelectTargetEvent(getId(), addSecondLineWithObjectName(target.getMessage(), source == null ? null : source.getSourceId(), game), possibleTargets, required, getOptions(target, null));
|
game.fireSelectTargetEvent(getId(), new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), possibleTargets, required, getOptions(target, null));
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
if (response.getUUID() != null) {
|
if (response.getUUID() != null) {
|
||||||
if (target.getTargets().contains(response.getUUID())) {
|
if (target.getTargets().contains(response.getUUID())) {
|
||||||
|
@ -438,7 +468,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
options.put("choosable", (Serializable) choosable);
|
options.put("choosable", (Serializable) choosable);
|
||||||
}
|
}
|
||||||
|
|
||||||
game.fireSelectTargetEvent(playerId, target.getMessage(), cards, required, options);
|
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage()), cards, required, options);
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
if (response.getUUID() != null) {
|
if (response.getUUID() != null) {
|
||||||
if (target.canTarget(response.getUUID(), cards, game)) {
|
if (target.canTarget(response.getUUID(), cards, game)) {
|
||||||
|
@ -492,7 +522,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
if (!choosable.isEmpty()) {
|
if (!choosable.isEmpty()) {
|
||||||
options.put("choosable", (Serializable) choosable);
|
options.put("choosable", (Serializable) choosable);
|
||||||
}
|
}
|
||||||
game.fireSelectTargetEvent(playerId, addSecondLineWithObjectName(target.getMessage(), source == null ? null : source.getSourceId(), game), cards, required, options);
|
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage(), getRelatedObjectName(source, game)), cards, required, options);
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
if (response.getUUID() != null) {
|
if (response.getUUID() != null) {
|
||||||
if (target.getTargets().contains(response.getUUID())) { // if already included remove it
|
if (target.getTargets().contains(response.getUUID())) { // if already included remove it
|
||||||
|
@ -521,7 +551,7 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
public boolean chooseTargetAmount(Outcome outcome, TargetAmount target, Ability source, Game game) {
|
||||||
updateGameStatePriority("chooseTargetAmount", game);
|
updateGameStatePriority("chooseTargetAmount", game);
|
||||||
while (!abort) {
|
while (!abort) {
|
||||||
game.fireSelectTargetEvent(playerId, addSecondLineWithObjectName(target.getMessage() + "\n Amount remaining:" + target.getAmountRemaining(), source == null ? null : source.getSourceId(), game),
|
game.fireSelectTargetEvent(playerId, new MessageToClient(target.getMessage() + "\n Amount remaining:" + target.getAmountRemaining(), getRelatedObjectName(source, game)),
|
||||||
target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game),
|
target.possibleTargets(source == null ? null : source.getSourceId(), playerId, game),
|
||||||
target.isRequired(source),
|
target.isRequired(source),
|
||||||
getOptions(target, null));
|
getOptions(target, null));
|
||||||
|
@ -1043,7 +1073,8 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
protected void selectCombatGroup(UUID defenderId, UUID blockerId, Game game) {
|
protected void selectCombatGroup(UUID defenderId, UUID blockerId, Game game) {
|
||||||
updateGameStatePriority("selectCombatGroup", game);
|
updateGameStatePriority("selectCombatGroup", game);
|
||||||
TargetAttackingCreature target = new TargetAttackingCreature();
|
TargetAttackingCreature target = new TargetAttackingCreature();
|
||||||
game.fireSelectTargetEvent(playerId, addSecondLineWithObjectName("Select attacker to block", blockerId, game), target.possibleTargets(null, playerId, game), false, getOptions(target, null));
|
game.fireSelectTargetEvent(playerId, new MessageToClient("Select attacker to block", getRelatedObjectName(blockerId, game)),
|
||||||
|
target.possibleTargets(null, playerId, game), false, getOptions(target, null));
|
||||||
waitForResponse(game);
|
waitForResponse(game);
|
||||||
if (response.getBoolean() != null) {
|
if (response.getBoolean() != null) {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@ -1354,11 +1385,43 @@ public class HumanPlayer extends PlayerImpl {
|
||||||
case TRIGGER_AUTO_ORDER_RESET_ALL:
|
case TRIGGER_AUTO_ORDER_RESET_ALL:
|
||||||
setTriggerAutoOrder(playerAction, game, data);
|
setTriggerAutoOrder(playerAction, game, data);
|
||||||
break;
|
break;
|
||||||
|
case REQUEST_AUTO_ANSWER_ID_NO:
|
||||||
|
case REQUEST_AUTO_ANSWER_ID_YES:
|
||||||
|
case REQUEST_AUTO_ANSWER_TEXT_NO:
|
||||||
|
case REQUEST_AUTO_ANSWER_TEXT_YES:
|
||||||
|
case REQUEST_AUTO_ANSWER_RESET_ALL:
|
||||||
|
setRequestAutoAnswer(playerAction, game, data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
super.sendPlayerAction(playerAction, game, data);
|
super.sendPlayerAction(playerAction, game, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setRequestAutoAnswer(PlayerAction playerAction, Game game, Object data) {
|
||||||
|
if (playerAction.equals(REQUEST_AUTO_ANSWER_RESET_ALL)) {
|
||||||
|
requestAutoAnswerId.clear();
|
||||||
|
requestAutoAnswerText.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data instanceof String) {
|
||||||
|
String key = (String) data;
|
||||||
|
switch (playerAction) {
|
||||||
|
case REQUEST_AUTO_ANSWER_ID_NO:
|
||||||
|
requestAutoAnswerId.put(key, false);
|
||||||
|
break;
|
||||||
|
case REQUEST_AUTO_ANSWER_TEXT_NO:
|
||||||
|
requestAutoAnswerText.put(key, false);
|
||||||
|
break;
|
||||||
|
case REQUEST_AUTO_ANSWER_ID_YES:
|
||||||
|
requestAutoAnswerId.put(key, true);
|
||||||
|
break;
|
||||||
|
case REQUEST_AUTO_ANSWER_TEXT_YES:
|
||||||
|
requestAutoAnswerText.put(key, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setTriggerAutoOrder(PlayerAction playerAction, Game game, Object data) {
|
private void setTriggerAutoOrder(PlayerAction playerAction, Game game, Object data) {
|
||||||
if (playerAction.equals(TRIGGER_AUTO_ORDER_RESET_ALL)) {
|
if (playerAction.equals(TRIGGER_AUTO_ORDER_RESET_ALL)) {
|
||||||
triggerAutoOrderAbilityFirst.clear();
|
triggerAutoOrderAbilityFirst.clear();
|
||||||
|
|
|
@ -228,7 +228,7 @@ public class GameController implements GameCallback {
|
||||||
try {
|
try {
|
||||||
switch (event.getQueryType()) {
|
switch (event.getQueryType()) {
|
||||||
case ASK:
|
case ASK:
|
||||||
ask(event.getPlayerId(), event.getMessage());
|
ask(event.getPlayerId(), event.getMessage(), event.getOptions());
|
||||||
break;
|
break;
|
||||||
case PICK_TARGET:
|
case PICK_TARGET:
|
||||||
target(event.getPlayerId(), event.getMessage(), event.getCards(), event.getPerms(), event.getTargets(), event.isRequired(), event.getOptions());
|
target(event.getPlayerId(), event.getMessage(), event.getCards(), event.getPerms(), event.getTargets(), event.isRequired(), event.getOptions());
|
||||||
|
@ -774,11 +774,11 @@ public class GameController implements GameCallback {
|
||||||
// TODO: inform watchers about game end and who won
|
// TODO: inform watchers about game end and who won
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void ask(UUID playerId, final String question) throws MageException {
|
private synchronized void ask(UUID playerId, final String question, final Map<String, Serializable> options) throws MageException {
|
||||||
perform(playerId, new Command() {
|
perform(playerId, new Command() {
|
||||||
@Override
|
@Override
|
||||||
public void execute(UUID playerId) {
|
public void execute(UUID playerId) {
|
||||||
getGameSession(playerId).ask(question);
|
getGameSession(playerId).ask(question, options);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -78,11 +78,11 @@ public class GameSessionPlayer extends GameSessionWatcher {
|
||||||
super.CleanUp();
|
super.CleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ask(final String question) {
|
public void ask(final String question, final Map<String, Serializable> options) {
|
||||||
if (!killed) {
|
if (!killed) {
|
||||||
User user = UserManager.getInstance().getUser(userId);
|
User user = UserManager.getInstance().getUser(userId);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
user.fireCallback(new ClientCallback("gameAsk", game.getId(), new GameClientMessage(getGameView(), question)));
|
user.fireCallback(new ClientCallback("gameAsk", game.getId(), new GameClientMessage(getGameView(), question, options)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,20 +25,17 @@
|
||||||
* authors and should not be interpreted as representing official policies, either expressed
|
* authors and should not be interpreted as representing official policies, either expressed
|
||||||
* or implied, of BetaSteward_at_googlemail.com.
|
* or implied, of BetaSteward_at_googlemail.com.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package mage.sets.tenthedition;
|
package mage.sets.tenthedition;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import mage.abilities.TriggeredAbilityImpl;
|
import mage.ObjectColor;
|
||||||
|
import mage.abilities.common.SpellCastAllTriggeredAbility;
|
||||||
import mage.abilities.effects.common.GainLifeEffect;
|
import mage.abilities.effects.common.GainLifeEffect;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Rarity;
|
import mage.constants.Rarity;
|
||||||
import mage.constants.Zone;
|
import mage.filter.FilterSpell;
|
||||||
import mage.game.Game;
|
import mage.filter.predicate.mageobject.ColorPredicate;
|
||||||
import mage.game.events.GameEvent;
|
|
||||||
import mage.game.events.GameEvent.EventType;
|
|
||||||
import mage.game.stack.Spell;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -46,10 +43,18 @@ import mage.game.stack.Spell;
|
||||||
*/
|
*/
|
||||||
public class AngelsFeather extends CardImpl {
|
public class AngelsFeather extends CardImpl {
|
||||||
|
|
||||||
|
private final static FilterSpell filter = new FilterSpell("a white spell");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(new ColorPredicate(ObjectColor.WHITE));
|
||||||
|
}
|
||||||
|
|
||||||
public AngelsFeather(UUID ownerId) {
|
public AngelsFeather(UUID ownerId) {
|
||||||
super(ownerId, 311, "Angel's Feather", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}");
|
super(ownerId, 311, "Angel's Feather", Rarity.UNCOMMON, new CardType[]{CardType.ARTIFACT}, "{2}");
|
||||||
this.expansionSetCode = "10E";
|
this.expansionSetCode = "10E";
|
||||||
this.addAbility(new AngelsFeatherAbility());
|
|
||||||
|
// Whenever a player casts a white spell, you may gain 1 life.
|
||||||
|
this.addAbility(new SpellCastAllTriggeredAbility(new GainLifeEffect(1), filter, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public AngelsFeather(final AngelsFeather card) {
|
public AngelsFeather(final AngelsFeather card) {
|
||||||
|
@ -62,36 +67,3 @@ public class AngelsFeather extends CardImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AngelsFeatherAbility extends TriggeredAbilityImpl {
|
|
||||||
|
|
||||||
public AngelsFeatherAbility() {
|
|
||||||
super(Zone.BATTLEFIELD, new GainLifeEffect(1), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AngelsFeatherAbility(final AngelsFeatherAbility ability) {
|
|
||||||
super(ability);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AngelsFeatherAbility copy() {
|
|
||||||
return new AngelsFeatherAbility(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkEventType(GameEvent event, Game game) {
|
|
||||||
return event.getType() == EventType.SPELL_CAST;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checkTrigger(GameEvent event, Game game) {
|
|
||||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
|
||||||
return spell != null && spell.getColor(game).isWhite();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getRule() {
|
|
||||||
return "Whenever a player casts a white spell, you may gain 1 life.";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -100,6 +100,35 @@ public class CastCreaturesTest extends CardTestPlayerBaseAI {
|
||||||
assertPermanentCount(playerA, "Blazing Specter", 1);
|
assertPermanentCount(playerA, "Blazing Specter", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleCast5() {
|
||||||
|
addCard(Zone.HAND, playerA, "Plains", 2);
|
||||||
|
addCard(Zone.HAND, playerA, "Mountain", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Silvercoat Lion", 3);
|
||||||
|
addCard(Zone.HAND, playerA, "Soul Warden");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Plains", 1);
|
||||||
|
assertPermanentCount(playerA, "Soul Warden", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleCast6() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Plains", 2);
|
||||||
|
addCard(Zone.HAND, playerA, "Mountain", 2);
|
||||||
|
addCard(Zone.HAND, playerA, "Silvercoat Lion", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Pillarfield Ox", 1);
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertPermanentCount(playerA, "Plains", 1);
|
||||||
|
assertPermanentCount(playerA, "Silvercoat Lion", 1);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCast4Creature() {
|
public void testCast4Creature() {
|
||||||
addCard(Zone.LIBRARY, playerA, "Swamp", 1);
|
addCard(Zone.LIBRARY, playerA, "Swamp", 1);
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2010 BetaSteward_at_googlemail.com. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
* permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
* conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
* of conditions and the following disclaimer in the documentation and/or other materials
|
||||||
|
* provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY BetaSteward_at_googlemail.com ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||||
|
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BetaSteward_at_googlemail.com OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* The views and conclusions contained in the software and documentation are those of the
|
||||||
|
* authors and should not be interpreted as representing official policies, either expressed
|
||||||
|
* or implied, of BetaSteward_at_googlemail.com.
|
||||||
|
*/
|
||||||
|
package org.mage.test.cards.triggers;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LevelX2
|
||||||
|
*/
|
||||||
|
public class TargetedTriggeredTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that the first spell that targets Kira, Great Glass-Spinner is
|
||||||
|
* countered.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
// this does currently not work in test, because the target event will be fired earlier during tests,
|
||||||
|
// so the zone change counter for the fixed target of the counterspell will not work
|
||||||
|
public void testKiraGreatGlassSpinnerFirstSpellTurn() {
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1);
|
||||||
|
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||||
|
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Kira, Great Glass-Spinner", 1);
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Kira, Great Glass-Spinner");
|
||||||
|
|
||||||
|
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||||
|
execute();
|
||||||
|
|
||||||
|
assertGraveyardCount(playerA, "Lightning Bolt", 1);
|
||||||
|
|
||||||
|
assertPermanentCount(playerB, "Kira, Great Glass-Spinner", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -60,5 +60,11 @@ public enum PlayerAction {
|
||||||
ADD_PERMISSION_TO_ROLLBACK_TURN,
|
ADD_PERMISSION_TO_ROLLBACK_TURN,
|
||||||
DENY_PERMISSON_TO_ROLLBACK_TURN,
|
DENY_PERMISSON_TO_ROLLBACK_TURN,
|
||||||
PERMISSION_REQUESTS_ALLOWED_ON,
|
PERMISSION_REQUESTS_ALLOWED_ON,
|
||||||
PERMISSION_REQUESTS_ALLOWED_OFF
|
PERMISSION_REQUESTS_ALLOWED_OFF,
|
||||||
|
REQUEST_AUTO_ANSWER_ID_YES,
|
||||||
|
REQUEST_AUTO_ANSWER_ID_NO,
|
||||||
|
REQUEST_AUTO_ANSWER_TEXT_YES,
|
||||||
|
REQUEST_AUTO_ANSWER_TEXT_NO,
|
||||||
|
REQUEST_AUTO_ANSWER_RESET_ALL,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ import mage.game.turn.Turn;
|
||||||
import mage.players.Player;
|
import mage.players.Player;
|
||||||
import mage.players.PlayerList;
|
import mage.players.PlayerList;
|
||||||
import mage.players.Players;
|
import mage.players.Players;
|
||||||
|
import mage.util.MessageToClient;
|
||||||
import mage.util.functions.ApplyToPermanent;
|
import mage.util.functions.ApplyToPermanent;
|
||||||
|
|
||||||
public interface Game extends MageItem, Serializable {
|
public interface Game extends MageItem, Serializable {
|
||||||
|
@ -221,13 +222,13 @@ public interface Game extends MageItem, Serializable {
|
||||||
|
|
||||||
void addPlayerQueryEventListener(Listener<PlayerQueryEvent> listener);
|
void addPlayerQueryEventListener(Listener<PlayerQueryEvent> listener);
|
||||||
|
|
||||||
void fireAskPlayerEvent(UUID playerId, String message);
|
void fireAskPlayerEvent(UUID playerId, MessageToClient message, Ability source);
|
||||||
|
|
||||||
void fireChooseChoiceEvent(UUID playerId, Choice choice);
|
void fireChooseChoiceEvent(UUID playerId, Choice choice);
|
||||||
|
|
||||||
void fireSelectTargetEvent(UUID playerId, String message, Set<UUID> targets, boolean required, Map<String, Serializable> options);
|
void fireSelectTargetEvent(UUID playerId, MessageToClient message, Set<UUID> targets, boolean required, Map<String, Serializable> options);
|
||||||
|
|
||||||
void fireSelectTargetEvent(UUID playerId, String message, Cards cards, boolean required, Map<String, Serializable> options);
|
void fireSelectTargetEvent(UUID playerId, MessageToClient message, Cards cards, boolean required, Map<String, Serializable> options);
|
||||||
|
|
||||||
void fireSelectTargetTriggeredAbilityEvent(UUID playerId, String message, List<TriggeredAbility> abilities);
|
void fireSelectTargetTriggeredAbilityEvent(UUID playerId, String message, List<TriggeredAbility> abilities);
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,7 @@ import mage.target.Target;
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.TargetPlayer;
|
import mage.target.TargetPlayer;
|
||||||
import mage.util.GameLog;
|
import mage.util.GameLog;
|
||||||
|
import mage.util.MessageToClient;
|
||||||
import mage.util.functions.ApplyToPermanent;
|
import mage.util.functions.ApplyToPermanent;
|
||||||
import mage.watchers.Watchers;
|
import mage.watchers.Watchers;
|
||||||
import mage.watchers.common.BlockedAttackerWatcher;
|
import mage.watchers.common.BlockedAttackerWatcher;
|
||||||
|
@ -1886,11 +1887,11 @@ public abstract class GameImpl implements Game, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fireAskPlayerEvent(UUID playerId, String message) {
|
public void fireAskPlayerEvent(UUID playerId, MessageToClient message, Ability source) {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playerQueryEventSource.ask(playerId, message);
|
playerQueryEventSource.ask(playerId, message.getMessage(), source, addMessageToOptions(message, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1914,19 +1915,19 @@ public abstract class GameImpl implements Game, Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fireSelectTargetEvent(UUID playerId, String message, Set<UUID> targets, boolean required, Map<String, Serializable> options) {
|
public void fireSelectTargetEvent(UUID playerId, MessageToClient message, Set<UUID> targets, boolean required, Map<String, Serializable> options) {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playerQueryEventSource.target(playerId, message, targets, required, options);
|
playerQueryEventSource.target(playerId, message.getMessage(), targets, required, addMessageToOptions(message, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fireSelectTargetEvent(UUID playerId, String message, Cards cards, boolean required, Map<String, Serializable> options) {
|
public void fireSelectTargetEvent(UUID playerId, MessageToClient message, Cards cards, boolean required, Map<String, Serializable> options) {
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
playerQueryEventSource.target(playerId, message, cards, required, options);
|
playerQueryEventSource.target(playerId, message.getMessage(), cards, required, addMessageToOptions(message, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2692,4 +2693,19 @@ public abstract class GameImpl implements Game, Serializable {
|
||||||
return enterWithCounters.get(sourceId);
|
return enterWithCounters.get(sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Map<String, Serializable> addMessageToOptions(MessageToClient message, Map<String, Serializable> options) {
|
||||||
|
if (message.getSecondMessage() != null) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new HashMap<>();
|
||||||
|
}
|
||||||
|
options.put("secondMessage", message.getSecondMessage());
|
||||||
|
}
|
||||||
|
if (message.getHintText() != null) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new HashMap<>();
|
||||||
|
}
|
||||||
|
options.put("hintText", message.getHintText());
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,8 +149,14 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlayerQueryEvent askEvent(UUID playerId, String message) {
|
public static PlayerQueryEvent askEvent(UUID playerId, String message, Ability source, Map<String, Serializable> options) {
|
||||||
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.ASK, 0, 0, false, null);
|
if (source != null) {
|
||||||
|
if (options == null) {
|
||||||
|
options = new HashMap<>();
|
||||||
|
}
|
||||||
|
options.put("originalId", source.getOriginalId());
|
||||||
|
}
|
||||||
|
return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.ASK, 0, 0, false, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
|
|
@ -32,6 +32,7 @@ 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;
|
||||||
|
import mage.abilities.Ability;
|
||||||
import mage.abilities.ActivatedAbility;
|
import mage.abilities.ActivatedAbility;
|
||||||
import mage.abilities.TriggeredAbility;
|
import mage.abilities.TriggeredAbility;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
|
@ -58,8 +59,8 @@ public class PlayerQueryEventSource implements EventSource<PlayerQueryEvent>, Se
|
||||||
dispatcher.removeAllListener();
|
dispatcher.removeAllListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ask(UUID playerId, String message) {
|
public void ask(UUID playerId, String message, Ability source, Map<String, Serializable> options) {
|
||||||
dispatcher.fireEvent(PlayerQueryEvent.askEvent(playerId, message));
|
dispatcher.fireEvent(PlayerQueryEvent.askEvent(playerId, message, source, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void select(UUID playerId, String message) {
|
public void select(UUID playerId, String message) {
|
||||||
|
|
43
Mage/src/mage/util/MessageToClient.java
Normal file
43
Mage/src/mage/util/MessageToClient.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* To change this license header, choose License Headers in Project Properties.
|
||||||
|
* To change this template file, choose Tools | Templates
|
||||||
|
* and open the template in the editor.
|
||||||
|
*/
|
||||||
|
package mage.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author LevelX2
|
||||||
|
*/
|
||||||
|
public class MessageToClient {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
private String secondMessage;
|
||||||
|
private String hintText;
|
||||||
|
|
||||||
|
public MessageToClient(String message) {
|
||||||
|
this(message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageToClient(String message, String secondMessage) {
|
||||||
|
this(message, secondMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageToClient(String message, String secondMessage, String hintText) {
|
||||||
|
this.message = message;
|
||||||
|
this.secondMessage = secondMessage;
|
||||||
|
this.hintText = hintText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecondMessage() {
|
||||||
|
return secondMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHintText() {
|
||||||
|
return hintText;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,6 @@
|
||||||
* authors and should not be interpreted as representing official policies, either expressed
|
* authors and should not be interpreted as representing official policies, either expressed
|
||||||
* or implied, of BetaSteward_at_googlemail.com.
|
* or implied, of BetaSteward_at_googlemail.com.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package mage.util;
|
package mage.util;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -34,14 +33,16 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com
|
* @author BetaSteward_at_googlemail.com
|
||||||
|
* @param <T>
|
||||||
*/
|
*/
|
||||||
public class TreeNode<T> {
|
public class TreeNode<T> {
|
||||||
|
|
||||||
protected T data;
|
protected T data;
|
||||||
protected List<TreeNode<T>> children;
|
protected List<TreeNode<T>> children;
|
||||||
|
|
||||||
public TreeNode() {
|
public TreeNode() {
|
||||||
super();
|
super();
|
||||||
children = new ArrayList<TreeNode<T>>();
|
children = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TreeNode(T data) {
|
public TreeNode(T data) {
|
||||||
|
@ -70,7 +71,7 @@ public class TreeNode<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChild(T child) {
|
public void addChild(T child) {
|
||||||
children.add(new TreeNode<T>(child));
|
children.add(new TreeNode<>(child));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChildAt(int index, TreeNode<T> child) throws IndexOutOfBoundsException {
|
public void addChildAt(int index, TreeNode<T> child) throws IndexOutOfBoundsException {
|
||||||
|
@ -93,7 +94,7 @@ public class TreeNode<T> {
|
||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(T data) {
|
private void setData(T data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,10 +117,7 @@ public class TreeNode<T> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final TreeNode<T> other = (TreeNode<T>) obj;
|
final TreeNode<T> other = (TreeNode<T>) obj;
|
||||||
if (this.data != other.data && (this.data == null || !this.data.equals(other.data))) {
|
return !(this.data != other.data && (this.data == null || !this.data.equals(other.data)));
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue