From dd5544109fb1804fda5c6f4dcc57034e5b236653 Mon Sep 17 00:00:00 2001 From: Retera Date: Sat, 10 Jul 2021 17:18:32 -0400 Subject: [PATCH] Some bug fixes and network changes --- .../warsmash/networking/WarsmashClient.java | 97 +++++++++++++++++-- .../warsmash/networking/WarsmashServer.java | 39 ++++---- .../warsmash/util/WarsmashConstants.java | 3 +- .../com/etheller/warsmash/viewer5/Node.java | 1 + .../warsmash/viewer5/SkeletalNode.java | 5 +- .../handlers/mdx/MdxComplexInstance.java | 17 ++-- .../handlers/w3x/rendersim/RenderWidget.java | 4 +- .../handlers/w3x/simulation/CSimulation.java | 24 +---- .../viewer5/handlers/w3x/ui/MeleeUI.java | 6 ++ 9 files changed, 139 insertions(+), 57 deletions(-) diff --git a/core/src/com/etheller/warsmash/networking/WarsmashClient.java b/core/src/com/etheller/warsmash/networking/WarsmashClient.java index 405878e..21f56a2 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashClient.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashClient.java @@ -3,8 +3,7 @@ package com.etheller.warsmash.networking; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import com.badlogic.gdx.Gdx; import com.etheller.warsmash.networking.udp.OrderedUdpClient; @@ -17,7 +16,9 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { private final War3MapViewer game; private final Map indexToExecutor = new HashMap<>(); private int latestCompletedTurn = -1; + private int latestLocallyRequestedTurn = -1; private final WarsmashClientWriter writer; + private final Queue queuedMessages = new ArrayDeque<>(); public WarsmashClient(final InetAddress serverAddress, final War3MapViewer game) throws UnknownHostException, IOException { @@ -52,7 +53,19 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - executor.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); + int currentServerTurnInProgress = latestCompletedTurn + 1; + if(currentServerTurnInProgress > latestLocallyRequestedTurn) { + queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + @Override + public void run() { + executor.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); + } + }); + } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + executor.issueTargetOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, queue); + } else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } } }); } @@ -64,7 +77,19 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue); + int currentServerTurnInProgress = latestCompletedTurn + 1; + if(currentServerTurnInProgress > latestLocallyRequestedTurn) { + queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + @Override + public void run() { + executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue); + } + }); + } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + executor.issuePointOrder(unitHandleId, abilityHandleId, orderId, x, y, queue);; + } else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } } }); @@ -77,7 +102,19 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, queue); + int currentServerTurnInProgress = latestCompletedTurn + 1; + if(currentServerTurnInProgress > latestLocallyRequestedTurn) { + queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + @Override + public void run() { + executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, queue); + } + }); + } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + executor.issueDropItemAtPointOrder(unitHandleId, abilityHandleId, orderId, targetHandleId, x, y, queue); + } else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } } }); } @@ -89,7 +126,19 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - executor.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); + int currentServerTurnInProgress = latestCompletedTurn + 1; + if(currentServerTurnInProgress > latestLocallyRequestedTurn) { + queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + @Override + public void run() { + executor.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); + } + }); + } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + executor.issueImmediateOrder(unitHandleId, abilityHandleId, orderId, queue); + } else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } } }); } @@ -100,13 +149,28 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { Gdx.app.postRunnable(new Runnable() { @Override public void run() { - executor.unitCancelTrainingItem(unitHandleId, cancelIndex); + int currentServerTurnInProgress = latestCompletedTurn + 1; + if(currentServerTurnInProgress > latestLocallyRequestedTurn) { + queuedMessages.add(new QueuedMessage(latestCompletedTurn) { + @Override + public void run() { + executor.unitCancelTrainingItem(unitHandleId, cancelIndex); + } + }); + } else if(currentServerTurnInProgress == latestLocallyRequestedTurn) { + executor.unitCancelTrainingItem(unitHandleId, cancelIndex); + } else { + System.err.println("Turn tick system mismatch: " + currentServerTurnInProgress + " < " + latestLocallyRequestedTurn); + } } }); } @Override public void finishedTurn(final int gameTurnTick) { + if(WarsmashConstants.VERBOSE_LOGGING) { + System.out.println("finishedTurn " + gameTurnTick); + } Gdx.app.postRunnable(new Runnable() { @Override public void run() { @@ -119,6 +183,10 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { public void turnCompleted(final int gameTurnTick) { this.writer.finishedTurn(gameTurnTick); this.writer.send(); + latestLocallyRequestedTurn = gameTurnTick; + while(!queuedMessages.isEmpty() && queuedMessages.peek().messageTurnTick == latestLocallyRequestedTurn) { + queuedMessages.poll().run(); + } } @Override @@ -142,6 +210,7 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { // Not doing anything here at the moment. The act of the server sending us that packet // will let the middle layer UDP system know to re-request any lost packets based // on the heartbeat seq no. But at app layer, here, we can ignore it. + System.out.println("got heartbeat() from server"); } @Override @@ -152,4 +221,18 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager { public WarsmashClientWriter getWriter() { return this.writer; } + + private static abstract class QueuedMessage implements Runnable { + private int messageTurnTick; + + public QueuedMessage(int messageTurnTick) { + this.messageTurnTick = messageTurnTick; + } + + public final int getMessageTurnTick() { + return messageTurnTick; + } + + public abstract void run(); + } } diff --git a/core/src/com/etheller/warsmash/networking/WarsmashServer.java b/core/src/com/etheller/warsmash/networking/WarsmashServer.java index 32973e4..82f4c5b 100644 --- a/core/src/com/etheller/warsmash/networking/WarsmashServer.java +++ b/core/src/com/etheller/warsmash/networking/WarsmashServer.java @@ -14,12 +14,13 @@ import com.etheller.warsmash.networking.udp.OrderedUdpServer; import com.etheller.warsmash.util.WarsmashConstants; public class WarsmashServer implements ClientToServerListener { + private static final int MAGIC_DELAY_OFFSET = 4; private final OrderedUdpServer udpServer; private final Map socketAddressToPlayerIndex = new HashMap<>(); - private final Set clientsAwaitingTurnFinished = new HashSet<>(); + private final Map clientToTurnFinished = new HashMap<>(); private final List turnActions = new ArrayList<>(); private final WarsmashServerWriter writer; - private int currentTurnTick = 0; + private int currentTurnTick = MAGIC_DELAY_OFFSET; private boolean gameStarted = false; private long lastServerHeartbeatTime = 0; @@ -40,7 +41,7 @@ public class WarsmashServer implements ClientToServerListener { } private void startTurn() { - this.clientsAwaitingTurnFinished.addAll(this.socketAddressToPlayerIndex.keySet()); + System.out.println("sending finishedTurn " + this.currentTurnTick); WarsmashServer.this.writer.finishedTurn(this.currentTurnTick); WarsmashServer.this.writer.send(); this.currentTurnTick++; @@ -139,25 +140,29 @@ public class WarsmashServer implements ClientToServerListener { } @Override - public void finishedTurn(final SocketAddress sourceAddress, final int gameTurnTick) { -// System.out.println("finishedTurn(" + gameTurnTick + ") from " + sourceAddress); + public void finishedTurn(final SocketAddress sourceAddress, final int clientGameTurnTick) { + int gameTurnTick = clientGameTurnTick + MAGIC_DELAY_OFFSET; + if(WarsmashConstants.VERBOSE_LOGGING) { + System.out.println("finishedTurn(" + gameTurnTick + ") from " + sourceAddress); + } if (!this.gameStarted) { throw new IllegalStateException( "Client should not send us finishedTurn() message when game has not started!"); } - if (gameTurnTick == this.currentTurnTick) { - this.clientsAwaitingTurnFinished.remove(sourceAddress); - if (this.clientsAwaitingTurnFinished.isEmpty()) { - for (final Runnable turnAction : this.turnActions) { - turnAction.run(); - } - this.turnActions.clear(); - startTurn(); + clientToTurnFinished.put(sourceAddress, clientGameTurnTick); + boolean allDone = true; + for(SocketAddress clientAddress: socketAddressToPlayerIndex.keySet()) { + Integer turnFinishedValue = clientToTurnFinished.get(clientAddress); + if(turnFinishedValue == null || turnFinishedValue < clientGameTurnTick) { + allDone = false; } } - else { - System.err.println("received bad finishedTurn() with remote gameTurnTick=" + gameTurnTick - + ", server local currenTurnTick=" + this.currentTurnTick); + if (allDone) { + for (final Runnable turnAction : this.turnActions) { + turnAction.run(); + } + this.turnActions.clear(); + startTurn(); } } @@ -167,7 +172,7 @@ public class WarsmashServer implements ClientToServerListener { long currentTimeMillis = System.currentTimeMillis(); if(currentTimeMillis - lastServerHeartbeatTime > 3000) { // 3 seconds of frame skipping, make sure we keep in contact with client - + System.out.println("sending server heartbeat()"); WarsmashServer.this.writer.heartbeat(); WarsmashServer.this.writer.send(); lastServerHeartbeatTime = currentTimeMillis; diff --git a/core/src/com/etheller/warsmash/util/WarsmashConstants.java b/core/src/com/etheller/warsmash/util/WarsmashConstants.java index e05f462..e519176 100644 --- a/core/src/com/etheller/warsmash/util/WarsmashConstants.java +++ b/core/src/com/etheller/warsmash/util/WarsmashConstants.java @@ -8,7 +8,7 @@ public class WarsmashConstants { */ public static int GAME_VERSION = 1; public static final int REPLACEABLE_TEXTURE_LIMIT = 64; - public static final float SIMULATION_STEP_TIME = 1 / 20f; + public static final float SIMULATION_STEP_TIME = 1 /20f; public static final int PORT_NUMBER = 6115; public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f; public static final int BUILD_QUEUE_SIZE = 7; @@ -26,4 +26,5 @@ public class WarsmashConstants { public static final String DEFAULT_STRING = "Default string"; public static final boolean CATCH_CURSOR = true; + public static final boolean VERBOSE_LOGGING = true; } diff --git a/core/src/com/etheller/warsmash/viewer5/Node.java b/core/src/com/etheller/warsmash/viewer5/Node.java index be2d95a..e17d1e9 100644 --- a/core/src/com/etheller/warsmash/viewer5/Node.java +++ b/core/src/com/etheller/warsmash/viewer5/Node.java @@ -299,6 +299,7 @@ public abstract class Node extends GenericNode { this.wasDirty = false; } + this.updateObject(dt, scene); this.updateChildren(dt, scene); } diff --git a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java index 16fb9e7..d27b0ec 100644 --- a/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java +++ b/core/src/com/etheller/warsmash/viewer5/SkeletalNode.java @@ -114,7 +114,10 @@ public abstract class SkeletalNode extends GenericNode { computedRotation = rotationHeap; computedRotation.set(this.parent.inverseWorldRotation); - computedRotation.mul(scene.camera.inverseRotation); + if(scene!=null) { + // TODO null scene is stupid, and happens rarely + computedRotation.mul(scene.camera.inverseRotation); + } this.convertBasis(computedRotation); } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java index 650102b..eefefeb 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/mdx/MdxComplexInstance.java @@ -293,12 +293,6 @@ public class MdxComplexInstance extends ModelInstance { final MdxModel model = (MdxModel) this.model; final List sortedGenericObjects = model.sortedGenericObjects; final Scene scene = this.scene; - if(scene == null) { - // too bad for this instance, this is not safe to update on null scene, got NPE from this during testing, - // so we are going to skip this cycle entirely! - // (Retera) - return; - } // Update the nodes for (int i = 0, l = sortedNodes.length; i < l; i++) { @@ -915,4 +909,15 @@ public class MdxComplexInstance extends ModelInstance { } } } + + public int clampFrame(int frameToClamp) { + final MdxModel model = (MdxModel) this.model; + final int sequenceId = this.sequence; + if(sequenceId >= 0 && sequenceId < model.sequences.size()) { + final Sequence sequence = model.sequences.get(sequenceId); + final long[] interval = sequence.getInterval(); + return (int)Math.max(interval[0], Math.min(interval[1], frameToClamp)); + } + return frameToClamp; + } } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java index 62a2e66..a9300da 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/rendersim/RenderWidget.java @@ -105,7 +105,7 @@ public interface RenderWidget { if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, allowRarityVariations) != null) { if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) { - instance.setFrame(lastWalkFrame); + instance.setFrame(instance.clampFrame(lastWalkFrame)); } this.currentAnimation = animationName; this.currentAnimationSecondaryTags = secondaryAnimationTags; @@ -130,7 +130,7 @@ public interface RenderWidget { allowRarityVariations); if (sequence != null) { if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) { - instance.setFrame(lastWalkFrame); + instance.setFrame(instance.clampFrame(lastWalkFrame)); } this.currentAnimation = animationName; this.currentAnimationSecondaryTags = secondaryAnimationTags; diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java index 012e30a..f6d15ab 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/simulation/CSimulation.java @@ -114,30 +114,9 @@ public class CSimulation implements CPlayerAPI { for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { final CBasePlayer configPlayer = config.getPlayer(i); final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex()); - CRace defaultRace; - switch(i) { - case 0: - defaultRace = CRace.NIGHTELF; - break; - case 1: - defaultRace = CRace.UNDEAD; - break; - case 2: - defaultRace = CRace.HUMAN; - break; - default: - defaultRace = CRace.OTHER; - break; - } + CRace defaultRace = CRace.ORC; final CPlayer newPlayer = new CPlayer(defaultRace, new float[] { startLoc.getX(), startLoc.getY() }, configPlayer); - if(i < 3) { - for(int j = 0; j < 3; j++) { - if(j != i) { - newPlayer.setAlliance(j, CAllianceType.PASSIVE, true); - } - } - } this.players.add(newPlayer); } this.players.get(this.players.size() - 4).setName(miscData.getLocalizedString("WESTRING_PLAYER_NA")); @@ -153,7 +132,6 @@ public class CSimulation implements CPlayerAPI { } this.commandErrorListener = commandErrorListener; - currentGameDayTimeElapsed = gameplayConstants.getGameDayLength()/2; } diff --git a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java index 5c45378..b579ef8 100644 --- a/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java +++ b/core/src/com/etheller/warsmash/viewer5/handlers/w3x/ui/MeleeUI.java @@ -1986,6 +1986,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma } private void reloadSelectedUnitUI(final RenderUnit unit) { + if(unit == null) { + return; + } final CUnit simulationUnit = unit.getSimulationUnit(); final float lifeRatioRemaining = simulationUnit.getLife() / simulationUnit.getMaxLife(); this.rootFrame.setText(this.unitLifeText, FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / " @@ -2419,6 +2422,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma @Override public void lifeChanged() { + if(selectedUnit == null) { + return; + } if (this.selectedUnit.getSimulationUnit().isDead()) { final RenderUnit preferredSelectionReplacement = this.selectedUnit.getPreferredSelectionReplacement(); final List newSelection;