diff --git a/.gitattributes b/.gitattributes index ccc55788214..1162ec620f0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -164,6 +164,7 @@ forge-core/.settings/org.eclipse.jdt.core.prefs -text forge-core/.settings/org.eclipse.m2e.core.prefs -text forge-core/pom.xml -text forge-core/pom.xml.releaseBackup -text +forge-core/src/main/java/forge/AIOption.java -text forge-core/src/main/java/forge/CardStorageReader.java -text forge-core/src/main/java/forge/FTrace.java -text forge-core/src/main/java/forge/ImageKeys.java -text @@ -17399,10 +17400,12 @@ forge-gui/src/main/java/forge/interfaces/IGuiBase.java -text forge-gui/src/main/java/forge/interfaces/IGuiGame.java -text forge-gui/src/main/java/forge/interfaces/IGuiTimer.java -text forge-gui/src/main/java/forge/interfaces/ILobby.java -text +forge-gui/src/main/java/forge/interfaces/ILobbyListener.java -text forge-gui/src/main/java/forge/interfaces/IMayViewCards.java -text forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java -text forge-gui/src/main/java/forge/interfaces/IProgressBar.java -text forge-gui/src/main/java/forge/interfaces/ITextField.java -text +forge-gui/src/main/java/forge/interfaces/IUpdateable.java -text forge-gui/src/main/java/forge/interfaces/IWinLoseView.java -text forge-gui/src/main/java/forge/itemmanager/BooleanExpression.java -text forge-gui/src/main/java/forge/itemmanager/ColumnDef.java -text @@ -17436,9 +17439,12 @@ forge-gui/src/main/java/forge/limited/WinstonDraft.java -text forge-gui/src/main/java/forge/limited/WinstonDraftAI.java -text forge-gui/src/main/java/forge/limited/package-info.java svneol=native#text/plain forge-gui/src/main/java/forge/match/AbstractGuiGame.java -text +forge-gui/src/main/java/forge/match/GameLobby.java -text forge-gui/src/main/java/forge/match/HostedMatch.java -text +forge-gui/src/main/java/forge/match/LobbySlot.java -text +forge-gui/src/main/java/forge/match/LocalLobby.java -text +forge-gui/src/main/java/forge/match/MatchButtonType.java -text forge-gui/src/main/java/forge/match/MatchConstants.java -text -forge-gui/src/main/java/forge/match/NetGuiGame.java -text forge-gui/src/main/java/forge/match/NextGameDecision.java -text forge-gui/src/main/java/forge/match/input/Input.java -text forge-gui/src/main/java/forge/match/input/InputAttack.java -text @@ -17470,9 +17476,13 @@ forge-gui/src/main/java/forge/model/MetaSet.java -text forge-gui/src/main/java/forge/model/MultipleForgeJarsFoundError.java -text forge-gui/src/main/java/forge/model/UnOpenedMeta.java -text forge-gui/src/main/java/forge/model/package-info.java svneol=native#text/plain +forge-gui/src/main/java/forge/net/ClientGameLobby.java -text forge-gui/src/main/java/forge/net/FGameClient.java -text forge-gui/src/main/java/forge/net/FServerManager.java -text -forge-gui/src/main/java/forge/net/NetGame.java -text +forge-gui/src/main/java/forge/net/LobbyUpdateEvent.java -text +forge-gui/src/main/java/forge/net/NetGameController.java -text +forge-gui/src/main/java/forge/net/NetGuiGame.java -text +forge-gui/src/main/java/forge/net/ServerGameLobby.java -text forge-gui/src/main/java/forge/net/package-info.java -text forge-gui/src/main/java/forge/planarconquest/ConquestAction.java -text forge-gui/src/main/java/forge/planarconquest/ConquestCommander.java -text @@ -17491,7 +17501,6 @@ forge-gui/src/main/java/forge/player/HumanCostDecision.java -text forge-gui/src/main/java/forge/player/HumanPlay.java -text forge-gui/src/main/java/forge/player/HumanPlaySpellAbility.java -text forge-gui/src/main/java/forge/player/LobbyPlayerHuman.java -text -forge-gui/src/main/java/forge/player/NetGameController.java -text forge-gui/src/main/java/forge/player/PlayerControllerHuman.java -text forge-gui/src/main/java/forge/player/TargetSelection.java -text forge-gui/src/main/java/forge/player/package-info.java -text @@ -17606,6 +17615,7 @@ forge-net/src/main/java/forge/net/IConnectionObserver.java -text forge-net/src/main/java/forge/net/Lobby.java -text forge-net/src/main/java/forge/net/LobbyPlayerRemote.java -text forge-net/src/main/java/forge/net/NetServer.java -text +forge-net/src/main/java/forge/net/ReplyPool.java -text forge-net/src/main/java/forge/net/client/GameServlet.java -text forge-net/src/main/java/forge/net/client/INetClient.java -text forge-net/src/main/java/forge/net/client/InvalidFieldInPacketException.java -text @@ -17618,15 +17628,14 @@ forge-net/src/main/java/forge/net/client/state/UnauthorizedClientState.java -tex forge-net/src/main/java/forge/net/client/state/package-info.java -text forge-net/src/main/java/forge/net/game/GuiGameEvent.java -text forge-net/src/main/java/forge/net/game/IRemote.java -text +forge-net/src/main/java/forge/net/game/IdentifiableNetEvent.java -text forge-net/src/main/java/forge/net/game/LobbySlotType.java -text -forge-net/src/main/java/forge/net/game/LobbyState.java -text -forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java -text forge-net/src/main/java/forge/net/game/LoginEvent.java -text forge-net/src/main/java/forge/net/game/LogoutEvent.java -text forge-net/src/main/java/forge/net/game/MessageEvent.java -text forge-net/src/main/java/forge/net/game/NetEvent.java -text -forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java -text -forge-net/src/main/java/forge/net/game/client/ILobbyListener.java -text +forge-net/src/main/java/forge/net/game/ReplyEvent.java -text +forge-net/src/main/java/forge/net/game/UpdateLobbyPlayerEvent.java -text forge-net/src/main/java/forge/net/game/client/IToServer.java -text forge-net/src/main/java/forge/net/game/client/package-info.java -text forge-net/src/main/java/forge/net/game/package-info.java -text diff --git a/forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java b/forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java index c8c7c20f5ba..62007cca23b 100644 --- a/forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java +++ b/forge-ai/src/main/java/forge/ai/LobbyPlayerAi.java @@ -1,7 +1,8 @@ package forge.ai; -import java.util.Map; +import java.util.Set; +import forge.AIOption; import forge.LobbyPlayer; import forge.game.Game; import forge.game.player.IGameEntitiesFactory; @@ -15,9 +16,9 @@ public class LobbyPlayerAi extends LobbyPlayer implements IGameEntitiesFactory { private boolean allowCheatShuffle; private boolean useSimulation; - public LobbyPlayerAi(String name, Map options) { + public LobbyPlayerAi(String name, Set options) { super(name); - if (options != null && "True".equals(options.get("UseSimulation"))) { + if (options != null && options.contains(AIOption.USE_SIMULATION)) { this.useSimulation = true; } } diff --git a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java index ff28d681828..3ae31f4bb0e 100644 --- a/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java +++ b/forge-ai/src/main/java/forge/ai/PlayerControllerAi.java @@ -36,6 +36,7 @@ import forge.game.player.DelayedReveal; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerController; +import forge.game.player.PlayerView; import forge.game.replacement.ReplacementEffect; import forge.game.spellability.*; import forge.game.trigger.Trigger; @@ -141,7 +142,7 @@ public class PlayerControllerAi extends PlayerController { @Override public T chooseSingleEntityForEffect(FCollectionView optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } ApiType api = sa.getApi(); if (null == api) { @@ -240,6 +241,11 @@ public class PlayerControllerAi extends PlayerController { // We don't know how to reveal cards to AI } + @Override + public void reveal(Collection cards, ZoneType zone, PlayerView owner, String messagePrefix) { + // We don't know how to reveal cards to AI + } + @Override public ImmutablePair arrangeForScry(CardCollection topN) { CardCollection toBottom = new CardCollection(); @@ -850,7 +856,7 @@ public class PlayerControllerAi extends PlayerController { String selectPrompt, boolean isOptional, Player decider) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider); } diff --git a/forge-core/src/main/java/forge/AIOption.java b/forge-core/src/main/java/forge/AIOption.java new file mode 100644 index 00000000000..045b3dad4ea --- /dev/null +++ b/forge-core/src/main/java/forge/AIOption.java @@ -0,0 +1,5 @@ +package forge; + +public enum AIOption { + USE_SIMULATION; +} diff --git a/forge-core/src/main/java/forge/card/CardType.java b/forge-core/src/main/java/forge/card/CardType.java index 17147bc5b92..3adc9c68d31 100644 --- a/forge-core/src/main/java/forge/card/CardType.java +++ b/forge-core/src/main/java/forge/card/CardType.java @@ -41,6 +41,8 @@ import java.util.Set; * @version $Id: java 9708 2011-08-09 19:34:12Z jendave $ */ public final class CardType implements Comparable, CardTypeView { + private static final long serialVersionUID = 7612877255541109847L; + public static final CardTypeView EMPTY = new CardType(); public enum CoreType { diff --git a/forge-core/src/main/java/forge/card/CardTypeView.java b/forge-core/src/main/java/forge/card/CardTypeView.java index ecc4c8478c9..4dbe7e5b2ed 100644 --- a/forge-core/src/main/java/forge/card/CardTypeView.java +++ b/forge-core/src/main/java/forge/card/CardTypeView.java @@ -1,5 +1,6 @@ package forge.card; +import java.io.Serializable; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -8,7 +9,7 @@ import forge.card.CardType.CoreType; import forge.card.CardType.Supertype; //Interface to expose only the desired functions of CardType without allowing modification -public interface CardTypeView extends Iterable { +public interface CardTypeView extends Iterable, Serializable { boolean isEmpty(); Iterable getCoreTypes(); Iterable getSupertypes(); diff --git a/forge-core/src/main/java/forge/card/ColorSet.java b/forge-core/src/main/java/forge/card/ColorSet.java index 7bda7c128b3..e5b20e56499 100644 --- a/forge-core/src/main/java/forge/card/ColorSet.java +++ b/forge-core/src/main/java/forge/card/ColorSet.java @@ -18,9 +18,11 @@ package forge.card; import com.google.common.collect.UnmodifiableIterator; + import forge.card.mana.ManaCost; import forge.util.BinaryUtil; +import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; @@ -34,7 +36,8 @@ import java.util.NoSuchElementException; * * */ -public final class ColorSet implements Comparable, Iterable { +public final class ColorSet implements Comparable, Iterable, Serializable { + private static final long serialVersionUID = 794691267379929080L; private final byte myColor; private final float orderWeight; diff --git a/forge-core/src/main/java/forge/card/mana/ManaCost.java b/forge-core/src/main/java/forge/card/mana/ManaCost.java index 4b3a4b61bf7..a59c7bf3c14 100644 --- a/forge-core/src/main/java/forge/card/mana/ManaCost.java +++ b/forge-core/src/main/java/forge/card/mana/ManaCost.java @@ -19,6 +19,7 @@ package forge.card.mana; import forge.card.MagicColor; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -35,7 +36,9 @@ import org.apache.commons.lang3.StringUtils; * @version $Id: CardManaCost.java 9708 2011-08-09 19:34:12Z jendave $ */ -public final class ManaCost implements Comparable, Iterable { +public final class ManaCost implements Comparable, Iterable, Serializable { + private static final long serialVersionUID = -2477430496624149226L; + private static final char DELIM = (char)6; private List shards; diff --git a/forge-core/src/main/java/forge/deck/CardPool.java b/forge-core/src/main/java/forge/deck/CardPool.java index f80f32e719b..d53360c03a6 100644 --- a/forge-core/src/main/java/forge/deck/CardPool.java +++ b/forge-core/src/main/java/forge/deck/CardPool.java @@ -17,6 +17,15 @@ */ package forge.deck; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + import com.google.common.collect.Lists; import forge.StaticData; @@ -26,17 +35,9 @@ import forge.util.ItemPool; import forge.util.ItemPoolSorter; import forge.util.MyRandom; -import org.apache.commons.lang3.StringUtils; - -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class CardPool extends ItemPool { + private static final long serialVersionUID = -5379091255613968393L; public CardPool() { super(PaperCard.class); diff --git a/forge-core/src/main/java/forge/item/PaperCard.java b/forge-core/src/main/java/forge/item/PaperCard.java index 22f353f59a3..8c6b603d1db 100644 --- a/forge-core/src/main/java/forge/item/PaperCard.java +++ b/forge-core/src/main/java/forge/item/PaperCard.java @@ -17,13 +17,17 @@ */ package forge.item; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; + import com.google.common.base.Function; import forge.ImageKeys; +import forge.StaticData; import forge.card.CardRarity; import forge.card.CardRules; - /** * A lightweight version of a card that matches real-world cards, to use outside of games (eg. inventory, decks, trade). *

