diff --git a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java index 48a77a1ec33..f3660f43081 100644 --- a/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/PlayAreaPanel.java @@ -75,22 +75,38 @@ public class PlayAreaPanel extends javax.swing.JPanel { JMenuItem menuItem; Pmenu = new JPopupMenu(); - menuItem = new JMenuItem("Concede"); + + menuItem = new JMenuItem("Concede game"); Pmenu.add(menuItem); // Concede menuItem.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { - if (JOptionPane.showConfirmDialog(PlayAreaPanel.this, "Are you sure you want to concede?", "Confirm concede", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + if (JOptionPane.showConfirmDialog(PlayAreaPanel.this, "Are you sure you want to concede the game?", "Confirm concede game", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { MageFrame.getSession().concedeGame(gameId); } } }); + menuItem = new JMenuItem("Quit match"); + Pmenu.add(menuItem); + + // Quit match + menuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (JOptionPane.showConfirmDialog(PlayAreaPanel.this, "Are you sure you want to quit the match?", "Confirm quit match", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + MageFrame.getSession().quitMatch(gameId); + } + } + }); + menuItem = new JMenuItem("Cancel"); Pmenu.add(menuItem); battlefieldPanel.getMainPanel().addMouseListener(new MouseAdapter() { + @Override public void mouseReleased(MouseEvent Me) { if (Me.isPopupTrigger() && playingMode) { Pmenu.show(Me.getComponent(), Me.getX(), Me.getY()); diff --git a/Mage.Common/src/mage/interfaces/MageServer.java b/Mage.Common/src/mage/interfaces/MageServer.java index 3944a381679..3c855cf7ff5 100644 --- a/Mage.Common/src/mage/interfaces/MageServer.java +++ b/Mage.Common/src/mage/interfaces/MageServer.java @@ -100,6 +100,7 @@ public interface MageServer { void sendPlayerBoolean(UUID gameId, String sessionId, Boolean data) throws MageException; void sendPlayerInteger(UUID gameId, String sessionId, Integer data) throws MageException; void concedeGame(UUID gameId, String sessionId) throws MageException; + void quitMatch(UUID gameId, String sessionId) throws MageException; void undo(UUID gameId, String sessionId) throws MageException; GameView getGameView(UUID gameId, String sessionId, UUID playerId) throws MageException; diff --git a/Mage.Common/src/mage/remote/SessionImpl.java b/Mage.Common/src/mage/remote/SessionImpl.java index 224350b2bc6..c20d3a7ad2f 100644 --- a/Mage.Common/src/mage/remote/SessionImpl.java +++ b/Mage.Common/src/mage/remote/SessionImpl.java @@ -929,6 +929,21 @@ public class SessionImpl implements Session { return false; } + @Override + public boolean quitMatch(UUID gameId) { + try { + if (isConnected()) { + server.quitMatch(gameId, sessionId); + return true; + } + } catch (MageException ex) { + handleMageException(ex); + } catch (Throwable t) { + handleThrowable(t); + } + return false; + } + @Override public boolean undo(UUID gameId) { try { diff --git a/Mage.Common/src/mage/remote/interfaces/GamePlay.java b/Mage.Common/src/mage/remote/interfaces/GamePlay.java index 3387d792e32..473441adba4 100644 --- a/Mage.Common/src/mage/remote/interfaces/GamePlay.java +++ b/Mage.Common/src/mage/remote/interfaces/GamePlay.java @@ -53,6 +53,8 @@ public interface GamePlay { boolean concedeGame(UUID gameId); + boolean quitMatch(UUID gameId); + boolean submitDeck(UUID tableId, DeckCardLists deck); boolean updateDeck(UUID tableId, DeckCardLists deck); diff --git a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java index e7bf9f418c3..9a8d5845482 100644 --- a/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java +++ b/Mage.Server.Plugins/Mage.Game.CommanderDuel/src/mage/game/CommanderDuel.java @@ -74,15 +74,15 @@ public class CommanderDuel extends GameImpl { @Override public void quit(UUID playerId) { super.quit(playerId); - end(); } @Override public Set getOpponents(UUID playerId) { Set opponents = new HashSet(); for (UUID opponentId: this.getPlayer(playerId).getInRange()) { - if (!opponentId.equals(playerId)) + if (!opponentId.equals(playerId)) { opponents.add(opponentId); + } } return opponents; } diff --git a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java index 349c2ce440c..d945b62dd0a 100644 --- a/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java +++ b/Mage.Server.Plugins/Mage.Game.TwoPlayerDuel/src/mage/game/TwoPlayerDuel.java @@ -71,7 +71,6 @@ public class TwoPlayerDuel extends GameImpl { @Override public void quit(UUID playerId) { super.quit(playerId); - end(); } @Override @@ -85,11 +84,6 @@ public class TwoPlayerDuel extends GameImpl { return opponents; } - @Override - public void leave(UUID playerId) { - super.leave(playerId); - } - @Override public TwoPlayerDuel copy() { return new TwoPlayerDuel(this); diff --git a/Mage.Server/src/main/java/mage/server/TableController.java b/Mage.Server/src/main/java/mage/server/TableController.java index b37d884d9b3..b8437f55270 100644 --- a/Mage.Server/src/main/java/mage/server/TableController.java +++ b/Mage.Server/src/main/java/mage/server/TableController.java @@ -358,22 +358,24 @@ public class TableController { String creator = null; String opponent = null; for (Entry entry: userPlayerMap.entrySet()) { - User user = UserManager.getInstance().getUser(entry.getKey()); - if (user != null) { - user.gameStarted(match.getGame().getId(), entry.getValue()); - if (creator == null) { - creator = user.getName(); - } else { - if (opponent == null) { - opponent = user.getName(); + if (!match.getPlayer(entry.getValue()).hasQuitted()) { + User user = UserManager.getInstance().getUser(entry.getKey()); + if (user != null) { + user.gameStarted(match.getGame().getId(), entry.getValue()); + if (creator == null) { + creator = user.getName(); + } else { + if (opponent == null) { + opponent = user.getName(); + } } } - } - else { - TableManager.getInstance().removeTable(table.getId()); - GameManager.getInstance().removeGame(match.getGame().getId()); - logger.warn("Unable to find player " + entry.getKey()); - break; + else { + TableManager.getInstance().removeTable(table.getId()); + GameManager.getInstance().removeGame(match.getGame().getId()); + logger.warn("Unable to find player " + entry.getKey()); + break; + } } } ServerMessagesUtil.getInstance().incGamesStarted(); 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 925539e12aa..1f87a853935 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -306,6 +306,10 @@ public class GameController implements GameCallback { game.concede(getPlayerId(userId)); } + public void quit(UUID userId) { + game.quit(getPlayerId(userId)); + } + public void undo(UUID userId) { game.undo(getPlayerId(userId)); } diff --git a/Mage.Server/src/main/java/mage/server/game/GameManager.java b/Mage.Server/src/main/java/mage/server/game/GameManager.java index 2042e5ce0af..1581b8d78f0 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameManager.java +++ b/Mage.Server/src/main/java/mage/server/game/GameManager.java @@ -103,6 +103,12 @@ public class GameManager { } } + public void quitMatch(UUID gameId, UUID userId) { + if (gameControllers.containsKey(gameId)) { + gameControllers.get(gameId).quit(userId); + } + } + public void undo(UUID gameId, UUID userId) { if (gameControllers.containsKey(gameId)) { gameControllers.get(gameId).undo(userId); diff --git a/Mage/src/mage/game/GameImpl.java b/Mage/src/mage/game/GameImpl.java index e73cd74e881..e73b12b8888 100644 --- a/Mage/src/mage/game/GameImpl.java +++ b/Mage/src/mage/game/GameImpl.java @@ -417,7 +417,7 @@ public abstract class GameImpl> implements Game, Serializa player.won(this); } } - state.endGame(); + end(); endTime = new Date(); return true; } @@ -736,9 +736,11 @@ public abstract class GameImpl> implements Game, Serializa @Override public void end() { - state.endGame(); - for (Player player: state.getPlayers().values()) { - player.abort(); + if (!state.isGameOver()) { + state.endGame(); + for (Player player: state.getPlayers().values()) { + player.abort(); + } } } @@ -797,8 +799,8 @@ public abstract class GameImpl> implements Game, Serializa public synchronized void quit(UUID playerId) { Player player = state.getPlayer(playerId); if (player != null) { - leave(playerId); - fireInformEvent(player.getName() + " has left the game."); + player.quit(this); + fireInformEvent(player.getName() + " has quitted the match."); } } @@ -1580,9 +1582,12 @@ public abstract class GameImpl> implements Game, Serializa @Override public synchronized void leave(UUID playerId) { Player player = getPlayer(playerId); + if (player.hasLeft()) { + return; + } player.leave(); if (this.isGameOver()) { - // no need to remove objects if only one player is left + // no need to remove objects if only one player is left so the game is over return; } //20100423 - 800.4a diff --git a/Mage/src/mage/game/GameState.java b/Mage/src/mage/game/GameState.java index 2361f7bd71b..48b97fe199c 100644 --- a/Mage/src/mage/game/GameState.java +++ b/Mage/src/mage/game/GameState.java @@ -398,7 +398,7 @@ public class GameState implements Serializable, Copyable { public PlayerList getPlayerList(UUID playerId) { PlayerList newPlayerList = new PlayerList(); for (Player player: players.values()) { - if (!player.hasLeft() && !player.hasLost()) { + if (!player.hasLeft()&& !player.hasLost()) { newPlayerList.add(player.getId()); } } diff --git a/Mage/src/mage/game/match/MatchImpl.java b/Mage/src/mage/game/match/MatchImpl.java index 5fcb9b018c2..edf6f0472b0 100644 --- a/Mage/src/mage/game/match/MatchImpl.java +++ b/Mage/src/mage/game/match/MatchImpl.java @@ -117,12 +117,20 @@ public abstract class MatchImpl implements Match { @Override public boolean isMatchOver() { + int activePlayers = 0; for (MatchPlayer player: players) { + if (!player.hasQuitted()) { + activePlayers++; + } if (player.getWins() >= options.getWinsNeeded()) { endTime = new Date(); return true; } } + if (activePlayers < 2) { + endTime = new Date(); + return true; + } return false; } @@ -157,10 +165,12 @@ public abstract class MatchImpl implements Match { protected void initGame(Game game) throws GameException { shufflePlayers(); for (MatchPlayer matchPlayer: this.players) { - matchPlayer.getPlayer().init(game); - game.loadCards(matchPlayer.getDeck().getCards(), matchPlayer.getPlayer().getId()); - game.loadCards(matchPlayer.getDeck().getSideboard(), matchPlayer.getPlayer().getId()); - game.addPlayer(matchPlayer.getPlayer(), matchPlayer.getDeck()); + if (!matchPlayer.hasQuitted()) { + matchPlayer.getPlayer().init(game); + game.loadCards(matchPlayer.getDeck().getCards(), matchPlayer.getPlayer().getId()); + game.loadCards(matchPlayer.getDeck().getSideboard(), matchPlayer.getPlayer().getId()); + game.addPlayer(matchPlayer.getPlayer(), matchPlayer.getDeck()); + } } game.setPriorityTime(options.getPriorityTime()); } @@ -175,6 +185,9 @@ public abstract class MatchImpl implements Match { for (MatchPlayer player: this.players) { Player p = game.getPlayer(player.getPlayer().getId()); if (p != null) { + if (p.hasQuitted()) { + player.setQuitted(true); + } if (p.hasWon()) { player.addWin(); } @@ -191,7 +204,7 @@ public abstract class MatchImpl implements Match { Game game = getGame(); for (MatchPlayer player: this.players) { Player p = game.getPlayer(player.getPlayer().getId()); - if (p != null && p.hasLost()) { + if (p != null && p.hasLost() && !p.hasQuitted()) { loserId = p.getId(); } } @@ -206,8 +219,10 @@ public abstract class MatchImpl implements Match { @Override public void sideboard() { for (MatchPlayer player: this.players) { - player.setSideboarding(); - player.getPlayer().sideboard(this, player.getDeck()); + if (!player.hasQuitted()) { + player.setSideboarding(); + player.getPlayer().sideboard(this, player.getDeck()); + } } synchronized(this) { while (!isDoneSideboarding()) { @@ -221,7 +236,7 @@ public abstract class MatchImpl implements Match { @Override public boolean isDoneSideboarding() { for (MatchPlayer player: this.players) { - if (!player.isDoneSideboarding()) { + if (!player.hasQuitted() && !player.isDoneSideboarding()) { return false; } } @@ -261,7 +276,11 @@ public abstract class MatchImpl implements Match { for (MatchPlayer mp :this.getPlayers()) { sb.append("- ").append(mp.getPlayer().getName()); sb.append(" (").append(mp.getWins()).append(mp.getWins()==1?" win / ":" wins / "); - sb.append(mp.getLoses()).append(mp.getLoses()==1?" loss)\n":" losses)\n"); + sb.append(mp.getLoses()).append(mp.getLoses()==1?" loss)":" losses)"); + if (mp.hasQuitted()) { + sb.append(" QUITTED"); + } + sb.append("\n"); } sb.append("\n").append(this.getWinsNeeded()).append(this.getWinsNeeded() == 1 ? " win":" wins").append(" needed to win the match\n"); sb.append("\nGame has started\n"); diff --git a/Mage/src/mage/game/match/MatchPlayer.java b/Mage/src/mage/game/match/MatchPlayer.java index c1583074936..6072ac9b77b 100644 --- a/Mage/src/mage/game/match/MatchPlayer.java +++ b/Mage/src/mage/game/match/MatchPlayer.java @@ -40,7 +40,9 @@ public class MatchPlayer { private int wins; private int loses; private Deck deck; + private Player player; + private boolean quitted; private boolean doneSideboarding; public MatchPlayer(Player player, Deck deck) { @@ -49,6 +51,7 @@ public class MatchPlayer { this.wins = 0; this.loses = 0; this.doneSideboarding = true; + this.quitted = false; } public int getWins() { @@ -102,4 +105,11 @@ public class MatchPlayer { return this.doneSideboarding; } + public boolean hasQuitted() { + return quitted; + } + + public void setQuitted(boolean quitted) { + this.quitted = quitted; + } } diff --git a/Mage/src/mage/players/Player.java b/Mage/src/mage/players/Player.java index 45134926757..6332a18a4ea 100644 --- a/Mage/src/mage/players/Player.java +++ b/Mage/src/mage/players/Player.java @@ -99,6 +99,8 @@ public interface Player extends MageItem, Copyable { boolean hasLost(); boolean hasWon(); + boolean hasQuitted(); + void quit(Game game); boolean hasLeft(); /** * Player is still active in game (has not left, lost or won the game). diff --git a/Mage/src/mage/players/PlayerImpl.java b/Mage/src/mage/players/PlayerImpl.java index 12bdedd2e58..6e055a071a1 100644 --- a/Mage/src/mage/players/PlayerImpl.java +++ b/Mage/src/mage/players/PlayerImpl.java @@ -28,16 +28,41 @@ package mage.players; +import java.io.Serializable; +import java.util.ArrayList; +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.Random; +import java.util.Set; +import java.util.UUID; import mage.MageObject; import mage.Mana; -import mage.abilities.*; +import mage.abilities.Abilities; +import mage.abilities.AbilitiesImpl; +import mage.abilities.Ability; +import mage.abilities.ActivatedAbility; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.Mode; +import mage.abilities.PlayLandAbility; +import mage.abilities.SpecialAction; +import mage.abilities.SpellAbility; +import mage.abilities.TriggeredAbility; import mage.abilities.common.PassAbility; import mage.abilities.common.delayed.AtTheEndOfTurnStepPostDelayedTriggeredAbility; import mage.abilities.costs.AdjustingSourceCosts; import mage.abilities.costs.AlternativeCost; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.common.LoseControlOnOtherPlayersControllerEffect; -import mage.abilities.keyword.*; +import mage.abilities.keyword.FlashbackAbility; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.InfectAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.ProtectionAbility; +import mage.abilities.keyword.ShroudAbility; import mage.abilities.mana.ManaAbility; import mage.abilities.mana.ManaOptions; import mage.actions.MageDrawAction; @@ -46,7 +71,13 @@ import mage.cards.Cards; import mage.cards.CardsImpl; import mage.cards.SplitCard; import mage.cards.decks.Deck; -import mage.constants.*; +import mage.constants.AsThoughEffectType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.RangeOfInfluence; +import mage.constants.SpellAbilityType; +import mage.constants.TimingRule; +import mage.constants.Zone; import mage.counters.Counter; import mage.counters.CounterType; import mage.counters.Counters; @@ -72,9 +103,6 @@ import mage.target.common.TargetDiscard; import mage.watchers.common.BloodthirstWatcher; import org.apache.log4j.Logger; -import java.io.Serializable; -import java.util.*; - public abstract class PlayerImpl> implements Player, Serializable { @@ -84,6 +112,7 @@ public abstract class PlayerImpl> implements Player, Ser /** * Means what exactly? + * No more actions? */ protected boolean abort; protected final UUID playerId; @@ -113,7 +142,12 @@ public abstract class PlayerImpl> implements Player, Ser * Note! This differs from passedTurn as it doesn't care about spells and abilities in the stack and will pass them as well. */ protected boolean passedAllTurns; + + // conceded or connection lost game protected boolean left; + // quitted match + protected boolean quitted; + protected RangeOfInfluence range; protected Set inRange = new HashSet(); protected boolean isTestMode = false; @@ -174,6 +208,7 @@ public abstract class PlayerImpl> implements Player, Ser this.turns = player.turns; this.left = player.left; + this.quitted = player.quitted; this.range = player.range; this.canGainLife = player.canGainLife; this.canLoseLife = player.canLoseLife; @@ -220,8 +255,8 @@ public abstract class PlayerImpl> implements Player, Ser this.manaPool = player.getManaPool().copy(); this.turns = player.getTurns(); - this.left = player.hasLeft(); + this.quitted = player.hasQuitted(); this.range = player.getRange(); this.canGainLife = player.isCanGainLife(); this.canLoseLife = player.isCanLoseLife(); @@ -274,6 +309,7 @@ public abstract class PlayerImpl> implements Player, Ser this.wins = false; this.loses = false; this.left = false; + // quittet won't be reset because the player stays quit this.passed = false; this.passedTurn = false; this.passedAllTurns = false; @@ -367,7 +403,7 @@ public abstract class PlayerImpl> implements Player, Ser if (!playerId.equals(this.getId())) { this.playersUnderYourControl.add(playerId); Player player = game.getPlayer(playerId); - if (!player.hasLeft() && !player.hasLost()) { + if (!player.hasLeft()&& !player.hasLost()) { player.setGameUnderYourControl(false); player.setTurnControlledBy(this.getId()); } @@ -1217,7 +1253,7 @@ public abstract class PlayerImpl> implements Player, Ser @Override public void resetPassed() { - if (!this.loses && !this.left) { + if (!this.loses && !this.hasLeft()) { this.passed = false; } else { @@ -1225,10 +1261,14 @@ public abstract class PlayerImpl> implements Player, Ser } } + @Override + public void quit(Game game) { + quitted = true; + this.concede(game); + } + @Override public void concede(Game game) { - this.loses = true; - this.abort(); game.leave(playerId); } @@ -1265,7 +1305,7 @@ public abstract class PlayerImpl> implements Player, Ser this.graveyard.clear(); this.library.clear(); } - + @Override public boolean hasLeft() { return this.left; @@ -1279,7 +1319,9 @@ public abstract class PlayerImpl> implements Player, Ser if (!this.wins) { game.fireEvent(GameEvent.getEvent(GameEvent.EventType.LOST, null, null, playerId)); } - game.leave(playerId); + if (!hasLeft()) { + game.leave(playerId); + } } } @@ -1802,4 +1844,10 @@ public abstract class PlayerImpl> implements Player, Ser public int getPriorityTimeLeft() { return priorityTimeLeft; } + + @Override + public boolean hasQuitted() { + return quitted; + } + }