Huge update to the GUI codebase.

- Make match screen in gui-desktop non-singleton, allowing multiple games to be played simultaneously.
- Separate global gui commands (IGuiBase) from game-specific gui commands (IGuiGame), allowing games to send their commands to the correct gui.
This commit is contained in:
elcnesh
2015-02-17 08:47:55 +00:00
parent 4ac5d67a02
commit 4a38b0d817
244 changed files with 5532 additions and 3554 deletions

View File

@@ -44,7 +44,7 @@ import forge.game.card.CardLists;
import forge.game.card.CardPredicates;
import forge.game.card.CardView;
import forge.game.combat.Combat;
import forge.game.event.GameEvent;
import forge.game.event.Event;
import forge.game.event.GameEventGameOutcome;
import forge.game.phase.Phase;
import forge.game.phase.PhaseHandler;
@@ -101,7 +101,6 @@ public class Game {
private final Match match;
private GameStage age = GameStage.BeforeMulligan;
private GameOutcome outcome;
private boolean disableAutoYields;
private final GameView view;
private final Tracker tracker = new Tracker();
@@ -537,14 +536,10 @@ public class Game {
* Fire only the events after they became real for gamestate and won't get replaced.<br>
* The events are sent to UI, log and sound system. Network listeners are under development.
*/
public void fireEvent(GameEvent event) {
// if (LOG_EVENTS) {
// System.out.println("GE: " + event.toString() + " \t\t " + FThreads.debugGetStackTraceItem(4, true));
// }
public void fireEvent(final Event event) {
events.post(event);
}
public void subscribeToEvents(Object subscriber) {
public void subscribeToEvents(final Object subscriber) {
events.register(subscriber);
}
@@ -605,13 +600,6 @@ public class Game {
return ++hiddenCardIdCounter;
}
public boolean getDisableAutoYields() {
return disableAutoYields;
}
public void setDisableAutoYields(boolean b0) {
disableAutoYields = b0;
}
public Multimap<Player, Card> chooseCardsForAnte(final boolean matchRarity) {
Multimap<Player, Card> anteed = ArrayListMultimap.create();

View File

@@ -25,4 +25,5 @@ public enum GameLogEntryType {
public String getCaption() {
return caption;
}
}

View File

@@ -8,7 +8,6 @@ import forge.deck.DeckFormat;
import forge.deck.DeckSection;
import forge.game.player.RegisteredPlayer;
public enum GameType {
Sealed (DeckFormat.Limited, true, true, true, "Sealed", ""),
Draft (DeckFormat.Limited, true, true, true, "Draft", ""),
@@ -21,9 +20,9 @@ public enum GameType {
Vanguard (DeckFormat.Vanguard, true, true, true, "Vanguard", "Each player has a special \"Avatar\" card that affects the game."),
Commander (DeckFormat.Commander, false, false, false, "Commander", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors."),
TinyLeaders (DeckFormat.TinyLeaders, false, false, false, "Tiny Leaders", "Each player has a legendary \"General\" card which can be cast at any time and determines deck colors. Each card must have CMC less than 4."),
Planechase (DeckFormat.Planechase, false, false, true, "Planechase", "Plane cards apply global effects. Plane card changed when a player rolls \"Chaos\" on the planar die."),
Archenemy (DeckFormat.Archenemy, false, false, true, "Archenemy", "One player is the Archenemy and can play scheme cards."),
ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "Archenemy Rumble", "All players are Archenemies and can play scheme cards."),
Planechase (DeckFormat.Planechase, false, false, true, "Planechase", "Plane cards apply global effects. The Plane card changes when a player rolls \"Planeswalk\" on the planar die."),
Archenemy (DeckFormat.Archenemy, false, false, true, "Archenemy", "One player is the Archenemy and fights the other players by playing Scheme cards."),
ArchenemyRumble (DeckFormat.Archenemy, false, false, true, "Archenemy Rumble", "All players are Archenemies and can play Scheme cards."),
MomirBasic (DeckFormat.Constructed, false, false, false, "Momir Basic", "Each player has a deck containing 60 basic lands and the Momir Vig avatar.", new Function<RegisteredPlayer, Deck>() {
@Override
public Deck apply(RegisteredPlayer player) {

View File

@@ -22,11 +22,6 @@ import forge.trackable.TrackableProperty;
import forge.util.FCollectionView;
public class GameView extends TrackableObject {
private static GameView currentGame;
public static GameView getCurrentGame() {
return currentGame;
}
/*private final TrackableIndex<CardView> cards = new TrackableIndex<CardView>();
private final TrackableIndex<PlayerView> players = new TrackableIndex<PlayerView>();
@@ -38,8 +33,8 @@ public class GameView extends TrackableObject {
public GameView(final Game game0) {
super(-1, game0.getTracker()); //ID not needed
currentGame = this;
game = game0;
set(TrackableProperty.Title, game.getMatch().getTitle());
set(TrackableProperty.WinningTeam, -1);
GameRules rules = game.getRules();
@@ -58,6 +53,9 @@ public class GameView extends TrackableObject {
return players;
}
public String getTitle() {
return get(TrackableProperty.Title);
}
public boolean isCommander() {
return get(TrackableProperty.Commander);
}
@@ -117,9 +115,6 @@ public class GameView extends TrackableObject {
set(TrackableProperty.GameOver, game.isGameOver());
set(TrackableProperty.MatchOver, game.getMatch().isMatchOver());
set(TrackableProperty.WinningTeam, game.getOutcome() == null ? -1 : game.getOutcome().getWinningTeam());
if (game.isGameOver()) {
currentGame = null;
}
}
public GameLog getGameLog() {
@@ -199,9 +194,9 @@ public class GameView extends TrackableObject {
return game.getMatch().getGamesWonBy(questPlayer);
}
public Deck getDeck(LobbyPlayer guiPlayer) {
for (Player p : game.getRegisteredPlayers()) {
if (p.getLobbyPlayer().equals(guiPlayer)) {
public Deck getDeck(final String lobbyPlayerName) {
for (final Player p : game.getRegisteredPlayers()) {
if (p.getLobbyPlayer().getName().equals(lobbyPlayerName)) {
return p.getRegisteredPlayer().getDeck();
}
}

View File

@@ -1,6 +1,21 @@
package forge.game;
import com.google.common.collect.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import forge.LobbyPlayer;
import forge.deck.CardPool;
@@ -19,25 +34,35 @@ import forge.item.PaperCard;
import forge.util.FCollectionView;
import forge.util.MyRandom;
import java.util.*;
import java.util.Map.Entry;
public class Match {
private final List<RegisteredPlayer> players;
private final GameRules rules;
private final String title;
private final List<GameOutcome> gamesPlayed = new ArrayList<GameOutcome>();
private final List<GameOutcome> gamesPlayedRo;
public Match(GameRules rules0, List<RegisteredPlayer> players0) {
public Match(final GameRules rules0, final List<RegisteredPlayer> players0, final String title) {
gamesPlayedRo = Collections.unmodifiableList(gamesPlayed);
players = Collections.unmodifiableList(Lists.newArrayList(players0));
rules = rules0;
this.title = title;
}
public GameRules getRules() {
return rules;
}
String getTitle() {
final Multiset<RegisteredPlayer> wins = getGamesWon();
final StringBuilder titleAppend = new StringBuilder(title);
titleAppend.append(" (");
for (final RegisteredPlayer rp : players) {
titleAppend.append(wins.count(rp)).append('-');
}
titleAppend.deleteCharAt(titleAppend.length() - 1);
titleAppend.append(')');
return titleAppend.toString();
}
public final List<GameOutcome> getPlayedGames() {
return gamesPlayedRo;
@@ -124,6 +149,13 @@ public class Match {
}
return sum;
}
public Multiset<RegisteredPlayer> getGamesWon() {
final Multiset<RegisteredPlayer> won = HashMultiset.create(players.size());
for (final GameOutcome go : gamesPlayedRo) {
won.add(go.getWinningPlayer().getRegisteredPlayer());
}
return won;
}
public boolean isWonBy(LobbyPlayer questPlayer) {
return getGamesWonBy(questPlayer) >= rules.getGamesToWinMatch();

View File

@@ -103,7 +103,7 @@ public final class AbilityFactory {
AbilityRecordType type = AbilityRecordType.getRecordType(mapParams);
if (null == type) {
String source = hostCard.getName().isEmpty() ? abString : hostCard.getName();
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + source);
throw new RuntimeException("AbilityFactory : getAbility -- no API in " + source + ": " + abString);
}
return getAbility(mapParams, type, hostCard);
}

View File

@@ -17,6 +17,18 @@
*/
package forge.game.card;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
@@ -32,18 +44,15 @@ import forge.card.mana.ManaCostShard;
import forge.game.Game;
import forge.game.GameEntity;
import forge.game.GameLogEntryType;
import forge.game.GameView;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardCollectionView;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
import forge.game.cost.CostPayment;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
import forge.game.player.PlayerView;
import forge.game.replacement.ReplacementEffect;
import forge.game.replacement.ReplacementHandler;
import forge.game.replacement.ReplacementLayer;
@@ -65,11 +74,6 @@ import forge.util.Aggregates;
import forge.util.FCollectionView;
import forge.util.Lang;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.Map.Entry;
/**
* <p>
* CardFactoryUtil class.
@@ -2081,28 +2085,6 @@ public class CardFactoryUtil {
}
*/
public static final String getCommanderInfo(final PlayerView originPlayer) {
GameView game = GameView.getCurrentGame();
if (game == null) { return ""; }
StringBuilder sb = new StringBuilder();
for (PlayerView p : game.getPlayers()) {
final String text;
if (p.equals(originPlayer)) {
text = "Commander Damage from own Commander: ";
}
else {
text = "Commander Damage to " + p.getName() + ": ";
}
int damage = p.getCommanderDamage(originPlayer.getCommander());
if (damage > 0) {
sb.append(text + damage + "\r\n");
}
}
return sb.toString();
}
/**
* <p>
* postFactoryKeywords.
@@ -3366,7 +3348,7 @@ public class CardFactoryUtil {
final String parse = card.getKeywords().get(n);
final String[] k = parse.split(":");
final int num = Integer.parseInt(k[1]);
UUID triggerSvar = UUID.randomUUID();
final String triggerSvar = "DBRipple";
final String actualTrigger = "Mode$ SpellCast | ValidCard$ Card.Self | " +
"Execute$ " + triggerSvar + " | Secondary$ True | TriggerDescription$" +

View File

@@ -531,13 +531,13 @@ public class CardView extends GameEntityView {
}
}
String rulesText = state.getRulesText();
final String rulesText = state.getRulesText();
if (!rulesText.isEmpty()) {
sb.append(rulesText).append("\r\n\r\n");
}
if (isCommander()) {
sb.append(getOwner()).append("'s Commander\r\n");
sb.append(CardFactoryUtil.getCommanderInfo(getOwner())).append("\r\n");
sb.append(getOwner().getCommanderInfo()).append("\r\n");
}
if (isSplitCard()) {

View File

@@ -0,0 +1,5 @@
package forge.game.event;
public abstract class Event {
}

View File

@@ -1,6 +1,6 @@
package forge.game.event;
public abstract class GameEvent {
public abstract class GameEvent extends Event {
public abstract <T> T visit(IGameEventVisitor<T> visitor);
}

View File

@@ -1,9 +1,16 @@
package forge.game.player;
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.Multimap;
import com.google.common.collect.Sets;
import forge.LobbyPlayer;
import forge.card.ColorSet;
@@ -25,7 +32,6 @@ import forge.game.cost.Cost;
import forge.game.cost.CostPart;
import forge.game.cost.CostPartMana;
import forge.game.mana.Mana;
import forge.game.phase.PhaseType;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
@@ -38,15 +44,6 @@ import forge.item.PaperCard;
import forge.util.FCollectionView;
import forge.util.ITriggerEvent;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A prototype for player controller class
*
@@ -75,7 +72,6 @@ public abstract class PlayerController {
protected final Game game;
private PhaseType autoPassUntilPhase = null;
protected final Player player;
protected final LobbyPlayer lobbyPlayer;
@@ -85,71 +81,10 @@ public abstract class PlayerController {
lobbyPlayer = lp;
}
/**
* Automatically pass priority until reaching the Cleanup phase of the
* current turn.
*/
public void autoPassUntilEndOfTurn() {
autoPassUntilPhase = PhaseType.CLEANUP;
}
protected PhaseType getAutoPassUntilPhase() {
return autoPassUntilPhase;
}
public void autoPassCancel() {
autoPassUntilPhase = null;
}
public boolean mayAutoPass() {
if (autoPassUntilPhase == null) { return false; }
PhaseType currentPhase = game.getPhaseHandler().getPhase();
if (currentPhase.isBefore(autoPassUntilPhase)) {
if (currentPhase == PhaseType.COMBAT_DECLARE_BLOCKERS && game.getPhaseHandler().isPlayerTurn(player)) {
if (!game.getCombat().getAllBlockers().isEmpty()) {
return false; //prevent auto-passing on Declare Blockers phase if it's your turn and your opponent blocks a creature you attacked with
}
}
return true;
}
return false;
}
public boolean isAI() {
return false;
}
// Abilities to auto-yield to
private final Set<String> autoYields = Sets.newHashSet();
public Iterable<String> getAutoYields() {
return autoYields;
}
public boolean shouldAutoYield(final String key) {
return autoYields.contains(key);
}
public void setShouldAutoYield(final String key, final boolean autoYield) {
if (autoYield) {
autoYields.add(key);
}
else {
autoYields.remove(key);
}
}
// Triggers preliminary choice: ask, decline or play
private Map<Integer, Boolean> triggersAlwaysAccept = new HashMap<Integer, Boolean>();
public boolean shouldAlwaysAcceptTrigger(Integer trigger) { return Boolean.TRUE.equals(triggersAlwaysAccept.get(trigger)); }
public boolean shouldAlwaysDeclineTrigger(Integer trigger) { return Boolean.FALSE.equals(triggersAlwaysAccept.get(trigger)); }
public boolean shouldAlwaysAskTrigger(Integer trigger) { return !triggersAlwaysAccept.containsKey(trigger); }
public void setShouldAlwaysAcceptTrigger(Integer trigger) { triggersAlwaysAccept.put(trigger, true); }
public void setShouldAlwaysDeclineTrigger(Integer trigger) { triggersAlwaysAccept.put(trigger, false); }
public void setShouldAlwaysAskTrigger(Integer trigger) { triggersAlwaysAccept.remove(trigger); }
// End of Triggers preliminary choice
public Game getGame() { return game; }
public Player getPlayer() { return player; }
public LobbyPlayer getLobbyPlayer() { return lobbyPlayer; }
@@ -290,6 +225,8 @@ public abstract class PlayerController {
// better to have this odd method than those if playerType comparison in ChangeZone
public abstract Card chooseSingleCardForZoneChange(ZoneType destination, List<ZoneType> origin, SpellAbility sa, CardCollection fetchList, DelayedReveal delayedReveal, String selectPrompt, boolean isOptional, Player decider);
public abstract void autoPassCancel();
public boolean isGuiPlayer() {
return false;
}

View File

@@ -1,9 +1,11 @@
package forge.game.player;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import forge.LobbyPlayer;
@@ -85,6 +87,25 @@ public class PlayerView extends GameEntityView {
return opponents != null && opponents.contains(other);
}
public final String getCommanderInfo() {
final StringBuilder sb = new StringBuilder();
for (final PlayerView p : Iterables.concat(Collections.singleton(this), getOpponents())) {
final String text;
if (p.equals(this)) {
text = "Commander Damage from own Commander: ";
} else {
text = "Commander Damage to " + p.getName() + ": ";
}
final int damage = p.getCommanderDamage(this.getCommander());
if (damage > 0) {
sb.append(text + damage + "\r\n");
}
}
return sb.toString();
}
@Override
public String toString() {
return getName();

View File

@@ -145,17 +145,22 @@ public class TriggerHandler {
}
public static Trigger parseTrigger(final Map<String, String> mapParams, final Card host, final boolean intrinsic) {
Trigger ret = null;
final TriggerType type = TriggerType.smartValueOf(mapParams.get("Mode"));
ret = type.createTrigger(mapParams, host, intrinsic);
final Trigger ret = type.createTrigger(mapParams, host, intrinsic);
String triggerZones = mapParams.get("TriggerZones");
final String triggerZones = mapParams.get("TriggerZones");
if (null != triggerZones) {
ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(triggerZones)));
} else if (type == TriggerType.ChangesZone) {
// Special case, because ChangesZone triggers on cards don't
// specify their TriggerZone
final String origin = mapParams.get("Destination");
if (!"Any".equals(origin)) {
ret.setActiveZone(EnumSet.copyOf(ZoneType.listValueOf(origin)));
}
}
String triggerPhases = mapParams.get("Phase");
final String triggerPhases = mapParams.get("Phase");
if (null != triggerPhases) {
ret.setTriggerPhases(PhaseType.parseRange(triggerPhases));
}

View File

@@ -225,7 +225,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
//cancel auto-pass for all opponents of activating player
//when a new non-triggered ability is put on the stack
if (!sp.isTrigger()) {
for (Player p : activator.getOpponents()) {
for (final Player p : activator.getOpponents()) {
p.getController().autoPassCancel();
}
}

View File

@@ -136,6 +136,7 @@ public enum TrackableProperty {
//Game
GameType(TrackableTypes.EnumType(GameType.class)),
Title(TrackableTypes.StringType),
Turn(TrackableTypes.IntegerType),
WinningTeam(TrackableTypes.IntegerType),
MatchOver(TrackableTypes.BooleanType),