Some bug fixes and network changes

This commit is contained in:
Retera 2021-07-10 17:18:32 -04:00
parent 7e5c8850e8
commit dd5544109f
9 changed files with 139 additions and 57 deletions

View File

@ -3,8 +3,7 @@ package com.etheller.warsmash.networking;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
import com.etheller.warsmash.networking.udp.OrderedUdpClient; import com.etheller.warsmash.networking.udp.OrderedUdpClient;
@ -17,7 +16,9 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager {
private final War3MapViewer game; private final War3MapViewer game;
private final Map<Integer, CPlayerUnitOrderExecutor> indexToExecutor = new HashMap<>(); private final Map<Integer, CPlayerUnitOrderExecutor> indexToExecutor = new HashMap<>();
private int latestCompletedTurn = -1; private int latestCompletedTurn = -1;
private int latestLocallyRequestedTurn = -1;
private final WarsmashClientWriter writer; private final WarsmashClientWriter writer;
private final Queue<QueuedMessage> queuedMessages = new ArrayDeque<>();
public WarsmashClient(final InetAddress serverAddress, final War3MapViewer game) public WarsmashClient(final InetAddress serverAddress, final War3MapViewer game)
throws UnknownHostException, IOException { throws UnknownHostException, IOException {
@ -52,7 +53,19 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager {
Gdx.app.postRunnable(new Runnable() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { 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() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { 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() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { 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() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { 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() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { 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 @Override
public void finishedTurn(final int gameTurnTick) { public void finishedTurn(final int gameTurnTick) {
if(WarsmashConstants.VERBOSE_LOGGING) {
System.out.println("finishedTurn " + gameTurnTick);
}
Gdx.app.postRunnable(new Runnable() { Gdx.app.postRunnable(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -119,6 +183,10 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager {
public void turnCompleted(final int gameTurnTick) { public void turnCompleted(final int gameTurnTick) {
this.writer.finishedTurn(gameTurnTick); this.writer.finishedTurn(gameTurnTick);
this.writer.send(); this.writer.send();
latestLocallyRequestedTurn = gameTurnTick;
while(!queuedMessages.isEmpty() && queuedMessages.peek().messageTurnTick == latestLocallyRequestedTurn) {
queuedMessages.poll().run();
}
} }
@Override @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 // 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 // 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. // on the heartbeat seq no. But at app layer, here, we can ignore it.
System.out.println("got heartbeat() from server");
} }
@Override @Override
@ -152,4 +221,18 @@ public class WarsmashClient implements ServerToClientListener, GameTurnManager {
public WarsmashClientWriter getWriter() { public WarsmashClientWriter getWriter() {
return this.writer; 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();
}
} }

View File

@ -14,12 +14,13 @@ import com.etheller.warsmash.networking.udp.OrderedUdpServer;
import com.etheller.warsmash.util.WarsmashConstants; import com.etheller.warsmash.util.WarsmashConstants;
public class WarsmashServer implements ClientToServerListener { public class WarsmashServer implements ClientToServerListener {
private static final int MAGIC_DELAY_OFFSET = 4;
private final OrderedUdpServer udpServer; private final OrderedUdpServer udpServer;
private final Map<SocketAddress, Integer> socketAddressToPlayerIndex = new HashMap<>(); private final Map<SocketAddress, Integer> socketAddressToPlayerIndex = new HashMap<>();
private final Set<SocketAddress> clientsAwaitingTurnFinished = new HashSet<>(); private final Map<SocketAddress, Integer> clientToTurnFinished = new HashMap<>();
private final List<Runnable> turnActions = new ArrayList<>(); private final List<Runnable> turnActions = new ArrayList<>();
private final WarsmashServerWriter writer; private final WarsmashServerWriter writer;
private int currentTurnTick = 0; private int currentTurnTick = MAGIC_DELAY_OFFSET;
private boolean gameStarted = false; private boolean gameStarted = false;
private long lastServerHeartbeatTime = 0; private long lastServerHeartbeatTime = 0;
@ -40,7 +41,7 @@ public class WarsmashServer implements ClientToServerListener {
} }
private void startTurn() { 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.finishedTurn(this.currentTurnTick);
WarsmashServer.this.writer.send(); WarsmashServer.this.writer.send();
this.currentTurnTick++; this.currentTurnTick++;
@ -139,25 +140,29 @@ public class WarsmashServer implements ClientToServerListener {
} }
@Override @Override
public void finishedTurn(final SocketAddress sourceAddress, final int gameTurnTick) { public void finishedTurn(final SocketAddress sourceAddress, final int clientGameTurnTick) {
// System.out.println("finishedTurn(" + gameTurnTick + ") from " + sourceAddress); int gameTurnTick = clientGameTurnTick + MAGIC_DELAY_OFFSET;
if(WarsmashConstants.VERBOSE_LOGGING) {
System.out.println("finishedTurn(" + gameTurnTick + ") from " + sourceAddress);
}
if (!this.gameStarted) { if (!this.gameStarted) {
throw new IllegalStateException( throw new IllegalStateException(
"Client should not send us finishedTurn() message when game has not started!"); "Client should not send us finishedTurn() message when game has not started!");
} }
if (gameTurnTick == this.currentTurnTick) { clientToTurnFinished.put(sourceAddress, clientGameTurnTick);
this.clientsAwaitingTurnFinished.remove(sourceAddress); boolean allDone = true;
if (this.clientsAwaitingTurnFinished.isEmpty()) { for(SocketAddress clientAddress: socketAddressToPlayerIndex.keySet()) {
for (final Runnable turnAction : this.turnActions) { Integer turnFinishedValue = clientToTurnFinished.get(clientAddress);
turnAction.run(); if(turnFinishedValue == null || turnFinishedValue < clientGameTurnTick) {
} allDone = false;
this.turnActions.clear();
startTurn();
} }
} }
else { if (allDone) {
System.err.println("received bad finishedTurn() with remote gameTurnTick=" + gameTurnTick for (final Runnable turnAction : this.turnActions) {
+ ", server local currenTurnTick=" + this.currentTurnTick); turnAction.run();
}
this.turnActions.clear();
startTurn();
} }
} }
@ -167,7 +172,7 @@ public class WarsmashServer implements ClientToServerListener {
long currentTimeMillis = System.currentTimeMillis(); long currentTimeMillis = System.currentTimeMillis();
if(currentTimeMillis - lastServerHeartbeatTime > 3000) { if(currentTimeMillis - lastServerHeartbeatTime > 3000) {
// 3 seconds of frame skipping, make sure we keep in contact with client // 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.heartbeat();
WarsmashServer.this.writer.send(); WarsmashServer.this.writer.send();
lastServerHeartbeatTime = currentTimeMillis; lastServerHeartbeatTime = currentTimeMillis;

View File

@ -8,7 +8,7 @@ public class WarsmashConstants {
*/ */
public static int GAME_VERSION = 1; public static int GAME_VERSION = 1;
public static final int REPLACEABLE_TEXTURE_LIMIT = 64; 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 int PORT_NUMBER = 6115;
public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f; public static final float BUILDING_CONSTRUCT_START_LIFE = 0.1f;
public static final int BUILD_QUEUE_SIZE = 7; 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 String DEFAULT_STRING = "Default string";
public static final boolean CATCH_CURSOR = true; public static final boolean CATCH_CURSOR = true;
public static final boolean VERBOSE_LOGGING = true;
} }

View File

@ -299,6 +299,7 @@ public abstract class Node extends GenericNode {
this.wasDirty = false; this.wasDirty = false;
} }
this.updateObject(dt, scene); this.updateObject(dt, scene);
this.updateChildren(dt, scene); this.updateChildren(dt, scene);
} }

View File

@ -114,7 +114,10 @@ public abstract class SkeletalNode extends GenericNode {
computedRotation = rotationHeap; computedRotation = rotationHeap;
computedRotation.set(this.parent.inverseWorldRotation); 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); this.convertBasis(computedRotation);
} }

View File

@ -293,12 +293,6 @@ public class MdxComplexInstance extends ModelInstance {
final MdxModel model = (MdxModel) this.model; final MdxModel model = (MdxModel) this.model;
final List<GenericObject> sortedGenericObjects = model.sortedGenericObjects; final List<GenericObject> sortedGenericObjects = model.sortedGenericObjects;
final Scene scene = this.scene; 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 // Update the nodes
for (int i = 0, l = sortedNodes.length; i < l; i++) { 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;
}
} }

View File

@ -105,7 +105,7 @@ public interface RenderWidget {
if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet, if (SequenceUtils.randomSequence(this.instance, animationName, this.recycleSet,
allowRarityVariations) != null) { allowRarityVariations) != null) {
if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) { if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) {
instance.setFrame(lastWalkFrame); instance.setFrame(instance.clampFrame(lastWalkFrame));
} }
this.currentAnimation = animationName; this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags; this.currentAnimationSecondaryTags = secondaryAnimationTags;
@ -130,7 +130,7 @@ public interface RenderWidget {
allowRarityVariations); allowRarityVariations);
if (sequence != null) { if (sequence != null) {
if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) { if(lastWalkFrame != -1 && animationName == PrimaryTag.WALK && currentAnimation != PrimaryTag.WALK) {
instance.setFrame(lastWalkFrame); instance.setFrame(instance.clampFrame(lastWalkFrame));
} }
this.currentAnimation = animationName; this.currentAnimation = animationName;
this.currentAnimationSecondaryTags = secondaryAnimationTags; this.currentAnimationSecondaryTags = secondaryAnimationTags;

View File

@ -114,30 +114,9 @@ public class CSimulation implements CPlayerAPI {
for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) { for (int i = 0; i < WarsmashConstants.MAX_PLAYERS; i++) {
final CBasePlayer configPlayer = config.getPlayer(i); final CBasePlayer configPlayer = config.getPlayer(i);
final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex()); final War3MapConfigStartLoc startLoc = config.getStartLoc(configPlayer.getStartLocationIndex());
CRace defaultRace; CRace defaultRace = CRace.ORC;
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;
}
final CPlayer newPlayer = new CPlayer(defaultRace, new float[] { startLoc.getX(), startLoc.getY() }, final CPlayer newPlayer = new CPlayer(defaultRace, new float[] { startLoc.getX(), startLoc.getY() },
configPlayer); 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.add(newPlayer);
} }
this.players.get(this.players.size() - 4).setName(miscData.getLocalizedString("WESTRING_PLAYER_NA")); 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; this.commandErrorListener = commandErrorListener;
currentGameDayTimeElapsed = gameplayConstants.getGameDayLength()/2;
} }

View File

@ -1986,6 +1986,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
} }
private void reloadSelectedUnitUI(final RenderUnit unit) { private void reloadSelectedUnitUI(final RenderUnit unit) {
if(unit == null) {
return;
}
final CUnit simulationUnit = unit.getSimulationUnit(); final CUnit simulationUnit = unit.getSimulationUnit();
final float lifeRatioRemaining = simulationUnit.getLife() / simulationUnit.getMaxLife(); final float lifeRatioRemaining = simulationUnit.getLife() / simulationUnit.getMaxLife();
this.rootFrame.setText(this.unitLifeText, FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / " this.rootFrame.setText(this.unitLifeText, FastNumberFormat.formatWholeNumber(simulationUnit.getLife()) + " / "
@ -2419,6 +2422,9 @@ public class MeleeUI implements CUnitStateListener, CommandButtonListener, Comma
@Override @Override
public void lifeChanged() { public void lifeChanged() {
if(selectedUnit == null) {
return;
}
if (this.selectedUnit.getSimulationUnit().isDead()) { if (this.selectedUnit.getSimulationUnit().isDead()) {
final RenderUnit preferredSelectionReplacement = this.selectedUnit.getPreferredSelectionReplacement(); final RenderUnit preferredSelectionReplacement = this.selectedUnit.getPreferredSelectionReplacement();
final List<RenderWidget> newSelection; final List<RenderWidget> newSelection;