Merge pull request #8013 from weirddan455/wish

[AFR] Implemented Wish
This commit is contained in:
Oleg Agafonov 2021-07-21 20:53:41 +04:00 committed by GitHub
commit 81193148e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 673 additions and 233 deletions

View file

@ -20,7 +20,7 @@
import java.util.*;
/**
* Panel for stack and hand zones
* Panel for stack and hand zones, component for lookAt and reveal windows (CardInfoWindowDialog)
*
* @author BetaSteward_at_googlemail.com, JayDi85
*/
@ -386,4 +386,14 @@
public void setZone(Zone zone) {
this.zone = zone;
}
/**
* For GUI: get mage card components for update (example: change playable status)
* Warning, do not change the list
*
* @return
*/
public Map<UUID, MageCard> getMageCardsForUpdate() {
return this.cards;
}
}

View file

@ -4,15 +4,13 @@ import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import javax.swing.*;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.plaf.basic.BasicInternalFrameUI;
import mage.cards.MageCard;
import mage.client.cards.BigCard;
import mage.client.util.GUISizeHelper;
import mage.client.util.ImageHelper;
@ -36,7 +34,7 @@ public class CardInfoWindowDialog extends MageDialog {
private static final Logger LOGGER = Logger.getLogger(CardInfoWindowDialog.class);
public enum ShowType {
REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, COMPANION, OTHER
REVEAL, REVEAL_TOP_LIBRARY, LOOKED_AT, EXILE, GRAVEYARD, COMPANION, SIDEBOARD, OTHER
}
private final ShowType showType;
@ -91,6 +89,17 @@ public class CardInfoWindowDialog extends MageDialog {
}
});
break;
case SIDEBOARD:
this.setFrameIcon(new ImageIcon(ImageHelper.getImageFromResources("/info/library.png")));
this.setClosable(true);
this.setDefaultCloseOperation(HIDE_ON_CLOSE);
this.addInternalFrameListener(new InternalFrameAdapter() {
@Override
public void internalFrameClosing(InternalFrameEvent e) {
CardInfoWindowDialog.this.hideDialog();
}
});
break;
case EXILE:
this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getExileImage()));
break;
@ -98,6 +107,7 @@ public class CardInfoWindowDialog extends MageDialog {
this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getTokenIconImage()));
this.setClosable(false);
break;
case OTHER:
default:
// no icon yet
}
@ -151,15 +161,35 @@ public class CardInfoWindowDialog extends MageDialog {
public void loadCards(CardsView showCards, BigCard bigCard, UUID gameId, boolean revertOrder) {
cards.loadCards(showCards, bigCard, gameId, revertOrder);
// additional info for grave windows
if (showType == ShowType.GRAVEYARD) {
int qty = qtyCardTypes(showCards);
String titel = name + "'s Graveyard (" + showCards.size() + ") - " + qty + ((qty == 1) ? " Card Type" : " Card Types");
setTitle(titel);
this.setTitelBarToolTip(titel);
String newTitle = name + "'s graveyard (" + showCards.size() + ") - " + qty + ((qty == 1) ? " card type" : " card types");
setTitle(newTitle);
this.setTitelBarToolTip(newTitle);
}
// additional info for sideboard window
if (showType == ShowType.SIDEBOARD) {
String newTitle = name + "'s sideboard";
setTitle(newTitle);
this.setTitelBarToolTip(newTitle);
}
showAndPositionWindow();
}
/**
* For GUI: get mage card components for update (example: change playable status)
* Warning, do not change the list
*
* @return
*/
public Map<UUID, MageCard> getMageCardsForUpdate() {
return this.cards.getMageCardsForUpdate();
}
@Override
public void show() {
if (showType == ShowType.EXILE) {

View file

@ -26,6 +26,7 @@ import mage.client.util.gui.ArrowBuilder;
import mage.client.util.gui.MageDialogState;
import mage.constants.*;
import mage.game.events.PlayerQueryEvent;
import mage.players.PlayableObjectStats;
import mage.players.PlayableObjectsList;
import mage.view.*;
import org.apache.log4j.Logger;
@ -76,9 +77,11 @@ public final class GamePanel extends javax.swing.JPanel {
private final Map<UUID, CardInfoWindowDialog> exiles = new HashMap<>();
private final Map<String, CardInfoWindowDialog> revealed = new HashMap<>();
private final Map<String, CardInfoWindowDialog> lookedAt = new HashMap<>();
private final Map<String, CardsView> graveyards = new HashMap<>(); // need to sync selection
private final Map<String, CardInfoWindowDialog> graveyardWindows = new HashMap<>();
private final Map<String, CardInfoWindowDialog> companion = new HashMap<>();
private final Map<String, CardsView> graveyards = new HashMap<>();
private final Map<String, CardsView> sideboards = new HashMap<>(); // need to sync selection
private final Map<String, CardInfoWindowDialog> sideboardWindows = new HashMap<>();
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
private final ArrayList<PickPileDialog> pickPile = new ArrayList<>();
private UUID gameId;
@ -245,25 +248,29 @@ public final class GamePanel extends javax.swing.JPanel {
if (pickMultiNumber != null) {
pickMultiNumber.removeDialog();
}
for (CardInfoWindowDialog exileDialog : exiles.values()) {
exileDialog.cleanUp();
exileDialog.removeDialog();
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) {
graveyardDialog.cleanUp();
graveyardDialog.removeDialog();
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog revealDialog : revealed.values()) {
revealDialog.cleanUp();
revealDialog.removeDialog();
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) {
lookedAtDialog.cleanUp();
lookedAtDialog.removeDialog();
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog companionDialog : companion.values()) {
companionDialog.cleanUp();
companionDialog.removeDialog();
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.cleanUp();
windowDialog.removeDialog();
}
clearPickTargetDialogs();
@ -307,26 +314,29 @@ public final class GamePanel extends javax.swing.JPanel {
playAreaPanel.changeGUISize();
}
for (CardInfoWindowDialog cardInfoWindowDialog : exiles.values()) {
cardInfoWindowDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog cardInfoWindowDialog : revealed.values()) {
cardInfoWindowDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog cardInfoWindowDialog : lookedAt.values()) {
cardInfoWindowDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog cardInfoWindowDialog : companion.values()) {
cardInfoWindowDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.changeGUISize();
}
for (CardInfoWindowDialog cardInfoWindowDialog : graveyardWindows.values()) {
cardInfoWindowDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.changeGUISize();
}
for (ShowCardsDialog showCardsDialog : pickTarget) {
showCardsDialog.changeGUISize();
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.changeGUISize();
}
for (PickPileDialog pickPileDialog : pickPile) {
pickPileDialog.changeGUISize();
for (ShowCardsDialog windowDialog : pickTarget) {
windowDialog.changeGUISize();
}
for (PickPileDialog windowDialog : pickPile) {
windowDialog.changeGUISize();
}
this.revalidate();
@ -574,7 +584,7 @@ public final class GamePanel extends javax.swing.JPanel {
}
PlayerView player = game.getPlayers().get(playerSeat);
PlayAreaPanel playAreaPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
new PlayAreaPanelOptions(game.isPlayer(), game.isPlayer(), game.isRollbackTurnsAllowed(), row == 0));
new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), game.isPlayer(), game.isRollbackTurnsAllowed(), row == 0));
players.put(player.getPlayerId(), playAreaPanel);
playersWhoLeft.put(player.getPlayerId(), false);
GridBagConstraints c = new GridBagConstraints();
@ -618,7 +628,7 @@ public final class GamePanel extends javax.swing.JPanel {
}
player = game.getPlayers().get(playerNum);
PlayAreaPanel playerPanel = new PlayAreaPanel(player, bigCard, gameId, game.getPriorityTime(), this,
new PlayAreaPanelOptions(game.isPlayer(), false, game.isRollbackTurnsAllowed(), row == 0));
new PlayAreaPanelOptions(game.isPlayer(), player.isHuman(), false, game.isRollbackTurnsAllowed(), row == 0));
players.put(player.getPlayerId(), playerPanel);
playersWhoLeft.put(player.getPlayerId(), false);
c = new GridBagConstraints();
@ -789,16 +799,29 @@ public final class GamePanel extends javax.swing.JPanel {
if (player.getPlayerId().equals(playerId)) {
skipButtons.updateFromPlayer(player);
}
// update open or remove closed graveyard windows
graveyards.put(player.getName(), player.getGraveyard());
if (graveyardWindows.containsKey(player.getName())) {
CardInfoWindowDialog cardInfoWindowDialog = graveyardWindows.get(player.getName());
if (cardInfoWindowDialog.isClosed()) {
CardInfoWindowDialog windowDialog = graveyardWindows.get(player.getName());
if (windowDialog.isClosed()) {
graveyardWindows.remove(player.getName());
} else {
cardInfoWindowDialog.loadCards(player.getGraveyard(), bigCard, gameId, false);
windowDialog.loadCards(player.getGraveyard(), bigCard, gameId, false);
}
}
// update open or remove closed sideboard windows
sideboards.put(player.getName(), player.getSideboard());
if (sideboardWindows.containsKey(player.getName())) {
CardInfoWindowDialog windowDialog = sideboardWindows.get(player.getName());
if (windowDialog.isClosed()) {
sideboardWindows.remove(player.getName());
} else {
windowDialog.loadCards(player.getSideboard(), bigCard, gameId, false);
}
}
// show top card window
if (player.getTopCard() != null) {
CardsView cardsView = new CardsView();
@ -851,8 +874,12 @@ public final class GamePanel extends javax.swing.JPanel {
exiles.get(exile.getId()).loadCards(exile, bigCard, gameId);
}
// reveal and look at dialogs can unattached, so windows opened by game doesn't have it
showRevealed(lastGameData.game);
showLookedAt(lastGameData.game);
// sideboard dialogs is unattached all the time -- user opens it by command
showCompanion(lastGameData.game);
if (!lastGameData.game.getCombat().isEmpty()) {
CombatManager.instance.showCombat(lastGameData.game.getCombat(), gameId);
@ -1146,40 +1173,46 @@ public final class GamePanel extends javax.swing.JPanel {
// Called if the game frame is deactivated because the tabled the deck editor or other frames go to foreground
public void deactivated() {
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
for (CardInfoWindowDialog exileDialog : exiles.values()) {
exileDialog.hideDialog();
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) {
graveyardDialog.hideDialog();
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog revealDialog : revealed.values()) {
revealDialog.hideDialog();
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) {
lookedAtDialog.hideDialog();
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog companionDialog : companion.values()) {
companionDialog.hideDialog();
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.hideDialog();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.hideDialog();
}
}
// Called if the game frame comes to front again
public void activated() {
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
for (CardInfoWindowDialog exileDialog : exiles.values()) {
exileDialog.show();
for (CardInfoWindowDialog windowDialog : exiles.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog graveyardDialog : graveyardWindows.values()) {
graveyardDialog.show();
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog revealDialog : revealed.values()) {
revealDialog.show();
for (CardInfoWindowDialog windowDialog : revealed.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog lookedAtDialog : lookedAt.values()) {
lookedAtDialog.show();
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog companionDialog : companion.values()) {
companionDialog.show();
for (CardInfoWindowDialog windowDialog : companion.values()) {
windowDialog.show();
}
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
windowDialog.show();
}
}
@ -1196,9 +1229,40 @@ public final class GamePanel extends javax.swing.JPanel {
CardInfoWindowDialog newGraveyard = new CardInfoWindowDialog(ShowType.GRAVEYARD, playerName);
graveyardWindows.put(playerName, newGraveyard);
MageFrame.getDesktop().add(newGraveyard, JLayeredPane.PALETTE_LAYER);
// use graveyards to sync selection (don't use player data here)
newGraveyard.loadCards(graveyards.get(playerName), bigCard, gameId, false);
}
public void openSideboardWindow(UUID playerId) {
if (lastGameData == null) {
return;
}
PlayerView playerView = lastGameData.game.getPlayers().stream()
.filter(p -> p.getPlayerId().equals(playerId))
.findFirst()
.orElse(null);
if (playerView == null) {
return;
}
if (sideboardWindows.containsKey(playerView.getName())) {
CardInfoWindowDialog windowDialog = sideboardWindows.get(playerView.getName());
if (windowDialog.isVisible()) {
windowDialog.hideDialog();
} else {
windowDialog.show();
}
return;
}
CardInfoWindowDialog windowDialog = new CardInfoWindowDialog(ShowType.SIDEBOARD, playerView.getName());
sideboardWindows.put(playerView.getName(), windowDialog);
MageFrame.getDesktop().add(windowDialog, JLayeredPane.PALETTE_LAYER);
// use sideboards to sync selection (don't use player data here)
windowDialog.loadCards(sideboards.get(playerView.getName()), bigCard, gameId, false);
}
public void openTopLibraryWindow(String playerName) {
String title = playerName + "'s top library card";
if (revealed.containsKey(title)) {
@ -1386,6 +1450,25 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
// sideboard
if (needZone == Zone.OUTSIDE || needZone == Zone.ALL) {
for (PlayerView player : lastGameData.game.getPlayers()) {
for (Map.Entry<UUID, CardView> card : player.getSideboard().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChoosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
}
// sideboards (old windows all the time, e.g. unattached from game data)
prepareSelectableWindows(sideboardWindows.values(), needSelectable, needChoosen, needPlayable);
// exile
if (needZone == Zone.EXILED || needZone == Zone.ALL) {
// exile from player panel
@ -1402,6 +1485,7 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
}
// exile from windows
for (ExileView exile : lastGameData.game.getExile()) {
for (Map.Entry<UUID, CardView> card : exile.entrySet()) {
@ -1435,21 +1519,6 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
// revealed
for (RevealedView rev : lastGameData.game.getRevealed()) {
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChoosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// companion
for (RevealedView rev : lastGameData.game.getCompanion()) {
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
@ -1465,7 +1534,24 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
// looked at
// revealed (current cards)
for (RevealedView rev : lastGameData.game.getRevealed()) {
for (Map.Entry<UUID, CardView> card : rev.getCards().entrySet()) {
if (needSelectable.contains(card.getKey())) {
card.getValue().setChoosable(true);
}
if (needChoosen.contains(card.getKey())) {
card.getValue().setSelected(true);
}
if (needPlayable.containsObject(card.getKey())) {
card.getValue().setPlayableStats(needPlayable.getStats(card.getKey()));
}
}
}
// revealed (old windows)
prepareSelectableWindows(revealed.values(), needSelectable, needChoosen, needPlayable);
// looked at (current cards)
for (LookedAtView look : lastGameData.game.getLookedAt()) {
for (Map.Entry<UUID, SimpleCardView> card : look.getCards().entrySet()) {
if (needPlayable.containsObject(card.getKey())) {
@ -1473,6 +1559,32 @@ public final class GamePanel extends javax.swing.JPanel {
}
}
}
// looked at (old windows)
prepareSelectableWindows(lookedAt.values(), needSelectable, needChoosen, needPlayable);
}
private void prepareSelectableWindows(
Collection<CardInfoWindowDialog> windows,
Set<UUID> needSelectable,
List<UUID> needChoosen,
PlayableObjectsList needPlayable
) {
// lookAt or reveals windows clean up on next priority, so users can see dialogs, but xmage can't restore it
// so it must be updated manually (it's ok to keep outdated cards in dialog, but not ok to show wrong selections)
for (CardInfoWindowDialog window : windows) {
for (MageCard mageCard : window.getMageCardsForUpdate().values()) {
CardView cardView = mageCard.getOriginal();
cardView.setChoosable(needSelectable.contains(cardView.getId()));
cardView.setSelected(needChoosen.contains(cardView.getId()));
if (needPlayable.containsObject(cardView.getId())) {
cardView.setPlayableStats(needPlayable.getStats(cardView.getId()));
} else {
cardView.setPlayableStats(new PlayableObjectStats());
}
// TODO: little bug with toggled night card after update/clicks, but that's ok (can't click on second side)
mageCard.update(cardView);
}
}
}
/**

View file

@ -1,89 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<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" alignment="0" attributes="0">
<Component id="jPanel1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="jScrollPane1" pref="357" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jScrollPane1" alignment="1" pref="278" max="32767" attributes="0"/>
<Component id="jPanel1" alignment="0" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JPanel" name="jPanel1">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.BevelBorderInfo">
<BevelBorder/>
</Border>
</Property>
</Properties>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="manaPool" alignment="0" pref="116" max="32767" attributes="1"/>
<Component id="playerPanel" alignment="0" max="32767" attributes="1"/>
<Component id="btnCheat" alignment="0" pref="116" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="playerPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
<Component id="manaPool" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="17" max="32767" attributes="0"/>
<Component id="btnCheat" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="mage.client.game.PlayerPanel" name="playerPanel">
</Component>
<Component class="mage.client.game.ManaPool" name="manaPool">
</Component>
<Component class="javax.swing.JButton" name="btnCheat">
<Properties>
<Property name="text" type="java.lang.String" value="Cheat"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnCheatActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JScrollPane" name="jScrollPane1">
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Container class="mage.client.game.BattlefieldPanel" name="battlefieldPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JLayeredPaneSupportLayout"/>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>

View file

@ -24,6 +24,8 @@ import java.util.UUID;
import static mage.client.dialog.PreferencesDialog.*;
/**
* GUI: play area panel (player with avatar/mana panel + battlefield panel)
*
* @author BetaSteward_at_googlemail.com
*/
public class PlayAreaPanel extends javax.swing.JPanel {
@ -441,14 +443,28 @@ public class PlayAreaPanel extends javax.swing.JPanel {
popupMenu.addSeparator();
menuItem = new JMenuItem("<html>View current deck");
menuItem.setMnemonic(KeyEvent.VK_V);
// view deck
menuItem = new JMenuItem("<html>View player's deck");
menuItem.setMnemonic(KeyEvent.VK_D);
popupMenu.add(menuItem);
// View limited deck
menuItem.addActionListener(e -> {
SessionHandler.sendPlayerAction(PlayerAction.VIEW_LIMITED_DECK, gameId, null);
});
// view sideboard (allows to view only own sideboard or computer)
// it's a client side checks... same checks must be on server side too (see PlayerView)
if (options.playerItself || !options.isHuman) {
String menuCaption = "<html>View my sideboard";
if (!options.isHuman) {
menuCaption = "<html>View computer's sideboard";
}
menuItem = new JMenuItem(menuCaption);
menuItem.setMnemonic(KeyEvent.VK_S);
popupMenu.add(menuItem);
menuItem.addActionListener(e -> {
SessionHandler.sendPlayerAction(PlayerAction.VIEW_SIDEBOARD, gameId, playerId);
});
}
}
private void addPopupMenuWatcher() {

View file

@ -8,8 +8,9 @@ package mage.client.game;
*/
public class PlayAreaPanelOptions {
public PlayAreaPanelOptions(boolean isPlayer, boolean playerItself, boolean rollbackTurnsAllowed, boolean topRow) {
public PlayAreaPanelOptions(boolean isPlayer, boolean isHuman, boolean playerItself, boolean rollbackTurnsAllowed, boolean topRow) {
this.isPlayer = isPlayer;
this.isHuman = isHuman;
this.playerItself = playerItself;
this.rollbackTurnsAllowed = rollbackTurnsAllowed;
this.topRow = topRow;
@ -18,22 +19,27 @@ public class PlayAreaPanelOptions {
/**
* true if the client is a player / false if the client is a watcher
*/
public boolean isPlayer = false;
public boolean isPlayer;
/**
* true if the player is the human, not computer
*/
public boolean isHuman;
/**
* true if the player is the client player itself, false if the player is
* another player playing with the clinet player
* another player playing with the client player
*/
public boolean playerItself = false;
public boolean playerItself;
/**
* true if the player can rollback turns if all players agree
*/
public boolean rollbackTurnsAllowed = false;
public boolean rollbackTurnsAllowed;
/**
* true if the battlefield is on the top row of player areas
*/
public boolean topRow = false;
public boolean topRow;
}

View file

@ -391,6 +391,12 @@ public class CallbackClientImpl implements CallbackClient {
break;
}
case VIEW_SIDEBOARD: {
TableClientMessage message = (TableClientMessage) callback.getData();
viewSideboard(message.getGameId(), message.getPlayerId());
break;
}
case CONSTRUCT: {
TableClientMessage message = (TableClientMessage) callback.getData();
DeckView deckView = message.getDeck();
@ -616,6 +622,15 @@ public class CallbackClientImpl implements CallbackClient {
frame.showDeckEditor(DeckEditorMode.VIEW_LIMITED_DECK, deck, tableId, time);
}
protected void viewSideboard(UUID gameId, UUID playerId) {
SwingUtilities.invokeLater(() -> {
GamePanel panel = MageFrame.getGame(gameId);
if (panel != null) {
panel.openSideboardWindow(playerId);
}
});
}
private void handleException(Exception ex) {
logger.fatal("Client error\n", ex);
String errorMessage = ex.getMessage();

View file

@ -14,6 +14,7 @@ public enum ClientCallbackMethod {
START_TOURNAMENT("startTournament"),
SIDEBOARD("sideboard"),
VIEW_LIMITED_DECK("viewLimitedDeck"),
VIEW_SIDEBOARD("viewSideboard"),
CONSTRUCT("construct"),
SHOW_USERMESSAGE("showUserMessage"),
WATCHGAME("watchGame"),

View file

@ -24,6 +24,7 @@ public class PlayerView implements Serializable {
private final UUID playerId;
private final String name;
private final boolean controlled; // gui: player is current user
private final boolean isHuman; // human or computer
private final int life;
private final Counters counters;
private final int wins;
@ -38,6 +39,7 @@ public class PlayerView implements Serializable {
private final ManaPoolView manaPool;
private final CardsView graveyard = new CardsView();
private final CardsView exile = new CardsView();
private final CardsView sideboard = new CardsView();
private final Map<UUID, PermanentView> battlefield = new LinkedHashMap<>();
private final CardView topCard;
private final UserData userData;
@ -58,6 +60,7 @@ public class PlayerView implements Serializable {
this.playerId = player.getId();
this.name = player.getName();
this.controlled = player.getId().equals(createdForPlayerId);
this.isHuman = player.isHuman();
this.life = player.getLife();
this.counters = player.getCounters();
this.wins = player.getMatchPlayer().getWins();
@ -85,6 +88,13 @@ public class PlayerView implements Serializable {
}
}
}
if (this.controlled || !player.isHuman()) {
// sideboard available for itself or for computer only
for (Card card : player.getSideboard().getCards(game)) {
sideboard.put(card.getId(), new CardView(card, game, false));
}
}
try {
for (Permanent permanent : state.getBattlefield().getAllPermanents()) {
if (showInBattlefield(permanent, state)) {
@ -167,6 +177,10 @@ public class PlayerView implements Serializable {
return this.controlled;
}
public boolean isHuman() {
return this.isHuman;
}
public int getLife() {
return this.life;
}
@ -207,6 +221,10 @@ public class PlayerView implements Serializable {
return exile;
}
public CardsView getSideboard() {
return this.sideboard;
}
public Map<UUID, PermanentView> getBattlefield() {
return this.battlefield;
}

View file

@ -259,6 +259,10 @@ public class User {
fireCallback(new ClientCallback(ClientCallbackMethod.VIEW_LIMITED_DECK, tableId, new TableClientMessage(deck, tableId, time, limited)));
}
public void ccViewSideboard(final UUID tableId, final UUID gameId, final UUID targetPlayerId) {
fireCallback(new ClientCallback(ClientCallbackMethod.VIEW_SIDEBOARD, tableId, new TableClientMessage(gameId, targetPlayerId)));
}
public void ccConstruct(final Deck deck, final UUID tableId, final int time) {
fireCallback(new ClientCallback(ClientCallbackMethod.CONSTRUCT, tableId, new TableClientMessage(deck, tableId, time)));
}

View file

@ -598,6 +598,12 @@ public class GameController implements GameCallback {
case VIEW_LIMITED_DECK:
viewLimitedDeck(getPlayerId(userId), userId);
break;
case VIEW_SIDEBOARD:
if (data instanceof UUID) {
UUID targetPlayerId = (UUID) data;
viewSideboard(getPlayerId(userId), userId, targetPlayerId);
}
break;
default:
game.sendPlayerAction(playerAction, getPlayerId(userId), data);
}
@ -656,13 +662,13 @@ public class GameController implements GameCallback {
}
}
private void viewLimitedDeck(UUID userIdRequester, UUID origId) {
Player viewLimitedDeckPlayer = game.getPlayer(userIdRequester);
private void viewLimitedDeck(UUID playerId, UUID userId) {
Player viewLimitedDeckPlayer = game.getPlayer(playerId);
if (viewLimitedDeckPlayer != null) {
if (viewLimitedDeckPlayer.isHuman()) {
for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) {
if (p.getPlayer().getId().equals(userIdRequester)) {
Optional<User> u = managerFactory.userManager().getUser(origId);
if (p.getPlayer().getId().equals(playerId)) {
Optional<User> u = managerFactory.userManager().getUser(userId);
if (u.isPresent() && p.getDeck() != null) {
u.get().ccViewLimitedDeck(p.getDeck(), tableId, requestsOpen, true);
}
@ -672,6 +678,18 @@ public class GameController implements GameCallback {
}
}
private void viewSideboard(UUID playerId, UUID userId, UUID targetPlayerId) {
Player needPlayer = game.getPlayer(playerId);
if (needPlayer != null && needPlayer.isHuman()) {
for (MatchPlayer p : managerFactory.tableManager().getTable(tableId).getMatch().getPlayers()) {
if (p.getPlayer().getId().equals(playerId)) {
Optional<User> u = managerFactory.userManager().getUser(userId);
u.ifPresent(user -> user.ccViewSideboard(tableId, game.getId(), targetPlayerId));
}
}
}
}
public void cheat(UUID userId, UUID playerId, DeckCardLists deckList) {
try {
Deck deck = Deck.load(deckList, false, false);

View file

@ -7,6 +7,7 @@ import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.combat.BlocksIfAbleTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.ReachAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -34,6 +35,7 @@ public final class AcademicDispute extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private AcademicDispute(final AcademicDispute card) {

View file

@ -2,6 +2,7 @@ package mage.cards.a;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -23,6 +24,7 @@ public final class ArcaneSubtraction extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private ArcaneSubtraction(final ArcaneSubtraction card) {

View file

@ -4,6 +4,7 @@ package mage.cards.b;
import java.util.UUID;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -26,6 +27,7 @@ public final class BurningWish extends CardImpl {
// You may choose a sorcery card you own from outside the game, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// Exile Burning Wish.
this.getSpellAbility().addEffect(new ExileSpellEffect());

View file

@ -3,6 +3,7 @@ package mage.cards.c;
import java.util.UUID;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -26,6 +27,7 @@ public final class CoaxFromTheBlindEternities extends CardImpl {
// You may choose an Eldrazi card you own from outside the game or in exile, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter, true, true));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private CoaxFromTheBlindEternities(final CoaxFromTheBlindEternities card) {

View file

@ -2,6 +2,7 @@ package mage.cards.c;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -21,6 +22,7 @@ public final class CramSession extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private CramSession(final CramSession card) {

View file

@ -4,6 +4,7 @@ package mage.cards.c;
import java.util.UUID;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -26,6 +27,7 @@ public final class CunningWish extends CardImpl {
// You may choose an instant card you own from outside the game, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// Exile Cunning Wish.
this.getSpellAbility().addEffect(new ExileSpellEffect());

View file

@ -3,6 +3,7 @@ package mage.cards.d;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.LoseHalfLifeEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -20,6 +21,7 @@ public final class DeathWish extends CardImpl {
// You may choose a card you own from outside the game and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(StaticFilters.FILTER_CARD_A, false));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// You lose half your life, rounded up.
this.getSpellAbility().addEffect(new LoseHalfLifeEffect());

View file

@ -2,6 +2,7 @@ package mage.cards.d;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -37,6 +38,7 @@ public final class DivideByZero extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private DivideByZero(final DivideByZero card) {

View file

@ -6,6 +6,7 @@ import mage.abilities.common.BecomesTargetTriggeredAbility;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.SacrificeSourceEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.constants.SubType;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
@ -36,7 +37,8 @@ public final class DreamStrix extends CardImpl {
));
// When Dream Strix dies, learn.
this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect()));
this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
}
private DreamStrix(final DreamStrix card) {

View file

@ -3,6 +3,7 @@ package mage.cards.e;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.continuous.BoostTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -31,6 +32,7 @@ public final class EnthusiasticStudy extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private EnthusiasticStudy(final EnthusiasticStudy card) {

View file

@ -3,6 +3,7 @@ package mage.cards.e;
import mage.MageInt;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -28,7 +29,8 @@ public final class Eyetwitch extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// When Eyetwitch dies, learn.
this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect()));
this.addAbility(new DiesSourceTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
}
private Eyetwitch(final Eyetwitch card) {

View file

@ -7,6 +7,7 @@ import mage.abilities.costs.common.DiscardTargetCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.AdventureCard;
import mage.cards.CardSetInfo;
@ -46,6 +47,7 @@ public final class FaeOfWishes extends AdventureCard {
// Granted
// You may choose a noncreature card you own from outside the game, reveal it, and put it into your hand.
this.getSpellCard().getSpellAbility().addEffect(new WishEffect(StaticFilters.FILTER_CARD_A_NON_CREATURE));
this.getSpellCard().getSpellAbility().addHint(OpenSideboardHint.instance);
}
private FaeOfWishes(final FaeOfWishes card) {

View file

@ -2,6 +2,7 @@ package mage.cards.f;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInPlayEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -32,6 +33,7 @@ public final class FieldTrip extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private FieldTrip(final FieldTrip card) {

View file

@ -5,6 +5,7 @@ import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -32,6 +33,7 @@ public final class FirstDayOfClass extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private FirstDayOfClass(final FirstDayOfClass card) {

View file

@ -4,6 +4,7 @@ package mage.cards.g;
import java.util.UUID;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -27,6 +28,7 @@ public final class GlitteringWish extends CardImpl {
// You may choose a multicolored card you own from outside the game, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// Exile Glittering Wish.
this.getSpellAbility().addEffect(new ExileSpellEffect());

View file

@ -3,6 +3,7 @@ package mage.cards.g;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -28,7 +29,8 @@ public final class GnarledProfessor extends CardImpl {
this.addAbility(TrampleAbility.getInstance());
// When Gnarled Professor enters the battlefield, learn.
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()));
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
}
private GnarledProfessor(final GnarledProfessor card) {

View file

@ -4,6 +4,7 @@ package mage.cards.g;
import java.util.UUID;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -29,6 +30,7 @@ public final class GoldenWish extends CardImpl {
// You may choose an artifact or enchantment card you own from outside the game, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// Exile Golden Wish.
this.getSpellAbility().addEffect(new ExileSpellEffect());

View file

@ -2,6 +2,7 @@ package mage.cards.g;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -22,8 +23,9 @@ public final class GuidingVoice extends CardImpl {
this.getSpellAbility().addEffect(new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
this.getSpellAbility().addTarget(new TargetCreaturePermanent());
// Learn (You may reveal a Lesson card you own from outside the game and p
// Learn (You may reveal a Lesson card you own from outside the game and put it into your hand, or discard a card to draw a card.)
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private GuidingVoice(final GuidingVoice card) {

View file

@ -2,6 +2,7 @@ package mage.cards.h;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -22,6 +23,7 @@ public final class HuntForSpecimens extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private HuntForSpecimens(final HuntForSpecimens card) {

View file

@ -2,6 +2,7 @@ package mage.cards.i;
import mage.abilities.effects.common.DamageTargetEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -23,6 +24,7 @@ public final class IgneousInspiration extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private IgneousInspiration(final IgneousInspiration card) {

View file

@ -7,6 +7,7 @@ import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.RestrictionEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
@ -50,7 +51,7 @@ public final class KarnTheGreatCreator extends CardImpl {
// -2: You may choose an artifact card you own from outside the game or in exile, reveal that card, and put it into your hand.
this.addAbility(new LoyaltyAbility(new WishEffect(
StaticFilters.FILTER_CARD_ARTIFACT_AN, true, true
), -2));
), -2).addHint(OpenSideboardHint.instance));
}
private KarnTheGreatCreator(final KarnTheGreatCreator card) {

View file

@ -5,6 +5,7 @@ import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.constants.SubType;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl;
@ -37,7 +38,8 @@ public final class LegionAngel extends CardImpl {
// When Legion Angel enters the battlefield, you may reveal a card you own named Legion Angel from outside the game and put it into your hand.
this.addAbility(new EntersBattlefieldTriggeredAbility(new WishEffect(filter, true, false)
.setText("you may reveal a card you own named Legion Angel from outside the game and put it into your hand")));
.setText("you may reveal a card you own named Legion Angel from outside the game and put it into your hand"))
.addHint(OpenSideboardHint.instance));
}
private LegionAngel(final LegionAngel card) {

View file

@ -4,6 +4,7 @@ package mage.cards.l;
import java.util.UUID;
import mage.abilities.effects.common.ExileSpellEffect;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -29,6 +30,7 @@ public final class LivingWish extends CardImpl {
// You may choose a creature or land card you own from outside the game, reveal that card, and put it into your hand.
this.getSpellAbility().addEffect(new WishEffect(filter));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
// Exile Living Wish.
this.getSpellAbility().addEffect(new ExileSpellEffect());

View file

@ -3,6 +3,7 @@ package mage.cards.m;
import mage.abilities.Mode;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -27,6 +28,7 @@ public final class MastermindsAcquisition extends CardImpl {
Mode mode = new Mode(new WishEffect(StaticFilters.FILTER_CARD_A, false)
.setText("Put a card you own from outside the game into your hand"));
this.getSpellAbility().addMode(mode);
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private MastermindsAcquisition(final MastermindsAcquisition card) {

View file

@ -8,6 +8,7 @@ import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.DefenderAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -38,6 +39,7 @@ public final class OvergrownArch extends CardImpl {
// {2}, Sacrifice Overgrown Arch: Learn.
Ability ability = new SimpleActivatedAbility(new LearnEffect(), new GenericManaCost(2));
ability.addCost(new SacrificeSourceCost());
ability.addHint(OpenSideboardHint.instance);
this.addAbility(ability);
}

View file

@ -7,6 +7,7 @@ import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.continuous.BoostEquippedEffect;
import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.EquipAbility;
import mage.abilities.keyword.LifelinkAbility;
import mage.cards.CardImpl;
@ -29,7 +30,8 @@ public final class PoetsQuill extends CardImpl {
this.subtype.add(SubType.EQUIPMENT);
// When Poet's Quill enters the battlefield, learn.
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()));
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
// Equipped creature gets +1/+1 and has lifelink.
Ability ability = new SimpleStaticAbility(new BoostEquippedEffect(1, 1));

View file

@ -2,6 +2,7 @@ package mage.cards.p;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -21,6 +22,7 @@ public final class PopQuiz extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private PopQuiz(final PopQuiz card) {

View file

@ -3,6 +3,7 @@ package mage.cards.p;
import mage.MageInt;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -24,7 +25,8 @@ public final class ProfessorOfSymbology extends CardImpl {
this.toughness = new MageInt(1);
// When Professor of Symbology enters the battlefield, learn.
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()));
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
}
private ProfessorOfSymbology(final ProfessorOfSymbology card) {

View file

@ -5,6 +5,7 @@ import java.util.UUID;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.*;
import mage.constants.CardType;
import mage.constants.Outcome;
@ -26,6 +27,7 @@ public final class ResearchDevelopment extends SplitCard {
// Choose up to four cards you own from outside the game and shuffle them into your library.
getLeftHalfCard().getSpellAbility().addEffect(new ResearchEffect());
getLeftHalfCard().getSpellAbility().addHint(OpenSideboardHint.instance);
// Create a 3/1 red Elemental creature token unless any opponent has you draw a card. Repeat this process two more times.
getRightHalfCard().getSpellAbility().addEffect(new DevelopmentEffect());

View file

@ -9,6 +9,7 @@ import mage.abilities.condition.common.CastFromEverywhereSourceCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
@ -43,7 +44,7 @@ public final class RetrieverPhoenix extends CardImpl {
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new LearnEffect()), CastFromEverywhereSourceCondition.instance,
"When {this} enters the battlefield, if you cast it, " + LearnEffect.getDefaultText()
));
).addHint(OpenSideboardHint.instance));
// As long as Retriever Phoenix is in your graveyard, if you would learn, you may instead return Retriever Phoenix to the battlefield.
this.addAbility(new SimpleStaticAbility(Zone.GRAVEYARD, new RetrieverPhoenixEffect()));

View file

@ -9,6 +9,7 @@ import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.WishEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -33,6 +34,7 @@ public final class RingOfMaruf extends CardImpl {
Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new RingOfMarufEffect(), new ManaCostsImpl("{5}"));
ability.addCost(new TapSourceCost());
ability.addCost(new ExileSourceCost());
ability.addHint(OpenSideboardHint.instance);
this.addAbility(ability);
}

View file

@ -2,6 +2,7 @@ package mage.cards.r;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -29,6 +30,7 @@ public final class RiseOfExtus extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private RiseOfExtus(final RiseOfExtus card) {

View file

@ -6,6 +6,7 @@ import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -23,7 +24,8 @@ public final class SparringRegimen extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
// When Sparring Regimen enters the battlefield, learn.
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect()));
this.addAbility(new EntersBattlefieldTriggeredAbility(new LearnEffect())
.addHint(OpenSideboardHint.instance));
// Whenever you attack, put a +1/+1 counter on target attacking creature and untap it.
Ability ability = new AttacksWithCreaturesTriggeredAbility(

View file

@ -7,6 +7,7 @@ import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.CastCardFromOutsideTheGameEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.AnnihilatorAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -43,7 +44,9 @@ public final class SpawnsireOfUlamog extends CardImpl {
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CreateTokenEffect(new EldraziSpawnToken(), 2), new GenericManaCost(4)));
// {20}: Cast any number of Eldrazi cards you own from outside the game without paying their mana costs.
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD, new CastCardFromOutsideTheGameEffect(filter, ruleText), new GenericManaCost(20)));
this.addAbility(new SimpleActivatedAbility(Zone.BATTLEFIELD,
new CastCardFromOutsideTheGameEffect(filter, ruleText), new GenericManaCost(20)
).addHint(OpenSideboardHint.instance));
}
private SpawnsireOfUlamog(final SpawnsireOfUlamog card) {

View file

@ -2,6 +2,7 @@ package mage.cards.s;
import mage.abilities.effects.common.LearnEffect;
import mage.abilities.effects.common.TapTargetEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -23,6 +24,7 @@ public final class StudyBreak extends CardImpl {
// Learn.
this.getSpellAbility().addEffect(new LearnEffect().concatBy("<br>"));
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private StudyBreak(final StudyBreak card) {

View file

@ -7,6 +7,7 @@ import java.util.UUID;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.common.SagaAbility;
import mage.abilities.effects.common.*;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.FlyingAbility;
import mage.constants.Duration;
import mage.constants.SagaChapter;
@ -51,6 +52,7 @@ public final class TheRavensWarning extends CardImpl {
sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III,
new WishEffect(StaticFilters.FILTER_CARD_A, false, false, true)
);
sagaAbility.addHint(OpenSideboardHint.instance);
this.addAbility(sagaAbility);
}

View file

@ -7,6 +7,7 @@ import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect
import mage.abilities.effects.common.WishEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.DistributeCountersEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -58,7 +59,8 @@ public final class VivienArkbowRanger extends CardImpl {
this.addAbility(ability);
// 5: You may choose a creature card you own from outside the game, reveal it, and put it into your hand.
this.addAbility(new LoyaltyAbility(new WishEffect(StaticFilters.FILTER_CARD_CREATURE_A), -5));
this.addAbility(new LoyaltyAbility(new WishEffect(StaticFilters.FILTER_CARD_CREATURE_A), -5)
.addHint(OpenSideboardHint.instance));
}
private VivienArkbowRanger(final VivienArkbowRanger card) {

View file

@ -0,0 +1,137 @@
package mage.cards.w;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import mage.MageIdentifier;
import mage.MageObject;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.hint.common.OpenSideboardHint;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.players.Player;
import mage.util.CardUtil;
import mage.watchers.Watcher;
/**
*
* @author weirddan455
*/
public final class Wish extends CardImpl {
public Wish(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{R}");
// You may play a card you own from outside the game this turn.
this.getSpellAbility().addEffect(new WishEffect());
this.getSpellAbility().setIdentifier(MageIdentifier.WishWatcher);
this.getSpellAbility().addWatcher(new WishWatcher());
this.getSpellAbility().addHint(OpenSideboardHint.instance);
}
private Wish(final Wish card) {
super(card);
}
@Override
public Wish copy() {
return new Wish(this);
}
}
class WishEffect extends OneShotEffect {
public WishEffect() {
super(Outcome.Benefit);
this.staticText = "You may play a card you own from outside the game this turn";
}
private WishEffect(final WishEffect effect) {
super(effect);
}
@Override
public WishEffect copy() {
return new WishEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null && !controller.getSideboard().isEmpty()) {
controller.lookAtCards(source, "Sideboard", controller.getSideboard(), game);
game.addEffect(new WishPlayFromSideboardEffect(), source);
return true;
}
return false;
}
}
class WishPlayFromSideboardEffect extends AsThoughEffectImpl {
public WishPlayFromSideboardEffect() {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.EndOfTurn, Outcome.Benefit, true);
}
private WishPlayFromSideboardEffect(final WishPlayFromSideboardEffect effect) {
super(effect);
}
@Override
public WishPlayFromSideboardEffect copy() {
return new WishPlayFromSideboardEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
return true;
}
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (source.getControllerId().equals(affectedControllerId)) {
Player controller = game.getPlayer(source.getControllerId());
MageObject sourceObject = source.getSourceObject(game);
UUID mainCardId = CardUtil.getMainCardId(game, objectId);
if (controller != null && sourceObject != null && controller.getSideboard().contains(mainCardId)) {
WishWatcher watcher = game.getState().getWatcher(WishWatcher.class);
return watcher != null && !watcher.isAbilityUsed(new MageObjectReference(sourceObject, game));
}
}
return false;
}
}
class WishWatcher extends Watcher {
private final Set<MageObjectReference> usedFrom = new HashSet<>();
WishWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if ((GameEvent.EventType.SPELL_CAST.equals(event.getType()) || GameEvent.EventType.LAND_PLAYED.equals(event.getType()))
&& event.hasApprovingIdentifier(MageIdentifier.WishWatcher)) {
usedFrom.add(event.getAdditionalReference().getApprovingMageObjectReference());
}
}
@Override
public void reset() {
super.reset();
usedFrom.clear();
}
boolean isAbilityUsed(MageObjectReference mor) {
return usedFrom.contains(mor);
}
}

View file

@ -263,6 +263,7 @@ public final class AdventuresInTheForgottenRealms extends ExpansionSet {
cards.add(new SetCardInfo("White Dragon", 41, Rarity.UNCOMMON, mage.cards.w.WhiteDragon.class));
cards.add(new SetCardInfo("Wight", 127, Rarity.RARE, mage.cards.w.Wight.class));
cards.add(new SetCardInfo("Wild Shape", 212, Rarity.UNCOMMON, mage.cards.w.WildShape.class));
cards.add(new SetCardInfo("Wish", 166, Rarity.RARE, mage.cards.w.Wish.class));
cards.add(new SetCardInfo("Wizard Class", 81, Rarity.UNCOMMON, mage.cards.w.WizardClass.class));
cards.add(new SetCardInfo("Wizard's Spellbook", 82, Rarity.RARE, mage.cards.w.WizardsSpellbook.class));
cards.add(new SetCardInfo("Xorn", 167, Rarity.RARE, mage.cards.x.Xorn.class));

View file

@ -94,47 +94,48 @@ public class KaradorGhostChieftainTest extends CardTestPlayerBase {
}
@Test
// @Ignore // It's not possible yet to select which ability to use to allow a asThoughtAs effect
public void test_castFromGraveyardWithDifferentApprovers() {
setStrictChooseMode(true);
skipInitShuffling();
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerA, "Island", 3);
//
// {1}{B}: Target attacking Zombie gains indestructible until end of turn.
addCard(Zone.LIBRARY, playerA, "Accursed Horde", 1); // Creature Zombie {3}{B}
skipInitShuffling();
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5);
//
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion", 5); // Creature {1}{W}
//
// Karador, Ghost Chieftain costs {1} less to cast for each creature card in your graveyard.
// During each of your turns, you may cast one creature card from your graveyard.
addCard(Zone.HAND, playerA, "Karador, Ghost Chieftain");// {5}{B}{G}{W}
//
// When Gisa and Geralf enters the battlefield, put the top four cards of your library into your graveyard.
// During each of your turns, you may cast a Zombie creature card from your graveyard.
addCard(Zone.HAND, playerA, "Gisa and Geralf"); // CREATURE {2}{U}{B} (4/4)
// prepare spels with same AsThough effects and puts creature to graveyard
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Karador, Ghost Chieftain");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Gisa and Geralf");
// you play any creatures due to two approve objects
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", true);
checkPlayableAbility("before", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", true);
// cast zombie creature and approves by Karagar
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Accursed Horde");
setChoice(playerA, "During each of your turns, you may cast a Zombie creature card from your graveyard"); // Choose the permitting object
setChoice(playerA, "Karador, Ghost Chieftain"); // choose the permitting object
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Silvercoat Lion");
// you can't cast lion due to approving object (Gisa needs zombie)
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Silvercoat Lion", false);
checkPlayableAbility("after", 3, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Accursed Horde", false);
setStrictChooseMode(true);
setStopAt(3, PhaseStep.BEGIN_COMBAT);
execute();
assertAllCommandsUsed();
assertPermanentCount(playerA, "Karador, Ghost Chieftain", 1);
assertPermanentCount(playerA, "Gisa and Geralf", 1);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertPermanentCount(playerA, "Accursed Horde", 1);
}
}

View file

@ -1977,7 +1977,14 @@ public class TestPlayer implements Player {
//Assert.fail("Wrong choice");
}
this.chooseStrictModeFailed("choice", game, choice.getMessage());
String choicesInfo;
if (choice.isKeyChoice()) {
choicesInfo = String.join("\n", choice.getKeyChoices().values());
} else {
choicesInfo = String.join("\n", choice.getChoices());
}
this.chooseStrictModeFailed("choice", game,
"Message: " + choice.getMessage() + "\nPossible choices:\n" + choicesInfo);
return computerPlayer.choose(outcome, choice, game);
}

View file

@ -11,5 +11,6 @@ public enum MageIdentifier {
KaradorGhostChieftainWatcher,
KessDissidentMageWatcher,
LurrusOfTheDreamDenWatcher,
MuldrothaTheGravetideWatcher
MuldrothaTheGravetideWatcher,
WishWatcher
}

View file

@ -0,0 +1,39 @@
package mage.abilities.dynamicvalue.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.game.Game;
import mage.players.Player;
/**
* @author JayDi85
*/
public enum SideboardCardsYouControlCount implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(sourceAbility.getControllerId());
if (player == null) {
return 0;
}
return player.getSideboard().size();
}
@Override
public SideboardCardsYouControlCount copy() {
return instance;
}
@Override
public String toString() {
return "1";
}
@Override
public String getMessage() {
return "cards in your sideboard";
}
}

View file

@ -592,9 +592,14 @@ public class ContinuousEffects implements Serializable {
Map<String, String> keyChoices = new HashMap<>();
for (ApprovingObject approvingObject : possibleApprovingObjects) {
MageObject mageObject = game.getObject(approvingObject.getApprovingAbility().getSourceId());
keyChoices.put(approvingObject.getApprovingAbility().getId().toString(),
(approvingObject.getApprovingAbility().getRule(mageObject == null ? "" : mageObject.getName()))
+ (mageObject == null ? "" : " (" + mageObject.getIdName() + ")"));
String choiceKey = approvingObject.getApprovingAbility().getId().toString();
String choiceValue;
if (mageObject == null) {
choiceValue = approvingObject.getApprovingAbility().getRule();
} else {
choiceValue = mageObject.getIdName() + ": " + approvingObject.getApprovingAbility().getRule(mageObject.getName());
}
keyChoices.put(choiceKey, choiceValue);
}
Choice choicePermitting = new ChoiceImpl(true);
choicePermitting.setMessage("Choose the permitting object");

View file

@ -1,4 +1,3 @@
package mage.abilities.effects.common;
import java.util.Set;

View file

@ -0,0 +1,26 @@
package mage.abilities.hint.common;
import mage.abilities.Ability;
import mage.abilities.dynamicvalue.common.SideboardCardsYouControlCount;
import mage.abilities.hint.Hint;
import mage.abilities.hint.ValueHint;
import mage.game.Game;
/**
* @author JayDi85
*/
public enum OpenSideboardHint implements Hint {
instance;
private static final Hint hint = new ValueHint("Cards in your sideboard", SideboardCardsYouControlCount.instance);
@Override
public String getText(Game game, Ability ability) {
return hint.getText(game, ability) + " (<i>Right click on battlefield to open player's sideboard at any time</i>)";
}
@Override
public Hint copy() {
return instance;
}
}

View file

@ -59,5 +59,6 @@ public enum PlayerAction {
HOLD_PRIORITY,
UNHOLD_PRIORITY,
VIEW_LIMITED_DECK,
VIEW_SIDEBOARD,
TOGGLE_RECORD_MACRO
}

View file

@ -3646,13 +3646,22 @@ public abstract class PlayerImpl implements Player, Serializable {
}
}
// check to play companion cards
// outside cards
if (fromAll || fromZone == Zone.OUTSIDE) {
// companion cards
for (Cards companionCards : game.getState().getCompanion().values()) {
for (Card card : companionCards.getCards(game)) {
getPlayableFromObjectAll(game, Zone.OUTSIDE, card, availableMana, playable);
}
}
// sideboard cards (example: Wish)
for (UUID sideboardCardId : this.getSideboard()) {
Card sideboardCard = game.getCard(sideboardCardId);
if (sideboardCard != null) {
getPlayableFromObjectAll(game, Zone.OUTSIDE, sideboardCard, availableMana, playable);
}
}
}
// check if it's possible to play the top card of a library