diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 8eaa0d1e00..326cbed204 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -46,6 +46,7 @@ import mage.client.util.Config; import mage.client.util.GameManager; import mage.client.util.PhaseManager; import mage.client.util.gui.ArrowBuilder; +import mage.constants.Constants; import mage.constants.EnlargeMode; import mage.constants.PhaseStep; import mage.remote.Session; @@ -62,6 +63,7 @@ import java.awt.*; import java.awt.event.*; import java.io.Serializable; import java.util.*; +import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; @@ -448,6 +450,10 @@ public final class GamePanel extends javax.swing.JPanel { } public synchronized void updateGame(GameView game) { + updateGame(game, null); + } + + public synchronized void updateGame(GameView game, Map<String, Serializable> options) { if (playerId == null || game.getHand() == null) { this.handContainer.setVisible(false); } else { @@ -506,8 +512,23 @@ public final class GamePanel extends javax.swing.JPanel { this.txtActivePlayer.setText(game.getActivePlayerName()); this.txtPriority.setText(game.getPriorityPlayerName()); this.txtTurn.setText(Integer.toString(game.getTurn())); + + List<UUID> possibleAttackers = new ArrayList<>(); + if (options != null && options.containsKey(Constants.Option.POSSIBLE_ATTACKERS)) { + if (options.get(Constants.Option.POSSIBLE_ATTACKERS) instanceof List) { + possibleAttackers.addAll((List) options.get(Constants.Option.POSSIBLE_ATTACKERS)); + } + } + for (PlayerView player: game.getPlayers()) { if (players.containsKey(player.getPlayerId())) { + if (!possibleAttackers.isEmpty()) { + for (UUID permanentId : possibleAttackers) { + if (player.getBattlefield().containsKey(permanentId)) { + player.getBattlefield().get(permanentId).setCanAttack(true); + } + } + } players.get(player.getPlayerId()).update(player); } else { logger.warn("Couldn't find player."); @@ -693,15 +714,15 @@ public final class GamePanel extends javax.swing.JPanel { return JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_OPTION); } - public void select(String message, GameView gameView, int messageId) { - updateGame(gameView); + public void select(String message, GameView gameView, int messageId, Map<String, Serializable> options) { + updateGame(gameView, options); String messageToDisplay = message; - Map<String, Serializable> options = null; + Map<String, Serializable> panelOptions = null; for (PlayerView playerView : gameView.getPlayers()) { if (playerView.getPlayerId().equals(playerId)) { if (playerView.isActive()) { - options = new HashMap<>(); - options.put("your_turn", true); + panelOptions = new HashMap<>(); + panelOptions.put("your_turn", true); messageToDisplay = message + " <div style='font-size:11pt'>Your turn</div>"; } // magenoxx: because of uncaught bug with saving state, rolling back and stack @@ -712,7 +733,7 @@ public final class GamePanel extends javax.swing.JPanel { break; } } - this.feedbackPanel.getFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), options, messageId); + this.feedbackPanel.getFeedback(FeedbackMode.SELECT, messageToDisplay, gameView.getSpecial(), panelOptions, messageId); if (PhaseManager.getInstance().isSkip(gameView, message)) { this.feedbackPanel.doClick(); logger.debug(new StringBuilder("Phase skipped: ").append(message).append(" id: ").append(messageId)); diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index f0cd521e30..2b3f5f2338 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -27,10 +27,6 @@ */ package mage.client.remote; -import java.util.List; -import java.util.UUID; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; import mage.cards.decks.Deck; import mage.client.MageFrame; import mage.client.chat.ChatPanel; @@ -38,25 +34,21 @@ import mage.client.constants.Constants.DeckEditorMode; import mage.client.draft.DraftPanel; import mage.client.game.GamePanel; import mage.client.plugins.impl.Plugins; -import mage.client.util.audio.AudioManager; import mage.client.util.DeckUtil; import mage.client.util.GameManager; +import mage.client.util.audio.AudioManager; import mage.client.util.object.SaveObjectUtil; import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.utils.CompressUtil; -import mage.view.AbilityPickerView; -import mage.view.ChatMessage; +import mage.view.*; import mage.view.ChatMessage.MessageType; -import mage.view.DeckView; -import mage.view.DraftClientMessage; -import mage.view.DraftView; -import mage.view.GameClientMessage; -import mage.view.GameEndView; -import mage.view.GameView; -import mage.view.TableClientMessage; import org.apache.log4j.Logger; +import javax.swing.*; +import java.util.List; +import java.util.UUID; + /** * * @author BetaSteward_at_googlemail.com @@ -218,7 +210,7 @@ public class CallbackClientImpl implements CallbackClient { GameClientMessage message = (GameClientMessage) callback.getData(); GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { - panel.select(message.getMessage(), message.getGameView(), callback.getMessageId()); + panel.select(message.getMessage(), message.getGameView(), callback.getMessageId(), message.getOptions()); } break; } case "gameChooseAbility": diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java index dc719e5a41..29b7bbf204 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/CardPanel.java @@ -82,6 +82,7 @@ public class CardPanel extends MagePermanent implements MouseListener, MouseMoti private boolean isSelected; private boolean isPlayable; private boolean isChoosable; + private boolean canAttack; private boolean showCastingCost; private boolean hasImage = false; private float alpha = 1.0f; @@ -434,6 +435,11 @@ public class CardPanel extends MagePermanent implements MouseListener, MouseMoti g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); } + if (canAttack) { + g2d.setColor(new Color(250, 250, 0, 200)); + g2d.fillRoundRect(cardXOffset + 1, cardYOffset + 1, cardWidth - 2, cardHeight - 2, cornerSize, cornerSize); + } + //TODO:uncomment /* @@ -702,6 +708,7 @@ public class CardPanel extends MagePermanent implements MouseListener, MouseMoti this.isPlayable = card.isPlayable(); this.isChoosable = card.isChoosable(); + this.canAttack = card.isCanAttack(); boolean updateImage = !gameCard.getName().equals(card.getName()) || gameCard.isFaceDown() != card.isFaceDown(); // update after e.g. turning a night/day card this.gameCard = card; diff --git a/Mage.Common/src/mage/constants/Constants.java b/Mage.Common/src/mage/constants/Constants.java index c6b76fcb3b..17e2a915d5 100644 --- a/Mage.Common/src/mage/constants/Constants.java +++ b/Mage.Common/src/mage/constants/Constants.java @@ -77,4 +77,11 @@ public final class Constants { DISCONNECTED, CONNECTED, CONNECTING, DISCONNECTING, SERVER_UNAVAILABLE, SERVER_STARTING; } + public enum Option { + ; + + public static final String POSSIBLE_ATTACKERS = "possibleAttackers"; + + } + } diff --git a/Mage.Common/src/mage/view/CardView.java b/Mage.Common/src/mage/view/CardView.java index e32d1826bd..3792d56c57 100644 --- a/Mage.Common/src/mage/view/CardView.java +++ b/Mage.Common/src/mage/view/CardView.java @@ -113,6 +113,7 @@ public class CardView extends SimpleCardView { protected boolean isPlayable; protected boolean isChoosable; + protected boolean canAttack; public CardView(Card card) { this(card, null, false); @@ -685,4 +686,12 @@ public class CardView extends SimpleCardView { public boolean isChoosable() { return isChoosable; } + + public boolean isCanAttack() { + return canAttack; + } + + public void setCanAttack(boolean canAttack) { + this.canAttack = canAttack; + } } diff --git a/Mage.Common/src/mage/view/GameClientMessage.java b/Mage.Common/src/mage/view/GameClientMessage.java index 17f7155482..aa27e0fe72 100644 --- a/Mage.Common/src/mage/view/GameClientMessage.java +++ b/Mage.Common/src/mage/view/GameClientMessage.java @@ -60,6 +60,12 @@ public class GameClientMessage implements Serializable { this.message = message; } + public GameClientMessage(GameView gameView, String message, Map<String, Serializable> options) { + this.gameView = gameView; + this.message = message; + this.options = options; + } + private GameClientMessage(GameView gameView, String question, CardsView cardView, Set<UUID> targets, boolean required) { this.gameView = gameView; this.message = question; diff --git a/Mage.Server.Plugins/Mage.Player.Human/pom.xml b/Mage.Server.Plugins/Mage.Player.Human/pom.xml index 04a30a272a..6fdda58580 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/pom.xml +++ b/Mage.Server.Plugins/Mage.Player.Human/pom.xml @@ -20,6 +20,11 @@ <artifactId>mage</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.mage</groupId> + <artifactId>mage-common</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index e16a1dbdd5..1c5b78a1bd 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -42,10 +42,7 @@ import mage.cards.Cards; import mage.cards.decks.Deck; import mage.choices.Choice; import mage.choices.ChoiceImpl; -import mage.constants.ManaType; -import mage.constants.Outcome; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.common.FilterAttackingCreature; import mage.filter.common.FilterBlockingCreature; import mage.filter.common.FilterCreatureForCombat; @@ -594,7 +591,18 @@ public class HumanPlayer extends PlayerImpl { if (passedAllTurns /*|| passedTurn*/) { return; } - game.fireSelectEvent(playerId, "Select attackers"); + Map<String, Serializable> options = new HashMap<>(); + + List<UUID> possibleAttackers = new ArrayList<>(); + + for (Permanent possibleAttacker : game.getBattlefield().getActivePermanents(filter, attackingPlayerId, game)) { + if (possibleAttacker.canAttack(game)) { + possibleAttackers.add(possibleAttacker.getId()); + } + } + options.put(Constants.Option.POSSIBLE_ATTACKERS, (Serializable)possibleAttackers); + + game.fireSelectEvent(playerId, "Select attackers", options); waitForResponse(game); if (response.getBoolean() != null) { // check if enough attackers are declared diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 001028c718..791c6c4373 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -28,24 +28,6 @@ package mage.server.game; -import java.io.BufferedOutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectOutput; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.zip.GZIPOutputStream; import mage.MageException; import mage.abilities.Ability; import mage.cards.Card; @@ -65,25 +47,25 @@ import mage.game.events.TableEvent; import mage.game.permanent.Permanent; import mage.interfaces.Action; import mage.players.Player; -import mage.server.ChatManager; -import mage.server.Main; -import mage.server.TableManager; -import mage.server.User; -import mage.server.UserManager; +import mage.server.*; import mage.server.util.ConfigSettings; import mage.server.util.Splitter; import mage.server.util.SystemUtil; import mage.server.util.ThreadExecutor; import mage.utils.timer.PriorityTimer; -import mage.view.AbilityPickerView; -import mage.view.CardsView; -import mage.view.ChatMessage; +import mage.view.*; import mage.view.ChatMessage.MessageColor; import mage.view.ChatMessage.MessageType; -import mage.view.GameView; -import mage.view.PermanentView; import org.apache.log4j.Logger; +import java.io.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.zip.GZIPOutputStream; + /** * * @author BetaSteward_at_googlemail.com @@ -211,7 +193,7 @@ public class GameController implements GameCallback { target(event.getPlayerId(), event.getMessage(), event.getAbilities(), event.isRequired(), event.getOptions()); break; case SELECT: - select(event.getPlayerId(), event.getMessage()); + select(event.getPlayerId(), event.getMessage(), event.getOptions()); break; case PLAY_MANA: playMana(event.getPlayerId(), event.getMessage()); @@ -617,11 +599,11 @@ public class GameController implements GameCallback { }); } - private synchronized void select(final UUID playerId, final String message) throws MageException { + private synchronized void select(final UUID playerId, final String message, final Map<String, Serializable> options) throws MageException { perform(playerId, new Command() { @Override public void execute(UUID playerId) { - getGameSession(playerId).select(message); + getGameSession(playerId).select(message, options); } }); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameSession.java b/Mage.Server/src/main/java/mage/server/game/GameSession.java index 63d9f13cc4..49e41f152c 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameSession.java +++ b/Mage.Server/src/main/java/mage/server/game/GameSession.java @@ -91,12 +91,12 @@ public class GameSession extends GameWatcher { } } - public void select(final String message) { + public void select(final String message, final Map<String, Serializable> options) { if (!killed) { setupTimeout(); User user = UserManager.getInstance().getUser(userId); if (user != null) { - user.fireCallback(new ClientCallback("gameSelect", game.getId(), new GameClientMessage(getGameView(), message))); + user.fireCallback(new ClientCallback("gameSelect", game.getId(), new GameClientMessage(getGameView(), message, options))); } } } diff --git a/Mage/src/mage/game/Game.java b/Mage/src/mage/game/Game.java index 242d3128b3..f1650bbf7c 100644 --- a/Mage/src/mage/game/Game.java +++ b/Mage/src/mage/game/Game.java @@ -28,13 +28,6 @@ package mage.game; -import java.io.Serializable; -import java.util.Collection; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; import mage.MageItem; import mage.MageObject; import mage.abilities.Ability; @@ -73,6 +66,9 @@ import mage.players.PlayerList; import mage.players.Players; import mage.util.functions.ApplyToPermanent; +import java.io.Serializable; +import java.util.*; + public interface Game extends MageItem, Serializable { MatchType getGameType(); @@ -161,6 +157,7 @@ public interface Game extends MageItem, Serializable { void fireSelectTargetEvent(UUID playerId, String message, List<TriggeredAbility> abilities); void fireSelectTargetEvent(UUID playerId, String message, List<Permanent> perms, boolean required); void fireSelectEvent(UUID playerId, String message); + void fireSelectEvent(UUID playerId, String message, Map<String, Serializable> options); void firePriorityEvent(UUID playerId); void firePlayManaEvent(UUID playerId, String message); void firePlayXManaEvent(UUID playerId, String message); diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index eb8f5b78e9..e3a442d512 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -28,23 +28,6 @@ package mage.game; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Random; -import java.util.Set; -import java.util.Stack; -import java.util.UUID; import mage.MageObject; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; @@ -69,13 +52,7 @@ import mage.cards.CardsImpl; import mage.cards.SplitCard; import mage.cards.decks.Deck; import mage.choices.Choice; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.MultiplayerAttackOption; -import mage.constants.Outcome; -import mage.constants.PhaseStep; -import mage.constants.RangeOfInfluence; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.filter.Filter; import mage.filter.FilterPermanent; @@ -90,14 +67,8 @@ import mage.game.combat.Combat; import mage.game.command.CommandObject; import mage.game.command.Commander; import mage.game.command.Emblem; -import mage.game.events.DamageEvent; -import mage.game.events.GameEvent; -import mage.game.events.Listener; -import mage.game.events.PlayerQueryEvent; -import mage.game.events.PlayerQueryEventSource; -import mage.game.events.TableEvent; +import mage.game.events.*; import mage.game.events.TableEvent.EventType; -import mage.game.events.TableEventSource; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -115,14 +86,14 @@ import mage.target.Target; import mage.target.TargetPermanent; import mage.target.TargetPlayer; import mage.util.functions.ApplyToPermanent; -import mage.watchers.common.CastSpellLastTurnWatcher; -import mage.watchers.common.MiracleWatcher; -import mage.watchers.common.MorbidWatcher; -import mage.watchers.common.PlayerDamagedBySourceWatcher; -import mage.watchers.common.PlayerLostLifeWatcher; -import mage.watchers.common.SoulbondWatcher; +import mage.watchers.common.*; import org.apache.log4j.Logger; +import java.io.IOException; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; + public abstract class GameImpl implements Game, Serializable { private static final transient Logger logger = Logger.getLogger(GameImpl.class); @@ -1660,6 +1631,15 @@ public abstract class GameImpl implements Game, Serializable { playerQueryEventSource.select(playerId, message); } + + @Override + public synchronized void fireSelectEvent(UUID playerId, String message, Map<String, Serializable> options) { + if (simulation) { + return; + } + playerQueryEventSource.select(playerId, message, options); + } + @Override public void firePlayManaEvent(UUID playerId, String message) { if (simulation) { diff --git a/Mage/src/mage/game/combat/Combat.java b/Mage/src/mage/game/combat/Combat.java index 3d80a1840e..3d5c4988fa 100644 --- a/Mage/src/mage/game/combat/Combat.java +++ b/Mage/src/mage/game/combat/Combat.java @@ -27,15 +27,6 @@ */ package mage.game.combat; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.effects.RequirementEffect; import mage.abilities.effects.RestrictionEffect; @@ -57,6 +48,9 @@ import mage.util.CardUtil; import mage.util.Copyable; import mage.util.trace.TraceUtil; +import java.io.Serializable; +import java.util.*; + /** * @author BetaSteward_at_googlemail.com */ @@ -424,7 +418,7 @@ public class Combat implements Serializable, Copyable<Combat> { * Retrieves all requirements that apply and creates a Map with blockers and attackers * // Map<creature that can block, Set< all attackers the creature can block and force it to block the attacker>> * - * @param player - attacker + * @param attackingPlayer - attacker * @param game */ private void retrieveMustBlockAttackerRequirements(Player attackingPlayer, Game game) { diff --git a/Mage/src/mage/game/events/PlayerQueryEvent.java b/Mage/src/mage/game/events/PlayerQueryEvent.java index 25e26b67b7..bbb33b30f6 100644 --- a/Mage/src/mage/game/events/PlayerQueryEvent.java +++ b/Mage/src/mage/game/events/PlayerQueryEvent.java @@ -28,13 +28,6 @@ package mage.game.events; -import java.io.Serializable; -import java.util.EventObject; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; import mage.abilities.Ability; import mage.abilities.ActivatedAbility; import mage.abilities.TriggeredAbility; @@ -42,6 +35,9 @@ import mage.cards.Card; import mage.cards.Cards; import mage.game.permanent.Permanent; +import java.io.Serializable; +import java.util.*; + /** * * @author BetaSteward_at_googlemail.com @@ -188,6 +184,10 @@ public class PlayerQueryEvent extends EventObject implements ExternalEvent, Seri return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false); } + public static PlayerQueryEvent selectEvent(UUID playerId, String message, Map<String, Serializable> options) { + return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.SELECT, 0, 0, false, options); + } + public static PlayerQueryEvent playManaEvent(UUID playerId, String message) { return new PlayerQueryEvent(playerId, message, null, null, null, null, QueryType.PLAY_MANA, 0, 0, false); } diff --git a/Mage/src/mage/game/events/PlayerQueryEventSource.java b/Mage/src/mage/game/events/PlayerQueryEventSource.java index d24719fc43..af5da92e76 100644 --- a/Mage/src/mage/game/events/PlayerQueryEventSource.java +++ b/Mage/src/mage/game/events/PlayerQueryEventSource.java @@ -63,6 +63,10 @@ public class PlayerQueryEventSource implements EventSource<PlayerQueryEvent>, Se dispatcher.fireEvent(PlayerQueryEvent.selectEvent(playerId, message)); } + public void select(UUID playerId, String message, Map<String, Serializable> options) { + dispatcher.fireEvent(PlayerQueryEvent.selectEvent(playerId, message, options)); + } + public void chooseAbility(UUID playerId, String message, String objectName, List<? extends ActivatedAbility> choices) { dispatcher.fireEvent(PlayerQueryEvent.chooseAbilityEvent(playerId, message, objectName, choices)); }