mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 02:38:02 +00:00
Refactor event handling system to be much faster and more reliable
This commit is contained in:
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -727,6 +727,7 @@ forge-gui-desktop/src/main/java/forge/ImageCache.java -text
|
||||
forge-gui-desktop/src/main/java/forge/ImageLoader.java -text
|
||||
forge-gui-desktop/src/main/java/forge/Singletons.java -text
|
||||
forge-gui-desktop/src/main/java/forge/control/FControl.java -text
|
||||
forge-gui-desktop/src/main/java/forge/control/GuiTimer.java -text
|
||||
forge-gui-desktop/src/main/java/forge/control/KeyboardShortcuts.java -text
|
||||
forge-gui-desktop/src/main/java/forge/control/RestartUtil.java -text
|
||||
forge-gui-desktop/src/main/java/forge/control/package-info.java -text
|
||||
@@ -1134,6 +1135,7 @@ forge-gui-mobile/src/forge/animation/AbilityEffect.java -text
|
||||
forge-gui-mobile/src/forge/animation/ForgeAnimation.java -text
|
||||
forge-gui-mobile/src/forge/animation/GifAnimation.java -text
|
||||
forge-gui-mobile/src/forge/animation/GifDecoder.java -text
|
||||
forge-gui-mobile/src/forge/animation/GuiTimer.java -text
|
||||
forge-gui-mobile/src/forge/assets/AssetsDownloader.java -text
|
||||
forge-gui-mobile/src/forge/assets/BitmapFontWriter.java -text
|
||||
forge-gui-mobile/src/forge/assets/FBufferedImage.java -text
|
||||
@@ -16963,6 +16965,7 @@ forge-gui/src/main/java/forge/interfaces/ICheckBox.java -text
|
||||
forge-gui/src/main/java/forge/interfaces/IComboBox.java -text
|
||||
forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java -text
|
||||
forge-gui/src/main/java/forge/interfaces/IGuiBase.java -text
|
||||
forge-gui/src/main/java/forge/interfaces/IGuiTimer.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/IWinLoseView.java -text
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.common.base.Function;
|
||||
|
||||
import forge.assets.FSkinProp;
|
||||
import forge.assets.ISkinImage;
|
||||
import forge.control.GuiTimer;
|
||||
import forge.deck.CardPool;
|
||||
import forge.error.BugReportDialog;
|
||||
import forge.game.GameObject;
|
||||
@@ -30,6 +31,7 @@ import forge.gui.CardListViewer;
|
||||
import forge.gui.GuiChoose;
|
||||
import forge.gui.framework.FScreen;
|
||||
import forge.interfaces.IGuiBase;
|
||||
import forge.interfaces.IGuiTimer;
|
||||
import forge.item.PaperCard;
|
||||
import forge.model.FModel;
|
||||
import forge.screens.deckeditor.CDeckEditorUI;
|
||||
@@ -93,6 +95,11 @@ public class GuiDesktop implements IGuiBase {
|
||||
return SwingUtilities.isEventDispatchThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGuiTimer createGuiTimer(Runnable proc, int interval) {
|
||||
return new GuiTimer(proc, interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISkinImage getSkinIcon(FSkinProp skinProp) {
|
||||
if (skinProp == null) { return null; }
|
||||
|
||||
25
forge-gui-desktop/src/main/java/forge/control/GuiTimer.java
Normal file
25
forge-gui-desktop/src/main/java/forge/control/GuiTimer.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package forge.control;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.Timer;
|
||||
|
||||
import forge.interfaces.IGuiTimer;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class GuiTimer extends Timer implements IGuiTimer {
|
||||
public GuiTimer(final Runnable proc0, int interval0) {
|
||||
super(interval0, new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
proc0.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterval(int interval0) {
|
||||
setDelay(interval0);
|
||||
}
|
||||
}
|
||||
@@ -205,8 +205,8 @@ public class Forge implements ApplicationListener {
|
||||
private static void setCurrentScreen(FScreen screen0) {
|
||||
try {
|
||||
endKeyInput(); //end key input before switching screens
|
||||
ForgeAnimation.endAll(); //end all active animations before switching screens
|
||||
|
||||
ForgeAnimation.stopAll(true); //end all active animations before switching screens
|
||||
|
||||
currentScreen = screen0;
|
||||
currentScreen.setSize(screenWidth, screenHeight);
|
||||
currentScreen.onActivate();
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.badlogic.gdx.Application.ApplicationType;
|
||||
import com.badlogic.gdx.graphics.Texture;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import forge.animation.GuiTimer;
|
||||
import forge.assets.FBufferedImage;
|
||||
import forge.assets.FDelayLoadImage;
|
||||
import forge.assets.FImage;
|
||||
@@ -24,6 +25,7 @@ import forge.deck.FSideboardDialog;
|
||||
import forge.error.BugReportDialog;
|
||||
import forge.game.player.IHasIcon;
|
||||
import forge.interfaces.IGuiBase;
|
||||
import forge.interfaces.IGuiTimer;
|
||||
import forge.item.PaperCard;
|
||||
import forge.properties.ForgeConstants;
|
||||
import forge.screens.match.MatchController;
|
||||
@@ -87,6 +89,11 @@ public class GuiMobile implements IGuiBase {
|
||||
return !ThreadUtil.isGameThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGuiTimer createGuiTimer(Runnable proc, int interval) {
|
||||
return new GuiTimer(proc, interval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ISkinImage getSkinIcon(FSkinProp skinProp) {
|
||||
if (skinProp == null) { return null; }
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.badlogic.gdx.Gdx;
|
||||
|
||||
public abstract class ForgeAnimation {
|
||||
private static final List<ForgeAnimation> activeAnimations = new ArrayList<ForgeAnimation>();
|
||||
private boolean stopped;
|
||||
|
||||
public void start() {
|
||||
if (activeAnimations.contains(this)) { return; } //prevent starting the same animation multiple times
|
||||
@@ -14,22 +15,43 @@ public abstract class ForgeAnimation {
|
||||
activeAnimations.add(this);
|
||||
}
|
||||
|
||||
public static void advanceAll() {
|
||||
if (activeAnimations.isEmpty()) { return; }
|
||||
protected boolean stopWhenScreenChanges() {
|
||||
return true;
|
||||
}
|
||||
|
||||
float dt = Gdx.graphics.getDeltaTime();
|
||||
for (int i = 0; i < activeAnimations.size(); i++) {
|
||||
if (!activeAnimations.get(i).advance(dt)) {
|
||||
activeAnimations.remove(i);
|
||||
i--;
|
||||
public void stop() {
|
||||
stopped = true; //will be removed on the next iteration
|
||||
}
|
||||
|
||||
public static void advanceAll() {
|
||||
synchronized (activeAnimations) {
|
||||
if (!activeAnimations.isEmpty()) {
|
||||
float dt = Gdx.graphics.getDeltaTime();
|
||||
for (int i = 0; i < activeAnimations.size(); i++) {
|
||||
ForgeAnimation animation = activeAnimations.get(i);
|
||||
if (animation.stopped || !animation.advance(dt)) {
|
||||
animation.stopped = true;
|
||||
activeAnimations.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void endAll() {
|
||||
if (activeAnimations.isEmpty()) { return; }
|
||||
|
||||
activeAnimations.clear();
|
||||
public static void stopAll(boolean forScreenChange) {
|
||||
synchronized (activeAnimations) {
|
||||
if (!activeAnimations.isEmpty()) {
|
||||
for (int i = 0; i < activeAnimations.size(); i++) {
|
||||
ForgeAnimation animation = activeAnimations.get(i);
|
||||
if (!forScreenChange || animation.stopWhenScreenChanges()) {
|
||||
animation.stopped = true;
|
||||
activeAnimations.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//return true if animation should continue, false to stop the animation
|
||||
|
||||
35
forge-gui-mobile/src/forge/animation/GuiTimer.java
Normal file
35
forge-gui-mobile/src/forge/animation/GuiTimer.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package forge.animation;
|
||||
|
||||
import forge.interfaces.IGuiTimer;
|
||||
|
||||
//implement a GuiTimer as an animation since it can utilize the same logic for advancing
|
||||
public class GuiTimer extends ForgeAnimation implements IGuiTimer {
|
||||
private final Runnable proc;
|
||||
private float elapsed;
|
||||
private int interval;
|
||||
|
||||
public GuiTimer(Runnable proc0, int interval0) {
|
||||
proc = proc0;
|
||||
interval = interval0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterval(int interval0) {
|
||||
interval = interval0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean advance(float dt) {
|
||||
elapsed += dt * 1000; //convert seconds to milliseconds
|
||||
if (elapsed >= interval) {
|
||||
elapsed = 0;
|
||||
proc.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean stopWhenScreenChanges() {
|
||||
return false; //don't stop timers just because screen changed
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public abstract class WaitCallback<T> extends Callback<T> implements Runnable {
|
||||
@Override
|
||||
public final void run(T result0) {
|
||||
result = result0;
|
||||
synchronized(lock) {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public abstract class WaitCallback<T> extends Callback<T> implements Runnable {
|
||||
FThreads.assertExecutedByEdt(GuiBase.getInterface(), false); //not supported if on UI thread
|
||||
FThreads.invokeInEdtLater(GuiBase.getInterface(), this);
|
||||
try {
|
||||
synchronized(lock) {
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
package forge.control;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import forge.FThreads;
|
||||
import forge.GuiBase;
|
||||
import forge.game.Game;
|
||||
import forge.game.card.Card;
|
||||
import forge.game.event.GameEvent;
|
||||
@@ -52,6 +46,8 @@ import forge.game.player.Player;
|
||||
import forge.game.zone.PlayerZone;
|
||||
import forge.game.zone.Zone;
|
||||
import forge.game.zone.ZoneType;
|
||||
import forge.interfaces.IGuiTimer;
|
||||
import forge.match.IMatchController;
|
||||
import forge.match.MatchUtil;
|
||||
import forge.match.input.ButtonUtil;
|
||||
import forge.match.input.InputBase;
|
||||
@@ -59,7 +55,6 @@ import forge.model.FModel;
|
||||
import forge.player.GamePlayerUtil;
|
||||
import forge.properties.ForgePreferences.FPref;
|
||||
import forge.util.Lang;
|
||||
import forge.util.ThreadUtil;
|
||||
import forge.util.gui.SGuiChoose;
|
||||
import forge.util.maps.MapOfLists;
|
||||
import forge.view.CardView;
|
||||
@@ -67,115 +62,141 @@ import forge.view.LocalGameView;
|
||||
import forge.view.PlayerView;
|
||||
|
||||
public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
|
||||
private static FControlGameEventHandler mainHandler;
|
||||
private static final String PLAYER_ZONE_DELIM = "|";
|
||||
private static FControlGameEventHandler instance;
|
||||
|
||||
public static void processEventsAfterInput() {
|
||||
if (mainHandler.processEventsSchedule != null) {
|
||||
mainHandler.processEventsSchedule.cancel(false); //cancel any currently scheduled processing
|
||||
}
|
||||
mainHandler.processEventsDelay = 10; //speed up event processing until an event has been raised
|
||||
mainHandler.processEventsSchedule = ThreadUtil.delay(mainHandler.processEventsDelay, mainHandler.processEvents);
|
||||
instance.processEventsTimer.setInterval(10); //speed up event processing until an event has been raised
|
||||
}
|
||||
|
||||
private final LocalGameView gameView;
|
||||
private final boolean isMainHandler;
|
||||
private final ConcurrentLinkedQueue<GameEvent> eventQueue = new ConcurrentLinkedQueue<GameEvent>();
|
||||
private final IGuiTimer processEventsTimer;
|
||||
private final HashSet<Card> cardsUpdate = new HashSet<Card>();
|
||||
private final HashSet<Card> cardsRefreshDetails = new HashSet<Card>();
|
||||
private final HashSet<Player> livesUpdate = new HashSet<Player>();
|
||||
private final HashSet<Player> manaPoolUpdate = new HashSet<Player>();
|
||||
private final HashSet<String> zonesUpdate = new HashSet<String>();
|
||||
|
||||
public FControlGameEventHandler(final LocalGameView gameView0, final boolean isMainHandler0) {
|
||||
private boolean eventReceived, needPhaseUpdate, needCombatUpdate, needStackUpdate, needPlayerControlUpdate;
|
||||
private boolean gameOver, gameFinished;
|
||||
private Player turnUpdate;
|
||||
|
||||
public FControlGameEventHandler(final LocalGameView gameView0) {
|
||||
gameView = gameView0;
|
||||
isMainHandler = isMainHandler0;
|
||||
if (isMainHandler) {
|
||||
mainHandler = this;
|
||||
}
|
||||
processEvents.run(); //start event processing loop
|
||||
instance = this;
|
||||
processEventsTimer = GuiBase.getInterface().createGuiTimer(processEvents, 100);
|
||||
processEventsTimer.start(); //start event processing loop
|
||||
}
|
||||
|
||||
private ScheduledFuture<?> processEventsSchedule;
|
||||
private int processEventsDelay = 100;
|
||||
private final Runnable processEvents = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!eventQueue.isEmpty()) {
|
||||
processEventsDelay = 100; //reset to 100 once an event has been raised
|
||||
gameView.updateViews();
|
||||
synchronized (processEventsTimer) {
|
||||
if (eventReceived) {
|
||||
processEventsTimer.setInterval(100); //reset to 100 once an event has been raised
|
||||
gameView.updateViews();
|
||||
|
||||
GameEvent ev;
|
||||
while ((ev = eventQueue.poll()) != null) {
|
||||
ev.visit(FControlGameEventHandler.this);
|
||||
IMatchController controller = MatchUtil.getController();
|
||||
if (!cardsUpdate.isEmpty()) {
|
||||
MatchUtil.updateCards(gameView.getCardViews(cardsUpdate));
|
||||
cardsUpdate.clear();
|
||||
}
|
||||
if (!cardsRefreshDetails.isEmpty()) {
|
||||
controller.refreshCardDetails(gameView.getCardViews(cardsRefreshDetails));
|
||||
cardsRefreshDetails.clear();
|
||||
}
|
||||
if (!livesUpdate.isEmpty()) {
|
||||
controller.updateLives(gameView.getPlayerViews(livesUpdate));
|
||||
livesUpdate.clear();
|
||||
}
|
||||
if (!manaPoolUpdate.isEmpty()) {
|
||||
controller.updateManaPool(gameView.getPlayerViews(manaPoolUpdate));
|
||||
manaPoolUpdate.clear();
|
||||
}
|
||||
if (!zonesUpdate.isEmpty()) {
|
||||
List<PlayerView> players = gameView.getPlayers();
|
||||
ArrayList<Pair<PlayerView, ZoneType>> zones = new ArrayList<Pair<PlayerView, ZoneType>>();
|
||||
for (String z : zonesUpdate) {
|
||||
int idx = z.indexOf(PLAYER_ZONE_DELIM);
|
||||
zones.add(Pair.of(players.get(Integer.parseInt(z.substring(0, idx))), ZoneType.valueOf(z.substring(idx + 1))));
|
||||
}
|
||||
controller.updateZones(zones);
|
||||
zonesUpdate.clear();
|
||||
}
|
||||
if (turnUpdate != null) {
|
||||
controller.updateTurn(gameView.getPlayerView(turnUpdate));
|
||||
turnUpdate = null;
|
||||
}
|
||||
if (needPhaseUpdate) {
|
||||
needPhaseUpdate = false;
|
||||
controller.updatePhase();
|
||||
}
|
||||
if (needCombatUpdate) {
|
||||
needCombatUpdate = false;
|
||||
gameView.refreshCombat();
|
||||
controller.showCombat(gameView.getCombat());
|
||||
}
|
||||
if (needStackUpdate) {
|
||||
needStackUpdate = false;
|
||||
controller.updateStack();
|
||||
}
|
||||
if (needPlayerControlUpdate) {
|
||||
needPlayerControlUpdate = false;
|
||||
controller.updatePlayerControl();
|
||||
}
|
||||
if (gameOver) {
|
||||
gameOver = false;
|
||||
gameView.getInputQueue().onGameOver(true); // this will unlock any game threads waiting for inputs to complete
|
||||
}
|
||||
if (gameFinished) {
|
||||
gameFinished = false;
|
||||
PlayerView localPlayer = gameView.getLocalPlayerView();
|
||||
InputBase.cancelAwaitNextInput(); //ensure "Waiting for opponent..." doesn't appear behind WinLo
|
||||
controller.showPromptMessage(localPlayer, ""); //clear prompt behind WinLose overlay
|
||||
ButtonUtil.update(localPlayer, "", "", false, false, false);
|
||||
controller.finishGame();
|
||||
gameView.updateAchievements();
|
||||
processEventsTimer.stop();
|
||||
instance = null; //this can be reset after game is finished
|
||||
}
|
||||
}
|
||||
}
|
||||
processEventsSchedule = ThreadUtil.delay(processEventsDelay, this); //only process events 10 times per second by default to improve performance
|
||||
}
|
||||
};
|
||||
|
||||
@Subscribe
|
||||
public void receiveGameEvent(final GameEvent ev) {
|
||||
eventQueue.add(ev);
|
||||
synchronized (processEventsTimer) { //ensure multiple events aren't processed at the same time
|
||||
eventReceived = true;
|
||||
ev.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
private final AtomicBoolean phaseUpdPlanned = new AtomicBoolean(false);
|
||||
@Override
|
||||
public Void visit(final GameEventTurnPhase ev) {
|
||||
if (!isMainHandler || phaseUpdPlanned.getAndSet(true)) { return null; }
|
||||
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
phaseUpdPlanned.set(false);
|
||||
MatchUtil.getController().updatePhase();
|
||||
}
|
||||
});
|
||||
needPhaseUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventPlayerPriority)
|
||||
*/
|
||||
private final AtomicBoolean combatUpdPlanned = new AtomicBoolean(false);
|
||||
@Override
|
||||
public Void visit(GameEventPlayerPriority event) {
|
||||
updateCombat();
|
||||
needCombatUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void updateCombat() {
|
||||
if (!isMainHandler || combatUpdPlanned.getAndSet(true)) { return; }
|
||||
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
combatUpdPlanned.set(false);
|
||||
MatchUtil.getController().showCombat(gameView.getCombat());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private final AtomicBoolean turnUpdPlanned = new AtomicBoolean(false);
|
||||
@Override
|
||||
public Void visit(final GameEventTurnBegan event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
turnUpdate = event.turnOwner;
|
||||
|
||||
if (FModel.getPreferences().getPrefBoolean(FPref.UI_STACK_CREATURES) && event.turnOwner != null) {
|
||||
// anything except stack will get here
|
||||
updateZone(Pair.of(gameView.getPlayerView(event.turnOwner), ZoneType.Battlefield));
|
||||
updateZone(event.turnOwner, ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
if (turnUpdPlanned.getAndSet(true)) { return null; }
|
||||
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
turnUpdPlanned.set(false);
|
||||
MatchUtil.getController().updateTurn(gameView.getPlayerView(event.turnOwner));
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventAnteCardsSelected ev) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
final List<CardView> options = Lists.newArrayList();
|
||||
for (final Entry<Player, Card> kv : ev.cards.entries()) {
|
||||
final CardView fakeCard = new CardView(-1); //use fake card so real cards appear with proper formatting
|
||||
@@ -192,111 +213,51 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
|
||||
if (ev.player.getGame().isGameOver()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
MatchUtil.getController().updatePlayerControl();
|
||||
}
|
||||
});
|
||||
needPlayerControlUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
private final Runnable unlockGameThreadOnGameOver = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
gameView.getInputQueue().onGameOver(true); // this will unlock any game threads waiting for inputs to complete
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventGameOutcome ev) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), unlockGameThreadOnGameOver);
|
||||
gameOver = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventGameFinished ev) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
PlayerView localPlayer = gameView.getLocalPlayerView();
|
||||
InputBase.cancelAwaitNextInput(); //ensure "Waiting for opponent..." doesn't appear behind WinLo
|
||||
MatchUtil.getController().showPromptMessage(localPlayer, ""); //clear prompt behind WinLose overlay
|
||||
ButtonUtil.update(localPlayer, "", "", false, false, false);
|
||||
if (isMainHandler) {
|
||||
MatchUtil.getController().finishGame();
|
||||
gameView.updateAchievements();
|
||||
mainHandler = null; //this can be reset after game is finished
|
||||
}
|
||||
}
|
||||
});
|
||||
gameFinished = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
private final AtomicBoolean stackUpdPlanned = new AtomicBoolean(false);
|
||||
private final Runnable updStack = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
stackUpdPlanned.set(false);
|
||||
MatchUtil.getController().updateStack();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventSpellAbilityCast event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
if (!stackUpdPlanned.getAndSet(true)) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
|
||||
}
|
||||
needStackUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventSpellResolved event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
if (!stackUpdPlanned.getAndSet(true)) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
|
||||
}
|
||||
needStackUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventSpellRemovedFromStack event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
if (!stackUpdPlanned.getAndSet(true)) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updStack);
|
||||
}
|
||||
needStackUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
private final List<Pair<PlayerView, ZoneType>> zonesToUpdate = new Vector<Pair<PlayerView,ZoneType>>();
|
||||
private final Runnable updZones = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (zonesToUpdate) {
|
||||
MatchUtil.getController().updateZones(zonesToUpdate);
|
||||
zonesToUpdate.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventZone event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
if (event.player != null) {
|
||||
// anything except stack will get here
|
||||
updateZone(Pair.of(gameView.getPlayerView(event.player), event.zoneType));
|
||||
updateZone(event.player, event.zoneType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventCardAttachment event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
final Game game = event.equipment.getGame();
|
||||
final PlayerZone zEq = (PlayerZone)game.getZoneOf(event.equipment);
|
||||
if (event.oldEntiy instanceof Card) {
|
||||
@@ -305,82 +266,55 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
|
||||
if (event.newTarget instanceof Card) {
|
||||
updateZone(game.getZoneOf((Card)event.newTarget));
|
||||
}
|
||||
return updateZone(zEq);
|
||||
}
|
||||
|
||||
private Void updateZone(final Zone z) {
|
||||
return updateZone(Pair.of(gameView.getPlayerView(z.getPlayer()), z.getZoneType()));
|
||||
}
|
||||
|
||||
private Void updateZone(final Pair<PlayerView, ZoneType> kv) {
|
||||
boolean needUpdate = false;
|
||||
synchronized (zonesToUpdate) {
|
||||
needUpdate = zonesToUpdate.isEmpty();
|
||||
if (!zonesToUpdate.contains(kv)) {
|
||||
zonesToUpdate.add(kv);
|
||||
}
|
||||
}
|
||||
if (needUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updZones);
|
||||
}
|
||||
updateZone(zEq);
|
||||
return null;
|
||||
}
|
||||
|
||||
private final Set<CardView> cardsToUpdate = Sets.newHashSet();
|
||||
private final Runnable updCards = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (cardsToUpdate) {
|
||||
MatchUtil.updateCards(cardsToUpdate);
|
||||
cardsToUpdate.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
private void updateZone(final Zone z) {
|
||||
if (z == null) { return; }
|
||||
updateZone(z.getPlayer(), z.getZoneType());
|
||||
}
|
||||
private void updateZone(Player p, ZoneType z) {
|
||||
if (p == null || z == null) { return; }
|
||||
zonesUpdate.add(p.getId() + PLAYER_ZONE_DELIM + z.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(final GameEventCardTapped event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
return updateSingleCard(gameView.getCardView(event.card));
|
||||
cardsUpdate.add(event.card);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(final GameEventCardPhased event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
return updateSingleCard(gameView.getCardView(event.card));
|
||||
cardsUpdate.add(event.card);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(final GameEventCardDamaged event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
return updateSingleCard(gameView.getCardView(event.card));
|
||||
cardsUpdate.add(event.card);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(final GameEventCardCounters event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
return updateSingleCard(gameView.getCardView(event.card));
|
||||
cardsUpdate.add(event.card);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(final GameEventBlockersDeclared event) { // This is to draw icons on blockers declared by AI
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
for (MapOfLists<Card, Card> kv : event.blockers.values()) {
|
||||
for (Collection<Card> blockers : kv.values()) {
|
||||
updateManyCards(gameView.getCardViews(blockers));
|
||||
cardsUpdate.addAll(blockers);
|
||||
}
|
||||
}
|
||||
return super.visit(event);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventAttackersDeclared event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
// Skip redraw for GUI player?
|
||||
if (event.player.getLobbyPlayer() == GamePlayerUtil.getGuiPlayer()) {
|
||||
return null;
|
||||
@@ -388,169 +322,69 @@ public class FControlGameEventHandler extends IGameEventVisitor.Base<Void> {
|
||||
|
||||
// Update all attackers.
|
||||
// Although they might have been updated when they were tapped, there could be someone with vigilance, not redrawn yet.
|
||||
updateManyCards(gameView.getCardViews(event.attackersMap.values()));
|
||||
|
||||
return super.visit(event);
|
||||
cardsUpdate.addAll(event.attackersMap.values());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventCombatChanged event) {
|
||||
gameView.refreshCombat();
|
||||
updateCombat();
|
||||
needCombatUpdate = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventCombatEnded event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
gameView.refreshCombat();
|
||||
updateCombat();
|
||||
needCombatUpdate = true;
|
||||
|
||||
// This should remove sword/shield icons from combatants by the time game moves to M2
|
||||
updateManyCards(gameView.getCardViews(event.attackers));
|
||||
updateManyCards(gameView.getCardViews(event.blockers));
|
||||
return null;
|
||||
}
|
||||
|
||||
private Void updateSingleCard(final CardView c) {
|
||||
boolean needUpdate = false;
|
||||
synchronized (cardsToUpdate) {
|
||||
needUpdate = cardsToUpdate.isEmpty();
|
||||
if (!cardsToUpdate.contains(c)) {
|
||||
cardsToUpdate.add(c);
|
||||
}
|
||||
}
|
||||
if (needUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updCards);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Void updateManyCards(final Iterable<CardView> cc) {
|
||||
boolean needUpdate = false;
|
||||
synchronized (cardsToUpdate) {
|
||||
needUpdate = cardsToUpdate.isEmpty();
|
||||
Iterables.addAll(cardsToUpdate, cc);
|
||||
}
|
||||
if (needUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updCards);
|
||||
}
|
||||
cardsUpdate.addAll(event.attackers);
|
||||
cardsUpdate.addAll(event.blockers);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventCardChangeZone event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
if (event.from != null) {
|
||||
updateZone(event.from);
|
||||
}
|
||||
if (event.to != null) {
|
||||
updateZone(event.to);
|
||||
}
|
||||
updateZone(event.from);
|
||||
updateZone(event.to);
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see forge.game.event.IGameEventVisitor.Base#visit(forge.game.event.GameEventCardStatsChanged)
|
||||
*/
|
||||
@Override
|
||||
public Void visit(GameEventCardStatsChanged event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
final Iterable<CardView> cardViews = gameView.getCardViews(event.cards);
|
||||
MatchUtil.getController().refreshCardDetails(cardViews);
|
||||
return updateManyCards(cardViews);
|
||||
cardsRefreshDetails.addAll(event.cards);
|
||||
cardsUpdate.addAll(event.cards);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventPlayerStatsChanged event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
for (final Player p : event.players) {
|
||||
MatchUtil.getController().refreshCardDetails(gameView.getCardViews(p.getAllCards()));
|
||||
cardsRefreshDetails.addAll(p.getAllCards());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventShuffle event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
updateZone(event.player.getZone(ZoneType.Library));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update manapool
|
||||
private final List<Player> manaPoolUpdate = new Vector<Player>();
|
||||
private final Runnable updManaPool = new Runnable() {
|
||||
@Override public void run() {
|
||||
synchronized (manaPoolUpdate) {
|
||||
MatchUtil.getController().updateManaPool(gameView.getPlayerViews(manaPoolUpdate));
|
||||
manaPoolUpdate.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventManaPool event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
boolean invokeUpdate = false;
|
||||
synchronized (manaPoolUpdate) {
|
||||
if (!manaPoolUpdate.contains(event.player)) {
|
||||
invokeUpdate = manaPoolUpdate.isEmpty();
|
||||
manaPoolUpdate.add(event.player);
|
||||
}
|
||||
}
|
||||
if (invokeUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updManaPool);
|
||||
}
|
||||
manaPoolUpdate.add(event.player);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Update lives counters
|
||||
private final List<Player> livesUpdate = new Vector<Player>();
|
||||
private final Runnable updLives = new Runnable() {
|
||||
@Override public void run() {
|
||||
synchronized (livesUpdate) {
|
||||
MatchUtil.getController().updateLives(gameView.getPlayerViews(livesUpdate));
|
||||
livesUpdate.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public Void visit(GameEventPlayerLivesChanged event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
boolean invokeUpdate = false;
|
||||
synchronized (livesUpdate) {
|
||||
if (!livesUpdate.contains(event.player)) {
|
||||
invokeUpdate = livesUpdate.isEmpty();
|
||||
livesUpdate.add(event.player);
|
||||
}
|
||||
}
|
||||
if (invokeUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updLives);
|
||||
}
|
||||
livesUpdate.add(event.player);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void visit(GameEventPlayerPoisoned event) {
|
||||
if (!isMainHandler) { return null; }
|
||||
|
||||
boolean invokeUpdate = false;
|
||||
synchronized (livesUpdate) {
|
||||
if (!livesUpdate.contains(event.receiver)) {
|
||||
invokeUpdate = livesUpdate.isEmpty();
|
||||
livesUpdate.add(event.receiver);
|
||||
}
|
||||
}
|
||||
if (invokeUpdate) {
|
||||
FThreads.invokeInEdtNowOrLater(gameView.getGui(), updLives);
|
||||
}
|
||||
livesUpdate.add(event.receiver);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public interface IGuiBase {
|
||||
void invokeInEdtLater(Runnable runnable);
|
||||
void invokeInEdtAndWait(final Runnable proc);
|
||||
boolean isGuiThread();
|
||||
IGuiTimer createGuiTimer(Runnable proc, int interval);
|
||||
ISkinImage getSkinIcon(FSkinProp skinProp);
|
||||
ISkinImage getUnskinnedIcon(String path);
|
||||
ISkinImage createLayeredImage(FSkinProp background, String overlayFilename, float opacity);
|
||||
|
||||
7
forge-gui/src/main/java/forge/interfaces/IGuiTimer.java
Normal file
7
forge-gui/src/main/java/forge/interfaces/IGuiTimer.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package forge.interfaces;
|
||||
|
||||
public interface IGuiTimer {
|
||||
void setInterval(int interval0);
|
||||
void start();
|
||||
void stop();
|
||||
}
|
||||
@@ -182,8 +182,8 @@ public class MatchUtil {
|
||||
LocalGameView gameView = controller.getGameView();
|
||||
if (humanCount == 0) {
|
||||
currentPlayer = p;
|
||||
game.subscribeToEvents(new FControlGameEventHandler(gameView));
|
||||
}
|
||||
game.subscribeToEvents(new FControlGameEventHandler(gameView, humanCount == 0));
|
||||
gameViews.add(gameView);
|
||||
humanCount++;
|
||||
}
|
||||
@@ -193,7 +193,7 @@ public class MatchUtil {
|
||||
LocalGameView gameView = new WatchLocalGame(GuiBase.getInterface(), game);
|
||||
currentPlayer = sortedPlayers.get(0);
|
||||
gameView.setLocalPlayer(currentPlayer);
|
||||
game.subscribeToEvents(new FControlGameEventHandler(gameView, true));
|
||||
game.subscribeToEvents(new FControlGameEventHandler(gameView));
|
||||
gameViews.add(gameView);
|
||||
}
|
||||
else if (humanCount == sortedPlayers.size() && controller.hotSeatMode()) {
|
||||
|
||||
Reference in New Issue
Block a user