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.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<Integer, CPlayerUnitOrderExecutor> indexToExecutor = new HashMap<>();
private int latestCompletedTurn = -1;
private int latestLocallyRequestedTurn = -1;
private final WarsmashClientWriter writer;
private final Queue<QueuedMessage> 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();
}
}

View File

@ -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<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 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;

View File

@ -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;
}

View File

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

View File

@ -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);
}

View File

@ -293,12 +293,6 @@ public class MdxComplexInstance extends ModelInstance {
final MdxModel model = (MdxModel) this.model;
final List<GenericObject> 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;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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<RenderWidget> newSelection;