@@ -31,9 +35,11 @@ import forge.card.CardRules; * * @author Forge */ -public final class PaperCard implements Comparable, InventoryItemFromSet, IPaperCard { +public final class PaperCard implements Comparable, InventoryItemFromSet, IPaperCard, Serializable { + private static final long serialVersionUID = 2942081982620691205L; + // Reference to rules - private final transient CardRules rules; + private transient CardRules rules; // These fields are kinda PK for PrintedCard private final String name; @@ -43,7 +49,7 @@ public final class PaperCard implements Comparable, InventoryItemFro private Boolean hasImage; // Calculated fields are below: - private final transient CardRarity rarity; // rarity is given in ctor when set is assigned + private transient CardRarity rarity; // rarity is given in ctor when set is assigned @Override public String getName() { @@ -199,4 +205,13 @@ public final class PaperCard implements Comparable, InventoryItemFro return Integer.compare(artIndex, o.getArtIndex()); } + + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + // default deserialization + ois.defaultReadObject(); + + final IPaperCard pc = StaticData.instance().getCommonCards().getCard(name, edition, artIndex); + this.rules = pc.getRules(); + this.rarity = pc.getRarity(); + } } diff --git a/forge-core/src/main/java/forge/util/ItemPool.java b/forge-core/src/main/java/forge/util/ItemPool.java index 6238c24c404..2d4c834d827 100644 --- a/forge-core/src/main/java/forge/util/ItemPool.java +++ b/forge-core/src/main/java/forge/util/ItemPool.java @@ -22,6 +22,7 @@ import com.google.common.base.Predicate; import forge.item.InventoryItem; +import java.io.Serializable; import java.util.*; import java.util.Map.Entry; @@ -34,7 +35,8 @@ import java.util.Map.Entry; * @param * an Object */ -public class ItemPool implements Iterable> { +public class ItemPool implements Iterable>, Serializable { + private static final long serialVersionUID = 6572047177527559797L; /** The fn to printed. */ public final transient Function, T> FN_GET_KEY = new Function, T>() { diff --git a/forge-game/src/main/java/forge/game/Game.java b/forge-game/src/main/java/forge/game/Game.java index d2e523cb8a9..b254c37056f 100644 --- a/forge-game/src/main/java/forge/game/Game.java +++ b/forge-game/src/main/java/forge/game/Game.java @@ -56,8 +56,10 @@ import forge.game.player.Player; import forge.game.player.PlayerView; import forge.game.player.RegisteredPlayer; import forge.game.replacement.ReplacementHandler; +import forge.game.spellability.Ability; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.SpellAbilityView; import forge.game.trigger.TriggerHandler; import forge.game.trigger.TriggerType; import forge.game.zone.MagicStack; @@ -105,20 +107,20 @@ public class Game { private final GameView view; private final Tracker tracker = new Tracker(); - private GameEntityCache playerCache = new GameEntityCache<>(); + private final GameEntityCache playerCache = new GameEntityCache<>(); public Player getPlayer(PlayerView playerView) { return playerCache.get(playerView); } - public void addPlayer(Integer id, Player player) { - playerCache.put(id, player); + public void addPlayer(int id, Player player) { + playerCache.put(Integer.valueOf(id), player); } - public GameEntityCache cardCache = new GameEntityCache<>(); + private final GameEntityCache cardCache = new GameEntityCache<>(); public Card getCard(CardView cardView) { return cardCache.get(cardView); } - public void addCard(Integer id, Card card) { - cardCache.put(id, card); + public void addCard(int id, Card card) { + cardCache.put(Integer.valueOf(id), card); } public CardCollection getCardList(Iterable cardViews) { CardCollection list = new CardCollection(); @@ -126,10 +128,20 @@ public class Game { return list; } + private final GameEntityCache spabCache = new GameEntityCache<>(); + public SpellAbility getSpellAbility(final SpellAbilityView view) { + return spabCache.get(view); + } + public void addSpellAbility(int id, SpellAbility spellAbility) { + spabCache.put(Integer.valueOf(id), spellAbility); + } + public Game(List players0, GameRules rules0, Match match0) { /* no more zones to map here */ rules = rules0; match = match0; + spabCache.put(Ability.PLAY_LAND_SURROGATE.getId(), Ability.PLAY_LAND_SURROGATE); + int highestTeam = -1; for (RegisteredPlayer psc : players0) { // Track highest team number for auto assigning unassigned teams diff --git a/forge-game/src/main/java/forge/game/GameEntityCache.java b/forge-game/src/main/java/forge/game/GameEntityCache.java index 57867b7bd08..4f641bd6cac 100644 --- a/forge-game/src/main/java/forge/game/GameEntityCache.java +++ b/forge-game/src/main/java/forge/game/GameEntityCache.java @@ -1,10 +1,12 @@ package forge.game; import java.util.ArrayList; -import java.util.List; import java.util.HashMap; +import java.util.List; -public class GameEntityCache { +import forge.trackable.TrackableObject; + +public class GameEntityCache { private HashMap entityCache = new HashMap(); public void put(Integer id, Entity entity) { diff --git a/forge-game/src/main/java/forge/game/GameEntityView.java b/forge-game/src/main/java/forge/game/GameEntityView.java index 25a16dc5e7e..4fbc65623e7 100644 --- a/forge-game/src/main/java/forge/game/GameEntityView.java +++ b/forge-game/src/main/java/forge/game/GameEntityView.java @@ -7,6 +7,8 @@ import forge.trackable.TrackableProperty; import forge.trackable.Tracker; public abstract class GameEntityView extends TrackableObject { + private static final long serialVersionUID = -5129089945124455670L; + public static GameEntityView get(GameEntity e) { return e == null ? null : e.getView(); } diff --git a/forge-game/src/main/java/forge/game/GameLog.java b/forge-game/src/main/java/forge/game/GameLog.java index 4b3cdea50e1..7afcae2a256 100644 --- a/forge-game/src/main/java/forge/game/GameLog.java +++ b/forge-game/src/main/java/forge/game/GameLog.java @@ -19,6 +19,7 @@ package forge.game; import forge.game.event.IGameEventVisitor; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Observable; @@ -30,10 +31,12 @@ import java.util.Observable; * @author Forge * @version $Id: GameLog.java 12297 2011-11-28 19:56:47Z slapshot5 $ */ -public class GameLog extends Observable { - private List log = new ArrayList(); +public class GameLog extends Observable implements Serializable { + private static final long serialVersionUID = 6465283802022948827L; - private GameLogFormatter formatter = new GameLogFormatter(this); + private final List log = new ArrayList(); + + private final transient GameLogFormatter formatter = new GameLogFormatter(this); /** Logging level: * 0 - Turn diff --git a/forge-game/src/main/java/forge/game/GameLogEntry.java b/forge-game/src/main/java/forge/game/GameLogEntry.java index 17399e9952c..41585a80db0 100644 --- a/forge-game/src/main/java/forge/game/GameLogEntry.java +++ b/forge-game/src/main/java/forge/game/GameLogEntry.java @@ -1,7 +1,10 @@ package forge.game; +import java.io.Serializable; + +public class GameLogEntry implements Serializable { + private static final long serialVersionUID = -5322859985172769630L; -public class GameLogEntry { public final String message; public final GameLogEntryType type; // might add here date and some other fields @@ -10,10 +13,9 @@ public class GameLogEntry { type = type0; message = messageIn; } - + @Override public String toString() { - // TODO Auto-generated method stub return type.getCaption() + ": " + message; } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/GameLogFormatter.java b/forge-game/src/main/java/forge/game/GameLogFormatter.java index 6d25b23151e..9047e6b5d7d 100644 --- a/forge-game/src/main/java/forge/game/GameLogFormatter.java +++ b/forge-game/src/main/java/forge/game/GameLogFormatter.java @@ -111,14 +111,14 @@ public class GameLogFormatter extends IGameEventVisitor.Base { @Override public GameLogEntry visit(final GameEventPlayerControl event) { - final LobbyPlayer newController = event.newController; + final LobbyPlayer newLobbyPlayer = event.newLobbyPlayer; final Player p = event.player; final String message; - if (newController == null) { + if (newLobbyPlayer == null) { message = p.getName() + " has restored control over themself"; } else { - message = String.format("%s is controlled by %s", p.getName(), newController.getName()); + message = String.format("%s is controlled by %s", p.getName(), newLobbyPlayer.getName()); } return new GameLogEntry(GameLogEntryType.PLAYER_CONROL, message); } diff --git a/forge-game/src/main/java/forge/game/GameOutcome.java b/forge-game/src/main/java/forge/game/GameOutcome.java index e324906bc92..7a60a34dde6 100644 --- a/forge-game/src/main/java/forge/game/GameOutcome.java +++ b/forge-game/src/main/java/forge/game/GameOutcome.java @@ -25,6 +25,7 @@ import forge.item.PaperCard; import org.apache.commons.lang3.tuple.Pair; +import java.io.Serializable; import java.util.*; import java.util.Map.Entry; @@ -41,7 +42,9 @@ import java.util.Map.Entry; // only getters) and // GameObserver class - who should be notified of any considerable ingame event public final class GameOutcome implements Iterable> { - public static class AnteResult { + public static class AnteResult implements Serializable { + private static final long serialVersionUID = 5087554550408543192L; + public final List lostCards; public final List wonCards; @@ -50,8 +53,7 @@ public final class GameOutcome implements Iterable(); - } - else { + } else { this.lostCards = cards; this.wonCards = new ArrayList<>(); } diff --git a/forge-game/src/main/java/forge/game/GameView.java b/forge-game/src/main/java/forge/game/GameView.java index dc76102a796..59c530a34d6 100644 --- a/forge-game/src/main/java/forge/game/GameView.java +++ b/forge-game/src/main/java/forge/game/GameView.java @@ -2,6 +2,8 @@ package forge.game; import java.util.List; +import com.google.common.collect.Iterables; + import forge.LobbyPlayer; import forge.deck.Deck; import forge.game.GameOutcome.AnteResult; @@ -22,6 +24,7 @@ import forge.trackable.TrackableProperty; import forge.util.FCollectionView; public class GameView extends TrackableObject { + private static final long serialVersionUID = 8522884512960961528L; /*private final TrackableIndex cards = new TrackableIndex(); private final TrackableIndex players = new TrackableIndex(); @@ -29,7 +32,7 @@ public class GameView extends TrackableObject { private final TrackableIndex stackItems = new TrackableIndex();*/ private final TrackableCollection players; private CombatView combatView; - private final Game game; //TODO: Remove this when possible before network support added + private final transient Game game; //TODO: Remove this when possible before network support added public GameView(final Game game0) { super(-1, game0.getTracker()); //ID not needed @@ -88,10 +91,17 @@ public class GameView extends TrackableObject { set(TrackableProperty.PlayerTurn, PlayerView.get(phaseHandler.getPlayerTurn())); } + public FCollectionView getStack() { + return get(TrackableProperty.Stack); + } + public StackItemView peekStack() { + return Iterables.getFirst(getStack(), null); + } public int getStormCount() { return get(TrackableProperty.StormCount); } - void updateStack(MagicStack stack) { + void updateStack(final MagicStack stack) { + set(TrackableProperty.Stack, StackItemView.getCollection(stack)); set(TrackableProperty.StormCount, stack.getSpellsCastThisTurn().size()); } @@ -166,10 +176,6 @@ public class GameView extends TrackableObject { //TODO: Find better ways to make this information available to all GUIs without using the Game class - public FCollectionView getStack() { - return StackItemView.getCollection(game.getStack()); - } - public boolean isMatchWonBy(LobbyPlayer questPlayer) { return game.getMatch().isWonBy(questPlayer); } @@ -182,10 +188,6 @@ public class GameView extends TrackableObject { return game.getOutcome().getWinningLobbyPlayer(); } - public StackItemView peekStack() { - return StackItemView.get(game.getStack().peek()); - } - public boolean isWinner(LobbyPlayer guiPlayer) { return game.getOutcome().isWinner(guiPlayer); } diff --git a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java index abb9b43fdae..a18a42f6acb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/ChangeZoneEffect.java @@ -16,6 +16,7 @@ import forge.game.event.GameEventCombatChanged; import forge.game.player.DelayedReveal; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; +import forge.game.player.PlayerView; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; @@ -700,10 +701,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect { final int fetchNum = Math.min(player.getCardsIn(ZoneType.Library).size(), 4); CardCollectionView shown = !decider.hasKeyword("LimitSearchLibrary") ? player.getCardsIn(ZoneType.Library) : player.getCardsIn(ZoneType.Library, fetchNum); // Look at whole library before moving onto choosing a card - delayedReveal = new DelayedReveal(shown, ZoneType.Library, player, source.getName() + " - Looking at cards in "); + delayedReveal = new DelayedReveal(shown, ZoneType.Library, PlayerView.get(player), source.getName() + " - Looking at cards in "); } else if (origin.contains(ZoneType.Hand) && player.isOpponentOf(decider)) { - delayedReveal = new DelayedReveal(player.getCardsIn(ZoneType.Hand), ZoneType.Hand, player, source.getName() + " - Looking at cards in "); + delayedReveal = new DelayedReveal(player.getCardsIn(ZoneType.Hand), ZoneType.Hand, PlayerView.get(player), source.getName() + " - Looking at cards in "); } } @@ -765,13 +766,13 @@ public class ChangeZoneEffect extends SpellAbilityEffect { } if (sa.hasParam("ShareLandType")) { for (final Card card : chosenCards) { - + fetchList = CardLists.filter(fetchList, new Predicate() { - @Override - public boolean apply(final Card c) { - return c.sharesLandTypeWith(card); - } - + @Override + public boolean apply(final Card c) { + return c.sharesLandTypeWith(card); + } + }); } } @@ -784,7 +785,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { Card c = null; if (sa.hasParam("AtRandom")) { if (delayedReveal != null) { - delayedReveal.reveal(decider.getController()); + decider.getController().reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } c = Aggregates.random(fetchList); } @@ -812,7 +813,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect { fetchList.remove(c); if (delayedReveal != null) { - delayedReveal.remove(c); + delayedReveal.remove(CardView.get(c)); } chosenCards.add(c); diff --git a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java index 48c2bd13282..38dbe88fde0 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/DigEffect.java @@ -10,6 +10,7 @@ import forge.game.card.CardCollectionView; import forge.game.card.CardLists; import forge.game.player.DelayedReveal; import forge.game.player.Player; +import forge.game.player.PlayerView; import forge.game.spellability.SpellAbility; import forge.game.spellability.TargetRestrictions; import forge.game.zone.PlayerZone; @@ -144,7 +145,7 @@ public class DigEffect extends SpellAbilityEffect { } else if (!sa.hasParam("NoLooking")) { // show the user the revealed cards - delayedReveal = new DelayedReveal(top, srcZone, p); + delayedReveal = new DelayedReveal(top, srcZone, PlayerView.get(p)); if (noMove) { // Let the activating player see the cards even if they're not moved diff --git a/forge-game/src/main/java/forge/game/card/CardCollection.java b/forge-game/src/main/java/forge/game/card/CardCollection.java index aafbeccae1b..438fbe6647f 100644 --- a/forge-game/src/main/java/forge/game/card/CardCollection.java +++ b/forge-game/src/main/java/forge/game/card/CardCollection.java @@ -4,6 +4,8 @@ import java.util.Collection; import forge.util.FCollection; public class CardCollection extends FCollection implements CardCollectionView { + private static final long serialVersionUID = -8133537013727100275L; + public static final CardCollectionView EMPTY = new CardCollection(); public static boolean hasCard(CardCollection cards) { diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java index 582030f3c86..1b14c12703c 100644 --- a/forge-game/src/main/java/forge/game/card/CardView.java +++ b/forge-game/src/main/java/forge/game/card/CardView.java @@ -10,10 +10,10 @@ import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Iterables; import forge.ImageKeys; -import forge.card.CardStateName; import forge.card.CardEdition; import forge.card.CardRarity; import forge.card.CardRules; +import forge.card.CardStateName; import forge.card.CardType; import forge.card.CardTypeView; import forge.card.ColorSet; @@ -32,6 +32,8 @@ import forge.trackable.Tracker; import forge.util.FCollectionView; public class CardView extends GameEntityView { + private static final long serialVersionUID = -3624090829028979255L; + public static CardView get(Card c) { return c == null ? null : c.getView(); } @@ -715,6 +717,8 @@ public class CardView extends GameEntityView { } public class CardStateView extends TrackableObject { + private static final long serialVersionUID = 6673944200513430607L; + private final CardStateName state; public CardStateView(final int id0, final CardStateName state0, final Tracker tracker) { diff --git a/forge-game/src/main/java/forge/game/combat/CombatView.java b/forge-game/src/main/java/forge/game/combat/CombatView.java index 1a7707dc246..8106738b249 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatView.java +++ b/forge-game/src/main/java/forge/game/combat/CombatView.java @@ -17,6 +17,8 @@ import forge.util.FCollection; public class CombatView extends TrackableObject { + private static final long serialVersionUID = 68085618912864941L; + public CombatView(final Tracker tracker) { super(-1, tracker); //ID not needed set(TrackableProperty.AttackersWithDefenders, new HashMap()); diff --git a/forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java b/forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java index 4840f2a3643..84a08910a3d 100644 --- a/forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java +++ b/forge-game/src/main/java/forge/game/event/GameEventPlayerControl.java @@ -2,20 +2,25 @@ package forge.game.event; import forge.LobbyPlayer; import forge.game.player.Player; +import forge.game.player.PlayerController; public class GameEventPlayerControl extends GameEvent { public final Player player; - public final LobbyPlayer oldController; - public final LobbyPlayer newController; + public final LobbyPlayer oldLobbyPlayer; + public final PlayerController oldController; + public final LobbyPlayer newLobbyPlayer; + public final PlayerController newController; - public GameEventPlayerControl(Player p, LobbyPlayer old, LobbyPlayer new1) { - player = p; - oldController = old; - newController = new1; + public GameEventPlayerControl(final Player p, final LobbyPlayer oldLobbyPlayer, final PlayerController oldController, final LobbyPlayer newLobbyPlayer, final PlayerController newController) { + this.player = p; + this.oldLobbyPlayer = oldLobbyPlayer; + this.oldController = oldController; + this.newLobbyPlayer = newLobbyPlayer; + this.newController = newController; } @Override - public T visit(IGameEventVisitor visitor) { + public T visit(final IGameEventVisitor visitor) { return visitor.visit(this); } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java index 1396870631d..3ba1954571a 100644 --- a/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java +++ b/forge-game/src/main/java/forge/game/keyword/KeywordCollection.java @@ -1,5 +1,6 @@ package forge.game.keyword; +import java.io.Serializable; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; @@ -7,8 +8,10 @@ import java.util.Iterator; import java.util.List; import java.util.Map.Entry; -public class KeywordCollection implements Iterable { - private KeywordCollectionView view; +public class KeywordCollection implements Iterable, Serializable { + private static final long serialVersionUID = -2882986558147844702L; + + private transient KeywordCollectionView view; private final EnumMap>> map = new EnumMap>>(Keyword.class); public boolean contains(Keyword keyword) { @@ -134,7 +137,9 @@ public class KeywordCollection implements Iterable { return view; } - public class KeywordCollectionView implements Iterable { + public class KeywordCollectionView implements Iterable, Serializable { + private static final long serialVersionUID = 7536969077044188264L; + protected KeywordCollectionView() { } diff --git a/forge-game/src/main/java/forge/game/player/DelayedReveal.java b/forge-game/src/main/java/forge/game/player/DelayedReveal.java index 99b6cce0c1f..fe9bd2ea430 100644 --- a/forge-game/src/main/java/forge/game/player/DelayedReveal.java +++ b/forge-game/src/main/java/forge/game/player/DelayedReveal.java @@ -1,28 +1,32 @@ package forge.game.player; +import java.util.Collection; + import forge.game.card.Card; -import forge.game.card.CardCollection; +import forge.game.card.CardView; import forge.game.zone.ZoneType; -//Stores information to reveal cards after a delay unless those cards can be revealed in the same dialog as cards being selected +/** + * Stores information to reveal cards after a delay unless those cards can be + * revealed in the same dialog as cards being selected + */ public class DelayedReveal { - private final CardCollection cards; + private final Collection cards; private final ZoneType zone; - private final Player owner; + private final PlayerView owner; private final String messagePrefix; - private boolean revealed; - public DelayedReveal(Iterable cards0, ZoneType zone0, Player owner0) { + public DelayedReveal(Iterable cards0, ZoneType zone0, PlayerView owner0) { this(cards0, zone0, owner0, null); } - public DelayedReveal(Iterable cards0, ZoneType zone0, Player owner0, String messagePrefix0) { - cards = new CardCollection(cards0); //create copy of list to allow modification + public DelayedReveal(Iterable cards0, ZoneType zone0, PlayerView owner0, String messagePrefix0) { + cards = CardView.getCollection(cards0); zone = zone0; owner = owner0; messagePrefix = messagePrefix0; } - public Iterable getCards() { + public Collection getCards() { return cards; } @@ -30,17 +34,16 @@ public class DelayedReveal { return zone; } - public Player getOwner() { + public PlayerView getOwner() { return owner; } - public void remove(Card card) { + public String getMessagePrefix() { + return messagePrefix; + } + + public void remove(CardView card) { cards.remove(card); } - public void reveal(PlayerController controller) { - if (revealed) { return; } //avoid revealing more than once - revealed = true; - controller.reveal(cards, zone, owner, messagePrefix); - } } diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java index bd9fe48857e..da4ea389528 100644 --- a/forge-game/src/main/java/forge/game/player/Player.java +++ b/forge-game/src/main/java/forge/game/player/Player.java @@ -2099,7 +2099,7 @@ public class Player extends GameEntity implements Comparable { return mindSlaveMaster; } - public final void setMindSlaveMaster(Player mindSlaveMaster0) { + public final void setMindSlaveMaster(final Player mindSlaveMaster0) { if (mindSlaveMaster == mindSlaveMaster0) { return; } @@ -2107,14 +2107,14 @@ public class Player extends GameEntity implements Comparable { view.updateMindSlaveMaster(this); if (mindSlaveMaster != null) { - LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); - IGameEntitiesFactory master = (IGameEntitiesFactory)mindSlaveMaster.getLobbyPlayer(); + final LobbyPlayer oldLobbyPlayer = getLobbyPlayer(); + final PlayerController oldController = getController(); + final IGameEntitiesFactory master = (IGameEntitiesFactory)mindSlaveMaster.getLobbyPlayer(); controller = master.createMindSlaveController(mindSlaveMaster, this); - game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, getLobbyPlayer())); - } - else { + game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), controller)); + } else { controller = controllerCreator; - game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), null)); + game.fireEvent(new GameEventPlayerControl(this, getLobbyPlayer(), controller, null, null)); } } diff --git a/forge-game/src/main/java/forge/game/player/PlayerController.java b/forge-game/src/main/java/forge/game/player/PlayerController.java index 8445b32062d..79e409c61ea 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerController.java +++ b/forge-game/src/main/java/forge/game/player/PlayerController.java @@ -26,6 +26,7 @@ import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; import forge.game.card.CardShields; +import forge.game.card.CardView; import forge.game.card.CounterType; import forge.game.combat.Combat; import forge.game.cost.Cost; @@ -140,6 +141,8 @@ public abstract class PlayerController { reveal(cards, zone, owner, null); } public abstract void reveal(CardCollectionView cards, ZoneType zone, Player owner, String messagePrefix); + public abstract void reveal(Collection cards, ZoneType zone, PlayerView owner, String messagePrefix); + /** Shows message to player to reveal chosen cardName, creatureType, number etc. AI must analyze API to understand what that is */ public abstract void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value); public abstract ImmutablePair arrangeForScry(CardCollection topN); diff --git a/forge-game/src/main/java/forge/game/player/PlayerView.java b/forge-game/src/main/java/forge/game/player/PlayerView.java index 7b5ac7c23e7..c0107a89adb 100644 --- a/forge-game/src/main/java/forge/game/player/PlayerView.java +++ b/forge-game/src/main/java/forge/game/player/PlayerView.java @@ -21,8 +21,9 @@ import forge.trackable.TrackableProperty; import forge.trackable.Tracker; import forge.util.FCollectionView; - public class PlayerView extends GameEntityView { + private static final long serialVersionUID = 7005892740909549086L; + public static PlayerView get(Player p) { return p == null ? null : p.getView(); } diff --git a/forge-game/src/main/java/forge/game/spellability/Ability.java b/forge-game/src/main/java/forge/game/spellability/Ability.java index c7c2e494f26..e68acc376b9 100644 --- a/forge-game/src/main/java/forge/game/spellability/Ability.java +++ b/forge-game/src/main/java/forge/game/spellability/Ability.java @@ -75,11 +75,11 @@ public abstract class Ability extends SpellAbility { return this.getHostCard().isInPlay() && !this.getHostCard().isFaceDown(); } - + public static final Ability PLAY_LAND_SURROGATE = new Ability(null, (Cost)null, null){ - @Override + @Override public boolean canPlay() { - return true; //if this ability is added anywhere, it can be assumed that land can be played + return true; //if this ability is added anywhere, it can be assumed that land can be played } @Override public void resolve() { diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java index 8ddfb44f3d2..8b4865f5185 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -147,12 +147,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit view0 = new SpellAbilityView(this); } view = view0; + if (hostCard != null && hostCard.getGame() != null) { + hostCard.getGame().addSpellAbility(id, this); + } } @Override public int getId() { return id; } + @Override + public int hashCode() { + return getId(); + } + @Override + public boolean equals(final Object obj) { + return obj instanceof SpellAbility && this.id == ((SpellAbility) obj).id; + }; @Override public void setHostCard(final Card c) { @@ -1247,6 +1258,10 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit } public SpellAbilityView getView() { + view.updateHostCard(this); + view.updateDescription(this); + view.updateCanPlay(this); + view.updatePromptIfOnlyPossibleAbility(this); return view; } } \ No newline at end of file diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityView.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityView.java index 0629875154a..6567ba6928e 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityView.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityView.java @@ -5,8 +5,9 @@ import forge.trackable.TrackableCollection; import forge.trackable.TrackableObject; import forge.trackable.TrackableProperty; - public class SpellAbilityView extends TrackableObject { + private static final long serialVersionUID = 2514234930798754769L; + public static SpellAbilityView get(SpellAbility spab) { return spab == null ? null : spab.getView(); } diff --git a/forge-game/src/main/java/forge/game/spellability/StackItemView.java b/forge-game/src/main/java/forge/game/spellability/StackItemView.java index e8661960eea..edcb9176679 100644 --- a/forge-game/src/main/java/forge/game/spellability/StackItemView.java +++ b/forge-game/src/main/java/forge/game/spellability/StackItemView.java @@ -7,8 +7,9 @@ import forge.trackable.TrackableObject; import forge.trackable.TrackableProperty; import forge.util.FCollectionView; - public class StackItemView extends TrackableObject { + private static final long serialVersionUID = 6733415646691356052L; + public static StackItemView get(SpellAbilityStackInstance si) { return si == null ? null : si.getView(); } diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java index e63515e2a5e..54827be304e 100644 --- a/forge-game/src/main/java/forge/game/zone/MagicStack.java +++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java @@ -17,6 +17,16 @@ */ package forge.game.zone; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Stack; +import java.util.concurrent.LinkedBlockingDeque; + import com.esotericsoftware.minlog.Log; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -45,14 +55,17 @@ import forge.game.player.PlayerController.ManaPaymentPurpose; import forge.game.replacement.ReplacementEffect; import forge.game.replacement.ReplacementHandler; import forge.game.replacement.ReplacementLayer; -import forge.game.spellability.*; +import forge.game.spellability.Ability; +import forge.game.spellability.AbilityStatic; +import forge.game.spellability.OptionalCost; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetChoices; +import forge.game.spellability.TargetRestrictions; import forge.game.trigger.Trigger; import forge.game.trigger.TriggerType; -import java.util.*; -import java.util.Map.Entry; -import java.util.concurrent.LinkedBlockingDeque; - /** *

* MagicStack class. @@ -518,6 +531,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable extends FCollection { + private static final long serialVersionUID = 1528674215758232314L; + public TrackableCollection() { } public TrackableCollection(T e) { diff --git a/forge-game/src/main/java/forge/trackable/TrackableObject.java b/forge-game/src/main/java/forge/trackable/TrackableObject.java index 48c6468ba71..0797b6d58fc 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableObject.java +++ b/forge-game/src/main/java/forge/trackable/TrackableObject.java @@ -1,5 +1,6 @@ package forge.trackable; +import java.io.Serializable; import java.util.EnumMap; import java.util.EnumSet; import java.util.Map; @@ -8,7 +9,9 @@ import java.util.Set; import forge.game.IIdentifiable; //base class for objects that can be tracked and synced between game server and GUI -public abstract class TrackableObject implements IIdentifiable { +public abstract class TrackableObject implements IIdentifiable, Serializable { + private static final long serialVersionUID = 7386836745378571056L; + private final int id; protected final transient Tracker tracker; private final Map props; @@ -60,6 +63,30 @@ public abstract class TrackableObject implements IIdentifiable { } } + /** + * Copy to this Trackable object the first object of the provided iterator + * whose id matches. + * + * @see TrackableObject#copy(TrackableObject) + */ + public final void copy(final Iterable others) { + for (final TrackableObject other : others) { + if (this.equals(other)) { + copy(other); + break; + } + } + } + + /** + * Copy all properties of another Trackable object to this object. + */ + public final void copy(final TrackableObject other) { + for (final TrackableProperty prop : other.props.keySet()) { + set(prop, other.get(prop)); + } + } + //use when updating collection type properties with using set protected final void flagAsChanged(final TrackableProperty key) { changedProps.add(key); diff --git a/forge-game/src/main/java/forge/trackable/TrackableProperty.java b/forge-game/src/main/java/forge/trackable/TrackableProperty.java index adefd71e589..6207d3a777b 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableProperty.java +++ b/forge-game/src/main/java/forge/trackable/TrackableProperty.java @@ -142,6 +142,7 @@ public enum TrackableProperty { MatchOver(TrackableTypes.BooleanType), NumGamesInMatch(TrackableTypes.IntegerType), NumPlayedGamesInMatch(TrackableTypes.IntegerType), + Stack(TrackableTypes.StackItemViewListType), StormCount(TrackableTypes.IntegerType), GameOver(TrackableTypes.BooleanType), PoisonCountersToLose(TrackableTypes.IntegerType), diff --git a/forge-game/src/main/java/forge/trackable/TrackableTypes.java b/forge-game/src/main/java/forge/trackable/TrackableTypes.java index 864abf4f84a..78d2b62cb3a 100644 --- a/forge-game/src/main/java/forge/trackable/TrackableTypes.java +++ b/forge-game/src/main/java/forge/trackable/TrackableTypes.java @@ -13,8 +13,8 @@ import forge.card.ColorSet; import forge.card.mana.ManaCost; import forge.game.GameEntityView; import forge.game.card.CardView; -import forge.game.card.CounterType; import forge.game.card.CardView.CardStateView; +import forge.game.card.CounterType; import forge.game.keyword.KeywordCollection.KeywordCollectionView; import forge.game.player.PlayerView; import forge.game.spellability.StackItemView; @@ -284,6 +284,22 @@ public class TrackableTypes { } } }; + public static final TrackableType> StackItemViewListType = new TrackableType>() { + @Override + protected TrackableCollection getDefaultValue() { + return new TrackableCollection(); + } + + @Override + protected TrackableCollection deserialize(TrackableDeserializer td, TrackableCollection oldValue) { + return td.readCollection(oldValue); + } + + @Override + protected void serialize(TrackableSerializer ts, TrackableCollection value) { + ts.write(value); + } + }; public static final TrackableType ManaCostType = new TrackableType() { @Override public ManaCost getDefaultValue() { diff --git a/forge-game/src/main/java/forge/util/FCollection.java b/forge-game/src/main/java/forge/util/FCollection.java index 8064d1dd36e..bcc59ee09ca 100644 --- a/forge-game/src/main/java/forge/util/FCollection.java +++ b/forge-game/src/main/java/forge/util/FCollection.java @@ -1,5 +1,6 @@ package forge.util; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -12,7 +13,9 @@ import java.util.ListIterator; import java.util.Set; //base class for a collection with quick lookup and that maintains order -public class FCollection implements List, Set, FCollectionView, Cloneable { +public class FCollection implements List, Set, FCollectionView, Cloneable, Serializable { + private static final long serialVersionUID = -1664555336364294106L; + private final HashSet set = new HashSet(); private final LinkedList list = new LinkedList(); diff --git a/forge-game/src/main/java/forge/util/MessageUtil.java b/forge-game/src/main/java/forge/util/MessageUtil.java index c6fa7a79b30..99e08a89dfb 100644 --- a/forge-game/src/main/java/forge/util/MessageUtil.java +++ b/forge-game/src/main/java/forge/util/MessageUtil.java @@ -4,6 +4,7 @@ import org.apache.commons.lang3.StringUtils; import forge.game.GameObject; import forge.game.player.Player; +import forge.game.player.PlayerView; import forge.game.spellability.SpellAbility; public class MessageUtil { @@ -17,6 +18,14 @@ public class MessageUtil { return message; } + public static String formatMessage(String message, PlayerView player, Object related) { + if (related instanceof Player && message.indexOf("{player") >= 0) { + String noun = mayBeYou(player, related); + message = message.replace("{player}", noun).replace("{player's}", Lang.getPossesive(noun)); + } + return message; + } + // These are not much related to PlayerController public static String formatNotificationMessage(SpellAbility sa, Player player, GameObject target, String value) { if (sa == null || sa.getApi() == null || sa.getHostCard() == null) { @@ -50,4 +59,7 @@ public class MessageUtil { public static String mayBeYou(Player player, Object what) { return what == null ? "(null)" : what == player ? "you" : what.toString(); } + public static String mayBeYou(PlayerView player, Object what) { + return what == null ? "(null)" : what == player ? "you" : what.toString(); + } } diff --git a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java index 60df3d14339..9d57b178961 100644 --- a/forge-gui-desktop/src/main/java/forge/GuiDesktop.java +++ b/forge-gui-desktop/src/main/java/forge/GuiDesktop.java @@ -29,7 +29,6 @@ import forge.download.GuiDownloader; import forge.error.BugReportDialog; import forge.gui.BoxedProductCardListViewer; import forge.gui.CardListViewer; -import forge.gui.FNetOverlay; import forge.gui.GuiChoose; import forge.gui.framework.FScreen; import forge.interfaces.IGuiBase; @@ -280,9 +279,4 @@ public class GuiDesktop implements IGuiBase { return match; } - @Override - public void netMessage(final String origin, final String message) { - FNetOverlay.SINGLETON_INSTANCE.showUp(""); - FNetOverlay.SINGLETON_INSTANCE.addMessage(origin, message); - } } \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java index 09398d3e90f..c7027a6948c 100644 --- a/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java +++ b/forge-gui-desktop/src/main/java/forge/gui/FNetOverlay.java @@ -134,10 +134,15 @@ public enum FNetOverlay { getPanel().setBounds(x, y, w, height); getPanel().validate(); } - - SimpleDateFormat inFormat = new SimpleDateFormat("HH:mm:ss"); - public void addMessage(String origin, String message) { - String toAdd = String.format("%n[%s] %s: %s", inFormat.format(new Date()), origin, message); + + private final static SimpleDateFormat inFormat = new SimpleDateFormat("HH:mm:ss"); + public void addMessage(final String origin, final String message) { + final String toAdd; + if (origin == null) { + toAdd = String.format("%n[%s] %s: %s", inFormat.format(new Date()), origin, message); + } else { + toAdd = String.format("%n[%s] %s", inFormat.format(new Date()), message); + } txtLog.append(toAdd); } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java index 04140c51225..e0560875086 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/CLobby.java @@ -2,39 +2,17 @@ package forge.screens.home; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.Vector; import javax.swing.SwingUtilities; -import com.beust.jcommander.internal.Maps; - -import forge.GuiBase; -import forge.LobbyPlayer; -import forge.deck.CardPool; import forge.deck.Deck; -import forge.deck.DeckFormat; -import forge.deck.DeckSection; import forge.deck.DeckType; -import forge.deck.DeckgenUtil; -import forge.game.GameType; -import forge.game.player.RegisteredPlayer; -import forge.gui.GuiDialog; -import forge.interfaces.IGuiGame; -import forge.item.PaperCard; -import forge.match.HostedMatch; import forge.model.CardCollections; import forge.model.FModel; -import forge.player.GamePlayerUtil; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.toolbox.FList; -import forge.toolbox.FOptionPane; -import forge.util.Aggregates; -import forge.util.storage.IStorage; public class CLobby { @@ -93,7 +71,7 @@ public class CLobby { // Planechase: reinit deck lists and restore last selections (if any) deckList = view.getPlanarDeckLists().get(i); - listData = new Vector(); + listData = new Vector(); listData.add("Use deck's planes section (random if unavailable)"); listData.add("Generate"); @@ -101,7 +79,7 @@ public class CLobby { listData.add("Random"); for (Deck planarDeck : cColl.getPlane()) { listData.add(planarDeck); - } + } } val = deckList.getSelectedValue(); @@ -118,7 +96,6 @@ public class CLobby { } // General updates when switching back to this view - view.updatePlayersFromPrefs(); view.getBtnStart().requestFocusInWindow(); } }); @@ -134,14 +111,6 @@ public class CLobby { view.getDeckChooser(6).initialize(FPref.CONSTRUCTED_P7_DECK_STATE, DeckType.COLOR_DECK); view.getDeckChooser(7).initialize(FPref.CONSTRUCTED_P8_DECK_STATE, DeckType.COLOR_DECK); - // Start button event handling - view.getBtnStart().addActionListener(new ActionListener() { - @Override - public void actionPerformed(final ActionEvent arg0) { - startGame(view.getAppliedVariants()); - } - }); - final ForgePreferences prefs = FModel.getPreferences(); // Checkbox event handling view.getCbSingletons().addActionListener(new ActionListener() { @@ -165,208 +134,4 @@ public class CLobby { view.getCbArtifacts().setSelected(prefs.getPrefBoolean(FPref.DECKGEN_ARTIFACTS)); } - /** Starts a match with the applied variants. */ - private void startGame(final Set variantTypes) { - if (!view.isEnoughTeams()) { - FOptionPane.showMessageDialog("There are not enough teams! Please adjust team allocations."); - return; - } - - for (final int i : view.getParticipants()) { - if (view.getDeckChooser(i).getPlayer() == null) { - FOptionPane.showMessageDialog("Please specify a deck for " + view.getPlayerName(i)); - return; - } - } // Is it even possible anymore? I think current implementation assigns decks automatically. - - GameType autoGenerateVariant = null; - boolean isCommanderMatch = false; - boolean isTinyLeadersMatch = false; - if (!variantTypes.isEmpty()) { - isTinyLeadersMatch = variantTypes.contains(GameType.TinyLeaders); - isCommanderMatch = isTinyLeadersMatch || variantTypes.contains(GameType.Commander); - if (!isCommanderMatch) { - for (GameType variant : variantTypes) { - if (variant.isAutoGenerated()) { - autoGenerateVariant = variant; - break; - } - } - } - } - - boolean checkLegality = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); - - //Auto-generated decks don't need to be checked here - //Commander deck replaces regular deck and is checked later - if (checkLegality && autoGenerateVariant == null && !isCommanderMatch) { - for (final int i : view.getParticipants()) { - String name = view.getPlayerName(i); - String errMsg = GameType.Constructed.getDeckFormat().getDeckConformanceProblem(view.getDeckChooser(i).getPlayer().getDeck()); - if (null != errMsg) { - FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Deck"); - return; - } - } - } - - final List players = new ArrayList(); - final Map guis = Maps.newHashMap(); - final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); - for (final int i : view.getParticipants()) { - final String name = view.getPlayerName(i); - final boolean isAI = view.isPlayerAI(i); - final LobbyPlayer lobbyPlayer = isAI - ? GamePlayerUtil.createAiPlayer(name, view.getPlayerAvatar(i), view.getAiOptions(i)) - : GamePlayerUtil.getGuiPlayer(name, i); - RegisteredPlayer rp = view.getDeckChooser(i).getPlayer(); - - if (variantTypes.isEmpty()) { - rp.setTeamNumber(view.getTeam(i)); - players.add(rp.setPlayer(lobbyPlayer)); - } else { - Deck deck = null; - PaperCard vanguardAvatar = null; - if (isCommanderMatch) { - final Object selected = view.getCommanderDeckLists().get(i).getSelectedValue(); - if (selected instanceof String) { - final String sel = (String) selected; - final IStorage comDecks = FModel.getDecks().getCommander(); - if (sel.equals("Random") && comDecks.size() > 0) { - deck = Aggregates.random(comDecks); - } - } else { - deck = (Deck) selected; - } - GameType commanderGameType = isTinyLeadersMatch ? GameType.TinyLeaders : GameType.Commander; - if (deck == null) { //Can be null if player deselects the list selection or chose Generate - deck = DeckgenUtil.generateCommanderDeck(isAI, commanderGameType); - } - if (checkLegality) { - String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck); - if (null != errMsg) { - FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid " + commanderGameType + " Deck"); - return; - } - } - } else if (autoGenerateVariant != null) { - deck = autoGenerateVariant.autoGenerateDeck(rp); - CardPool avatarPool = deck.get(DeckSection.Avatar); - if (avatarPool != null) { - vanguardAvatar = avatarPool.get(0); - } - } - - // Initialise variables for other variants - deck = deck == null ? rp.getDeck() : deck; - Iterable schemes = null; - final boolean playerIsArchenemy = view.isPlayerArchenemy(i); - Iterable planes = null; - - //Archenemy - if (variantTypes.contains(GameType.ArchenemyRumble) - || (variantTypes.contains(GameType.Archenemy) && playerIsArchenemy)) { - Object selected = view.getSchemeDeckLists().get(i).getSelectedValue(); - CardPool schemePool = null; - if (selected instanceof String) { - String sel = (String) selected; - if (sel.contains("Use deck's scheme section")) { - if (deck.has(DeckSection.Schemes)) { - schemePool = deck.get(DeckSection.Schemes); - } else { - sel = "Random"; - } - } - IStorage sDecks = FModel.getDecks().getScheme(); - if (sel.equals("Random") && sDecks.size() != 0) { - schemePool = Aggregates.random(sDecks).get(DeckSection.Schemes); - } - } else { - schemePool = ((Deck) selected).get(DeckSection.Schemes); - } - if (schemePool == null) { //Can be null if player deselects the list selection or chose Generate - schemePool = DeckgenUtil.generateSchemePool(); - } - if (checkLegality) { - String errMsg = DeckFormat.getSchemeSectionConformanceProblem(schemePool); - if (null != errMsg) { - FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Scheme Deck"); - return; - } - } - schemes = schemePool.toFlatList(); - } - - //Planechase - if (variantTypes.contains(GameType.Planechase)) { - Object selected = view.getPlanarDeckLists().get(i).getSelectedValue(); - CardPool planePool = null; - if (selected instanceof String) { - String sel = (String) selected; - if (sel.contains("Use deck's planes section")) { - if (deck.has(DeckSection.Planes)) { - planePool = deck.get(DeckSection.Planes); - } else { - sel = "Random"; - } - } - IStorage pDecks = FModel.getDecks().getPlane(); - if (sel.equals("Random") && pDecks.size() != 0) { - planePool = Aggregates.random(pDecks).get(DeckSection.Planes); - } - } else { - planePool = ((Deck) selected).get(DeckSection.Planes); - } - if (planePool == null) { //Can be null if player deselects the list selection or chose Generate - planePool = DeckgenUtil.generatePlanarPool(); - } - if (checkLegality) { - String errMsg = DeckFormat.getPlaneSectionConformanceProblem(planePool); - if (null != errMsg) { - FOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Planar Deck"); - return; - } - } - planes = planePool.toFlatList(); - } - - //Vanguard - if (variantTypes.contains(GameType.Vanguard)) { - Object selected = view.getVanguardLists().get(i).getSelectedValue(); - if (selected instanceof String) { - String sel = (String) selected; - if (sel.contains("Use deck's default avatar") && deck.has(DeckSection.Avatar)) { - vanguardAvatar = deck.get(DeckSection.Avatar).get(0); - } else { //Only other string is "Random" - if (isAI) { //AI - vanguardAvatar = Aggregates.random(view.getNonRandomAiAvatars()); - } else { //Human - vanguardAvatar = Aggregates.random(view.getNonRandomHumanAvatars()); - } - } - } else { - vanguardAvatar = (PaperCard)selected; - } - if (vanguardAvatar == null) { //ERROR! null if avatar deselected on list - GuiDialog.message("No Vanguard avatar selected for " + name - + ". Please choose one or disable the Vanguard variant"); - return; - } - } - - rp = RegisteredPlayer.forVariants(variantTypes, deck, schemes, playerIsArchenemy, planes, vanguardAvatar); - rp.setTeamNumber(view.getTeam(i)); - players.add(rp.setPlayer(lobbyPlayer)); - } - - if (!isAI) { - guis.put(rp, gui); - } - view.getDeckChooser(i).saveState(); - } - - final HostedMatch hostedMatch = GuiBase.getInterface().hostMatch(); - hostedMatch.startMatch(GameType.Constructed, variantTypes, players, guis); - } - } diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java index 57929774540..74d9a43d3b2 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/PlayerPanel.java @@ -6,7 +6,9 @@ import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.MouseEvent; +import java.util.Collections; import java.util.List; +import java.util.Set; import javax.swing.ButtonGroup; import javax.swing.JCheckBoxMenuItem; @@ -17,7 +19,9 @@ import net.miginfocom.swing.MigLayout; import org.apache.commons.lang3.StringUtils; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import forge.AIOption; import forge.Singletons; import forge.UiCommand; import forge.assets.FSkinProp; @@ -25,6 +29,7 @@ import forge.deck.DeckSection; import forge.game.GameType; import forge.gui.framework.FScreen; import forge.item.PaperCard; +import forge.match.LobbySlot; import forge.model.FModel; import forge.net.game.LobbySlotType; import forge.properties.ForgePreferences; @@ -32,7 +37,6 @@ import forge.properties.ForgePreferences.FPref; import forge.screens.deckeditor.CDeckEditorUI; import forge.screens.deckeditor.controllers.CEditorCommander; import forge.screens.deckeditor.controllers.CEditorVariant; -import forge.screens.home.VLobby.LobbyType; import forge.screens.home.sanctioned.AvatarSelector; import forge.toolbox.FComboBox; import forge.toolbox.FComboBoxWrapper; @@ -53,17 +57,16 @@ public class PlayerPanel extends FPanel { private final static ForgePreferences prefs = FModel.getPreferences(); private static final SkinColor unfocusedPlayerOverlay = FSkin.getColor(FSkin.Colors.CLR_OVERLAY).alphaColor(120); + private LobbySlotType type; private final int index; - private final LobbyType lobbyType; - - private LobbySlotType type = LobbySlotType.LOCAL; - private boolean editableForClient; + private String playerName = StringUtils.EMPTY; + private boolean mayEdit, mayControl, mayRemove; private final FLabel nameRandomiser; private final FLabel avatarLabel = new FLabel.Builder().opaque(true).hoverable(true).iconScaleFactor(0.99f).iconInBackground(true).build(); private int avatarIndex; - private final FTextField txtPlayerName = new FTextField.Builder().text("Player name").build(); + private final FTextField txtPlayerName = new FTextField.Builder().build(); private FRadioButton radioHuman; private FRadioButton radioAi; private JCheckBoxMenuItem radioAiUseSimulation; @@ -78,7 +81,6 @@ public class PlayerPanel extends FPanel { private final String variantBtnConstraints = "height 30px, hidemode 3"; - private boolean playerIsArchenemy = false; private final FLabel scmDeckSelectorBtn = new FLabel.ButtonBuilder().text("Select a scheme deck").build(); private final FLabel scmDeckEditor = new FLabel.ButtonBuilder().text("Scheme Deck Editor").build(); private final FLabel scmLabel; @@ -95,26 +97,25 @@ public class PlayerPanel extends FPanel { private final FLabel vgdLabel; private final VLobby lobby; - public PlayerPanel(final VLobby lobby, final int index, final LobbyType lobbyType) { + public PlayerPanel(final VLobby lobby, final boolean allowNetworking, final int index, final LobbySlot slot, final boolean mayEdit, final boolean mayControl) { super(); + this.lobby = lobby; + this.index = index; + this.mayEdit = mayEdit; + this.mayControl = mayControl; + this.deckLabel = lobby.newLabel("Deck:"); this.scmLabel = lobby.newLabel("Scheme deck:"); this.cmdLabel = lobby.newLabel("Commander deck:"); this.pchLabel = lobby.newLabel("Planar deck:"); this.vgdLabel = lobby.newLabel("Vanguard:"); - this.index = index; - this.lobbyType = lobbyType; - this.playerIsArchenemy = index == 0; - setLayout(new MigLayout("insets 10px, gap 5px")); // Add a button to players 3+ (or if server) to remove them from the setup closeBtn = createCloseButton(); - if (index >= 2 || lobbyType == LobbyType.SERVER) { - this.add(closeBtn, "w 20, h 20, pos (container.w-20) 0"); - } + this.add(closeBtn, "w 20, h 20, pos (container.w-20) 0"); createAvatar(); this.add(avatarLabel, "spany 2, width 80px, height 80px"); @@ -132,12 +133,18 @@ public class PlayerPanel extends FPanel { this.add(lobby.newLabel("Team:"), "w 40px, h 30px"); populateTeamsComboBoxes(); + + // Set these before action listeners are added + this.setTeam(slot == null ? index : slot.getTeam()); + this.setIsArchenemy(slot != null && slot.isArchenemy()); + teamComboBox.addActionListener(teamListener); aeTeamComboBox.addActionListener(teamListener); teamComboBox.addTo(this, variantBtnConstraints + ", pushx, growx, gaptop 5px"); aeTeamComboBox.addTo(this, variantBtnConstraints + ", pushx, growx, gaptop 5px"); - if (lobbyType == LobbyType.SERVER) { - this.add(radioOpen, "gapleft 1px"); + + if (allowNetworking) { + this.add(radioOpen, "cell 4 1, ax left, sx 2, wrap"); } this.add(deckLabel, variantBtnConstraints + ", cell 0 2, sx 2, ax right"); @@ -161,7 +168,6 @@ public class PlayerPanel extends FPanel { this.add(vgdSelectorBtn, variantBtnConstraints + ", cell 2 6, sx 4, growx, wrap"); addHandlersToVariantsControls(); - updateVariantControlsVisibility(); this.addMouseListener(new FMouseAdapter() { @Override public final void onLeftMouseDown(final MouseEvent e) { @@ -169,46 +175,62 @@ public class PlayerPanel extends FPanel { } }); + this.type = slot == null ? LobbySlotType.LOCAL : slot.getType(); + this.setPlayerName(slot == null ? "" : slot.getName()); + this.setAvatar(slot == null ? 0 : slot.getAvatarIndex()); + update(); } void update() { - if (type != LobbySlotType.REMOTE) { - if (radioHuman.isSelected()) { - type = LobbySlotType.LOCAL; - } else if (radioAi.isSelected()) { - type = LobbySlotType.AI; - } else if (radioOpen.isSelected()) { - type = LobbySlotType.OPEN; - } + avatarLabel.setEnabled(mayEdit); + avatarLabel.setIcon(FSkin.getAvatars().get(Integer.valueOf(type == LobbySlotType.OPEN ? -1 : avatarIndex))); + avatarLabel.repaintSelf(); + + txtPlayerName.setEnabled(mayEdit); + txtPlayerName.setText(type == LobbySlotType.OPEN ? StringUtils.EMPTY : playerName); + nameRandomiser.setEnabled(mayEdit); + deckLabel.setVisible(mayEdit); + deckBtn.setVisible(mayEdit); + + closeBtn.setVisible(mayRemove); + + if (mayRemove) { + radioHuman.setEnabled(mayControl); + radioAi.setEnabled(mayControl); + radioOpen.setEnabled(mayControl); + } else { + radioHuman.setVisible(mayControl); + radioAi.setVisible(mayControl); + radioOpen.setVisible(mayControl); } - final boolean isEditable = lobbyType == LobbyType.LOCAL || type == LobbySlotType.LOCAL || - (lobbyType == LobbyType.SERVER && (type == LobbySlotType.LOCAL || type == LobbySlotType.AI)) || - (lobbyType == LobbyType.CLIENT && editableForClient); - avatarLabel.setEnabled(isEditable); - txtPlayerName.setEnabled(isEditable); - nameRandomiser.setEnabled(isEditable); - deckLabel.setVisible(isEditable); - deckBtn.setVisible(isEditable); + radioHuman.setSelected(type == LobbySlotType.LOCAL); + radioAi.setSelected(type == LobbySlotType.AI); + radioOpen.setSelected(type == LobbySlotType.OPEN); - final boolean hasSlotControls = lobbyType == LobbyType.LOCAL || (lobbyType == LobbyType.SERVER && type != LobbySlotType.REMOTE); - closeBtn.setVisible(hasSlotControls); - radioAi.setVisible(hasSlotControls); - radioHuman.setVisible(hasSlotControls); - radioOpen.setVisible(hasSlotControls && lobbyType == LobbyType.SERVER); + updateVariantControlsVisibility(); } - private final FMouseAdapter radioMouseAdapter = new FMouseAdapter() { - @Override public final void onLeftClick(final MouseEvent e) { - avatarLabel.requestFocusInWindow(); - lobby.updateVanguardList(index); - update(); - } + private final FMouseAdapter radioMouseAdapter(final FRadioButton source, final LobbySlotType type) { + return new FMouseAdapter() { + @Override public final void onLeftClick(final MouseEvent e) { + if (!source.isEnabled()) { + return; + } + setType(type); + lobby.firePlayerChangeListener(index); + avatarLabel.requestFocusInWindow(); + lobby.updateVanguardList(index); + lobby.update(); + } + }; }; - /** Listens to name text fields and gives the appropriate player focus. - * Also saves the name preference when leaving player one's text field. */ + /** + * Listens to name text fields and gives the appropriate player focus. Also + * saves the name preference when leaving player one's text field. + */ private FocusAdapter nameFocusListener = new FocusAdapter() { @Override public void focusGained(FocusEvent e) { @@ -226,7 +248,8 @@ public class PlayerPanel extends FPanel { prefs.setPref(FPref.PLAYER_NAME, newName); prefs.save(); } - lobby.firePlayerChangeListener(); + lobby.firePlayerChangeListener(index); + lobby.update(); } } }; @@ -247,10 +270,10 @@ public class PlayerPanel extends FPanel { final FLabel avatar = (FLabel)e.getSource(); - PlayerPanel.this.lobby.changePlayerFocus(index); + lobby.changePlayerFocus(index); avatar.requestFocusInWindow(); - final AvatarSelector aSel = new AvatarSelector(getPlayerName(), avatarIndex, PlayerPanel.this.lobby.getUsedAvatars()); + final AvatarSelector aSel = new AvatarSelector(playerName, avatarIndex, lobby.getUsedAvatars()); for (final FLabel lbl : aSel.getSelectables()) { lbl.setCommand(new UiCommand() { @Override @@ -265,64 +288,35 @@ public class PlayerPanel extends FPanel { aSel.dispose(); if (index < 2) { - PlayerPanel.this.lobby.updateAvatarPrefs(); + lobby.updateAvatarPrefs(); } - lobby.firePlayerChangeListener(); + lobby.firePlayerChangeListener(index); } @Override public final void onRightClick(final MouseEvent e) { if (!avatarLabel.isEnabled()) { return; } - PlayerPanel.this.lobby.changePlayerFocus(index); + lobby.changePlayerFocus(index); avatarLabel.requestFocusInWindow(); setRandomAvatar(); if (index < 2) { - PlayerPanel.this.lobby.updateAvatarPrefs(); + lobby.updateAvatarPrefs(); } } }; - public void updateVariantControlsVisibility() { - boolean isCommanderApplied = false; - boolean isPlanechaseApplied = false; - boolean isVanguardApplied = false; - boolean isArchenemyApplied = false; - boolean archenemyVisiblity = false; - boolean isDeckBuildingAllowed = true; - - for (final GameType variant : lobby.getAppliedVariants()) { - switch (variant) { - case Archenemy: - isArchenemyApplied = true; - if (playerIsArchenemy) { - archenemyVisiblity = true; - } - break; - case ArchenemyRumble: - archenemyVisiblity = true; - break; - case Commander: - case TinyLeaders: - isCommanderApplied = true; - isDeckBuildingAllowed = false; //Commander deck replaces basic deck, so hide that - break; - case Planechase: - isPlanechaseApplied = true; - break; - case Vanguard: - isVanguardApplied = true; - break; - default: - if (variant.isAutoGenerated()) { - isDeckBuildingAllowed = false; - } - break; - } - } + private void updateVariantControlsVisibility() { + boolean isCommanderApplied = mayEdit && (lobby.hasVariant(GameType.Commander) || lobby.hasVariant(GameType.TinyLeaders)); + boolean isPlanechaseApplied = mayEdit && lobby.hasVariant(GameType.Planechase); + boolean isVanguardApplied = mayEdit && lobby.hasVariant(GameType.Vanguard); + boolean isArchenemyApplied = mayEdit && lobby.hasVariant(GameType.Archenemy); + boolean archenemyVisiblity = mayEdit && lobby.hasVariant(GameType.ArchenemyRumble) || (isArchenemyApplied && isArchenemy()); + // Commander deck building replaces normal one, so hide it + boolean isDeckBuildingAllowed = mayEdit && !isCommanderApplied && !lobby.hasVariant(GameType.MomirBasic); deckLabel.setVisible(isDeckBuildingAllowed); deckBtn.setVisible(isDeckBuildingAllowed); @@ -336,7 +330,6 @@ public class PlayerPanel extends FPanel { teamComboBox.setVisible(!isArchenemyApplied); aeTeamComboBox.setVisible(isArchenemyApplied); - aeTeamComboBox.setEnabled(!(isArchenemyApplied && playerIsArchenemy)); pchDeckSelectorBtn.setVisible(isPlanechaseApplied); pchDeckEditor.setVisible(isPlanechaseApplied); @@ -367,30 +360,53 @@ public class PlayerPanel extends FPanel { return type == LobbySlotType.AI; } - public boolean isSimulatedAi() { + public Set getAiOptions() { + return isSimulatedAi() + ? ImmutableSet.of(AIOption.USE_SIMULATION) + : Collections.emptySet(); + } + private boolean isSimulatedAi() { return radioAi.isSelected() && radioAiUseSimulation.isSelected(); } + public void setUseAiSimulation(final boolean useSimulation) { + radioAi.setSelected(useSimulation); + } public boolean isLocal() { return type == LobbySlotType.LOCAL; } public boolean isArchenemy() { - return playerIsArchenemy; + return aeTeamComboBox.getSelectedIndex() == 0; } - public void setRemote(final boolean remote) { - if (remote) { - type = LobbySlotType.REMOTE; - } else { + public void setType(final LobbySlotType type) { + this.type = type; + switch (type) { + case LOCAL: + radioHuman.setSelected(true); + break; + case AI: + radioAi.setSelected(true); + break; + case OPEN: radioOpen.setSelected(true); - type = LobbySlotType.OPEN; + break; + case REMOTE: + break; } update(); } - public void setEditableForClient(final boolean editable) { - editableForClient = editable; + public void setRemote(final boolean remote) { + if (remote) { + setType(LobbySlotType.REMOTE); + radioHuman.setSelected(false); + radioAi.setSelected(false); + radioOpen.setSelected(false); + } else { + setType(LobbySlotType.OPEN); + } } public void setVanguardButtonText(String text) { @@ -408,77 +424,46 @@ public class PlayerPanel extends FPanel { private void populateTeamsComboBoxes() { aeTeamComboBox.addItem("Archenemy"); aeTeamComboBox.addItem("Heroes"); - aeTeamComboBox.setSelectedIndex(lobby.getArchenemyTeams().get(index) - 1); - aeTeamComboBox.setEnabled(playerIsArchenemy); for (int i = 1; i <= VLobby.MAX_PLAYERS; i++) { teamComboBox.addItem(i); } - teamComboBox.setSelectedIndex(lobby.getTeams().get(index) - 1); teamComboBox.setEnabled(true); } private ActionListener teamListener = new ActionListener() { @SuppressWarnings("unchecked") - @Override - public void actionPerformed(ActionEvent e) { - FComboBox cb = (FComboBox)e.getSource(); + @Override public final void actionPerformed(final ActionEvent e) { + final FComboBox cb = (FComboBox) e.getSource(); cb.requestFocusInWindow(); - Object selection = cb.getSelectedItem(); + final Object selection = cb.getSelectedItem(); - if (null == selection) { - return; + if (null != selection) { + lobby.changePlayerFocus(index); + lobby.firePlayerChangeListener(index); } - if (PlayerPanel.this.lobby.getAppliedVariants().contains(GameType.Archenemy)) { - String sel = (String) selection; - if (sel.contains("Archenemy")) { - PlayerPanel.this.lobby.setLastArchenemy(index); - for (PlayerPanel pp : PlayerPanel.this.lobby.getPlayerPanels()) { - int i = pp.index; - PlayerPanel.this.lobby.getArchenemyTeams().set(i, i == PlayerPanel.this.lobby.getLastArchenemy() ? 1 : 2); - pp.aeTeamComboBox.setSelectedIndex(i == PlayerPanel.this.lobby.getLastArchenemy() ? 0 : 1); - pp.toggleIsPlayerArchenemy(); - } - } - } else { - Integer sel = (Integer) selection; - PlayerPanel.this.lobby.getTeams().set(index, sel); - } - - PlayerPanel.this.lobby.changePlayerFocus(index); } }; - public void toggleIsPlayerArchenemy() { - if (lobby.getAppliedVariants().contains(GameType.Archenemy)) { - playerIsArchenemy = lobby.getLastArchenemy() == index; - } else { - playerIsArchenemy = lobby.getAppliedVariants().contains(GameType.ArchenemyRumble); - } - updateVariantControlsVisibility(); - } - /** * @param index */ private void addHandlersToVariantsControls() { - // Archenemy buttons + // Archenemy buttons scmDeckSelectorBtn.setCommand(new Runnable() { - @Override - public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(PlayerPanel.this.lobby.getVntArchenemy().isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble); + @Override public final void run() { + lobby.setCurrentGameMode(lobby.getVntArchenemy().isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble); scmDeckSelectorBtn.requestFocusInWindow(); - PlayerPanel.this.lobby.changePlayerFocus(index, PlayerPanel.this.lobby.getCurrentGameMode()); + lobby.changePlayerFocus(index); } }); scmDeckEditor.setCommand(new UiCommand() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(PlayerPanel.this.lobby.getVntArchenemy().isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble); + lobby.setCurrentGameMode(lobby.getVntArchenemy().isSelected() ? GameType.Archenemy : GameType.ArchenemyRumble); Predicate predSchemes = new Predicate() { - @Override - public boolean apply(PaperCard arg0) { + @Override public final boolean apply(final PaperCard arg0) { return arg0.getRules().getType().isScheme(); } }; @@ -493,16 +478,16 @@ public class PlayerPanel extends FPanel { cmdDeckSelectorBtn.setCommand(new Runnable() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(PlayerPanel.this.lobby.getVntTinyLeaders().isSelected() ? GameType.TinyLeaders : GameType.Commander); + lobby.setCurrentGameMode(lobby.getVntTinyLeaders().isSelected() ? GameType.TinyLeaders : GameType.Commander); cmdDeckSelectorBtn.requestFocusInWindow(); - PlayerPanel.this.lobby.changePlayerFocus(index, PlayerPanel.this.lobby.getCurrentGameMode()); + lobby.changePlayerFocus(index); } }); cmdDeckEditor.setCommand(new UiCommand() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(PlayerPanel.this.lobby.getVntTinyLeaders().isSelected() ? GameType.TinyLeaders : GameType.Commander); + lobby.setCurrentGameMode(lobby.getVntTinyLeaders().isSelected() ? GameType.TinyLeaders : GameType.Commander); Singletons.getControl().setCurrentScreen(FScreen.DECK_EDITOR_COMMANDER); CDeckEditorUI.SINGLETON_INSTANCE.setEditorController(new CEditorCommander(CDeckEditorUI.SINGLETON_INSTANCE.getCDetailPicture())); } @@ -512,16 +497,16 @@ public class PlayerPanel extends FPanel { pchDeckSelectorBtn.setCommand(new Runnable() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(GameType.Planechase); + lobby.setCurrentGameMode(GameType.Planechase); pchDeckSelectorBtn.requestFocusInWindow(); - PlayerPanel.this.lobby.changePlayerFocus(index, GameType.Planechase); + lobby.changePlayerFocus(index, GameType.Planechase); } }); pchDeckEditor.setCommand(new UiCommand() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(GameType.Planechase); + lobby.setCurrentGameMode(GameType.Planechase); Predicate predPlanes = new Predicate() { @Override public boolean apply(PaperCard arg0) { @@ -539,9 +524,9 @@ public class PlayerPanel extends FPanel { vgdSelectorBtn.setCommand(new Runnable() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(GameType.Vanguard); + lobby.setCurrentGameMode(GameType.Vanguard); vgdSelectorBtn.requestFocusInWindow(); - PlayerPanel.this.lobby.changePlayerFocus(index, GameType.Vanguard); + lobby.changePlayerFocus(index, GameType.Vanguard); } }); } @@ -550,18 +535,22 @@ public class PlayerPanel extends FPanel { * @param index */ private void createPlayerTypeOptions() { - final boolean isServer = lobbyType == LobbyType.SERVER; - radioHuman = new FRadioButton(isServer ? "Local" : "Human", index == 0); - radioAi = new FRadioButton("AI", !isServer && index != 0); - radioOpen = new FRadioButton("Open", isServer && index != 0); + radioHuman = new FRadioButton("Human"); + radioAi = new FRadioButton("AI"); + radioOpen = new FRadioButton("Open"); + final JPopupMenu menu = new JPopupMenu(); radioAiUseSimulation = new JCheckBoxMenuItem("Use Simulation"); menu.add(radioAiUseSimulation); + radioAiUseSimulation.addActionListener(new ActionListener() { + @Override public final void actionPerformed(final ActionEvent e) { + lobby.firePlayerChangeListener(index); + } }); radioAi.setComponentPopupMenu(menu); - radioHuman.addMouseListener(radioMouseAdapter); - radioAi.addMouseListener(radioMouseAdapter); - radioOpen.addMouseListener(radioMouseAdapter); + radioHuman.addMouseListener(radioMouseAdapter(radioHuman, LobbySlotType.LOCAL)); + radioAi.addMouseListener (radioMouseAdapter(radioAi, LobbySlotType.AI)); + radioOpen.addMouseListener (radioMouseAdapter(radioOpen, LobbySlotType.OPEN)); final ButtonGroup tempBtnGroup = new ButtonGroup(); tempBtnGroup.add(radioHuman); @@ -576,9 +565,9 @@ public class PlayerPanel extends FPanel { deckBtn.setCommand(new Runnable() { @Override public void run() { - PlayerPanel.this.lobby.setCurrentGameMode(GameType.Constructed); + lobby.setCurrentGameMode(GameType.Constructed); deckBtn.requestFocusInWindow(); - PlayerPanel.this.lobby.changePlayerFocus(index, GameType.Constructed); + lobby.changePlayerFocus(index, GameType.Constructed); } }); } @@ -594,7 +583,7 @@ public class PlayerPanel extends FPanel { newNameBtn.setCommand(new UiCommand() { @Override public void run() { - String newName = PlayerPanel.this.lobby.getNewName(); + String newName = lobby.getNewName(); if (null == newName) { return; } @@ -605,7 +594,7 @@ public class PlayerPanel extends FPanel { prefs.save(); } txtPlayerName.requestFocus(); - PlayerPanel.this.lobby.changePlayerFocus(index); + lobby.changePlayerFocus(index); } }); newNameBtn.addFocusListener(nameFocusListener); @@ -640,10 +629,10 @@ public class PlayerPanel extends FPanel { .icon(FSkin.getIcon(FSkinProp.ICO_CLOSE)).hoverable(true).build(); closeBtn.setCommand(new Runnable() { @Override public final void run() { - if (type == LobbySlotType.REMOTE && !SOptionPane.showConfirmDialog(String.format("Really kick %s?", getPlayerName()), "Kick", false)) { + if (type == LobbySlotType.REMOTE && !SOptionPane.showConfirmDialog(String.format("Really kick %s?", playerName), "Kick", false)) { return; } - PlayerPanel.this.lobby.removePlayer(index); + lobby.removePlayer(index); } }); return closeBtn; @@ -654,9 +643,8 @@ public class PlayerPanel extends FPanel { if (index < currentPrefs.length) { avatarIndex = Integer.parseInt(currentPrefs[index]); avatarLabel.setIcon(FSkin.getAvatars().get(avatarIndex)); - } - else { - setRandomAvatar(); + } else { + setRandomAvatar(false); } avatarLabel.setToolTipText("L-click: Select avatar. R-click: Randomize avatar."); @@ -664,25 +652,22 @@ public class PlayerPanel extends FPanel { avatarLabel.addMouseListener(avatarMouseListener); } - /** Applies a random avatar, avoiding avatars already used. - * @param playerIndex */ - public void setRandomAvatar() { + /** Applies a random avatar, avoiding avatars already used. */ + private void setRandomAvatar() { + setRandomAvatar(true); + } + private void setRandomAvatar(final boolean fireListeners) { int random = 0; - List usedAvatars = lobby.getUsedAvatars(); + final List usedAvatars = lobby.getUsedAvatars(); do { random = MyRandom.getRandom().nextInt(FSkin.getAvatars().size()); } while (usedAvatars.contains(random)); setAvatar(random); - lobby.firePlayerChangeListener(); - } - - public void setAvatar(int newAvatarIndex) { - avatarIndex = newAvatarIndex; - SkinImage icon = FSkin.getAvatars().get(newAvatarIndex); - avatarLabel.setIcon(icon); - avatarLabel.repaintSelf(); + if (fireListeners) { + lobby.firePlayerChangeListener(index); + } } private final FSkin.LineSkinBorder focusedBorder = new FSkin.LineSkinBorder(FSkin.getColor(FSkin.Colors.CLR_BORDERS).alphaColor(255), 3); @@ -690,18 +675,53 @@ public class PlayerPanel extends FPanel { public void setFocused(boolean focused) { avatarLabel.setBorder(focused ? focusedBorder : defaultBorder); - avatarLabel.setHoverable(focused); + } + + String getPlayerName() { + return txtPlayerName.getText(); + } + public void setPlayerName(final String string) { + playerName = string; + txtPlayerName.setText(string); } public int getAvatarIndex() { return avatarIndex; } - - public void setPlayerName(String string) { - txtPlayerName.setText(string); + public void setAvatar(final int newAvatarIndex) { + avatarIndex = newAvatarIndex; + final SkinImage icon = FSkin.getAvatars().get(newAvatarIndex); + avatarLabel.setIcon(icon); + avatarLabel.repaintSelf(); } - public String getPlayerName() { - return txtPlayerName.getText(); + public int getTeam() { + return teamComboBox.getSelectedIndex(); + } + public void setTeam(final int team) { + teamComboBox.suppressActionListeners(); + teamComboBox.setSelectedIndex(team); + teamComboBox.unsuppressActionListeners(); + } + + public int getArchenemyTeam() { + return aeTeamComboBox.getSelectedIndex(); + } + public void setIsArchenemy(final boolean isArchenemy) { + aeTeamComboBox.suppressActionListeners(); + aeTeamComboBox.setSelectedIndex(isArchenemy ? 0 : 1); + aeTeamComboBox.unsuppressActionListeners(); + } + + public void setMayEdit(final boolean mayEdit) { + this.mayEdit = mayEdit; + } + + public void setMayControl(final boolean mayControl) { + this.mayControl = mayControl; + } + + public void setMayRemove(final boolean mayRemove) { + this.mayRemove = mayRemove; } } \ No newline at end of file diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java index 219cf8333e4..5e83e8300fa 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VHomeUI.java @@ -137,7 +137,7 @@ public enum VHomeUI implements IVTopLevelUI { allSubmenus.add(VSubmenuSealed.SINGLETON_INSTANCE); //allSubmenus.add(VSubmenuWinston.SINGLETON_INSTANCE); - allSubmenus.add(VSubmenuOnlineLobby.SINGLETON_INSTANCE); + //allSubmenus.add(VSubmenuOnlineLobby.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuDuels.SINGLETON_INSTANCE); allSubmenus.add(VSubmenuChallenges.SINGLETON_INSTANCE); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java index 531c312a727..abe54e90e9b 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/VLobby.java @@ -6,14 +6,9 @@ import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; import java.util.Vector; import javax.swing.JButton; @@ -24,25 +19,31 @@ import javax.swing.event.ListSelectionListener; import net.miginfocom.swing.MigLayout; -import org.apache.commons.lang3.StringUtils; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import forge.AIOption; import forge.UiCommand; +import forge.deck.CardPool; +import forge.deck.Deck; import forge.deck.DeckProxy; +import forge.deck.DeckSection; import forge.deck.DeckType; +import forge.deck.DeckgenUtil; import forge.deckchooser.DecksComboBoxEvent; import forge.deckchooser.FDeckChooser; import forge.deckchooser.IDecksComboBoxListener; import forge.game.GameType; import forge.game.card.CardView; import forge.gui.CardDetailPanel; -import forge.interfaces.ILobby; import forge.interfaces.IPlayerChangeListener; +import forge.interfaces.IUpdateable; import forge.item.PaperCard; +import forge.match.GameLobby; +import forge.match.LobbySlot; import forge.model.FModel; import forge.net.game.LobbySlotType; -import forge.net.game.LobbyState; -import forge.net.game.LobbyState.LobbyPlayerData; -import forge.net.game.server.RemoteClient; +import forge.net.game.UpdateLobbyPlayerEvent; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.toolbox.FCheckBox; @@ -55,39 +56,34 @@ import forge.toolbox.FScrollPanel; import forge.toolbox.FSkin; import forge.toolbox.FSkin.SkinImage; import forge.toolbox.FTextField; +import forge.util.Aggregates; import forge.util.Lang; import forge.util.NameGenerator; +import forge.util.storage.IStorage; /** * Lobby view. View of a number of players at the deck selection stage. * *

(V at beginning of class name denotes a view class.) */ -public class VLobby implements ILobby { +public class VLobby implements IUpdateable { static final int MAX_PLAYERS = 8; private static final ForgePreferences prefs = FModel.getPreferences(); - public enum LobbyType { LOCAL, SERVER, CLIENT; } - // General variables - private final LobbyType type; - private int localPlayer = 0; + private final GameLobby lobby; private IPlayerChangeListener playerChangeListener = null; private final LblHeader lblTitle = new LblHeader("Sanctioned Format: Constructed"); - private int activePlayersNum = 2; + private int activePlayersNum = 0; private int playerWithFocus = 0; // index of the player that currently has focus private PlayerPanel playerPanelWithFocus; - private GameType currentGameMode = GameType.Constructed; - private List teams = new ArrayList(MAX_PLAYERS); - private List archenemyTeams = new ArrayList(MAX_PLAYERS); private final StartButton btnStart = new StartButton(); private final JPanel pnlStart = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); private final JPanel constructedFrame = new JPanel(new MigLayout("insets 0, gap 0, wrap 2")); // Main content frame // Variants frame and variables - private final Set appliedVariants = new TreeSet(); private final FPanel variantsPanel = new FPanel(new MigLayout("insets 10, gapx 10")); private final VariantCheckBox vntVanguard = new VariantCheckBox(GameType.Vanguard); private final VariantCheckBox vntMomirBasic = new VariantCheckBox(GameType.MomirBasic); @@ -96,6 +92,8 @@ public class VLobby implements ILobby { private final VariantCheckBox vntPlanechase = new VariantCheckBox(GameType.Planechase); private final VariantCheckBox vntArchenemy = new VariantCheckBox(GameType.Archenemy); private final VariantCheckBox vntArchenemyRumble = new VariantCheckBox(GameType.ArchenemyRumble); + private final ImmutableList vntBoxes = + ImmutableList.of(vntVanguard, vntMomirBasic, vntCommander, vntTinyLeaders, vntPlanechase, vntArchenemy, vntArchenemyRumble); // Player frame elements private final JPanel playersFrame = new JPanel(new MigLayout("insets 0, gap 0 5, wrap, hidemode 3")); @@ -109,6 +107,7 @@ public class VLobby implements ILobby { private final List deckChoosers = new ArrayList(8); private final FCheckBox cbSingletons = new FCheckBox("Singleton Mode"); private final FCheckBox cbArtifacts = new FCheckBox("Remove Artifacts"); + private final Deck[] decks = new Deck[MAX_PLAYERS]; // Variants private final List> schemeDeckLists = new ArrayList>(); @@ -132,8 +131,8 @@ public class VLobby implements ILobby { private final Vector aiListData = new Vector(); // CTR - public VLobby(final LobbyType type) { - this.type = type; + public VLobby(final GameLobby lobby) { + this.lobby = lobby; lblTitle.setBackground(FSkin.getColor(FSkin.Colors.CLR_THEME2)); @@ -142,55 +141,23 @@ public class VLobby implements ILobby { variantsPanel.setOpaque(false); variantsPanel.add(newLabel("Variants:")); - variantsPanel.add(vntVanguard); - variantsPanel.add(vntMomirBasic); - variantsPanel.add(vntCommander); - variantsPanel.add(vntTinyLeaders); - variantsPanel.add(vntPlanechase); - variantsPanel.add(vntArchenemy); - variantsPanel.add(vntArchenemyRumble); + for (final VariantCheckBox vcb : vntBoxes) { + variantsPanel.add(vcb); + } constructedFrame.add(new FScrollPane(variantsPanel, false, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), "w 100%, h 45px!, gapbottom 10px, spanx 2, wrap"); - //////////////////////////////////////////////////////// - ///////////////////// Player Panel ///////////////////// - - // Construct individual player panels - String constraints = "pushx, growx, wrap, hidemode 3"; - for (int i = 0; i < MAX_PLAYERS; i++) { - teams.add(i + 1); - archenemyTeams.add(i == 0 ? 1 : 2); - - final PlayerPanel player = new PlayerPanel(this, i, type); - if (type == LobbyType.CLIENT) { - player.setRemote(true); - } - playerPanels.add(player); - - // Populate players panel - player.setVisible(i < activePlayersNum); - - playersScroll.add(player, constraints); - - if (i == 0) { - constraints += ", gaptop 5px"; - } - } - - playerPanelWithFocus = playerPanels.get(0); - playerPanelWithFocus.setFocused(true); - playersFrame.setOpaque(false); playersFrame.add(playersScroll, "w 100%, h 100%-35px"); - if (type != LobbyType.CLIENT) { + if (lobby.hasControl()) { addPlayerBtn.setFocusable(true); addPlayerBtn.setCommand(new Runnable() { @Override public final void run() { - addPlayer(); + lobby.addSlot(); } }); playersFrame.add(addPlayerBtn, "height 30px!, growx, pushx"); @@ -209,9 +176,16 @@ public class VLobby implements ILobby { decksFrame.setOpaque(false); // Start Button - if (type != LobbyType.CLIENT) { + if (lobby.hasControl()) { pnlStart.setOpaque(false); pnlStart.add(btnStart, "align center"); + + // Start button event handling + btnStart.addActionListener(new ActionListener() { + @Override public final void actionPerformed(final ActionEvent arg0) { + lobby.startGame(); + } + }); } } @@ -227,143 +201,95 @@ public class VLobby implements ILobby { } populateDeckPanel(GameType.Constructed); populateVanguardLists(); - } - private int addPlayerInFreeSlot(final String name) { - if (activePlayersNum >= MAX_PLAYERS) { - return -1; - } - - for (final PlayerPanel pp : getPlayerPanels()) { - if (pp.isVisible() && ( - pp.getType() == LobbySlotType.OPEN || (pp.isLocal() && type == LobbyType.SERVER))) { - final int index = pp.getIndex(); - addPlayer(index); - pp.setPlayerName(name); - System.out.println("Put player " + name + " in slot " + index); - - return index; - } - } - return -1; - } - private void addPlayer() { - if (activePlayersNum >= MAX_PLAYERS) { - return; - } - - int freeIndex = -1; - for (int i = 0; i < MAX_PLAYERS; i++) { - if (!playerPanels.get(i).isVisible()) { - freeIndex = i; - break; - } - } - addPlayer(freeIndex); - } - private void addPlayer(final int slot) { - playerPanels.get(slot).setVisible(true); - - activePlayersNum++; + public void update() { + activePlayersNum = lobby.getNumberOfSlots(); addPlayerBtn.setEnabled(activePlayersNum < MAX_PLAYERS); - playerPanels.get(slot).setVisible(true); - playerPanels.get(slot).focusOnAvatar(); - - firePlayerChangeListener(); - } - - void removePlayer(final int playerIndex) { - if (activePlayersNum <= playerIndex) { - return; + for (final VariantCheckBox vcb : vntBoxes) { + vcb.setSelected(hasVariant(vcb.variant)); + vcb.setEnabled(lobby.hasControl()); } - activePlayersNum--; - final FPanel player = playerPanels.get(playerIndex); - player.setVisible(false); - addPlayerBtn.setEnabled(true); - //find closest player still in game and give focus - int min = MAX_PLAYERS; - final List participants = getParticipants(); - if (!participants.isEmpty()) { - int closest = 2; - - for (final int participantIndex : getParticipants()) { - final int diff = Math.abs(playerIndex - participantIndex); - - if (diff < min) { - min = diff; - closest = participantIndex; + final boolean allowNetworking = lobby.isAllowNetworking(); + for (int i = 0; i < MAX_PLAYERS; i++) { + final boolean hasPanel = i < playerPanels.size(); + if (i < activePlayersNum) { + // visible panels + final LobbySlot slot = lobby.getSlot(i); + final PlayerPanel panel; + if (hasPanel) { + panel = playerPanels.get(i); + panel.setVisible(true); + } else { + panel = new PlayerPanel(this, allowNetworking, i, slot, lobby.mayEdit(i), lobby.hasControl()); + playerPanels.add(panel); + String constraints = "pushx, growx, wrap, hidemode 3"; + if (i == 0) { + constraints += ", gaptop 5px"; + playerPanelWithFocus = panel; + playerPanelWithFocus.setFocused(true); + } + playersScroll.add(panel, constraints); } + + panel.setType(slot.getType()); + panel.setPlayerName(slot.getName()); + panel.setAvatar(slot.getAvatarIndex()); + panel.setTeam(slot.getTeam()); + panel.setIsArchenemy(slot.isArchenemy()); + panel.setUseAiSimulation(slot.getAiOptions().contains(AIOption.USE_SIMULATION)); + panel.setMayEdit(lobby.mayEdit(i)); + panel.setMayControl(lobby.mayControl(i)); + panel.setMayRemove(lobby.mayRemove(i)); + panel.update(); + + deckChoosers.get(i).setIsAi(slot.getType() == LobbySlotType.AI); + } else if (hasPanel) { + playerPanels.get(i).setVisible(false); } - - changePlayerFocus(closest); - playerPanels.get(closest).focusOnAvatar(); } - firePlayerChangeListener(); - } - @Override - public int login(final RemoteClient client) { - return addPlayerInFreeSlot(client.getUsername()); - } - - @Override - public void logout(final RemoteClient client) { - removePlayer(client.getIndex()); - } - - @Override - public LobbyState getState() { - final LobbyState state = new LobbyState(); - for (int i = 0; i < activePlayersNum; i++) { - state.addPlayer(getData(i)); + if (playerWithFocus >= activePlayersNum) { + playerWithFocus = activePlayersNum - 1; } - return state; - } - - public void setState(final LobbyState state) { - setLocalPlayer(state.getLocalPlayer()); - - final List players = state.getPlayers(); - final int pSize = players.size(); - activePlayersNum = pSize; - for (int i = 0; i < pSize; i++) { - final LobbyPlayerData player = players.get(i); - final PlayerPanel panel = playerPanels.get(i); - - if (type == LobbyType.CLIENT) { - panel.setRemote(i != localPlayer); - panel.setEditableForClient(i == localPlayer); - } else { - panel.setRemote(player.getType() == LobbySlotType.REMOTE); - panel.setEditableForClient(false); - } - panel.setPlayerName(player.getName()); - panel.setAvatar(player.getAvatarIndex()); - panel.setVisible(true); - panel.update(); - } - } - - private void setLocalPlayer(final int index) { - localPlayer = index; + changePlayerFocus(playerWithFocus); + refreshPanels(true, true); } public void setPlayerChangeListener(final IPlayerChangeListener listener) { this.playerChangeListener = listener; } - void firePlayerChangeListener() { + void firePlayerChangeListener(final int index) { if (playerChangeListener != null) { - playerChangeListener.update(getData(localPlayer)); + playerChangeListener.update(index, getSlot(index)); + } + } + void fireDeckChangeListener(final int index, final Deck deck) { + decks[index] = deck; + if (playerChangeListener != null) { + playerChangeListener.update(index, UpdateLobbyPlayerEvent.deckUpdate(deck)); + } + } + void fireDeckSectionChangeListener(final int index, final DeckSection section, final CardPool cards) { + decks[index].putSection(section, cards); + if (playerChangeListener != null) { + playerChangeListener.update(index, UpdateLobbyPlayerEvent.deckUpdate(section, cards)); } } - private LobbyPlayerData getData(final int index) { + void removePlayer(final int index) { + lobby.removeSlot(index); + } + boolean hasVariant(final GameType variant) { + return lobby.hasVariant(variant); + } + + private UpdateLobbyPlayerEvent getSlot(final int index) { final PlayerPanel panel = playerPanels.get(index); - return new LobbyPlayerData(panel.getPlayerName(), panel.getAvatarIndex(), panel.getType()); + return UpdateLobbyPlayerEvent.create(panel.getType(), panel.getPlayerName(), panel.getAvatarIndex(), panel.getTeam(), panel.isArchenemy(), panel.getAiOptions()); } /** Builds the actual deck panel layouts for each player. @@ -374,53 +300,142 @@ public class VLobby implements ILobby { String labelConstraints = "gaptop 10px, gapbottom 5px"; // Main deck - final FDeckChooser mainChooser = new FDeckChooser(null, isPlayerAI(playerIndex)); + final FDeckChooser mainChooser = new FDeckChooser(null, false); mainChooser.initialize(); mainChooser.getLstDecks().setSelectCommand(new UiCommand() { - @Override - public void run() { + @Override public final void run() { VLobby.this.onDeckClicked(playerIndex, mainChooser.getSelectedDeckType(), mainChooser.getLstDecks().getSelectedItems()); } }); deckChoosers.add(mainChooser); // Scheme deck list - FPanel schemeDeckPanel = new FPanel(); + final FPanel schemeDeckPanel = new FPanel(); schemeDeckPanel.setBorderToggle(false); schemeDeckPanel.setLayout(new MigLayout(sectionConstraints)); schemeDeckPanel.add(new FLabel.Builder().text("Select Scheme deck:").build(), labelConstraints); - FList schemeDeckList = new FList(); + final FList schemeDeckList = new FList(); schemeDeckList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + schemeDeckList.addListSelectionListener(new ListSelectionListener() { + @Override public final void valueChanged(final ListSelectionEvent e) { + if (playerIndex >= activePlayersNum) { + return; + } - FScrollPane scrSchemes = new FScrollPane(schemeDeckList, true, + final Object selected = schemeDeckList.getSelectedValue(); + final Deck deck = decks[playerIndex]; + CardPool schemePool = null; + if (selected instanceof String) { + String sel = (String) selected; + if (sel.contains("Use deck's scheme section")) { + if (deck.has(DeckSection.Schemes)) { + schemePool = deck.get(DeckSection.Schemes); + } else { + sel = "Random"; + } + } + final IStorage sDecks = FModel.getDecks().getScheme(); + if (sel.equals("Random") && sDecks.size() != 0) { + schemePool = Aggregates.random(sDecks).get(DeckSection.Schemes); + } + } else if (selected instanceof Deck) { + schemePool = ((Deck) selected).get(DeckSection.Schemes); + } + if (schemePool == null) { //Can be null if player deselects the list selection or chose Generate + schemePool = DeckgenUtil.generateSchemePool(); + } + fireDeckSectionChangeListener(playerIndex, DeckSection.Schemes, schemePool); + getDeckChooser(playerIndex).saveState(); + } + }); + + final FScrollPane scrSchemes = new FScrollPane(schemeDeckList, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); schemeDeckPanel.add(scrSchemes, "grow, push"); schemeDeckLists.add(schemeDeckList); schemeDeckPanels.add(schemeDeckPanel); // Commander deck list - FPanel commanderDeckPanel = new FPanel(); + final FPanel commanderDeckPanel = new FPanel(); commanderDeckPanel.setBorderToggle(false); commanderDeckPanel.setLayout(new MigLayout(sectionConstraints)); commanderDeckPanel.add(new FLabel.Builder().text("Select Commander deck:").build(), labelConstraints); - FList commanderDeckList = new FList(); + final FList commanderDeckList = new FList(); commanderDeckList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + commanderDeckList.addListSelectionListener(new ListSelectionListener() { + @Override public final void valueChanged(final ListSelectionEvent e) { + if (playerIndex >= activePlayersNum) { + return; + } - FScrollPane scrCommander = new FScrollPane(commanderDeckList, true, + final Object selected = commanderDeckList.getSelectedValue(); + Deck deck = null; + if (selected instanceof String) { + final String sel = (String) selected; + final IStorage comDecks = FModel.getDecks().getCommander(); + if (sel.equals("Random") && comDecks.size() > 0) { + deck = Aggregates.random(comDecks); + } + } else if (selected instanceof Deck) { + deck = (Deck) selected; + } + final GameType commanderGameType = hasVariant(GameType.TinyLeaders) ? GameType.TinyLeaders : GameType.Commander; + if (deck == null) { //Can be null if player deselects the list selection or chose Generate + deck = DeckgenUtil.generateCommanderDeck(isPlayerAI(playerIndex), commanderGameType); + } + fireDeckChangeListener(playerIndex, deck); + getDeckChooser(playerIndex).saveState(); + } + }); + + + final FScrollPane scrCommander = new FScrollPane(commanderDeckList, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); commanderDeckPanel.add(scrCommander, "grow, push"); commanderDeckLists.add(commanderDeckList); commanderDeckPanels.add(commanderDeckPanel); // Planar deck list - FPanel planarDeckPanel = new FPanel(); + final FPanel planarDeckPanel = new FPanel(); planarDeckPanel.setBorderToggle(false); planarDeckPanel.setLayout(new MigLayout(sectionConstraints)); planarDeckPanel.add(new FLabel.Builder().text("Select Planar deck:").build(), labelConstraints); - FList planarDeckList = new FList(); + final FList planarDeckList = new FList(); planarDeckList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + planarDeckList.addListSelectionListener(new ListSelectionListener() { + @Override public final void valueChanged(final ListSelectionEvent e) { + if (playerIndex >= activePlayersNum) { + return; + } - FScrollPane scrPlanes = new FScrollPane(planarDeckList, true, + final Object selected = planarDeckList.getSelectedValue(); + final Deck deck = decks[playerIndex]; + CardPool planePool = null; + if (selected instanceof String) { + String sel = (String) selected; + if (sel.contains("Use deck's planes section")) { + if (deck.has(DeckSection.Planes)) { + planePool = deck.get(DeckSection.Planes); + } else { + sel = "Random"; + } + } + final IStorage pDecks = FModel.getDecks().getPlane(); + if (sel.equals("Random") && pDecks.size() != 0) { + planePool = Aggregates.random(pDecks).get(DeckSection.Planes); + } + } else if (selected instanceof Deck) { + planePool = ((Deck) selected).get(DeckSection.Planes); + } + if (planePool == null) { //Can be null if player deselects the list selection or chose Generate + planePool = DeckgenUtil.generatePlanarPool(); + } + fireDeckSectionChangeListener(playerIndex, DeckSection.Planes, planePool); + getDeckChooser(playerIndex).saveState(); + } + }); + + final FScrollPane scrPlanes = new FScrollPane(planarDeckList, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); planarDeckPanel.add(scrPlanes, "grow, push"); planarDeckLists.add(planarDeckList); @@ -430,15 +445,15 @@ public class VLobby implements ILobby { FPanel vgdDeckPanel = new FPanel(); vgdDeckPanel.setBorderToggle(false); - FList vgdAvatarList = new FList(); + final FList vgdAvatarList = new FList(); vgdAvatarList.setListData(isPlayerAI(playerIndex) ? aiListData : humanListData); vgdAvatarList.setSelectedIndex(0); vgdAvatarList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); vgdAvatarList.addListSelectionListener(vgdLSListener); - FScrollPane scrAvatars = new FScrollPane(vgdAvatarList, true, + final FScrollPane scrAvatars = new FScrollPane(vgdAvatarList, true, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - CardDetailPanel vgdDetail = new CardDetailPanel(); + final CardDetailPanel vgdDetail = new CardDetailPanel(); vgdAvatarDetails.add(vgdDetail); vgdDeckPanel.setLayout(new MigLayout(sectionConstraints)); @@ -449,16 +464,19 @@ public class VLobby implements ILobby { vgdPanels.add(vgdDeckPanel); } - protected void onDeckClicked(int iPlayer, DeckType type, Collection selectedDecks) { - String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); - playerPanels.get(iPlayer).setDeckSelectorButtonText(text); + protected void onDeckClicked(final int iPlayer, final DeckType type, final Collection selectedDecks) { + if (iPlayer < activePlayersNum && lobby.mayEdit(iPlayer)) { + final String text = type.toString() + ": " + Lang.joinHomogenous(selectedDecks, DeckProxy.FN_GET_NAME); + playerPanels.get(iPlayer).setDeckSelectorButtonText(text); + fireDeckChangeListener(iPlayer, selectedDecks.iterator().next().getDeck()); + } } /** Populates the deck panel with the focused player's deck choices. */ private void populateDeckPanel(final GameType forGameType) { decksFrame.removeAll(); - if (playerPanelWithFocus.getType() == LobbySlotType.OPEN || playerPanelWithFocus.getType() == LobbySlotType.REMOTE) { + if (!lobby.mayEdit(playerWithFocus)) { return; } @@ -487,7 +505,7 @@ public class VLobby implements ILobby { } /** @return {@link javax.swing.JButton} */ - public JButton getBtnStart() { + JButton getBtnStart() { return this.btnStart; } @@ -497,18 +515,18 @@ public class VLobby implements ILobby { public List getDeckChoosers() { return Collections.unmodifiableList(deckChoosers); } /** Gets the random deck checkbox for Singletons. */ - public FCheckBox getCbSingletons() { return cbSingletons; } + FCheckBox getCbSingletons() { return cbSingletons; } /** Gets the random deck checkbox for Artifacts. */ - public FCheckBox getCbArtifacts() { return cbArtifacts; } + FCheckBox getCbArtifacts() { return cbArtifacts; } - public FCheckBox getVntArchenemy() { return vntArchenemy; } - public FCheckBox getVntArchenemyRumble() { return vntArchenemyRumble; } - public FCheckBox getVntCommander() { return vntCommander; } - public FCheckBox getVntMomirBasic() { return vntMomirBasic; } - public FCheckBox getVntPlanechase() { return vntPlanechase; } - public FCheckBox getVntTinyLeaders() { return vntTinyLeaders; } - public FCheckBox getVntVanguard() { return vntVanguard; } + FCheckBox getVntArchenemy() { return vntArchenemy; } + FCheckBox getVntArchenemyRumble() { return vntArchenemyRumble; } + FCheckBox getVntCommander() { return vntCommander; } + FCheckBox getVntMomirBasic() { return vntMomirBasic; } + FCheckBox getVntPlanechase() { return vntPlanechase; } + FCheckBox getVntTinyLeaders() { return vntTinyLeaders; } + FCheckBox getVntVanguard() { return vntVanguard; } public int getLastArchenemy() { return lastArchenemy; } public void setLastArchenemy(final int archenemy) { lastArchenemy = archenemy; } @@ -524,38 +542,22 @@ public class VLobby implements ILobby { return deckChoosers.get(playernum); } - public List getTeams() { return teams; } - public List getArchenemyTeams() { return archenemyTeams; } - public GameType getCurrentGameMode() { return currentGameMode; } - public void setCurrentGameMode(final GameType mode) { currentGameMode = mode; } - - public boolean isPlayerAI(int playernum) { - return playerPanels.get(playernum).isAi(); + GameType getCurrentGameMode() { + return lobby.getGameType(); + } + void setCurrentGameMode(final GameType mode) { + lobby.setGameType(mode); + update(); } - public Map getAiOptions(int playernum) { - if (playerPanels.get(playernum).isSimulatedAi()) { - Map options = new HashMap(); - options.put("UseSimulation", "True"); - return options; - } - return null; + public boolean isPlayerAI(final int playernum) { + return playernum < activePlayersNum ? playerPanels.get(playernum).isAi() : false; } public int getNumPlayers() { return activePlayersNum; } - public final List getParticipants() { - final List participants = new ArrayList(activePlayersNum); - for (final PlayerPanel panel : playerPanels) { - if (panel.isVisible()) { - participants.add(playerPanels.indexOf(panel)); - } - } - return participants; - } - /** Revalidates the player and deck sections. Necessary after adding or hiding any panels. */ private void refreshPanels(boolean refreshPlayerFrame, boolean refreshDeckFrame) { if (refreshPlayerFrame) { @@ -569,7 +571,7 @@ public class VLobby implements ILobby { } public void changePlayerFocus(int newFocusOwner) { - changePlayerFocus(newFocusOwner, appliedVariants.contains(currentGameMode) ? currentGameMode : GameType.Constructed); + changePlayerFocus(newFocusOwner, lobby.getGameType()); } void changePlayerFocus(int newFocusOwner, GameType gType) { @@ -593,32 +595,15 @@ public class VLobby implements ILobby { prefs.save(); } - /** Updates the avatars from preferences on update. */ - public void updatePlayersFromPrefs() { - ForgePreferences prefs = FModel.getPreferences(); - - // Avatar - String[] avatarPrefs = prefs.getPref(FPref.UI_AVATARS).split(","); - for (int i = 0; i < avatarPrefs.length; i++) { - int avatarIndex = Integer.parseInt(avatarPrefs[i]); - playerPanels.get(i).setAvatar(avatarIndex); - } - - // Name - String prefName = prefs.getPref(FPref.PLAYER_NAME); - playerPanels.get(0).setPlayerName(StringUtils.isBlank(prefName) ? "Human" : prefName); - } - /** Adds a pre-styled FLabel component with the specified title. */ FLabel newLabel(String title) { return new FLabel.Builder().text(title).fontSize(14).fontStyle(Font.ITALIC).build(); } List getUsedAvatars() { - List usedAvatars = Arrays.asList(-1,-1,-1,-1,-1,-1,-1,-1); - int i = 0; - for (PlayerPanel pp : playerPanels) { - usedAvatars.set(i++, pp.getAvatarIndex()); + final List usedAvatars = Lists.newArrayListWithCapacity(MAX_PLAYERS); + for (final PlayerPanel pp : playerPanels) { + usedAvatars.add(pp.getAvatarIndex()); } return usedAvatars; } @@ -660,92 +645,30 @@ public class VLobby implements ILobby { return names; } - public String getPlayerName(int i) { - return playerPanels.get(i).getPlayerName(); - } - - public int getPlayerAvatar(int i) { - return playerPanels.get(i).getAvatarIndex(); - } - - public boolean isEnoughTeams() { - int lastTeam = -1; - final List teamList = appliedVariants.contains(GameType.Archenemy) ? archenemyTeams : teams; - - for (final int i : getParticipants()) { - if (lastTeam == -1) { - lastTeam = teamList.get(i); - } else if (lastTeam != teamList.get(i)) { - return true; - } - } - return false; - } - ///////////////////////////////////////////// //========== Various listeners in build order @SuppressWarnings("serial") private class VariantCheckBox extends FCheckBox { - private final GameType variantType; - - private VariantCheckBox(GameType variantType0) { - super(variantType0.toString()); - - variantType = variantType0; + private final GameType variant; + private VariantCheckBox(final GameType variantType) { + super(variantType.toString()); + this.variant = variantType; setToolTipText(variantType.getDescription()); - addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { + @Override public final void itemStateChanged(final ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { - appliedVariants.add(variantType); - currentGameMode = variantType; - - //ensure other necessary variants are unchecked - switch (variantType) { - case Archenemy: - vntArchenemyRumble.setSelected(false); - break; - case ArchenemyRumble: - vntArchenemy.setSelected(false); - break; - case Commander: - vntTinyLeaders.setSelected(false); - vntMomirBasic.setSelected(false); - break; - case TinyLeaders: - vntCommander.setSelected(false); - vntMomirBasic.setSelected(false); - break; - case Vanguard: - vntMomirBasic.setSelected(false); - break; - case MomirBasic: - vntCommander.setSelected(false); - vntVanguard.setSelected(false); - break; - default: - break; - } + lobby.applyVariant(variantType); + } else { + lobby.removeVariant(variantType); } - else { - appliedVariants.remove(variantType); - if (currentGameMode == variantType) { - currentGameMode = GameType.Constructed; - } - } - - for (PlayerPanel pp : playerPanels) { - pp.toggleIsPlayerArchenemy(); - } - changePlayerFocus(playerWithFocus, currentGameMode); + VLobby.this.update(); } }); } } - ActionListener nameListener = new ActionListener() { + final ActionListener nameListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { FTextField nField = (FTextField)e.getSource(); @@ -756,24 +679,44 @@ public class VLobby implements ILobby { /** This listener will look for a vanguard avatar being selected in the lists / and update the corresponding detail panel. */ private ListSelectionListener vgdLSListener = new ListSelectionListener() { + @Override public final void valueChanged(final ListSelectionEvent e) { + final int index = vgdAvatarLists.indexOf(e.getSource()); + if (index >= activePlayersNum) { + return; + } + final Object selected = vgdAvatarLists.get(index).getSelectedValue(); + final PlayerPanel pp = playerPanels.get(index); + final CardDetailPanel cdp = vgdAvatarDetails.get(index); - @Override - public void valueChanged(ListSelectionEvent e) { - int index = vgdAvatarLists.indexOf(e.getSource()); - Object obj = vgdAvatarLists.get(index).getSelectedValue(); - PlayerPanel pp = playerPanels.get(index); - CardDetailPanel cdp = vgdAvatarDetails.get(index); - - if (obj instanceof PaperCard) { - pp.setVanguardButtonText(((PaperCard) obj).getName()); - cdp.setCard(CardView.getCardForUi((PaperCard) obj)); + final PaperCard vanguardAvatar; + final Deck deck = decks[index]; + if (selected instanceof PaperCard) { + pp.setVanguardButtonText(((PaperCard) selected).getName()); + cdp.setCard(CardView.getCardForUi((PaperCard) selected)); cdp.setVisible(true); refreshPanels(false, true); - } - else { - pp.setVanguardButtonText((String) obj); + + vanguardAvatar = (PaperCard)selected; + } else { + String sel = (String) selected; + pp.setVanguardButtonText(sel); cdp.setVisible(false); + + if (sel.contains("Use deck's default avatar") && deck.has(DeckSection.Avatar)) { + vanguardAvatar = deck.get(DeckSection.Avatar).get(0); + } else { //Only other string is "Random" + if (playerPanels.get(index).isAi()) { //AI + vanguardAvatar = Aggregates.random(getNonRandomAiAvatars()); + } else { //Human + vanguardAvatar = Aggregates.random(getNonRandomHumanAvatars()); + } + } } + + final CardPool avatarOnce = new CardPool(); + avatarOnce.add(vanguardAvatar); + fireDeckSectionChangeListener(index, DeckSection.Avatar, avatarOnce); + getDeckChooser(index).saveState(); } }; @@ -781,14 +724,6 @@ public class VLobby implements ILobby { ///////////////////////////////////// //========== METHODS FOR VARIANTS - public Set getAppliedVariants() { - return Collections.unmodifiableSet(appliedVariants); - } - - public int getTeam(final int playerIndex) { - return appliedVariants.contains(GameType.Archenemy) ? archenemyTeams.get(playerIndex) : teams.get(playerIndex); - } - /** Gets the list of planar deck lists. */ public List> getPlanarDeckLists() { return planarDeckLists; diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java index 63461ba09ee..fce4169abd2 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java @@ -8,74 +8,102 @@ import javax.swing.JMenu; import forge.GuiBase; import forge.Singletons; import forge.UiCommand; -import forge.game.GameRules; -import forge.game.GameType; import forge.gui.FNetOverlay; import forge.gui.framework.FScreen; import forge.gui.framework.ICDoc; +import forge.interfaces.IGuiGame; +import forge.interfaces.ILobbyListener; import forge.interfaces.IPlayerChangeListener; +import forge.interfaces.IUpdateable; +import forge.match.GameLobby.GameLobbyData; import forge.menus.IMenuProvider; import forge.menus.MenuUtil; import forge.model.FModel; +import forge.net.ClientGameLobby; import forge.net.FGameClient; import forge.net.FServerManager; -import forge.net.game.LobbyState; -import forge.net.game.LobbyState.LobbyPlayerData; -import forge.net.game.LoginEvent; -import forge.net.game.client.ILobbyListener; +import forge.net.ServerGameLobby; +import forge.net.game.IRemote; +import forge.net.game.IdentifiableNetEvent; +import forge.net.game.MessageEvent; +import forge.net.game.NetEvent; +import forge.net.game.UpdateLobbyPlayerEvent; import forge.properties.ForgePreferences.FPref; import forge.screens.home.VLobby; -import forge.screens.home.VLobby.LobbyType; import forge.screens.home.sanctioned.ConstructedGameMenu; public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { SINGLETON_INSTANCE; final void host(final int portNumber) { - final VLobby lobby = VOnlineLobby.SINGLETON_INSTANCE.setLobby(LobbyType.SERVER); + final FServerManager server = FServerManager.getInstance(); + final ServerGameLobby lobby = new ServerGameLobby(); + final VLobby view = VOnlineLobby.SINGLETON_INSTANCE.setLobby(lobby); - FServerManager.getInstance().startServer(portNumber); - FServerManager.getInstance().setLobby(lobby); - FServerManager.getInstance().hostGame(new GameRules(GameType.Constructed)); + server.startServer(portNumber); + server.setLobby(lobby); FNetOverlay.SINGLETON_INSTANCE.showUp("Hosting game"); - lobby.setPlayerChangeListener(new IPlayerChangeListener() { - @Override public final void update(final LobbyPlayerData data) { - FServerManager.getInstance().updateLobbyState(); + lobby.setListener(new IUpdateable() { + @Override public final void update() { + view.update(); + server.updateLobbyState(); + } + }); + view.setPlayerChangeListener(new IPlayerChangeListener() { + @Override public final void update(final int index, final UpdateLobbyPlayerEvent event) { + server.updateSlot(index, event); + server.updateLobbyState(); } }); - final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", GuiBase.getInterface().getNewGuiGame()); - FNetOverlay.SINGLETON_INSTANCE.setGameClient(client); - client.addLobbyListener(new ILobbyListener() { - @Override public final void update(final LobbyState state) { - lobby.setState(state); + server.setLobbyListener(new ILobbyListener() { + @Override public final void update(final GameLobbyData state, final int slot) { + // NO-OP, lobby connected directly } @Override public final void message(final String source, final String message) { FNetOverlay.SINGLETON_INSTANCE.addMessage(source, message); } }); - client.connect("localhost", portNumber); + FNetOverlay.SINGLETON_INSTANCE.setGameClient(new IRemote() { + @Override public final void send(final NetEvent event) { + if (event instanceof MessageEvent) { + final MessageEvent message = (MessageEvent) event; + FNetOverlay.SINGLETON_INSTANCE.addMessage(message.getSource(), message.getMessage()); + server.broadcast(event); + } + } + @Override public final Object sendAndWait(final IdentifiableNetEvent event) { + send(event); + return null; + } + }); + + view.update(); Singletons.getControl().setCurrentScreen(FScreen.ONLINE_LOBBY); FNetOverlay.SINGLETON_INSTANCE.showUp(String.format("Hosting on port %d", portNumber)); } final void join(final String hostname, final int port) { - final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", GuiBase.getInterface().getNewGuiGame()); + final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); + final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui); FNetOverlay.SINGLETON_INSTANCE.setGameClient(client); - final VLobby lobby = VOnlineLobby.SINGLETON_INSTANCE.setLobby(LobbyType.CLIENT); + final ClientGameLobby lobby = new ClientGameLobby(); + final VLobby view = VOnlineLobby.SINGLETON_INSTANCE.setLobby(lobby); + lobby.setListener(view); client.addLobbyListener(new ILobbyListener() { - @Override public final void update(final LobbyState state) { - lobby.setState(state); - } @Override public final void message(final String source, final String message) { FNetOverlay.SINGLETON_INSTANCE.addMessage(source, message); } + @Override public final void update(final GameLobbyData state, final int slot) { + lobby.setLocalPlayer(slot); + lobby.setData(state); + } }); - lobby.setPlayerChangeListener(new IPlayerChangeListener() { - @Override public final void update(final LobbyPlayerData data) { - client.send(new LoginEvent(data.getName())); + view.setPlayerChangeListener(new IPlayerChangeListener() { + @Override public final void update(final int index, final UpdateLobbyPlayerEvent event) { + client.send(event); } }); client.connect(hostname, port); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java index f79ad1fb23f..3c43541b380 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/VOnlineLobby.java @@ -12,8 +12,8 @@ import forge.gui.framework.EDocID; import forge.gui.framework.FScreen; import forge.gui.framework.IVDoc; import forge.gui.framework.IVTopLevelUI; +import forge.match.GameLobby; import forge.screens.home.VLobby; -import forge.screens.home.VLobby.LobbyType; import forge.toolbox.FPanel; import forge.util.gui.SOptionPane; import forge.view.FView; @@ -33,9 +33,9 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { VLobby getLobby() { return lobby; } - VLobby setLobby(final LobbyType type) { - this.lobby = new VLobby(type); - getLayoutControl().setLobby(lobby); + VLobby setLobby(final GameLobby lobby) { + this.lobby = new VLobby(lobby); + getLayoutControl().setLobby(this.lobby); return this.lobby; } @@ -65,7 +65,9 @@ public enum VOnlineLobby implements IVDoc, IVTopLevelUI { container.repaint(); } - lobby.changePlayerFocus(0); + if (!lobby.getPlayerPanels().isEmpty()) { + lobby.changePlayerFocus(0); + } } @Override diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java index 1aecfad89d3..f492686844f 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/sanctioned/VSubmenuConstructed.java @@ -9,11 +9,14 @@ import forge.deckchooser.IDecksComboBoxListener; import forge.gui.framework.DragCell; import forge.gui.framework.DragTab; import forge.gui.framework.EDocID; +import forge.interfaces.IPlayerChangeListener; +import forge.match.GameLobby; +import forge.match.LocalLobby; +import forge.net.game.UpdateLobbyPlayerEvent; import forge.screens.home.EMenuGroup; import forge.screens.home.IVSubmenu; -import forge.screens.home.VLobby; import forge.screens.home.VHomeUI; -import forge.screens.home.VLobby.LobbyType; +import forge.screens.home.VLobby; /** * Assembles Swing components of constructed submenu singleton. @@ -28,12 +31,22 @@ public enum VSubmenuConstructed implements IVSubmenu { private DragCell parentCell; private final DragTab tab = new DragTab("Constructed Mode"); - private final VLobby lobby = new VLobby(LobbyType.LOCAL); + private final GameLobby lobby = new LocalLobby(); + private final VLobby vLobby = new VLobby(lobby); private VSubmenuConstructed() { + lobby.setListener(vLobby); + + vLobby.setPlayerChangeListener(new IPlayerChangeListener() { + @Override public final void update(final int index, final UpdateLobbyPlayerEvent event) { + lobby.applyToSlot(index, event); + } + }); + + vLobby.update(); } public VLobby getLobby() { - return lobby; + return vLobby; } ///////////////////////////////////// @@ -112,25 +125,27 @@ public enum VSubmenuConstructed implements IVSubmenu { container.removeAll(); container.setLayout(new MigLayout("insets 0, gap 0, wrap 1, ax right")); - container.add(lobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx"); + container.add(vLobby.getLblTitle(), "w 80%, h 40px!, gap 0 0 15px 15px, span 2, al right, pushx"); - for (final FDeckChooser fdc : lobby.getDeckChoosers()) { + for (final FDeckChooser fdc : vLobby.getDeckChoosers()) { fdc.populate(); fdc.getDecksComboBox().addListener(new IDecksComboBoxListener() { @Override public final void deckTypeSelected(final DecksComboBoxEvent ev) { - lobby.getPlayerPanelWithFocus().focusOnAvatar(); + vLobby.getPlayerPanelWithFocus().focusOnAvatar(); } }); } - container.add(lobby.getConstructedFrame(), "gap 20px 20px 20px 0px, push, grow"); - container.add(lobby.getPanelStart(), "gap 0 0 3.5%! 3.5%!, ax center"); + container.add(vLobby.getConstructedFrame(), "gap 20px 20px 20px 0px, push, grow"); + container.add(vLobby.getPanelStart(), "gap 0 0 3.5%! 3.5%!, ax center"); if (container.isShowing()) { container.validate(); container.repaint(); } - lobby.changePlayerFocus(0); + if (!vLobby.getPlayerPanels().isEmpty()) { + vLobby.changePlayerFocus(0); + } } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java index ff95a6953ee..5bb57788b9f 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/CMatchUI.java @@ -51,7 +51,6 @@ import forge.control.KeyboardShortcuts; import forge.deck.CardPool; import forge.deck.Deck; import forge.deckchooser.FDeckViewer; -import forge.game.GameEntity; import forge.game.GameEntityView; import forge.game.GameView; import forge.game.card.CardView; @@ -61,9 +60,8 @@ import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; import forge.game.player.Player; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; -import forge.gui.FNetOverlay; import forge.gui.GuiChoose; import forge.gui.GuiDialog; import forge.gui.GuiUtils; @@ -78,9 +76,9 @@ import forge.interfaces.IButton; import forge.item.InventoryItem; import forge.item.PaperCard; import forge.match.AbstractGuiGame; +import forge.match.MatchButtonType; import forge.menus.IMenuProvider; import forge.model.FModel; -import forge.player.PlayerControllerHuman; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.match.controllers.CAntes; @@ -103,6 +101,7 @@ import forge.toolbox.FSkin.SkinImage; import forge.toolbox.MouseTriggerEvent; import forge.toolbox.special.PhaseIndicator; import forge.toolbox.special.PhaseLabel; +import forge.trackable.TrackableCollection; import forge.util.FCollectionView; import forge.util.ITriggerEvent; import forge.util.gui.SOptionPane; @@ -183,6 +182,21 @@ public final class CMatchUI public void setGameView(final GameView gameView) { super.setGameView(gameView); screen.setTabCaption(gameView.getTitle()); + if (sortedPlayers != null) { + for (final PlayerView pv : sortedPlayers) { + pv.copy(gameView.getPlayers()); + } + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + for (final VField f : getFieldViews()) { + f.getTabletop().setupPlayZone(); + } + for (final VHand h : getHandViews()) { + h.getLayoutControl().updateHand(); + } + } + }); + } } @Override @@ -248,7 +262,7 @@ public final class CMatchUI view.getLblAvatar().getResizeTimer().start(); } - public void initMatch(final FCollectionView sortedPlayers, final Iterable myPlayers) { + private void initMatch(final FCollectionView sortedPlayers, final Iterable myPlayers) { this.sortedPlayers = sortedPlayers; this.setLocalPlayers(myPlayers); allHands = sortedPlayers.size() == getLocalPlayerCount(); @@ -356,7 +370,7 @@ public final class CMatchUI @Override public final boolean stopAtPhase(final PlayerView turn, final PhaseType phase) { final VField vf = getFieldViewFor(turn); - final PhaseLabel label = vf.getPhaseIndicator() .getLabelFor(phase); + final PhaseLabel label = vf.getPhaseIndicator().getLabelFor(phase); return label == null || label.getEnabled(); } @@ -541,12 +555,6 @@ public final class CMatchUI return panels; } - @Override - public boolean resetForNewGame() { - FloatingCardArea.closeAll(); - return true; - } - @Override public IButton getBtnOK(final PlayerView playerView) { return view.getBtnOK(); @@ -558,13 +566,15 @@ public final class CMatchUI } @Override - public void focusButton(final IButton button) { + public void focusButton(final MatchButtonType button) { // ensure we don't steal focus from an overlay if (!SOverlayUtils.overlayHasFocus()) { FThreads.invokeInEdtLater(new Runnable() { - @Override - public void run() { - ((FButton)button).requestFocusInWindow(); + @Override public final void run() { + final FButton btn = button == MatchButtonType.OK + ? getCPrompt().getView().getBtnOK() + : getCPrompt().getView().getBtnCancel(); + btn.requestFocusInWindow(); } }); } @@ -579,7 +589,7 @@ public final class CMatchUI public void updatePhase() { final PlayerView p = getGameView().getPlayerTurn(); final PhaseType ph = getGameView().getPhase(); - PhaseLabel lbl = getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph); + final PhaseLabel lbl = p == null ? null : getFieldViewFor(p).getPhaseIndicator().getLabelFor(ph); resetAllPhaseButtons(); if (lbl != null) { @@ -629,7 +639,11 @@ public final class CMatchUI @Override public void updateStack() { - getCStack().update(); + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + getCStack().update(); + } + }); } /** @@ -664,7 +678,7 @@ public final class CMatchUI } @Override - public SpellAbility getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { + public SpellAbilityView getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { if (triggerEvent == null) { if (abilities.isEmpty()) { return null; @@ -691,7 +705,7 @@ public final class CMatchUI boolean enabled; boolean hasEnabled = false; int shortcut = KeyEvent.VK_1; //use number keys as shortcuts for abilities 1-9 - for (final SpellAbility ab : abilities) { + for (final SpellAbilityView ab : abilities) { enabled = ab.canPlay(); if (enabled) { hasEnabled = true; @@ -763,12 +777,7 @@ public final class CMatchUI } @Override - public void hear(LobbyPlayer player, String message) { - FNetOverlay.SINGLETON_INSTANCE.addMessage(player.getName(), message); - } - - @Override - public void openView(final Iterable myPlayers) { + public void openView(final TrackableCollection myPlayers) { final GameView gameView = getGameView(); gameView.getGameLog().addObserver(getCLog()); @@ -841,16 +850,14 @@ public final class CMatchUI } @Override - public GameEntityView chooseSingleEntityForEffect(final String title, final FCollectionView optionList, final DelayedReveal delayedReveal, final boolean isOptional, final PlayerControllerHuman controller) { + public GameEntityView chooseSingleEntityForEffect(final String title, final Collection optionList, final DelayedReveal delayedReveal, final boolean isOptional) { if (delayedReveal != null) { - delayedReveal.reveal(controller); //TODO: Merge this into search dialog + reveal(delayedReveal.getMessagePrefix(), delayedReveal.getCards()); //TODO: Merge this into search dialog } - controller.tempShow(optionList); - final List gameEntityViews = GameEntityView.getEntityCollection(optionList); if (isOptional) { - return oneOrNone(title, gameEntityViews); + return oneOrNone(title, optionList); } - return one(title, gameEntityViews); + return one(title, optionList); } @Override diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/VMatchUI.java b/forge-gui-desktop/src/main/java/forge/screens/match/VMatchUI.java index 3acbdc012c4..7bc6c14b360 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/VMatchUI.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/VMatchUI.java @@ -1,6 +1,7 @@ package forge.screens.match; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.swing.SwingUtilities; @@ -91,21 +92,49 @@ public class VMatchUI implements IVTopLevelUI { } } - // Add extra hands to existing hand panel. - for (int i = 1; i < lstHands.size(); i++) { - // If already in layout, no need to add again. - if (lstHands.get(i).getParentCell() == null) { // if i == 0, we get NPE in two lines - DragCell cellWithHand = lstHands.get(0).getParentCell(); - cellWithHand.addDoc(lstHands.get(i)); + // Determine (an) existing hand panel + DragCell cellWithHands = null; + for (final EDocID handId : EDocID.Hands) { + cellWithHands = handId.getDoc().getParentCell(); + if (cellWithHands != null) { + break; } } + if (cellWithHands == null) { + // Default to a cell we know exists + cellWithHands = EDocID.REPORT_LOG.getDoc().getParentCell(); + } + for (final EDocID handId : EDocID.Hands) { + final DragCell parentCell = handId.getDoc().getParentCell(); + VHand myVHand = null; + for (final VHand vHand : lstHands) { + if (handId.equals(vHand.getDocumentID())) { + myVHand = vHand; + break; + } + } - // Remove any hand panels that aren't needed anymore - for (int i = EDocID.Hands.length - 1; i >= lstHands.size(); i--) { - DragCell cellWithHand = EDocID.Hands[i].getDoc().getParentCell(); - if (cellWithHand != null) { - cellWithHand.removeDoc(EDocID.Hands[i].getDoc()); - EDocID.Hands[i].setDoc(new VEmptyDoc(EDocID.Hands[i])); + if (myVHand == null) { + // Hand not present, remove cell if necessary + if (parentCell != null) { + parentCell.removeDoc(handId.getDoc()); + handId.setDoc(new VEmptyDoc(handId)); + } + } else { + // Hand present, add it if necessary + if (parentCell == null) { + final EDocID fieldDoc = EDocID.Fields[Arrays.asList(EDocID.Hands).indexOf(handId)]; + if (fieldDoc.getDoc().getParentCell() != null) { + fieldDoc.getDoc().getParentCell().addDoc(myVHand); + continue; + } + final EDocID commandDoc = EDocID.Commands[Arrays.asList(EDocID.Hands).indexOf(handId)]; + if (commandDoc.getDoc().getParentCell() != null) { + commandDoc.getDoc().getParentCell().addDoc(myVHand); + continue; + } + cellWithHands.addDoc(myVHand); + } } } diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java index 0f3120d3194..218736b867a 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java @@ -20,6 +20,7 @@ package forge.screens.match.controllers; import java.util.Iterator; import com.google.common.collect.Iterators; +import com.google.common.primitives.Ints; import forge.Singletons; import forge.UiCommand; @@ -130,7 +131,8 @@ public class CDock implements ICDoc { public void initialize() { final String temp = FModel.getPreferences() .getPref(FPref.UI_TARGETING_OVERLAY); - setArcState(ArcState.values()[Integer.valueOf(temp)]); + final Integer arcState = Ints.tryParse(temp); + setArcState(ArcState.values()[arcState == null ? 0 : arcState.intValue()]); refreshArcStateDisplay(); view.getBtnConcede().setCommand(new UiCommand() { diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java index 179cbd2cb59..7f63b45b273 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java +++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java @@ -32,7 +32,7 @@ import forge.UiCommand; import forge.game.GameView; import forge.game.card.CardView; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.gui.framework.ICDoc; import forge.gui.framework.SDisplayUtil; import forge.screens.match.CMatchUI; @@ -120,7 +120,7 @@ public class CPrompt implements ICDoc { return matchUI.getGameController().selectCard(cardView, otherCardViewsToSelect, triggerEvent); } - public void selectAbility(final SpellAbility sa) { + public void selectAbility(final SpellAbilityView sa) { matchUI.getGameController().selectAbility(sa); } diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java index 1db54d85517..be22e5c848d 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/FComboBoxWrapper.java @@ -1,18 +1,22 @@ package forge.toolbox; -import forge.interfaces.IComboBox; -import forge.toolbox.FComboBox.TextAlignment; -import forge.toolbox.FSkin.SkinFont; - -import javax.swing.*; - -import java.awt.*; +import java.awt.Container; import java.awt.event.ActionListener; import java.awt.event.ItemListener; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.Vector; +import javax.swing.ComboBoxModel; +import javax.swing.JComponent; +import javax.swing.ListCellRenderer; + +import com.google.common.collect.ObjectArrays; + +import forge.interfaces.IComboBox; +import forge.toolbox.FComboBox.TextAlignment; +import forge.toolbox.FSkin.SkinFont; + /** * Wrapper for combo box with extra logic (either FComboBoxWrapper or FComboBoxPanel should be used instead of FComboBox so skinning works) * @@ -28,6 +32,7 @@ public class FComboBoxWrapper implements IComboBox { } private FComboBox comboBox; + private ActionListener[] suppressedActionListeners = null; private Object constraints; public FComboBoxWrapper() { @@ -111,6 +116,24 @@ public class FComboBoxWrapper implements IComboBox { comboBox.addItemListener(l); } + public void suppressActionListeners() { + final ActionListener[] listeners = comboBox.getActionListeners(); + for (final ActionListener al : listeners) { + comboBox.removeActionListener(al); + } + suppressedActionListeners = suppressedActionListeners == null + ? listeners + : ObjectArrays.concat(suppressedActionListeners, listeners, ActionListener.class); + } + public void unsuppressActionListeners() { + if (suppressedActionListeners != null) { + for (final ActionListener al : suppressedActionListeners) { + comboBox.addActionListener(al); + } + suppressedActionListeners = null; + } + } + public void addKeyListener(KeyListener l) { comboBox.addKeyListener(l); } diff --git a/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java b/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java index ebfce75c604..e414b547954 100644 --- a/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java +++ b/forge-gui-desktop/src/main/java/forge/toolbox/MouseTriggerEvent.java @@ -1,11 +1,14 @@ package forge.toolbox; import java.awt.event.MouseEvent; +import java.io.Serializable; import forge.util.ITriggerEvent; //MouseEvent wrapper used for passing trigger to input classes -public class MouseTriggerEvent implements ITriggerEvent { +public class MouseTriggerEvent implements ITriggerEvent, Serializable { + private static final long serialVersionUID = -4746127117012991732L; + private final MouseEvent event; public MouseTriggerEvent(MouseEvent event0) { diff --git a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java index 89d7ca55c44..83869ca7bfc 100644 --- a/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java +++ b/forge-gui-desktop/src/test/java/forge/gamesimulationtests/util/PlayerControllerForTests.java @@ -1,5 +1,13 @@ package forge.gamesimulationtests.util; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Iterables; @@ -27,6 +35,7 @@ import forge.game.card.Card; import forge.game.card.CardCollection; import forge.game.card.CardCollectionView; import forge.game.card.CardShields; +import forge.game.card.CardView; import forge.game.card.CounterType; import forge.game.combat.Combat; import forge.game.combat.CombatUtil; @@ -39,8 +48,13 @@ import forge.game.player.DelayedReveal; import forge.game.player.Player; import forge.game.player.PlayerActionConfirmMode; import forge.game.player.PlayerController; +import forge.game.player.PlayerView; import forge.game.replacement.ReplacementEffect; -import forge.game.spellability.*; +import forge.game.spellability.AbilitySub; +import forge.game.spellability.Spell; +import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.TargetChoices; import forge.game.trigger.Trigger; import forge.game.trigger.WrappedAbility; import forge.game.zone.ZoneType; @@ -48,18 +62,17 @@ import forge.gamesimulationtests.util.card.CardSpecification; import forge.gamesimulationtests.util.card.CardSpecificationHandler; import forge.gamesimulationtests.util.player.PlayerSpecification; import forge.gamesimulationtests.util.player.PlayerSpecificationHandler; -import forge.gamesimulationtests.util.playeractions.*; -import forge.player.HumanPlay; +import forge.gamesimulationtests.util.playeractions.ActivateAbilityAction; +import forge.gamesimulationtests.util.playeractions.CastSpellFromHandAction; +import forge.gamesimulationtests.util.playeractions.DeclareAttackersAction; +import forge.gamesimulationtests.util.playeractions.DeclareBlockersAction; +import forge.gamesimulationtests.util.playeractions.PlayerActions; import forge.item.PaperCard; +import forge.player.HumanPlay; import forge.util.FCollectionView; import forge.util.ITriggerEvent; import forge.util.MyRandom; -import org.apache.commons.lang3.tuple.ImmutablePair; -import org.apache.commons.lang3.tuple.Pair; - -import java.util.*; - /** * Default harmless implementation for tests. * Test-specific behaviour can easily be added by mocking (parts of) this class. @@ -166,7 +179,7 @@ public class PlayerControllerForTests extends PlayerController { @Override public T chooseSingleEntityForEffect(FCollectionView optionList, DelayedReveal delayedReveal, SpellAbility sa, String title, boolean isOptional, Player targetedPlayer) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } return chooseItem(optionList); } @@ -224,6 +237,11 @@ public class PlayerControllerForTests extends PlayerController { //nothing needs to be done here } + @Override + public void reveal(Collection cards, ZoneType zone, PlayerView owner, String messagePrefix) { + //nothing needs to be done here + } + @Override public void notifyOfValue(SpellAbility saSource, GameObject realtedTarget, String value) { //nothing needs to be done here @@ -628,7 +646,7 @@ public class PlayerControllerForTests extends PlayerController { String selectPrompt, boolean isOptional, Player decider) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } return ChangeZoneAi.chooseCardToHiddenOriginChangeZone(destination, origin, sa, fetchList, player, decider); } diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index 9007f9d3ecc..a0f6b5204b7 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -288,7 +288,4 @@ public class GuiMobile implements IGuiBase { return Forge.hostedMatch = new HostedMatch(); } - @Override - public void netMessage(final String origin, final String message) { - } } diff --git a/forge-gui-mobile/src/forge/card/GameEntityPicker.java b/forge-gui-mobile/src/forge/card/GameEntityPicker.java index ae71d1c9bb6..02cd70e2a2d 100644 --- a/forge-gui-mobile/src/forge/card/GameEntityPicker.java +++ b/forge-gui-mobile/src/forge/card/GameEntityPicker.java @@ -21,7 +21,7 @@ import forge.util.Callback; public class GameEntityPicker extends TabPageScreen { private final FOptionPane optionPane; - public GameEntityPicker(String title, List choiceList, List revealList, String revealListCaption, FImage revealListImage, boolean isOptional, final Callback callback) { + public GameEntityPicker(String title, Collection choiceList, Collection revealList, String revealListCaption, FImage revealListImage, boolean isOptional, final Callback callback) { super(new PickerTab[] { new PickerTab(choiceList, "Choices", FSkinImage.DECKLIST, 1), new PickerTab(revealList, revealListCaption, revealListImage, 0) diff --git a/forge-gui-mobile/src/forge/menu/FMenuItem.java b/forge-gui-mobile/src/forge/menu/FMenuItem.java index e73589f47a4..a921368b6b3 100644 --- a/forge-gui-mobile/src/forge/menu/FMenuItem.java +++ b/forge-gui-mobile/src/forge/menu/FMenuItem.java @@ -124,10 +124,6 @@ public class FMenuItem extends FDisplayObject implements IButton { g.drawLine(1, SEPARATOR_COLOR, 0, h, w, h); } - @Override - public String getText() { - return text; - } @Override public void setText(String text0) { } diff --git a/forge-gui-mobile/src/forge/screens/constructed/ConstructedScreen.java b/forge-gui-mobile/src/forge/screens/constructed/ConstructedScreen.java index 2154f5ca4c0..9bbdfa22a97 100644 --- a/forge-gui-mobile/src/forge/screens/constructed/ConstructedScreen.java +++ b/forge-gui-mobile/src/forge/screens/constructed/ConstructedScreen.java @@ -296,7 +296,7 @@ public class ConstructedScreen extends LaunchScreen { PlayerPanel playerPanel = playerPanels.get(i); String name = getPlayerName(i); LobbyPlayer lobbyPlayer = playerPanel.isPlayerAI() ? GamePlayerUtil.createAiPlayer(name, - getPlayerAvatar(i)) : GamePlayerUtil.getGuiPlayer(name, i); + getPlayerAvatar(i)) : GamePlayerUtil.getGuiPlayer(name, i == 0); RegisteredPlayer rp = playerPanel.deckChooser.getPlayer(); if (appliedVariants.isEmpty()) { diff --git a/forge-gui-mobile/src/forge/screens/match/MatchController.java b/forge-gui-mobile/src/forge/screens/match/MatchController.java index cc276c8dbf5..eb57cc3fe07 100644 --- a/forge-gui-mobile/src/forge/screens/match/MatchController.java +++ b/forge-gui-mobile/src/forge/screens/match/MatchController.java @@ -26,7 +26,6 @@ import forge.card.CardRenderer; import forge.card.GameEntityPicker; import forge.deck.CardPool; import forge.deck.FSideboardDialog; -import forge.game.GameEntity; import forge.game.GameEntityView; import forge.game.GameView; import forge.game.card.CardView; @@ -34,17 +33,16 @@ import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; import forge.interfaces.IButton; import forge.item.PaperCard; import forge.match.AbstractGuiGame; +import forge.match.MatchButtonType; import forge.model.FModel; -import forge.player.PlayerControllerHuman; import forge.properties.ForgePreferences; import forge.properties.ForgePreferences.FPref; import forge.screens.match.views.VAssignDamage; -import forge.screens.match.views.VCardDisplayArea.CardAreaPanel; import forge.screens.match.views.VPhaseIndicator; import forge.screens.match.views.VPhaseIndicator.PhaseLabel; import forge.screens.match.views.VPlayerPanel; @@ -53,6 +51,7 @@ import forge.screens.match.views.VPrompt; import forge.screens.match.winlose.ViewWinLose; import forge.toolbox.FDisplayObject; import forge.toolbox.FOptionPane; +import forge.trackable.TrackableCollection; import forge.util.FCollectionView; import forge.util.ITriggerEvent; import forge.util.MessageUtil; @@ -102,18 +101,12 @@ public class MatchController extends AbstractGuiGame { } } - @Override - public boolean resetForNewGame() { - CardAreaPanel.resetForNewGame(); //ensure card panels reset between games - return true; - } - public boolean hotSeatMode() { return FModel.getPreferences().getPrefBoolean(FPref.MATCH_HOT_SEAT_MODE); } @Override - public void openView(final Iterable myPlayers) { + public void openView(final TrackableCollection myPlayers) { setLocalPlayers(myPlayers); final boolean noHumans = !hasLocalPlayers(); @@ -162,7 +155,7 @@ public class MatchController extends AbstractGuiGame { } @Override - public void focusButton(final IButton button) { + public void focusButton(final MatchButtonType button) { //not needed for mobile game } @@ -228,7 +221,7 @@ public class MatchController extends AbstractGuiGame { } @Override - public SpellAbility getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { + public SpellAbilityView getAbilityToPlay(List abilities, ITriggerEvent triggerEvent) { if (abilities.isEmpty()) { return null; } @@ -324,10 +317,6 @@ public class MatchController extends AbstractGuiGame { } } - @Override - public void hear(LobbyPlayer player, String message) { - } - @Override public boolean stopAtPhase(PlayerView turn, PhaseType phase) { return view.stopAtPhase(turn, phase); @@ -475,27 +464,23 @@ public class MatchController extends AbstractGuiGame { } @Override - public GameEntityView chooseSingleEntityForEffect(final String title, final FCollectionView optionList, final DelayedReveal delayedReveal, final boolean isOptional, final PlayerControllerHuman controller) { - controller.tempShow(optionList); - final List choiceList = GameEntityView.getEntityCollection(optionList); - + public GameEntityView chooseSingleEntityForEffect(final String title, final Collection optionList, final DelayedReveal delayedReveal, final boolean isOptional) { if (delayedReveal == null || Iterables.isEmpty(delayedReveal.getCards())) { if (isOptional) { - return SGuiChoose.oneOrNone(title, choiceList); + return SGuiChoose.oneOrNone(title, optionList); } - return SGuiChoose.one(title, choiceList); + return SGuiChoose.one(title, optionList); } - controller.tempShow(delayedReveal.getCards()); - final List revealList = CardView.getCollection(delayedReveal.getCards()); - final String revealListCaption = StringUtils.capitalize(MessageUtil.formatMessage("{player's} " + delayedReveal.getZone().name(), controller.getPlayer(), delayedReveal.getOwner())); + final Collection revealList = delayedReveal.getCards(); + final String revealListCaption = StringUtils.capitalize(MessageUtil.formatMessage("{player's} " + delayedReveal.getZone().name(), delayedReveal.getOwner(), delayedReveal.getOwner())); final FImage revealListImage = MatchController.getView().getPlayerPanels().values().iterator().next().getZoneTab(delayedReveal.getZone()).getIcon(); //use special dialog for choosing card and offering ability to see all revealed cards at the same time return new WaitCallback() { @Override public void run() { - GameEntityPicker picker = new GameEntityPicker(title, choiceList, revealList, revealListCaption, revealListImage, isOptional, this); + GameEntityPicker picker = new GameEntityPicker(title, optionList, revealList, revealListCaption, revealListImage, isOptional, this); picker.show(); } }.invokeAndWait(); diff --git a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java index 45d77eecb01..fbf78d49565 100644 --- a/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java +++ b/forge-gui/src/main/java/forge/control/FControlGameEventHandler.java @@ -242,6 +242,9 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base { if (ev.player.getGame().isGameOver()) { return null; } + if (ev.newController instanceof PlayerControllerHuman) { + matchController.setGameController(PlayerView.get(ev.player), (PlayerControllerHuman) ev.newController); + } needPlayerControlUpdate = true; return processEvent(); } diff --git a/forge-gui/src/main/java/forge/control/WatchLocalGame.java b/forge-gui/src/main/java/forge/control/WatchLocalGame.java index 0e16b07d62e..d18b02717b9 100644 --- a/forge-gui/src/main/java/forge/control/WatchLocalGame.java +++ b/forge-gui/src/main/java/forge/control/WatchLocalGame.java @@ -9,7 +9,7 @@ import forge.LobbyPlayer; import forge.game.Game; import forge.game.card.CardView; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.interfaces.IDevModeCheats; import forge.interfaces.IGuiGame; import forge.match.input.Input; @@ -90,7 +90,7 @@ public class WatchLocalGame extends PlayerControllerHuman { } @Override - public void selectAbility(final SpellAbility sa) { + public void selectAbility(final SpellAbilityView sa) { } @Override diff --git a/forge-gui/src/main/java/forge/interfaces/IButton.java b/forge-gui/src/main/java/forge/interfaces/IButton.java index b249c5bbbec..0232ffd1b95 100644 --- a/forge-gui/src/main/java/forge/interfaces/IButton.java +++ b/forge-gui/src/main/java/forge/interfaces/IButton.java @@ -4,11 +4,8 @@ import forge.UiCommand; import forge.assets.FSkinProp; public interface IButton { - boolean isEnabled(); void setEnabled(boolean b0); - boolean isVisible(); void setVisible(boolean b0); - String getText(); void setText(String text0); boolean isSelected(); void setSelected(boolean b0); diff --git a/forge-gui/src/main/java/forge/interfaces/IGameController.java b/forge-gui/src/main/java/forge/interfaces/IGameController.java index 29fe616b5b9..a01334a21fc 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGameController.java +++ b/forge-gui/src/main/java/forge/interfaces/IGameController.java @@ -4,7 +4,7 @@ import java.util.List; import forge.game.card.CardView; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.match.NextGameDecision; import forge.util.ITriggerEvent; @@ -33,7 +33,7 @@ public interface IGameController { boolean selectCard(CardView cardView, List otherCardViewsToSelect, ITriggerEvent triggerEvent); - void selectAbility(SpellAbility sa); + void selectAbility(SpellAbilityView sa); boolean tryUndoLastAction(); diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java index 62be73c9365..af1d1b28890 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiBase.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiBase.java @@ -51,5 +51,4 @@ public interface IGuiBase { void showBazaar(); IGuiGame getNewGuiGame(); HostedMatch hostMatch(); - void netMessage(String origin, String message); } \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java index e89815d6f8f..dbf758f2678 100644 --- a/forge-gui/src/main/java/forge/interfaces/IGuiGame.java +++ b/forge-gui/src/main/java/forge/interfaces/IGuiGame.java @@ -11,7 +11,6 @@ import com.google.common.base.Function; import forge.LobbyPlayer; import forge.assets.FSkinProp; import forge.deck.CardPool; -import forge.game.GameEntity; import forge.game.GameEntityView; import forge.game.GameView; import forge.game.card.CardView; @@ -19,25 +18,24 @@ import forge.game.phase.PhaseType; import forge.game.player.DelayedReveal; import forge.game.player.IHasIcon; import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; +import forge.game.spellability.SpellAbilityView; import forge.game.zone.ZoneType; import forge.item.PaperCard; -import forge.player.PlayerControllerHuman; -import forge.util.FCollectionView; +import forge.match.MatchButtonType; +import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; public interface IGuiGame { void setGameView(GameView gameView); void setGameController(PlayerView player, IGameController gameController); - boolean resetForNewGame(); - void openView(Iterable myPlayers); + void openView(TrackableCollection myPlayers); void afterGameEnd(); void showCombat(); void showPromptMessage(PlayerView playerView, String message); boolean stopAtPhase(PlayerView playerTurn, PhaseType phase); IButton getBtnOK(PlayerView playerView); IButton getBtnCancel(PlayerView playerView); - void focusButton(IButton button); + void focusButton(MatchButtonType button); void flashIncorrectAction(); void updatePhase(); void updateTurn(PlayerView player); @@ -54,8 +52,7 @@ public interface IGuiGame { void updateManaPool(Iterable manaPoolUpdate); void updateLives(Iterable livesUpdate); void setPanelSelection(CardView hostCard); - void hear(LobbyPlayer player, String message); - SpellAbility getAbilityToPlay(List abilities, + SpellAbilityView getAbilityToPlay(List abilities, ITriggerEvent triggerEvent); Map assignDamage(CardView attacker, List blockers, int damage, GameEntityView defender, @@ -161,9 +158,8 @@ public interface IGuiGame { List sideboard(CardPool sideboard, CardPool main); GameEntityView chooseSingleEntityForEffect(String title, - FCollectionView optionList, - DelayedReveal delayedReveal, boolean isOptional, - PlayerControllerHuman controller); + Collection optionList, + DelayedReveal delayedReveal, boolean isOptional); void setCard(CardView card); void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi); boolean openZones(Collection zones, Map players); diff --git a/forge-gui/src/main/java/forge/interfaces/ILobby.java b/forge-gui/src/main/java/forge/interfaces/ILobby.java index a052ac67d9b..054dd0f3f37 100644 --- a/forge-gui/src/main/java/forge/interfaces/ILobby.java +++ b/forge-gui/src/main/java/forge/interfaces/ILobby.java @@ -1,10 +1,10 @@ package forge.interfaces; -import forge.net.game.LobbyState; +import forge.match.GameLobby; import forge.net.game.server.RemoteClient; public interface ILobby { - LobbyState getState(); + GameLobby getState(); int login(RemoteClient client); void logout(RemoteClient client); } diff --git a/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java b/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java new file mode 100644 index 00000000000..08aa1dad4f3 --- /dev/null +++ b/forge-gui/src/main/java/forge/interfaces/ILobbyListener.java @@ -0,0 +1,8 @@ +package forge.interfaces; + +import forge.match.GameLobby.GameLobbyData; + +public interface ILobbyListener { + void message(String source, String message); + void update(GameLobbyData state, int slot); +} diff --git a/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java b/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java index 49e0821da3f..373f50e03e4 100644 --- a/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java +++ b/forge-gui/src/main/java/forge/interfaces/IPlayerChangeListener.java @@ -1,7 +1,7 @@ package forge.interfaces; -import forge.net.game.LobbyState.LobbyPlayerData; +import forge.net.game.UpdateLobbyPlayerEvent; public interface IPlayerChangeListener { - void update(LobbyPlayerData data); + void update(int index, UpdateLobbyPlayerEvent event); } diff --git a/forge-gui/src/main/java/forge/interfaces/IUpdateable.java b/forge-gui/src/main/java/forge/interfaces/IUpdateable.java new file mode 100644 index 00000000000..fdce8345bf5 --- /dev/null +++ b/forge-gui/src/main/java/forge/interfaces/IUpdateable.java @@ -0,0 +1,5 @@ +package forge.interfaces; + +public interface IUpdateable { + void update(); +} diff --git a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java index 234d2498861..bd5204cdf9c 100644 --- a/forge-gui/src/main/java/forge/match/AbstractGuiGame.java +++ b/forge-gui/src/main/java/forge/match/AbstractGuiGame.java @@ -210,9 +210,9 @@ public abstract class AbstractGuiGame implements IGuiGame, IMayViewCards { btnOk.setEnabled(okEnabled); btnCancel.setEnabled(cancelEnabled); if (okEnabled && focusOk) { - focusButton(btnOk); + focusButton(MatchButtonType.OK); } else if (cancelEnabled) { - focusButton(btnCancel); + focusButton(MatchButtonType.CANCEL); } } diff --git a/forge-gui/src/main/java/forge/match/GameLobby.java b/forge-gui/src/main/java/forge/match/GameLobby.java new file mode 100644 index 00000000000..49694b99883 --- /dev/null +++ b/forge-gui/src/main/java/forge/match/GameLobby.java @@ -0,0 +1,442 @@ +package forge.match; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.primitives.Ints; + +import forge.AIOption; +import forge.GuiBase; +import forge.LobbyPlayer; +import forge.deck.CardPool; +import forge.deck.Deck; +import forge.deck.DeckFormat; +import forge.deck.DeckSection; +import forge.game.GameType; +import forge.game.GameView; +import forge.game.player.Player; +import forge.game.player.RegisteredPlayer; +import forge.interfaces.IGameController; +import forge.interfaces.IGuiGame; +import forge.interfaces.IUpdateable; +import forge.item.PaperCard; +import forge.model.FModel; +import forge.net.game.LobbySlotType; +import forge.net.game.UpdateLobbyPlayerEvent; +import forge.player.GamePlayerUtil; +import forge.properties.ForgePreferences.FPref; +import forge.util.NameGenerator; +import forge.util.gui.SOptionPane; + +public abstract class GameLobby { + private final static int MAX_PLAYERS = 8; + + private GameLobbyData data = new GameLobbyData(); + private int lastArchenemy = 0; + + private IUpdateable listener; + + private final boolean allowNetworking; + private HostedMatch hostedMatch; + private final Map gameControllers = Maps.newHashMap(); + protected GameLobby(final boolean allowNetworking) { + this.allowNetworking = allowNetworking; + } + + public final boolean isAllowNetworking() { + return allowNetworking; + } + + public void setListener(final IUpdateable listener) { + this.listener = listener; + } + + public GameLobbyData getData() { + return data; + } + public void setData(final GameLobbyData data) { + this.data = data; + updateView(); + } + + public GameType getGameType() { + return data.currentGameMode; + } + public void setGameType(final GameType type) { + data.currentGameMode = type; + updateView(); + } + + public boolean hasVariant(final GameType variant) { + return data.appliedVariants.contains(variant); + } + + public int getNumberOfSlots() { + return data.slots.size(); + } + public LobbySlot getSlot(final int index) { + if (index < 0 || index >= getNumberOfSlots()) { + return null; + } + return data.slots.get(index); + } + public void applyToSlot(final int index, final UpdateLobbyPlayerEvent event) { + final LobbySlot slot = getSlot(index); + if (slot == null) { + throw new NullPointerException(); + } + + final int nSlots = getNumberOfSlots(); + final boolean triesToChangeArchenemy = event.getArchenemy() != null; + final boolean archenemyRemoved = triesToChangeArchenemy && !event.getArchenemy().booleanValue(); + final boolean hasArchenemyChanged = triesToChangeArchenemy && slot.isArchenemy() != event.getArchenemy().booleanValue(); + slot.apply(event); + + // Change archenemy teams + if (hasVariant(GameType.Archenemy) && hasArchenemyChanged) { + final int newArchenemy = archenemyRemoved ? lastArchenemy : index; + if (archenemyRemoved) { + lastArchenemy = index; + } + for (int otherIndex = 0; otherIndex < nSlots; otherIndex++) { + final LobbySlot otherSlot = getSlot(otherIndex); + final boolean becomesArchenemy = otherIndex == newArchenemy; + if (!archenemyRemoved && otherSlot.isArchenemy() && !becomesArchenemy) { + lastArchenemy = otherIndex; + } + otherSlot.setIsArchenemy(becomesArchenemy); + } + updateView(); + } + } + + public IGameController getController(final int index) { + return gameControllers.get(getSlot(index)); + } + public GameView getGameView() { + return hostedMatch.getGameView(); + } + + public abstract boolean hasControl(); + public abstract boolean mayEdit(final int index); + public abstract boolean mayControl(final int index); + public abstract boolean mayRemove(final int index); + public abstract IGuiGame getGui(final int index); + + public void addSlot() { + final int newIndex = getNumberOfSlots(); + addSlot(new LobbySlot(allowNetworking ? LobbySlotType.OPEN : LobbySlotType.LOCAL, null, newIndex, newIndex, false, Collections.emptySet())); + } + protected final void addSlot(final LobbySlot slot) { + if (data.slots.size() >= MAX_PLAYERS) { + return; + } + + data.slots.add(slot); + if (StringUtils.isEmpty(slot.getName())) { + slot.setName(randomName()); + } + if (data.slots.size() == 1) { + // If first slot, make archenemy + slot.setIsArchenemy(true); + lastArchenemy = 0; + } + updateView(); + } + private String randomName() { + final List names = Lists.newArrayListWithCapacity(MAX_PLAYERS); + for (final LobbySlot slot : data.slots) { + names.add(slot.getName()); + } + return NameGenerator.getRandomName("Any", "Any", names); + } + protected final String localName() { + return FModel.getPreferences().getPref(FPref.PLAYER_NAME); + } + protected final int[] localAvatarIndices() { + final String[] sAvatars = FModel.getPreferences().getPref(FPref.UI_AVATARS).split(","); + final int[] result = new int[sAvatars.length]; + for (int i = 0; i < sAvatars.length; i++) { + final Integer val = Ints.tryParse(sAvatars[i]); + result[i] = val == null ? -1 : val.intValue(); + } + return result; + } + + public void removeSlot(final int index) { + if (index < 0 || index >= data.slots.size()) { + return; + } + + if (getSlot(index).isArchenemy()) { + getSlot(lastArchenemy).setIsArchenemy(true); + // Should actually be a stack here, but that's rather involved for + // such a nonimportant feature + lastArchenemy = 0; + } else if (lastArchenemy == index) { + lastArchenemy = 0; + } else { + lastArchenemy--; + } + data.slots.remove(index); + updateView(); + } + + public void applyVariant(final GameType variant) { + data.currentGameMode = variant; + data.appliedVariants.add(variant); + + //ensure other necessary variants are unchecked + switch (variant) { + case Archenemy: + data.appliedVariants.remove(GameType.ArchenemyRumble); + break; + case ArchenemyRumble: + data.appliedVariants.remove(GameType.Archenemy); + break; + case Commander: + data.appliedVariants.remove(GameType.TinyLeaders); + data.appliedVariants.remove(GameType.MomirBasic); + break; + case TinyLeaders: + data.appliedVariants.remove(GameType.Commander); + data.appliedVariants.remove(GameType.MomirBasic); + break; + case Vanguard: + data.appliedVariants.remove(GameType.MomirBasic); + break; + case MomirBasic: + data.appliedVariants.remove(GameType.Commander); + data.appliedVariants.remove(GameType.Vanguard); + break; + default: + break; + } + updateView(); + } + + public void removeVariant(final GameType variant) { + data.appliedVariants.remove(variant); + if (data.appliedVariants.isEmpty()) { + data.appliedVariants.add(GameType.Constructed); + } + updateView(); + } + + private boolean isEnoughTeams() { + int lastTeam = -1; + final boolean useArchenemyTeams = data.appliedVariants.contains(GameType.Archenemy); + for (final LobbySlot slot : data.slots) { + final int team = useArchenemyTeams ? (slot.isArchenemy() ? 0 : 1) : slot.getTeam(); + if (lastTeam == -1) { + lastTeam = team; + } else if (lastTeam != team) { + return true; + } + } + return false; + } + + protected final void updateView() { + if (listener != null) { + listener.update(); + } + } + + /** Starts a match with the applied variants. */ + public void startGame() { + if (!isEnoughTeams()) { + SOptionPane.showMessageDialog("There are not enough teams! Please adjust team allocations."); + return; + } + + final List activeSlots = Lists.newArrayListWithCapacity(getNumberOfSlots()); + for (final LobbySlot slot : data.getSlots()) { + if (slot.getType() != LobbySlotType.OPEN) { + activeSlots.add(slot); + } + } + + for (final LobbySlot slot : activeSlots) { + if (slot.getDeck() == null) { + SOptionPane.showMessageDialog(String.format("Please specify a deck for %s", slot.getName())); + return; + } + } + + final Set variantTypes = data.appliedVariants; + + GameType autoGenerateVariant = null; + boolean isCommanderMatch = false; + boolean isTinyLeadersMatch = false; + if (!variantTypes.isEmpty()) { + isTinyLeadersMatch = variantTypes.contains(GameType.TinyLeaders); + isCommanderMatch = isTinyLeadersMatch || variantTypes.contains(GameType.Commander); + if (!isCommanderMatch) { + for (final GameType variant : variantTypes) { + if (variant.isAutoGenerated()) { + autoGenerateVariant = variant; + break; + } + } + } + } + + boolean checkLegality = FModel.getPreferences().getPrefBoolean(FPref.ENFORCE_DECK_LEGALITY); + + //Auto-generated decks don't need to be checked here + //Commander deck replaces regular deck and is checked later + if (checkLegality && autoGenerateVariant == null && !isCommanderMatch) { + for (final LobbySlot slot : activeSlots) { + final String name = slot.getName(); + String errMsg = GameType.Constructed.getDeckFormat().getDeckConformanceProblem(slot.getDeck()); + if (null != errMsg) { + SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Deck"); + return; + } + } + } + + final List players = new ArrayList(); + final Map guis = Maps.newHashMap(); + final Map playerToSlot = Maps.newHashMap(); + boolean hasNameBeenSet = false; + for (final LobbySlot slot : activeSlots) { + final IGuiGame gui = getGui(data.slots.indexOf(slot)); + final String name = slot.getName(); + final int avatar = slot.getAvatarIndex(); + final int team = slot.getTeam(); + final boolean isArchenemy = slot.isArchenemy(); + final Set aiOptions = slot.getAiOptions(); + + final boolean isAI = slot.getType() == LobbySlotType.AI; + final LobbyPlayer lobbyPlayer; + if (isAI) { + lobbyPlayer = GamePlayerUtil.createAiPlayer(name, avatar, aiOptions); + } else { + boolean setNameNow = false; + if (!hasNameBeenSet && slot.getType() == LobbySlotType.LOCAL) { + setNameNow = true; + hasNameBeenSet = true; + } + lobbyPlayer = GamePlayerUtil.getGuiPlayer(name, setNameNow); + } + + Deck deck = slot.getDeck(); + RegisteredPlayer rp = new RegisteredPlayer(deck); + + if (variantTypes.isEmpty()) { + rp.setTeamNumber(team); + players.add(rp.setPlayer(lobbyPlayer)); + } else { + PaperCard vanguardAvatar = null; + if (isCommanderMatch) { + final GameType commanderGameType = isTinyLeadersMatch ? GameType.TinyLeaders : GameType.Commander; + if (checkLegality) { + String errMsg = commanderGameType.getDeckFormat().getDeckConformanceProblem(deck); + if (null != errMsg) { + SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid " + commanderGameType + " Deck"); + return; + } + } + } else if (autoGenerateVariant != null) { + deck = autoGenerateVariant.autoGenerateDeck(rp); + final CardPool avatarPool = deck.get(DeckSection.Avatar); + if (avatarPool != null) { + vanguardAvatar = avatarPool.get(0); + } + } + + // Initialise variables for other variants + deck = deck == null ? rp.getDeck() : deck; + Iterable schemes = null; + Iterable planes = null; + + //Archenemy + if (variantTypes.contains(GameType.ArchenemyRumble) + || (variantTypes.contains(GameType.Archenemy) && isArchenemy)) { + final CardPool schemePool = deck.get(DeckSection.Schemes); + if (checkLegality) { + String errMsg = DeckFormat.getSchemeSectionConformanceProblem(schemePool); + if (null != errMsg) { + SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Scheme Deck"); + return; + } + } + schemes = schemePool.toFlatList(); + } + + //Planechase + if (variantTypes.contains(GameType.Planechase)) { + final CardPool planePool = deck.get(DeckSection.Planes); + if (checkLegality) { + String errMsg = DeckFormat.getPlaneSectionConformanceProblem(planePool); + if (null != errMsg) { + SOptionPane.showErrorDialog(name + "'s deck " + errMsg, "Invalid Planar Deck"); + return; + } + } + planes = planePool.toFlatList(); + } + + //Vanguard + if (variantTypes.contains(GameType.Vanguard)) { + if (vanguardAvatar == null) { //ERROR! null if avatar deselected on list + SOptionPane.showMessageDialog("No Vanguard avatar selected for " + name + + ". Please choose one or disable the Vanguard variant"); + return; + } + } + + rp = RegisteredPlayer.forVariants(variantTypes, deck, schemes, isArchenemy, planes, vanguardAvatar); + rp.setTeamNumber(team); + players.add(rp.setPlayer(lobbyPlayer)); + } + + if (!isAI) { + guis.put(rp, gui); + } + playerToSlot.put(rp, slot); + } + + hostedMatch = GuiBase.getInterface().hostMatch(); + hostedMatch.startMatch(GameType.Constructed, variantTypes, players, guis); + + for (final Player p : hostedMatch.getGame().getPlayers()) { + final LobbySlot slot = playerToSlot.get(p.getRegisteredPlayer()); + if (p.getController() instanceof IGameController) { + gameControllers.put(slot, (IGameController) p.getController()); + } + } + } + + public final static class GameLobbyData implements Serializable { + private static final long serialVersionUID = -9038854736144187540L; + + private transient GameType currentGameMode = GameType.Constructed; + private final Set appliedVariants = EnumSet.of(GameType.Constructed); + private final List slots = Lists.newArrayList(); + + public GameType getCurrentGameMode() { + return currentGameMode; + } + public void setCurrentGameMode(GameType currentGameMode) { + this.currentGameMode = currentGameMode; + } + public Set getAppliedVariants() { + return Collections.unmodifiableSet(appliedVariants); + } + public List getSlots() { + return Collections.unmodifiableList(slots); + } + } +} diff --git a/forge-gui/src/main/java/forge/match/HostedMatch.java b/forge-gui/src/main/java/forge/match/HostedMatch.java index f4bea5342ed..5c390bd7cc7 100644 --- a/forge-gui/src/main/java/forge/match/HostedMatch.java +++ b/forge-gui/src/main/java/forge/match/HostedMatch.java @@ -44,8 +44,8 @@ import forge.properties.ForgePreferences.FPref; import forge.quest.QuestController; import forge.sound.MusicPlaylist; import forge.sound.SoundSystem; +import forge.trackable.TrackableCollection; import forge.util.CollectionSuppliers; -import forge.util.FCollection; import forge.util.GuiDisplayUtil; import forge.util.NameGenerator; import forge.util.maps.HashMapOfLists; @@ -137,8 +137,6 @@ public class HostedMatch { game.subscribeToEvents(SoundSystem.instance); game.subscribeToEvents(visitor); - final String[] indices = FModel.getPreferences().getPref(FPref.UI_AVATARS).split(","); - // Instantiate all required field slots (user at 0) final List sortedPlayers = Lists.newArrayList(game.getRegisteredPlayers()); Collections.sort(sortedPlayers, new Comparator() { @@ -150,18 +148,23 @@ public class HostedMatch { } }); + final String[] avatarIndices = FModel.getPreferences().getPref(FPref.UI_AVATARS).split(","); final GameView gameView = getGameView(); - int i = 0; - int avatarIndex = 0; humanCount = 0; final MapOfLists playersPerGui = new HashMapOfLists(CollectionSuppliers.arrayLists()); - for (final Player p : sortedPlayers) { - if (i < indices.length) { - avatarIndex = Integer.parseInt(indices[i]); - i++; + for (int iPlayer = 0; iPlayer < sortedPlayers.size(); iPlayer++) { + final RegisteredPlayer rp = match.getPlayers().get(iPlayer); + final Player p = sortedPlayers.get(iPlayer); + + p.getLobbyPlayer().setAvatarIndex(rp.getPlayer().getAvatarIndex()); + if (p.getLobbyPlayer().getAvatarIndex() == -1) { + if (iPlayer < avatarIndices.length) { + p.getLobbyPlayer().setAvatarIndex(Integer.parseInt(avatarIndices[iPlayer])); + } else { + p.getLobbyPlayer().setAvatarIndex(0); + } } - p.getLobbyPlayer().setAvatarIndex(avatarIndex); p.updateAvatar(); if (p.getController() instanceof PlayerControllerHuman) { @@ -177,8 +180,9 @@ public class HostedMatch { humanCount++; } } + for (final Entry> e : playersPerGui.entrySet()) { - e.getKey().openView(new FCollection(e.getValue())); + e.getKey().openView(new TrackableCollection(e.getValue())); } if (humanCount == 0) { //watch game but do not participate diff --git a/forge-gui/src/main/java/forge/match/LobbySlot.java b/forge-gui/src/main/java/forge/match/LobbySlot.java new file mode 100644 index 00000000000..319e262ee6a --- /dev/null +++ b/forge-gui/src/main/java/forge/match/LobbySlot.java @@ -0,0 +1,109 @@ +package forge.match; + +import java.io.Serializable; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; + +import forge.AIOption; +import forge.deck.Deck; +import forge.net.game.LobbySlotType; +import forge.net.game.UpdateLobbyPlayerEvent; + +public final class LobbySlot implements Serializable { + private static final long serialVersionUID = 6918205436608794289L; + + private LobbySlotType type; + private String name; + private int avatarIndex; + private int team; + private boolean isArchenemy; + private Deck deck; + private ImmutableSet aiOptions; + + public LobbySlot(final LobbySlotType type, final String name, final int avatarIndex, final int team, final boolean isArchenemy, final Set aiOptions) { + this.type = type; + this.name = name; + this.avatarIndex = avatarIndex; + this.team = team; + this.isArchenemy = isArchenemy; + this.setAiOptions(aiOptions); + } + + void apply(final UpdateLobbyPlayerEvent data) { + if (data.getType() != null) { + setType(data.getType()); + } + if (data.getName() != null) { + setName(data.getName()); + } + if (data.getAvatarIndex() != -1) { + setAvatarIndex(data.getAvatarIndex()); + } + if (data.getTeam() != -1) { + setTeam(data.getTeam()); + } + if (data.getArchenemy() != null) { + setIsArchenemy(data.getArchenemy().booleanValue()); + } + if (data.getAiOptions() != null) { + setAiOptions(data.getAiOptions()); + } + if (data.getDeck() != null) { + setDeck(data.getDeck()); + } else if (getDeck() != null && data.getSection() != null && data.getCards() != null) { + getDeck().putSection(data.getSection(), data.getCards()); + } + } + + public LobbySlotType getType() { + return type; + } + public void setType(final LobbySlotType type) { + this.type = type; + } + + public String getName() { + return name; + } + public void setName(final String name) { + this.name = name; + } + + public int getAvatarIndex() { + return avatarIndex; + } + public void setAvatarIndex(final int avatarIndex) { + this.avatarIndex = avatarIndex; + } + + public int getTeam() { + return team; + } + public void setTeam(final int team) { + this.team = team; + } + + public boolean isArchenemy() { + return isArchenemy; + } + public void setIsArchenemy(final boolean isArchenemy) { + this.isArchenemy = isArchenemy; + } + + public Deck getDeck() { + return deck; + } + public void setDeck(final Deck deck) { + this.deck = deck; + } + + public ImmutableSet getAiOptions() { + return aiOptions; + } + + public void setAiOptions(final Set aiOptions) { + this.aiOptions = aiOptions == null ? ImmutableSet.of() : ImmutableSet.copyOf(aiOptions); + } + +} \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/match/LocalLobby.java b/forge-gui/src/main/java/forge/match/LocalLobby.java new file mode 100644 index 00000000000..f3ee60556d2 --- /dev/null +++ b/forge-gui/src/main/java/forge/match/LocalLobby.java @@ -0,0 +1,44 @@ +package forge.match; + +import java.util.Collections; + +import forge.AIOption; +import forge.GuiBase; +import forge.interfaces.IGuiGame; +import forge.net.game.LobbySlotType; + +public final class LocalLobby extends GameLobby { + + public LocalLobby() { + super(false); + + final String humanName = localName(); + final int[] avatarIndices = localAvatarIndices(); + + final LobbySlot slot0 = new LobbySlot(LobbySlotType.LOCAL, humanName, avatarIndices[0], 0, true, Collections.emptySet()); + addSlot(slot0); + + final LobbySlot slot1 = new LobbySlot(LobbySlotType.AI, null, avatarIndices[1], 1, false, Collections.emptySet()); + addSlot(slot1); + } + + @Override public boolean hasControl() { + return true; + } + + @Override public boolean mayEdit(final int index) { + return true; + } + + @Override public boolean mayControl(final int index) { + return true; + } + + @Override public boolean mayRemove(final int index) { + return index >= 2; + } + + @Override public IGuiGame getGui(final int index) { + return GuiBase.getInterface().getNewGuiGame(); + } +} diff --git a/forge-gui/src/main/java/forge/match/MatchButtonType.java b/forge-gui/src/main/java/forge/match/MatchButtonType.java new file mode 100644 index 00000000000..f0c25c37abb --- /dev/null +++ b/forge-gui/src/main/java/forge/match/MatchButtonType.java @@ -0,0 +1,6 @@ +package forge.match; + +public enum MatchButtonType { + OK, + CANCEL; +} diff --git a/forge-gui/src/main/java/forge/match/NetGuiGame.java b/forge-gui/src/main/java/forge/match/NetGuiGame.java deleted file mode 100644 index 721c2a10fdc..00000000000 --- a/forge-gui/src/main/java/forge/match/NetGuiGame.java +++ /dev/null @@ -1,415 +0,0 @@ -package forge.match; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.tuple.Pair; - -import com.google.common.base.Function; - -import forge.LobbyPlayer; -import forge.UiCommand; -import forge.assets.FSkinProp; -import forge.deck.CardPool; -import forge.game.GameEntity; -import forge.game.GameEntityView; -import forge.game.GameView; -import forge.game.card.CardView; -import forge.game.phase.PhaseType; -import forge.game.player.DelayedReveal; -import forge.game.player.IHasIcon; -import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; -import forge.game.zone.ZoneType; -import forge.interfaces.IButton; -import forge.item.PaperCard; -import forge.net.game.GuiGameEvent; -import forge.net.game.server.IToClient; -import forge.player.PlayerControllerHuman; -import forge.trackable.TrackableObject; -import forge.util.FCollectionView; -import forge.util.ITriggerEvent; - -public class NetGuiGame extends AbstractGuiGame { - - private final IToClient client; - public NetGuiGame(final IToClient client) { - this.client = client; - } - - private void send(final String method) { - send(method, Collections.emptySet()); - } - private void send(final String method, final TrackableObject object) { - send(method, Collections.singleton(object)); - } - private void send(final String method, final Iterable objects) { - client.send(new GuiGameEvent(method, objects)); - } - - @Override - public void setGameView(final GameView gameView) { - super.setGameView(gameView); - send("setGameView", gameView); - } - - @Override - public boolean resetForNewGame() { - send("resetForNewGame"); - return true; - } - - @Override - public void openView(final Iterable myPlayers) { - send("openView", myPlayers); - } - - @Override - public void afterGameEnd() { - send("afterGameEnd"); - } - - @Override - public void showCombat() { - send("showCombat"); - } - - @Override - public void showPromptMessage(PlayerView playerView, String message) { - // TODO Auto-generated method stub - - } - - @Override - public boolean stopAtPhase(PlayerView playerTurn, PhaseType phase) { - // TODO Auto-generated method stub - return false; - } - - @Override - public IButton getBtnOK(final PlayerView playerView) { - return new NetButton(playerView, "OK"); - } - - @Override - public IButton getBtnCancel(final PlayerView playerView) { - return new NetButton(playerView, "Cancel"); - } - - @Override - public void focusButton(IButton button) { - // TODO Auto-generated method stub - - } - - @Override - public void flashIncorrectAction() { - // TODO Auto-generated method stub - - } - - @Override - public void updatePhase() { - // TODO Auto-generated method stub - - } - - @Override - public void updateTurn(PlayerView player) { - // TODO Auto-generated method stub - - } - - @Override - public void updatePlayerControl() { - // TODO Auto-generated method stub - - } - - @Override - public void enableOverlay() { - // TODO Auto-generated method stub - - } - - @Override - public void disableOverlay() { - // TODO Auto-generated method stub - - } - - @Override - public void finishGame() { - // TODO Auto-generated method stub - - } - - @Override - public Object showManaPool(PlayerView player) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void hideManaPool(PlayerView player, Object zoneToRestore) { - // TODO Auto-generated method stub - - } - - @Override - public void updateStack() { - // TODO Auto-generated method stub - - } - - @Override - public void updateZones(List> zonesToUpdate) { - // TODO Auto-generated method stub - - } - - @Override - public void updateSingleCard(CardView card) { - // TODO Auto-generated method stub - - } - - @Override - public void updateManaPool(Iterable manaPoolUpdate) { - // TODO Auto-generated method stub - - } - - @Override - public void updateLives(Iterable livesUpdate) { - // TODO Auto-generated method stub - - } - - @Override - public void setPanelSelection(CardView hostCard) { - // TODO Auto-generated method stub - - } - - @Override - public void hear(LobbyPlayer player, String message) { - // TODO Auto-generated method stub - - } - - @Override - public SpellAbility getAbilityToPlay(List abilities, - ITriggerEvent triggerEvent) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Map assignDamage(CardView attacker, - List blockers, int damage, GameEntityView defender, - boolean overrideOrder) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void message(String message, String title) { - // TODO Auto-generated method stub - - } - - @Override - public void showErrorDialog(String message, String title) { - // TODO Auto-generated method stub - - } - - @Override - public boolean showConfirmDialog(String message, String title, - String yesButtonText, String noButtonText, boolean defaultYes) { - // TODO Auto-generated method stub - return false; - } - - @Override - public int showOptionDialog(String message, String title, FSkinProp icon, - String[] options, int defaultOption) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public int showCardOptionDialog(CardView card, String message, - String title, FSkinProp icon, String[] options, int defaultOption) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public String showInputDialog(String message, String title, FSkinProp icon, - String initialInput, String[] inputOptions) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean confirm(CardView c, String question, boolean defaultIsYes, - String[] options) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List getChoices(String message, int min, int max, - Collection choices, T selected, Function display) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List order(String title, String top, int remainingObjectsMin, - int remainingObjectsMax, List sourceChoices, - List destChoices, CardView referenceCard, - boolean sideboardingMode) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List sideboard(CardPool sideboard, CardPool main) { - // TODO Auto-generated method stub - return null; - } - - @Override - public GameEntityView chooseSingleEntityForEffect(String title, - FCollectionView optionList, - DelayedReveal delayedReveal, boolean isOptional, - PlayerControllerHuman controller) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setCard(CardView card) { - // TODO Auto-generated method stub - - } - - @Override - public void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi) { - // TODO Auto-generated method stub - - } - - @Override - public boolean openZones(Collection zones, - Map players) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void restoreOldZones(Map playersToRestoreZonesFor) { - // TODO Auto-generated method stub - - } - - @Override - public boolean isUiSetToSkipPhase(PlayerView playerTurn, PhaseType phase) { - // TODO Auto-generated method stub - return false; - } - - @Override - protected void updateCurrentPlayer(PlayerView player) { - // TODO Auto-generated method stub - - } - - private final static class NetButton implements IButton { - - private final PlayerView playerView; - private final String button; - private NetButton(final PlayerView playerView, final String button) { - this.playerView = playerView; - this.button = button; - } - - @Override - public boolean isEnabled() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setEnabled(boolean b0) { - // TODO Auto-generated method stub - - } - - @Override - public boolean isVisible() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setVisible(boolean b0) { - // TODO Auto-generated method stub - - } - - @Override - public String getText() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setText(String text0) { - // TODO Auto-generated method stub - - } - - @Override - public boolean isSelected() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setSelected(boolean b0) { - // TODO Auto-generated method stub - - } - - @Override - public boolean requestFocusInWindow() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setCommand(UiCommand command0) { - // TODO Auto-generated method stub - - } - - @Override - public void setTextColor(FSkinProp color) { - // TODO Auto-generated method stub - - } - - @Override - public void setTextColor(int r, int g, int b) { - // TODO Auto-generated method stub - - } - - } -} diff --git a/forge-gui/src/main/java/forge/match/input/InputConfirmMulligan.java b/forge-gui/src/main/java/forge/match/input/InputConfirmMulligan.java index 1b152900d0f..1b8bfafad6c 100644 --- a/forge-gui/src/main/java/forge/match/input/InputConfirmMulligan.java +++ b/forge-gui/src/main/java/forge/match/input/InputConfirmMulligan.java @@ -83,6 +83,11 @@ public class InputConfirmMulligan extends InputSyncronizedBase { showMessage(sb.toString()); } + @Override + protected final boolean allowAwaitNextInput() { + return true; + } + /** {@inheritDoc} */ @Override protected final void onOk() { diff --git a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java index fef154550e9..64b69484df6 100644 --- a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java +++ b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java @@ -125,13 +125,13 @@ public class InputPassPriority extends InputSyncronizedBase { @Override protected boolean onCardSelected(final Card card, final List otherCardsToSelect, final ITriggerEvent triggerEvent) { //remove unplayable unless triggerEvent specified, in which case unplayable may be shown as disabled options - List abilities = card.getAllPossibleAbilities(getController().getPlayer(), triggerEvent == null); - if (abilities.isEmpty()) { + List abilities = card.getAllPossibleAbilities(getController().getPlayer(), triggerEvent == null); + if (abilities.isEmpty()) { return false; - } + } final SpellAbility ability = getController().getAbilityToPlay(abilities, triggerEvent); - if (ability != null) { + if (ability != null) { chosenSa = new ArrayList(); chosenSa.add(ability); if (otherCardsToSelect != null && ability.isManaAbility()) { diff --git a/forge-gui/src/main/java/forge/match/input/InputProxy.java b/forge-gui/src/main/java/forge/match/input/InputProxy.java index 5eb7a9a79c2..1cbc7b48988 100644 --- a/forge-gui/src/main/java/forge/match/input/InputProxy.java +++ b/forge-gui/src/main/java/forge/match/input/InputProxy.java @@ -149,7 +149,7 @@ public class InputProxy implements Observer { } public final void selectAbility(final SpellAbility sa) { - final Input inp = getInput(); + final Input inp = getInput(); if (inp != null) { if (sa != null) { inp.selectAbility(sa); diff --git a/forge-gui/src/main/java/forge/net/ClientGameLobby.java b/forge-gui/src/main/java/forge/net/ClientGameLobby.java new file mode 100644 index 00000000000..31a06afc063 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/ClientGameLobby.java @@ -0,0 +1,36 @@ +package forge.net; + +import forge.interfaces.IGuiGame; +import forge.match.GameLobby; + +public final class ClientGameLobby extends GameLobby { + private int localPlayer = -1; + + public ClientGameLobby() { + super(true); + } + + public void setLocalPlayer(final int index) { + this.localPlayer = index; + } + + @Override public boolean hasControl() { + return false; + } + + @Override public boolean mayEdit(final int index) { + return index == localPlayer; + } + + @Override public boolean mayControl(final int index) { + return false; + } + + @Override public boolean mayRemove(final int index) { + return false; + } + + @Override public IGuiGame getGui(final int index) { + return null; + } +} diff --git a/forge-gui/src/main/java/forge/net/FGameClient.java b/forge-gui/src/main/java/forge/net/FGameClient.java index c9d2ea9bb17..64879befe3b 100644 --- a/forge-gui/src/main/java/forge/net/FGameClient.java +++ b/forge-gui/src/main/java/forge/net/FGameClient.java @@ -15,22 +15,44 @@ import io.netty.handler.codec.serialization.ClassResolvers; import io.netty.handler.codec.serialization.ObjectDecoder; import io.netty.handler.codec.serialization.ObjectEncoder; +import java.io.Serializable; +import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.base.Function; import com.google.common.collect.Lists; +import forge.FThreads; +import forge.UiCommand; +import forge.assets.FSkinProp; +import forge.deck.CardPool; +import forge.game.GameEntityView; import forge.game.GameView; +import forge.game.card.CardView; +import forge.game.phase.PhaseType; +import forge.game.player.DelayedReveal; import forge.game.player.PlayerView; +import forge.game.spellability.SpellAbilityView; +import forge.game.zone.ZoneType; +import forge.interfaces.IButton; import forge.interfaces.IGuiGame; +import forge.interfaces.ILobbyListener; +import forge.match.MatchButtonType; import forge.model.FModel; import forge.net.game.GuiGameEvent; -import forge.net.game.LobbyUpdateEvent; +import forge.net.game.IdentifiableNetEvent; import forge.net.game.LoginEvent; import forge.net.game.MessageEvent; import forge.net.game.NetEvent; -import forge.net.game.client.ILobbyListener; +import forge.net.game.ReplyEvent; import forge.net.game.client.IToServer; import forge.properties.ForgePreferences.FPref; +import forge.trackable.TrackableCollection; +import forge.util.ITriggerEvent; public class FGameClient implements IToServer { private final IGuiGame clientGui; @@ -39,6 +61,7 @@ public class FGameClient implements IToServer { } private final List lobbyListeners = Lists.newArrayList(); + private final ReplyPool replies = new ReplyPool(); static final int SIZE = Integer.parseInt(System.getProperty("size", "256")); @@ -81,14 +104,32 @@ public class FGameClient implements IToServer { } } + @Override public void send(final NetEvent event) { + System.out.println("Client sent " + event); channel.writeAndFlush(event); } + @Override + public Object sendAndWait(final IdentifiableNetEvent event) throws TimeoutException { + replies.initialize(event.getId()); + + send(event); + + // Wait for reply + return replies.get(event.getId()); + } + public void addLobbyListener(final ILobbyListener listener) { lobbyListeners.add(listener); } + private void setGameControllers(final Iterable myPlayers) { + for (final PlayerView p : myPlayers) { + clientGui.setGameController(p, new NetGameController(this)); + } + } + private class GameClientHandler extends ChannelInboundHandlerAdapter { /** * Creates a client-side handler. @@ -98,24 +139,214 @@ public class FGameClient implements IToServer { @Override public void channelActive(final ChannelHandlerContext ctx) { - // Don't use send here, as this.channel is not yet set! - ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME))); + // Don't use send() here, as this.channel is not yet set! + ctx.channel().writeAndFlush(new LoginEvent(FModel.getPreferences().getPref(FPref.PLAYER_NAME), Integer.parseInt(FModel.getPreferences().getPref(FPref.UI_AVATARS).split(",")[0]))); } @SuppressWarnings("unchecked") @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { System.out.println("Client received: " + msg); - if (msg instanceof GuiGameEvent) { + if (msg instanceof ReplyEvent) { + final ReplyEvent event = (ReplyEvent) msg; + replies.complete(event.getIndex(), event.getReply()); + } else if (msg instanceof GuiGameEvent) { final GuiGameEvent event = (GuiGameEvent) msg; + final Object[] args = event.getObjects(); + Serializable reply = null; + boolean doReply = false; + + final IButton btn; + if (args.length >= 2 && args[0] instanceof PlayerView && args[1] instanceof MatchButtonType) { + btn = args[1] == MatchButtonType.OK ? clientGui.getBtnOK((PlayerView) args[0]) : clientGui.getBtnCancel((PlayerView) args[0]); + } else { + btn = null; + } + switch (event.getMethod()) { case "setGameView": - clientGui.setGameView((GameView) event.getObject()); + clientGui.setGameView((GameView) args[0]); break; case "openView": - clientGui.openView((Iterable) event.getObjects()); - default: + final TrackableCollection myPlayers = (TrackableCollection) args[0]; + setGameControllers(myPlayers); + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + //clientGui.setGameView(new NetGameView(FGameClient.this)); + clientGui.openView(myPlayers); + } + }); break; + case "afterGameEnd": + clientGui.afterGameEnd(); + break; + case "showCombat": + clientGui.showCombat(); + break; + case "showPromptMessage": + clientGui.showPromptMessage((PlayerView) args[0], (String) args[1]); + break; + case "stopAtPhase": + reply = clientGui.stopAtPhase((PlayerView) args[0], (PhaseType) args[1]); + doReply = true; + break; + case "focusButton": + clientGui.focusButton((MatchButtonType) args[0]); + break; + case "flashIncorrectAction": + clientGui.flashIncorrectAction(); + break; + case "updatePhase": + clientGui.updatePhase(); + break; + case "updateTurn": + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + clientGui.updateTurn((PlayerView) args[0]); + } + }); + break; + case "udpdatePlayerControl": + clientGui.updatePlayerControl(); + break; + case "enableOverlay": + clientGui.enableOverlay(); + break; + case "disbleOverlay": + clientGui.disableOverlay(); + break; + case "finishGame": + clientGui.finishGame(); + break; + case "showManaPool": + clientGui.showManaPool((PlayerView) args[0]); + break; + case "hideManaPool": + clientGui.hideManaPool((PlayerView) args[0], args[1]); + break; + case "updateStack": + clientGui.updateStack(); + break; + case "updateZones": + FThreads.invokeInEdtNowOrLater(new Runnable() { + @Override public final void run() { + clientGui.updateZones((List>) args[0]); + } + }); + break; + case "updateSingleCard": + clientGui.updateSingleCard((CardView) args[0]); + break; + case "updateManaPool": + clientGui.updateManaPool((Iterable) args[0]); + break; + case "updateLives": + clientGui.updateLives((Iterable) args[0]); + break; + case "setPanelSelection": + clientGui.setPanelSelection((CardView) args[0]); + break; + case "getAbilityToPlay": + reply = clientGui.getAbilityToPlay((List) args[0], (ITriggerEvent) args[1]); + doReply = true; + break; + case "assignDamage": + reply = (Serializable) clientGui.assignDamage((CardView) args[0], (List) args[1], (int) args[2], (GameEntityView) args[3], (boolean) args[4]); + doReply = true; + break; + case "message": + clientGui.message((String) args[0], (String) args[1]); + break; + case "showErrorDialog": + clientGui.showErrorDialog((String) args[0], (String) args[1]); + break; + case "showConfirmDialog": + reply = clientGui.showConfirmDialog((String) args[0], (String) args[1], (String) args[2], (String) args[3], (boolean) args[4]); + doReply = true; + break; + case "showOptionDialog": + reply = clientGui.showOptionDialog((String) args[0], (String) args[1], (FSkinProp) args[2], (String[]) args[3], (int) args[4]); + doReply = true; + break; + case "showCardOptionDialog": + reply = clientGui.showCardOptionDialog((CardView) args[0], (String) args[1], (String) args[2], (FSkinProp) args[3], (String[]) args[4], (int) args[5]); + doReply = true; + break; + case "showInputDialog": + reply = clientGui.showInputDialog((String) args[0], (String) args[1], (FSkinProp) args[2], (String) args[3], (String[]) args[4]); + doReply = true; + break; + case "confirm": + reply = clientGui.confirm((CardView) args[0], (String) args[1], (boolean) args[2], (String[]) args[3]); + doReply = true; + break; + case "getChoices": + reply = (Serializable) clientGui.getChoices((String) args[0], (int) args[1], (int) args[2], (Collection) args[3], args[4], (Function) args[5]); + doReply = true; + break; + case "order": + reply = (Serializable) clientGui.order((String) args[0], (String) args[1], (int) args[2], (int) args[3], (List) args[4], (List) args[5], (CardView) args[6], (boolean) args[7]); + doReply = true; + break; + case "sideboard": + reply = (Serializable) clientGui.sideboard((CardPool) args[0], (CardPool) args[1]); + doReply = true; + break; + case "chooseSingleEntityForEffect": + reply = clientGui.chooseSingleEntityForEffect((String) args[0], (TrackableCollection) args[1], (DelayedReveal) args[2], (boolean) args[3]); + doReply = true; + break; + case "setCard": + clientGui.setCard((CardView) args[0]); + break; + // TODO case "setPlayerAvatar": + case "openZones": + reply = clientGui.openZones((Collection) args[0], (Map) args[1]); + doReply = true; + break; + case "restoreOldZones": + clientGui.restoreOldZones((Map) args[0]); + break; + case "isUiSetToSkipPhase": + reply = clientGui.isUiSetToSkipPhase((PlayerView) args[0], (PhaseType) args[1]); + doReply = true; + break; + // BUTTONS + case "btn_setEnabled": + btn.setEnabled((boolean) args[2]); + break; + case "btn_setVisible": + btn.setVisible((boolean) args[2]); + break; + case "btn_setText": + btn.setText((String) args[2]); + break; + case "btn_isSelected": + reply = btn.isSelected(); + doReply = true; + break; + case "btn_setSelected": + btn.setSelected((boolean) args[2]); + break; + case "btn_requestFocusInWindows": + reply = btn.requestFocusInWindow(); + doReply = true; + break; + case "btn_setCommand": + btn.setCommand((UiCommand) args[2]); + break; + case "btn_setTextColor": + if (args.length == 3) { + btn.setTextColor((FSkinProp) args[2]); + } else { + btn.setTextColor((int) args[2], (int) args[3], (int) args[4]); + } + default: + System.err.println("Unsupported game event " + event.getMethod()); + break; + } + if (doReply) { + send(new ReplyEvent(event.getId(), reply)); } } } @@ -145,7 +376,8 @@ public class FGameClient implements IToServer { public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { if (msg instanceof LobbyUpdateEvent) { for (final ILobbyListener listener : lobbyListeners) { - listener.update(((LobbyUpdateEvent) msg).getState()); + final LobbyUpdateEvent event = (LobbyUpdateEvent) msg; + listener.update(event.getState(), event.getSlot()); } } super.channelRead(ctx, msg); diff --git a/forge-gui/src/main/java/forge/net/FServerManager.java b/forge-gui/src/main/java/forge/net/FServerManager.java index a1914c79003..4328f301df3 100644 --- a/forge-gui/src/main/java/forge/net/FServerManager.java +++ b/forge-gui/src/main/java/forge/net/FServerManager.java @@ -1,15 +1,27 @@ package forge.net; -import forge.game.GameRules; -import forge.interfaces.ILobby; -import forge.net.game.LobbyState; -import forge.net.game.LobbyUpdateEvent; +import forge.FThreads; +import forge.GuiBase; +import forge.LobbyPlayer; +import forge.game.GameView; +import forge.game.card.CardView; +import forge.game.player.PlayerView; +import forge.game.spellability.SpellAbilityView; +import forge.interfaces.IGameController; +import forge.interfaces.IGuiGame; +import forge.interfaces.ILobbyListener; +import forge.match.LobbySlot; +import forge.match.NextGameDecision; +import forge.net.game.GuiGameEvent; +import forge.net.game.LobbySlotType; import forge.net.game.LoginEvent; import forge.net.game.LogoutEvent; import forge.net.game.MessageEvent; import forge.net.game.NetEvent; -import forge.net.game.RegisterDeckEvent; +import forge.net.game.ReplyEvent; +import forge.net.game.UpdateLobbyPlayerEvent; import forge.net.game.server.RemoteClient; +import forge.util.ITriggerEvent; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -27,8 +39,14 @@ import io.netty.handler.codec.serialization.ObjectEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; import com.google.common.collect.Maps; public final class FServerManager { @@ -36,18 +54,13 @@ public final class FServerManager { private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); private final EventLoopGroup workerGroup = new NioEventLoopGroup(); - private final Map games = Maps.newTreeMap(); - private int id = 0; private final Map clients = Maps.newTreeMap(); - private ILobby localLobby; + private ServerGameLobby localLobby; + private ILobbyListener lobbyListener; private FServerManager() { } - private int nextId() { - return id++; - } - public static FServerManager getInstance() { if (instance == null) { instance = new FServerManager(); @@ -62,15 +75,14 @@ public final class FServerManager { .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer() { - @Override - public void initChannel(SocketChannel ch) throws Exception { - ChannelPipeline p = ch.pipeline(); + @Override public final void initChannel(final SocketChannel ch) { + final ChannelPipeline p = ch.pipeline(); p.addLast( new ObjectEncoder(), new ObjectDecoder(ClassResolvers.cacheDisabled(null)), new MessageHandler(), new RegisterClientHandler(), - new ToLobbyListenersHandler(), + new LobbyInputHandler(), new DeregisterClientHandler(), new GameServerHandler()); } @@ -101,29 +113,53 @@ public final class FServerManager { } public void broadcast(final NetEvent event) { - for (final RemoteClient client : clients.values()) { + broadcastTo(event, clients.values()); + } + public void broadcastExcept(final NetEvent event, final RemoteClient notTo) { + broadcastExcept(event, Collections.singleton(notTo)); + } + public void broadcastExcept(final NetEvent event, final Collection notTo) { + broadcastTo(event, Iterables.filter(clients.values(), Predicates.not(Predicates.in(notTo)))); + } + private void broadcastTo(final NetEvent event, final Iterable to) { + for (final RemoteClient client : to) { event.updateForClient(client); client.send(event); } } - public void setLobby(final ILobby lobby) { + public void setLobby(final ServerGameLobby lobby) { this.localLobby = lobby; } - public NetGame hostGame(final GameRules rules) { - final int id = nextId(); - final NetGame game = new NetGame(rules); - games.put(id, game); - return game; + public void setLobbyListener(final ILobbyListener listener) { + this.lobbyListener = listener; } public void updateLobbyState() { - final LobbyState state = localLobby.getState(); - final LobbyUpdateEvent event = new LobbyUpdateEvent(state); + final LobbyUpdateEvent event = new LobbyUpdateEvent(localLobby.getData()); broadcast(event); } + public void updateSlot(final int index, final UpdateLobbyPlayerEvent event) { + localLobby.applyToSlot(index, event); + } + + public IGuiGame getGui(final int index) { + final LobbySlot slot = localLobby.getSlot(index); + final LobbySlotType type = slot.getType(); + if (type == LobbySlotType.LOCAL) { + return GuiBase.getInterface().getNewGuiGame(); + } else if (type == LobbySlotType.REMOTE) { + for (final RemoteClient client : clients.values()) { + if (client.getIndex() == index) { + return new NetGuiGame(client); + } + } + } + return null; + } + @Override protected void finalize() throws Throwable { super.finalize(); @@ -141,9 +177,187 @@ public final class FServerManager { } private class GameServerHandler extends ChannelInboundHandlerAdapter { + @SuppressWarnings("unchecked") @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) { System.out.println("Server received: " + msg); + final RemoteClient client = clients.get(ctx.channel()); + if (msg instanceof ReplyEvent) { + client.setReply(((ReplyEvent) msg).getIndex(), ((ReplyEvent) msg).getReply()); + } else if (msg instanceof GuiGameEvent) { + final GuiGameEvent event = (GuiGameEvent) msg; + final GameView gameView = localLobby.getGameView(); + final IGameController controller = localLobby.getController(client.getIndex()); + final Object[] args = event.getObjects(); + + FThreads.invokeInBackgroundThread(new Runnable() { + @Override public final void run() { + Serializable reply = null; + boolean doReply = false; + + switch (event.getMethod()) { + // From GameController + case "useMana": + controller.useMana((byte) args[0]); + break; + case "tryUndoLastAction": + reply = controller.tryUndoLastAction(); + doReply = true; + break; + case "selectPlayer": + controller.selectPlayer((PlayerView) args[0], (ITriggerEvent) args[1]); + break; + case "selectCard": + reply = controller.selectCard((CardView) args[0], (List) args[1], (ITriggerEvent) args[2]); + doReply = true; + break; + case "selectButtonOk": + controller.selectButtonOk(); + break; + case "selectButtonCancel": + controller.selectButtonCancel(); + break; + case "selectAbility": + controller.selectAbility((SpellAbilityView) args[0]); + break; + case "passPriorityUntilEndOfTurn": + reply = controller.passPriorityUntilEndOfTurn(); + doReply = true; + break; + case "passPriority": + reply = controller.passPriority(); + doReply = true; + break; + case "nextGameDecision": + controller.nextGameDecision((NextGameDecision) args[0]); + break; + case "mayLookAtAllCards": + reply = controller.mayLookAtAllCards(); + doReply = true; + break; + case "getActivateDescription": + reply = controller.getActivateDescription((CardView) args[0]); + doReply = true; + break; + case "concede": + controller.concede(); + break; + case "alphaStrike": + controller.alphaStrike(); + break; + // From GameView + case "getPlayers": + reply = (Serializable) gameView.getPlayers(); + doReply = true; + break; + case "getTitle": + reply = gameView.getTitle(); + doReply = true; + break; + case "isCommander": + reply = gameView.isCommander(); + doReply = true; + break; + case "getGameType": + reply = gameView.getGameType(); + doReply = true; + break; + case "getPoisonCountersToLose": + reply = gameView.getPoisonCountersToLose(); + doReply = true; + break; + case "getNumGamesInMatch": + reply = gameView.getNumGamesInMatch(); + doReply = true; + break; + case "getTurn": + reply = gameView.getTurn(); + doReply = true; + break; + case "getPhase": + reply = gameView.getPhase(); + doReply = true; + break; + case "getPlayerTurn": + reply = gameView.getPlayerTurn(); + doReply = true; + break; + case "getStack": + reply = (Serializable) gameView.getStack(); + doReply = true; + break; + case "peekStack": + reply = gameView.peekStack(); + doReply = true; + break; + case "getStormCount": + reply = gameView.getStormCount(); + doReply = true; + break; + case "isFirstGameInMatch": + reply = gameView.isFirstGameInMatch(); + doReply = true; + break; + case "getNumPlayedGamesInMatch": + reply = gameView.getNumPlayedGamesInMatch(); + doReply = true; + break; + case "isGameOver": + reply = gameView.isGameOver(); + doReply = true; + break; + case "isMatchOver": + reply = gameView.isMatchOver(); + doReply = true; + break; + case "getWinningTeam": + reply = gameView.getWinningTeam(); + doReply = true; + break; + case "getGameLog": + reply = gameView.getGameLog(); + doReply = true; + break; + case "getCombat": + reply = gameView.getCombat(); + doReply = true; + break; + case "isMatchWonBy": + reply = gameView.isMatchWonBy((LobbyPlayer) args[0]); + doReply = true; + break; + case "getOutcomesOfMatch": + reply = (Serializable) gameView.getOutcomesOfMatch(); + doReply = true; + break; + // TODO case "getWinningPlayer": + case "isWinner": + reply = gameView.isWinner((LobbyPlayer) args[0]); + doReply = true; + break; + case "getGamesWonBy": + reply = gameView.getGamesWonBy((LobbyPlayer) args[0]); + doReply = true; + break; + case "getDeck": + reply = gameView.getDeck((String) args[0]); + doReply = true; + break; + case "getAnteResult": + reply = gameView.getAnteResult((PlayerView) args[0]); + doReply = true; + break; + default: + System.err.println(String.format("Unknown incoming client command %s", event.getMethod())); + break; + } + + if (doReply) { + client.send(new ReplyEvent(event.getId(), reply)); + } + } + }); + } } @Override @@ -158,8 +372,7 @@ public final class FServerManager { public void channelActive(final ChannelHandlerContext ctx) throws Exception { final RemoteClient client = new RemoteClient(ctx.channel()); clients.put(ctx.channel(), client); - games.get(0).addClient(client); - System.out.println("User connected to server at " + ctx.channel().remoteAddress()); + System.out.println("Client connected to server at " + ctx.channel().remoteAddress()); updateLobbyState(); super.channelActive(ctx); } @@ -168,47 +381,45 @@ public final class FServerManager { public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof LoginEvent) { - client.setUsername(((LoginEvent) msg).getUsername()); + final String username = ((LoginEvent) msg).getUsername(); + client.setUsername(username); + broadcast(new MessageEvent(null, String.format("%s joined the room", username))); updateLobbyState(); - } else if (msg instanceof RegisterDeckEvent) { - games.get(0).registerDeck(client, ((RegisterDeckEvent) msg).getDeck()); + } else if (msg instanceof UpdateLobbyPlayerEvent) { + localLobby.applyToSlot(client.getIndex(), (UpdateLobbyPlayerEvent) msg); } super.channelRead(ctx, msg); } } - private class ToLobbyListenersHandler extends ChannelInboundHandlerAdapter { + private class LobbyInputHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { final RemoteClient client = clients.get(ctx.channel()); if (msg instanceof LoginEvent) { final LoginEvent event = (LoginEvent) msg; - final int index = localLobby.login(client); + final int index = localLobby.connectPlayer(event.getUsername(), event.getAvatarIndex()); if (index == -1) { ctx.close(); } else { client.setIndex(index); broadcast(event); + updateLobbyState(); } + } else if (msg instanceof UpdateLobbyPlayerEvent) { + updateSlot(client.getIndex(), (UpdateLobbyPlayerEvent) msg); } else if (msg instanceof MessageEvent) { final MessageEvent event = (MessageEvent) msg; - broadcast(event); + lobbyListener.message(event.getSource(), event.getMessage()); } super.channelRead(ctx, msg); } - - @Override public void channelInactive(final ChannelHandlerContext ctx) throws Exception { - final RemoteClient client = clients.get(ctx.channel()); - localLobby.logout(client); - updateLobbyState(); - super.channelInactive(ctx); - } } private class DeregisterClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { final RemoteClient client = clients.remove(ctx.channel()); - // TODO remove client from games + localLobby.disconnectPlayer(client.getIndex()); broadcast(new LogoutEvent(client.getUsername())); super.channelInactive(ctx); } diff --git a/forge-gui/src/main/java/forge/net/LobbyUpdateEvent.java b/forge-gui/src/main/java/forge/net/LobbyUpdateEvent.java new file mode 100644 index 00000000000..cec6c055b16 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/LobbyUpdateEvent.java @@ -0,0 +1,28 @@ +package forge.net; + +import forge.match.GameLobby.GameLobbyData; +import forge.net.game.NetEvent; +import forge.net.game.server.RemoteClient; + +public class LobbyUpdateEvent implements NetEvent { + private static final long serialVersionUID = 7114918637727047985L; + + private final GameLobbyData state; + private int slot; + public LobbyUpdateEvent(final GameLobbyData state) { + this.state = state; + } + + @Override + public void updateForClient(final RemoteClient client) { + this.slot = client.getIndex(); + } + + public GameLobbyData getState() { + return state; + } + + public int getSlot() { + return slot; + } +} diff --git a/forge-gui/src/main/java/forge/net/NetGame.java b/forge-gui/src/main/java/forge/net/NetGame.java deleted file mode 100644 index 5748726db28..00000000000 --- a/forge-gui/src/main/java/forge/net/NetGame.java +++ /dev/null @@ -1,69 +0,0 @@ -package forge.net; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - -import forge.ai.LobbyPlayerAi; -import forge.deck.Deck; -import forge.game.GameRules; -import forge.game.player.RegisteredPlayer; -import forge.interfaces.IGuiGame; -import forge.match.HostedMatch; -import forge.match.NetGuiGame; -import forge.net.game.server.RemoteClient; -import forge.player.LobbyPlayerHuman; - -public final class NetGame { - - private final Map clients = Maps.newHashMap(); - private final GameRules rules; - private final HostedMatch match = new HostedMatch(); - public NetGame(final GameRules rules) { - this.rules = rules; - } - - public void addClient(final RemoteClient client) { - clients.put(client, new NetPlayer(client, new NetGuiGame(client))); - } - - public void startMatch() { - final List registeredPlayers = Lists.newArrayListWithCapacity(clients.size()); - final Map guis = Maps.newHashMap(); - for (final NetPlayer np : clients.values()) { - if (np.player == null) { - System.err.println("No deck registered for player " + np.client.getUsername()); - return; - } - registeredPlayers.add(np.player); - guis.put(np.player, np.gui); - } - - // DEBUG - if (registeredPlayers.size() == 1) { - RegisteredPlayer r = new RegisteredPlayer(new Deck()); - registeredPlayers.add(r); - r.setPlayer(new LobbyPlayerAi("AI", new HashMap())); - } - match.startMatch(rules, null, registeredPlayers, guis); - } - - public void registerDeck(final RemoteClient client, final Deck deck) { - final RegisteredPlayer r = new RegisteredPlayer(deck); - clients.get(client).player = r; - r.setPlayer(new LobbyPlayerHuman(client.getUsername())); - } - - private static final class NetPlayer { - private final RemoteClient client; - private RegisteredPlayer player = null; - private final IGuiGame gui; - private NetPlayer(final RemoteClient client, final IGuiGame gui) { - this.client = client; - this.gui = gui; - } - } -} diff --git a/forge-gui/src/main/java/forge/net/NetGameController.java b/forge-gui/src/main/java/forge/net/NetGameController.java new file mode 100644 index 00000000000..b0ad5abc87f --- /dev/null +++ b/forge-gui/src/main/java/forge/net/NetGameController.java @@ -0,0 +1,129 @@ +package forge.net; + +import java.util.List; +import java.util.concurrent.TimeoutException; + +import forge.game.card.CardView; +import forge.game.player.PlayerView; +import forge.game.spellability.SpellAbilityView; +import forge.interfaces.IDevModeCheats; +import forge.interfaces.IGameController; +import forge.match.NextGameDecision; +import forge.net.game.GuiGameEvent; +import forge.net.game.client.IToServer; +import forge.util.ITriggerEvent; + +public class NetGameController implements IGameController { + + private final IToServer server; + public NetGameController(final IToServer server) { + this.server = server; + } + + private String methodName() { + boolean passedFirst = false; + for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { + if (ste.getClassName() == getClass().getName()) { + if (passedFirst) { + return ste.getMethodName(); + } + passedFirst = true; + } + } + return null; + } + + private void send(final String method, final Object... args) { + server.send(new GuiGameEvent(method, args)); + } + @SuppressWarnings("unchecked") + private T sendAndWait(final String method, final Object... args) { + try { + return (T) server.sendAndWait(new GuiGameEvent(method, args)); + } catch (final TimeoutException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public boolean useMana(final byte color) { + return sendAndWait(methodName(), color); + } + + @Override + public boolean tryUndoLastAction() { + return sendAndWait(methodName()); + } + + @Override + public void selectPlayer(final PlayerView playerView, final ITriggerEvent triggerEvent) { + send(methodName(), playerView, triggerEvent); + } + + @Override + public boolean selectCard(final CardView cardView, final List otherCardViewsToSelect, final ITriggerEvent triggerEvent) { + return sendAndWait(methodName(), cardView, otherCardViewsToSelect, triggerEvent); + } + + @Override + public void selectButtonOk() { + send(methodName()); + } + + @Override + public void selectButtonCancel() { + send(methodName()); + } + + @Override + public void selectAbility(final SpellAbilityView sa) { + send(methodName(), sa); + } + + @Override + public boolean passPriorityUntilEndOfTurn() { + return sendAndWait(methodName()); + } + + @Override + public boolean passPriority() { + return sendAndWait(methodName()); + } + + @Override + public void nextGameDecision(final NextGameDecision decision) { + send(methodName(), decision); + } + + @Override + public boolean mayLookAtAllCards() { + // Don't do this over network + return false; + } + + public String getActivateDescription(final CardView card) { + return sendAndWait(methodName(), card); + } + + @Override + public void concede() { + send(methodName()); + } + + @Override + public IDevModeCheats cheat() { + return IDevModeCheats.NO_CHEAT; + } + + @Override + public boolean canPlayUnlimitedLands() { + return false; + } + + @Override + public void alphaStrike() { + send(methodName()); + } + +} diff --git a/forge-gui/src/main/java/forge/net/NetGuiGame.java b/forge-gui/src/main/java/forge/net/NetGuiGame.java new file mode 100644 index 00000000000..b9c06fbfc7f --- /dev/null +++ b/forge-gui/src/main/java/forge/net/NetGuiGame.java @@ -0,0 +1,380 @@ +package forge.net; + +import java.util.Collection; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.apache.commons.lang3.tuple.Pair; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; + +import forge.LobbyPlayer; +import forge.UiCommand; +import forge.assets.FSkinProp; +import forge.deck.CardPool; +import forge.game.GameEntityView; +import forge.game.GameView; +import forge.game.card.CardView; +import forge.game.phase.PhaseType; +import forge.game.player.DelayedReveal; +import forge.game.player.IHasIcon; +import forge.game.player.PlayerView; +import forge.game.spellability.SpellAbilityView; +import forge.game.zone.ZoneType; +import forge.interfaces.IButton; +import forge.item.PaperCard; +import forge.match.AbstractGuiGame; +import forge.match.MatchButtonType; +import forge.net.game.GuiGameEvent; +import forge.net.game.server.IToClient; +import forge.trackable.TrackableCollection; +import forge.util.ITriggerEvent; + +public class NetGuiGame extends AbstractGuiGame { + + private final IToClient client; + private final Map> btns = new EnumMap>(MatchButtonType.class); + public NetGuiGame(final IToClient client) { + this.client = client; + for (final MatchButtonType type : MatchButtonType.values()) { + btns.put(type, Maps.newHashMap()); + } + } + + private String methodName() { + boolean passedFirst = false; + for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { + if (ste.getClassName() == getClass().getName()) { + if (passedFirst) { + return ste.getMethodName(); + } + passedFirst = true; + } + } + return null; + } + + private void send(final String method, final Object... args) { + client.send(new GuiGameEvent(method, args)); + } + @SuppressWarnings("unchecked") + private T sendAndWait(final String method, final Object... args) { + try { + return (T) client.sendAndWait(new GuiGameEvent(method, args)); + } catch (final TimeoutException e) { + e.printStackTrace(); + } + return null; + } + + private void updateGameView() { + send("setGameView", getGameView()); + } + + @Override + public void setGameView(final GameView gameView) { + super.setGameView(gameView); + updateGameView(); + } + + @Override + public void openView(final TrackableCollection myPlayers) { + for (final MatchButtonType type : MatchButtonType.values()) { + btns.get(type).clear(); + for (final PlayerView player : myPlayers) { + btns.get(type).put(player, new NetButton(player, type)); + } + } + send(methodName(), myPlayers); + updateGameView(); + } + + @Override + public void afterGameEnd() { + send(methodName()); + } + + @Override + public void showCombat() { + send(methodName()); + } + + @Override + public void showPromptMessage(final PlayerView playerView, final String message) { + updateGameView(); + send(methodName(), playerView, message); + } + + @Override + public boolean stopAtPhase(final PlayerView playerTurn, final PhaseType phase) { + return sendAndWait(methodName(), playerTurn, phase); + } + + @Override + public IButton getBtnOK(final PlayerView playerView) { + return btns.get(MatchButtonType.OK).get(playerView); + } + + @Override + public IButton getBtnCancel(final PlayerView playerView) { + return btns.get(MatchButtonType.CANCEL).get(playerView); + } + + @Override + public void focusButton(final MatchButtonType button) { + send(methodName(), button); + } + + @Override + public void flashIncorrectAction() { + send(methodName()); + } + + @Override + public void updatePhase() { + updateGameView(); + send(methodName()); + } + + @Override + public void updateTurn(final PlayerView player) { + updateGameView(); + send(methodName(), player); + } + + @Override + public void updatePlayerControl() { + updateGameView(); + send(methodName()); + } + + @Override + public void enableOverlay() { + send(methodName()); + } + + @Override + public void disableOverlay() { + send(methodName()); + } + + @Override + public void finishGame() { + send(methodName()); + } + + @Override + public Object showManaPool(final PlayerView player) { + send(methodName(), player); + return null; + } + + @Override + public void hideManaPool(final PlayerView player, final Object zoneToRestore) { + send(methodName(), player, zoneToRestore); + } + + @Override + public void updateStack() { + updateGameView(); + send(methodName()); + } + + @Override + public void updateZones(final List> zonesToUpdate) { + updateGameView(); + send(methodName(), zonesToUpdate); + } + + @Override + public void updateSingleCard(final CardView card) { + updateGameView(); + send(methodName(), card); + } + + @Override + public void updateManaPool(final Iterable manaPoolUpdate) { + updateGameView(); + send(methodName(), manaPoolUpdate); + } + + @Override + public void updateLives(final Iterable livesUpdate) { + updateGameView(); + send(methodName(), livesUpdate); + } + + @Override + public void setPanelSelection(final CardView hostCard) { + updateGameView(); + send(methodName(), hostCard); + } + + @Override + public SpellAbilityView getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { + return sendAndWait(methodName(), abilities, triggerEvent); + } + + @Override + public Map assignDamage(final CardView attacker, final List blockers, final int damage, final GameEntityView defender, final boolean overrideOrder) { + return sendAndWait(methodName(), attacker, blockers, damage, defender, overrideOrder); + } + + @Override + public void message(final String message, final String title) { + send(methodName(), message, title); + } + + @Override + public void showErrorDialog(final String message, final String title) { + send(methodName(), message, title); + } + + @Override + public boolean showConfirmDialog(final String message, final String title, final String yesButtonText, final String noButtonText, final boolean defaultYes) { + return sendAndWait(methodName(), message, title, yesButtonText, noButtonText, defaultYes); + } + + @Override + public int showOptionDialog(final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) { + return sendAndWait(methodName(), message, title, icon, options, defaultOption); + } + + @Override + public int showCardOptionDialog(final CardView card, final String message, final String title, final FSkinProp icon, final String[] options, final int defaultOption) { + return sendAndWait(methodName(), card, message, title, icon, options, defaultOption); + } + + @Override + public String showInputDialog(final String message, final String title, final FSkinProp icon, final String initialInput, final String[] inputOptions) { + return sendAndWait(methodName(), message, title, icon, initialInput, inputOptions); + } + + @Override + public boolean confirm(final CardView c, final String question, final boolean defaultIsYes, final String[] options) { + return sendAndWait(methodName(), c, question, defaultIsYes, options); + } + + @Override + public List getChoices(final String message, final int min, final int max, final Collection choices, final T selected, final Function display) { + return sendAndWait(methodName(), message, min, max, choices, selected, display); + } + + @Override + public List order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax, final List sourceChoices, final List destChoices, final CardView referenceCard, final boolean sideboardingMode) { + return sendAndWait(methodName(), title, top, remainingObjectsMin, remainingObjectsMax, sourceChoices, destChoices, referenceCard, sideboardingMode); + } + + @Override + public List sideboard(final CardPool sideboard, final CardPool main) { + return sendAndWait(methodName(), sideboard, main); + } + + @Override + public GameEntityView chooseSingleEntityForEffect(final String title, final Collection optionList, final DelayedReveal delayedReveal, final boolean isOptional) { + return sendAndWait(methodName(), title, optionList, delayedReveal, isOptional); + } + + @Override + public void setCard(final CardView card) { + updateGameView(); + send(methodName(), card); + } + + @Override + public void setPlayerAvatar(LobbyPlayer player, IHasIcon ihi) { + // TODO Auto-generated method stub + } + + @Override + public boolean openZones(final Collection zones, final Map players) { + updateGameView(); + return sendAndWait(methodName(), zones, players); + } + + @Override + public void restoreOldZones(final Map playersToRestoreZonesFor) { + send(methodName(), playersToRestoreZonesFor); + } + + @Override + public boolean isUiSetToSkipPhase(final PlayerView playerTurn, final PhaseType phase) { + return sendAndWait(methodName(), playerTurn, phase); + } + + @Override + protected void updateCurrentPlayer(final PlayerView player) { + // TODO Auto-generated method stub + } + + private final class NetButton implements IButton { + + private String methodName() { + boolean passedFirst = false; + for (final StackTraceElement ste : Thread.currentThread().getStackTrace()) { + if (ste.getClassName() == getClass().getName()) { + if (passedFirst) { + return ste.getMethodName(); + } + passedFirst = true; + } + } + return null; + } + + private final PlayerView playerView; + private final MatchButtonType type; + private NetButton(final PlayerView playerView, final MatchButtonType type) { + this.playerView = playerView; + this.type = type; + } + + @Override + public void setEnabled(final boolean b0) { + send("btn_" + methodName(), playerView, type, b0); + } + + @Override + public void setVisible(final boolean b0) { + send("btn_" + methodName(), playerView, type, b0); + } + + @Override + public void setText(final String text0) { + send("btn_" + methodName(), playerView, type, text0); + } + + @Override + public boolean isSelected() { + return sendAndWait("btn_" + methodName(), playerView, type); + } + + @Override + public void setSelected(final boolean b0) { + send("btn_" + methodName(), playerView, type, b0); + } + + @Override + public boolean requestFocusInWindow() { + return sendAndWait("btn_" + methodName(), playerView, type); + } + + @Override + public void setCommand(final UiCommand command0) { + send("btn_" + methodName(), playerView, type, command0); + } + + @Override + public void setTextColor(final FSkinProp color) { + send("btn_" + methodName(), playerView, type, color); + } + + @Override + public void setTextColor(final int r, final int g, final int b) { + send("btn_" + methodName(), playerView, type, r, g, b); + } + } +} diff --git a/forge-gui/src/main/java/forge/net/ServerGameLobby.java b/forge-gui/src/main/java/forge/net/ServerGameLobby.java new file mode 100644 index 00000000000..21f55ab8352 --- /dev/null +++ b/forge-gui/src/main/java/forge/net/ServerGameLobby.java @@ -0,0 +1,65 @@ +package forge.net; + +import java.util.Collections; + +import org.apache.commons.lang3.StringUtils; + +import forge.AIOption; +import forge.interfaces.IGuiGame; +import forge.match.GameLobby; +import forge.match.LobbySlot; +import forge.net.game.LobbySlotType; + +public final class ServerGameLobby extends GameLobby { + + public ServerGameLobby() { + super(true); + addSlot(new LobbySlot(LobbySlotType.LOCAL, localName(), localAvatarIndices()[0], 0, true, Collections.emptySet())); + addSlot(new LobbySlot(LobbySlotType.OPEN, null, -1, 1, false, Collections.emptySet())); + } + + public int connectPlayer(final String name, final int avatarIndex) { + final int nSlots = getNumberOfSlots(); + for (int index = 0; index < nSlots; index++) { + final LobbySlot slot = getSlot(index); + if (slot.getType() == LobbySlotType.OPEN) { + connectPlayer(name, avatarIndex, slot); + return index; + } + } + return -1; + } + private void connectPlayer(final String name, final int avatarIndex, final LobbySlot slot) { + slot.setType(LobbySlotType.REMOTE); + slot.setName(name); + slot.setAvatarIndex(avatarIndex); + updateView(); + } + public void disconnectPlayer(final int index) { + final LobbySlot slot = getSlot(index); + slot.setType(LobbySlotType.OPEN); + slot.setName(StringUtils.EMPTY); + updateView(); + } + + @Override public boolean hasControl() { + return true; + } + + @Override public boolean mayEdit(final int index) { + final LobbySlotType type = getSlot(index).getType(); + return type != LobbySlotType.REMOTE && type != LobbySlotType.OPEN; + } + + @Override public boolean mayControl(final int index) { + return getSlot(index).getType() != LobbySlotType.REMOTE; + } + + @Override public boolean mayRemove(final int index) { + return true; + } + + @Override public IGuiGame getGui(final int index) { + return FServerManager.getInstance().getGui(index); + } +} diff --git a/forge-gui/src/main/java/forge/player/GamePlayerUtil.java b/forge-gui/src/main/java/forge/player/GamePlayerUtil.java index 9f5ee1780f6..3f6935c1d7c 100644 --- a/forge-gui/src/main/java/forge/player/GamePlayerUtil.java +++ b/forge-gui/src/main/java/forge/player/GamePlayerUtil.java @@ -1,7 +1,10 @@ package forge.player; -import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; + +import forge.AIOption; import forge.GuiBase; import forge.LobbyPlayer; import forge.ai.AiProfileUtil; @@ -13,8 +16,6 @@ import forge.util.GuiDisplayUtil; import forge.util.MyRandom; import forge.util.gui.SOptionPane; -import org.apache.commons.lang3.StringUtils; - public final class GamePlayerUtil { private GamePlayerUtil() { }; @@ -22,8 +23,8 @@ public final class GamePlayerUtil { public static final LobbyPlayer getGuiPlayer() { return guiPlayer; } - public static final LobbyPlayer getGuiPlayer(String name, int index) { - if (index == 0) { + public static final LobbyPlayer getGuiPlayer(final String name, final boolean writePref) { + if (writePref) { if (!name.equals(guiPlayer.getName())) { guiPlayer.setName(name); FModel.getPreferences().setPref(FPref.PLAYER_NAME, name); @@ -49,7 +50,7 @@ public final class GamePlayerUtil { public final static LobbyPlayer createAiPlayer(String name, int avatarIndex) { return createAiPlayer(name, avatarIndex, null); } - public final static LobbyPlayer createAiPlayer(String name, int avatarIndex, Map options) { + public final static LobbyPlayer createAiPlayer(String name, int avatarIndex, Set options) { LobbyPlayerAi player = new LobbyPlayerAi(name, options); // TODO: implement specific AI profiles for quest mode. diff --git a/forge-gui/src/main/java/forge/player/NetGameController.java b/forge-gui/src/main/java/forge/player/NetGameController.java deleted file mode 100644 index 608239999b2..00000000000 --- a/forge-gui/src/main/java/forge/player/NetGameController.java +++ /dev/null @@ -1,128 +0,0 @@ -package forge.player; - -import java.util.Collections; -import java.util.List; - -import forge.game.card.CardView; -import forge.game.player.PlayerView; -import forge.game.spellability.SpellAbility; -import forge.interfaces.IDevModeCheats; -import forge.interfaces.IGameController; -import forge.match.NextGameDecision; -import forge.net.game.client.IToServer; -import forge.trackable.TrackableObject; -import forge.util.ITriggerEvent; - -public class NetGameController implements IGameController { - - private final IToServer server; - public NetGameController(final IToServer server) { - this.server = server; - } - - private void send(final String method) { - send(method, Collections.emptySet()); - } - private void send(final String method, final TrackableObject object) { - send(method, Collections.singleton(object)); - } - private void send(final String method, final Iterable objects) { - //server.send(new (method, objects)); - } - - @Override - public boolean mayLookAtAllCards() { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean canPlayUnlimitedLands() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void concede() { - send("concede"); - } - - @Override - public void alphaStrike() { - send("alphaStrike"); - } - - @Override - public boolean useMana(byte color) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void selectButtonOk() { - // TODO Auto-generated method stub - - } - - @Override - public void selectButtonCancel() { - // TODO Auto-generated method stub - - } - - @Override - public boolean passPriority() { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean passPriorityUntilEndOfTurn() { - // TODO Auto-generated method stub - return false; - } - - @Override - public void selectPlayer(PlayerView playerView, ITriggerEvent triggerEvent) { - // TODO Auto-generated method stub - - } - - @Override - public boolean selectCard(CardView cardView, - List otherCardViewsToSelect, ITriggerEvent triggerEvent) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void selectAbility(SpellAbility sa) { - // TODO Auto-generated method stub - - } - - @Override - public boolean tryUndoLastAction() { - // TODO Auto-generated method stub - return false; - } - - @Override - public IDevModeCheats cheat() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void nextGameDecision(NextGameDecision decision) { - // TODO Auto-generated method stub - - } - - @Override - public String getActivateDescription(CardView card) { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java index 96d5139540e..7f3d4048d3b 100644 --- a/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java +++ b/forge-gui/src/main/java/forge/player/PlayerControllerHuman.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -74,6 +75,7 @@ import forge.game.spellability.AbilityManaPart; import forge.game.spellability.AbilitySub; import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbilityStackInstance; +import forge.game.spellability.SpellAbilityView; import forge.game.spellability.TargetChoices; import forge.game.trigger.Trigger; import forge.game.trigger.WrappedAbility; @@ -217,8 +219,8 @@ public class PlayerControllerHuman * Uses GUI to learn which spell the player (human in our case) would like to play */ public SpellAbility getAbilityToPlay(final List abilities, final ITriggerEvent triggerEvent) { - return getGui().getAbilityToPlay(abilities, triggerEvent); - //return HostedMatch.getController().getAbilityToPlay(abilities, triggerEvent); + final SpellAbilityView resultView = getGui().getAbilityToPlay(SpellAbilityView.getCollection(abilities), triggerEvent); + return getGame().getSpellAbility(resultView); } @Override @@ -389,13 +391,13 @@ public class PlayerControllerHuman // Human is supposed to read the message and understand from it what to choose if (optionList.isEmpty()) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } return null; } if (!isOptional && optionList.size() == 1) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } return Iterables.getFirst(optionList, null); } @@ -416,7 +418,7 @@ public class PlayerControllerHuman if (canUseSelectCardsInput) { if (delayedReveal != null) { - delayedReveal.reveal(this); + reveal(delayedReveal.getCards(), delayedReveal.getZone(), delayedReveal.getOwner(), delayedReveal.getMessagePrefix()); } InputSelectEntitiesFromList input = new InputSelectEntitiesFromList(this, isOptional ? 0 : 1, 1, optionList); input.setCancelAllowed(isOptional); @@ -425,8 +427,10 @@ public class PlayerControllerHuman return Iterables.getFirst(input.getSelected(), null); } - final GameEntityView result = getGui().chooseSingleEntityForEffect(title, optionList, delayedReveal, isOptional, this); - endTempShowCards(); //assume tempShow called by GuiBase.getInterface().chooseSingleEntityForEffect + tempShow(optionList); + final GameEntityView result = getGui().chooseSingleEntityForEffect(title, GameEntityView.getEntityCollection(optionList), delayedReveal, isOptional); + endTempShowCards(); + if (result instanceof CardView) { return (T) game.getCard((CardView)result); } @@ -438,16 +442,16 @@ public class PlayerControllerHuman @Override public int chooseNumber(SpellAbility sa, String title, int min, int max) { - if (min >= max) { - return min; - } + if (min >= max) { + return min; + } final Integer[] choices = new Integer[max + 1 - min]; for (int i = 0; i <= max - min; i++) { choices[i] = Integer.valueOf(i + min); } return getGui().one(title, choices).intValue(); } - + @Override public int chooseNumber(SpellAbility sa, String title, List choices, Player relatedPlayer) { return getGui().one(title, choices).intValue(); @@ -563,19 +567,22 @@ public class PlayerControllerHuman @Override public void reveal(CardCollectionView cards, ZoneType zone, Player owner, String message) { + reveal(CardView.getCollection(cards), zone, PlayerView.get(owner), message); + } + + @Override + public void reveal(Collection cards, ZoneType zone, PlayerView owner, String message) { if (StringUtils.isBlank(message)) { message = "Looking at cards in {player's} " + zone.name().toLowerCase(); - } - else { + } else { message += "{player's} " + zone.name().toLowerCase(); } String fm = MessageUtil.formatMessage(message, player, owner); if (!cards.isEmpty()) { - tempShowCards(cards); - getGui().reveal(fm, CardView.getCollection(cards)); + tempShowCards(game.getCardList(cards)); + getGui().reveal(fm, cards); endTempShowCards(); - } - else { + } else { getGui().message(MessageUtil.formatMessage("There are no cards in {player's} " + zone.name().toLowerCase(), player, owner), fm); } @@ -993,17 +1000,18 @@ public class PlayerControllerHuman return Iterables.getFirst(allTargets, null); } - final Function, String> fnToString = new Function, String>() { - @Override - public String apply(Pair targ) { - return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription(); - } - }; - - List> chosen = getGui().getChoices(saSpellskite.getHostCard().getName(), 1, 1, allTargets, null, fnToString); + final List> chosen = getGui().getChoices(saSpellskite.getHostCard().getName(), 1, 1, allTargets, null, new FnTargetToString()); return Iterables.getFirst(chosen, null); } + private final static class FnTargetToString implements Function, String>, Serializable { + private static final long serialVersionUID = -4779137632302777802L; + + @Override public String apply(final Pair targ) { + return targ.getRight().toString() + " - " + targ.getLeft().getStackDescription(); + } + } + @Override public void notifyOfValue(SpellAbility sa, GameObject realtedTarget, String value) { String message = MessageUtil.formatNotificationMessage(sa, player, realtedTarget, value); @@ -1352,8 +1360,8 @@ public class PlayerControllerHuman return inputProxy.selectCard(cardView, otherCardViewsToSelect, triggerEvent); } - public void selectAbility(final SpellAbility sa) { - inputProxy.selectAbility(sa); + public void selectAbility(final SpellAbilityView sa) { + inputProxy.selectAbility(getGame().getSpellAbility(sa)); } public void alphaStrike() { diff --git a/forge-net/src/main/java/forge/net/ReplyPool.java b/forge-net/src/main/java/forge/net/ReplyPool.java new file mode 100644 index 00000000000..0c24fd467a9 --- /dev/null +++ b/forge-net/src/main/java/forge/net/ReplyPool.java @@ -0,0 +1,41 @@ +package forge.net; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import com.google.common.collect.Maps; + +public class ReplyPool { + + private final Map> pool = Maps.newHashMap(); + + public ReplyPool() { + } + + public void initialize(final int index) { + synchronized (pool) { + pool.put(Integer.valueOf(index), new CompletableFuture()); + } + } + + public void complete(final int index, final Object value) { + synchronized (pool) { + pool.get(Integer.valueOf(index)).complete(value); + } + } + + public Object get(final int index) throws TimeoutException { + final CompletableFuture future; + synchronized (pool) { + future = pool.get(Integer.valueOf(index)); + } + try { + return future.get(1, TimeUnit.MINUTES); + } catch (final InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/forge-net/src/main/java/forge/net/game/GuiGameEvent.java b/forge-net/src/main/java/forge/net/game/GuiGameEvent.java index 5d4c81b7a59..0cbe0a5ffce 100644 --- a/forge-net/src/main/java/forge/net/game/GuiGameEvent.java +++ b/forge-net/src/main/java/forge/net/game/GuiGameEvent.java @@ -1,35 +1,40 @@ package forge.net.game; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; - import forge.net.game.server.RemoteClient; -import forge.trackable.TrackableObject; -public final class GuiGameEvent implements NetEvent { +public final class GuiGameEvent implements IdentifiableNetEvent { private static final long serialVersionUID = 6223690008522514574L; + private static int staticId = 0; + private final int id; private final String method; - private final Iterable objects; + private final Object[] objects; - public GuiGameEvent(final String method, final Iterable objects) { + public GuiGameEvent(final String method, final Object ... objects) { + this.id = staticId++; this.method = method; - this.objects = objects == null ? ImmutableSet.of() : objects; + this.objects = objects == null ? new Object[0] : objects; + } + + @Override + public String toString() { + return String.format("GuiGameEvent %d: %s (%d args)", id, method, objects.length); } @Override public void updateForClient(final RemoteClient client) { } + @Override + public int getId() { + return id; + } + public String getMethod() { return method; } - public TrackableObject getObject() { - return Iterables.getFirst(objects, null); - } - - public Iterable getObjects() { + public Object[] getObjects() { return objects; } } diff --git a/forge-net/src/main/java/forge/net/game/IRemote.java b/forge-net/src/main/java/forge/net/game/IRemote.java index 819e7e6f20b..0d50de83ec4 100644 --- a/forge-net/src/main/java/forge/net/game/IRemote.java +++ b/forge-net/src/main/java/forge/net/game/IRemote.java @@ -1,5 +1,8 @@ package forge.net.game; +import java.util.concurrent.TimeoutException; + public interface IRemote { void send(NetEvent event); + Object sendAndWait(IdentifiableNetEvent event) throws TimeoutException; } diff --git a/forge-net/src/main/java/forge/net/game/IdentifiableNetEvent.java b/forge-net/src/main/java/forge/net/game/IdentifiableNetEvent.java new file mode 100644 index 00000000000..efbdfcf3eff --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/IdentifiableNetEvent.java @@ -0,0 +1,6 @@ +package forge.net.game; + +import forge.game.IIdentifiable; + +public interface IdentifiableNetEvent extends NetEvent, IIdentifiable { +} diff --git a/forge-net/src/main/java/forge/net/game/LobbyState.java b/forge-net/src/main/java/forge/net/game/LobbyState.java deleted file mode 100644 index eff30a2de55..00000000000 --- a/forge-net/src/main/java/forge/net/game/LobbyState.java +++ /dev/null @@ -1,51 +0,0 @@ -package forge.net.game; - -import java.io.Serializable; -import java.util.Collections; -import java.util.List; - -import com.google.common.collect.Lists; - -public class LobbyState implements Serializable { - private static final long serialVersionUID = 3899410700896996173L; - - private final List players = Lists.newArrayList(); - private int localPlayer = -1; - public int getLocalPlayer() { - return localPlayer; - } - public void setLocalPlayer(final int localPlayer) { - this.localPlayer = localPlayer; - } - - public void addPlayer(final LobbyPlayerData data) { - players.add(data); - } - public List getPlayers() { - return Collections.unmodifiableList(players); - } - - public final static class LobbyPlayerData implements Serializable { - private static final long serialVersionUID = 8642923786206592216L; - - private final String name; - private final int avatarIndex; - private final LobbySlotType type; - public LobbyPlayerData(final String name, final int avatarIndex, final LobbySlotType type) { - this.name = name; - this.avatarIndex = avatarIndex; - this.type = type; - } - - public String getName() { - return name; - } - public int getAvatarIndex() { - return avatarIndex; - } - public LobbySlotType getType() { - return type; - } - - } -} diff --git a/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java b/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java deleted file mode 100644 index 3196498c903..00000000000 --- a/forge-net/src/main/java/forge/net/game/LobbyUpdateEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package forge.net.game; - -import forge.net.game.server.RemoteClient; - -public class LobbyUpdateEvent implements NetEvent { - private static final long serialVersionUID = -3176971304173703949L; - - private final LobbyState state; - public LobbyUpdateEvent(final LobbyState state) { - this.state = state; - } - - @Override - public void updateForClient(final RemoteClient client) { - state.setLocalPlayer(client.getIndex()); - } - - public LobbyState getState() { - return state; - } -} diff --git a/forge-net/src/main/java/forge/net/game/LoginEvent.java b/forge-net/src/main/java/forge/net/game/LoginEvent.java index 4b7d2be35b2..0d7db4853d8 100644 --- a/forge-net/src/main/java/forge/net/game/LoginEvent.java +++ b/forge-net/src/main/java/forge/net/game/LoginEvent.java @@ -6,8 +6,10 @@ public class LoginEvent implements NetEvent { private static final long serialVersionUID = -8865183377417377938L; private final String username; - public LoginEvent(final String username) { + private final int avatarIndex; + public LoginEvent(final String username, final int avatarIndex) { this.username = username; + this.avatarIndex = avatarIndex; } @Override @@ -17,4 +19,8 @@ public class LoginEvent implements NetEvent { public String getUsername() { return username; } + + public int getAvatarIndex() { + return avatarIndex; + } } diff --git a/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java b/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java deleted file mode 100644 index e7c42dd100b..00000000000 --- a/forge-net/src/main/java/forge/net/game/RegisterDeckEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package forge.net.game; - -import forge.deck.Deck; -import forge.net.game.server.RemoteClient; - -public class RegisterDeckEvent implements NetEvent { - private static final long serialVersionUID = -6553476654530937343L; - - private final Deck deck; - public RegisterDeckEvent(final Deck deck) { - this.deck = deck; - } - public void updateForClient(final RemoteClient client) { - } - - public final Deck getDeck() { - return deck; - } -} diff --git a/forge-net/src/main/java/forge/net/game/ReplyEvent.java b/forge-net/src/main/java/forge/net/game/ReplyEvent.java new file mode 100644 index 00000000000..6c8e3b574f2 --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/ReplyEvent.java @@ -0,0 +1,31 @@ +package forge.net.game; + +import java.io.Serializable; + +import forge.net.game.server.RemoteClient; + +public final class ReplyEvent implements NetEvent { + private static final long serialVersionUID = -2814651319617795386L; + + private final int index; + private final Serializable reply; + public ReplyEvent(final int index, final Serializable reply) { + this.index = index; + this.reply = reply; + } + + public int getIndex() { + return index; + } + public Object getReply() { + return reply; + } + + @Override public void updateForClient(final RemoteClient client) { + } + + @Override + public String toString() { + return String.format("Reply (%d): %s", index, reply); + } +} diff --git a/forge-net/src/main/java/forge/net/game/UpdateLobbyPlayerEvent.java b/forge-net/src/main/java/forge/net/game/UpdateLobbyPlayerEvent.java new file mode 100644 index 00000000000..3a93b88820e --- /dev/null +++ b/forge-net/src/main/java/forge/net/game/UpdateLobbyPlayerEvent.java @@ -0,0 +1,77 @@ +package forge.net.game; + +import java.util.Collections; +import java.util.Set; + +import forge.AIOption; +import forge.deck.CardPool; +import forge.deck.Deck; +import forge.deck.DeckSection; +import forge.net.game.server.RemoteClient; + +public final class UpdateLobbyPlayerEvent implements NetEvent { + private static final long serialVersionUID = -5073305607515425968L; + + private final LobbySlotType type; + private final String name; + private final int avatarIndex; + private final int team; + private final Boolean isArchenemy; + private final Deck deck; + private final DeckSection section; + private final CardPool cards; + private final Set aiOptions; + + public static UpdateLobbyPlayerEvent create(final LobbySlotType type, final String name, final int avatarIndex, final int team, final boolean isArchenemy, final Set aiOptions) { + return new UpdateLobbyPlayerEvent(type, name, avatarIndex, team, isArchenemy, null, null, null, aiOptions); + } + public static UpdateLobbyPlayerEvent deckUpdate(final Deck deck) { + return new UpdateLobbyPlayerEvent(null, null, -1, -1, null, deck, null, null, null); + } + public static UpdateLobbyPlayerEvent deckUpdate(final DeckSection section, final CardPool cards) { + return new UpdateLobbyPlayerEvent(null, null, -1, -1, null, null, section, cards, null); + } + + private UpdateLobbyPlayerEvent(final LobbySlotType type, final String name, final int avatarIndex, final int team, final Boolean isArchenemy, final Deck deck, final DeckSection section, final CardPool cards, final Set aiOptions) { + this.type = type; + this.name = name; + this.avatarIndex = avatarIndex; + this.team = team; + this.isArchenemy = isArchenemy; + this.deck = deck; + this.section = section; + this.cards = cards; + this.aiOptions = aiOptions; + } + + public void updateForClient(final RemoteClient client) { + } + + public LobbySlotType getType() { + return type; + } + public String getName() { + return name; + } + public int getAvatarIndex() { + return avatarIndex; + } + public int getTeam() { + return team; + } + public Boolean getArchenemy() { + return isArchenemy; + } + public Deck getDeck() { + return deck; + } + public DeckSection getSection() { + return section; + } + public CardPool getCards() { + return cards; + } + public Set getAiOptions() { + return aiOptions == null ? null : Collections.unmodifiableSet(aiOptions); + } +} diff --git a/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java b/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java deleted file mode 100644 index 090fce2cfdb..00000000000 --- a/forge-net/src/main/java/forge/net/game/client/ILobbyListener.java +++ /dev/null @@ -1,8 +0,0 @@ -package forge.net.game.client; - -import forge.net.game.LobbyState; - -public interface ILobbyListener { - void message(String source, String message); - void update(LobbyState state); -} diff --git a/forge-net/src/main/java/forge/net/game/server/RemoteClient.java b/forge-net/src/main/java/forge/net/game/server/RemoteClient.java index 2f18a5df21a..4d67c782c6a 100644 --- a/forge-net/src/main/java/forge/net/game/server/RemoteClient.java +++ b/forge-net/src/main/java/forge/net/game/server/RemoteClient.java @@ -1,22 +1,37 @@ package forge.net.game.server; +import forge.net.ReplyPool; +import forge.net.game.IdentifiableNetEvent; import forge.net.game.NetEvent; import io.netty.channel.Channel; +import java.util.concurrent.TimeoutException; + public final class RemoteClient implements IToClient { private final Channel channel; private String username; private int index; + private ReplyPool replies = new ReplyPool(); public RemoteClient(final Channel channel) { this.channel = channel; } @Override public void send(final NetEvent event) { + System.out.println("Sending event " + event + " to " + channel); channel.writeAndFlush(event); } + @Override + public Object sendAndWait(final IdentifiableNetEvent event) throws TimeoutException { + replies.initialize(event.getId()); + + send(event); + + return replies.get(event.getId()); + } + public String getUsername() { return username; } @@ -30,4 +45,8 @@ public final class RemoteClient implements IToClient { public void setIndex(final int index) { this.index = index; } + + public void setReply(final int id, final Object reply) { + replies.complete(id, reply); + } }