Set up basic deterministic lockstep
This commit is contained in:
1 parent
28c6e83eda
commit
23fef6f349
26 files changed
+1172
-239
No files matched your search
+53
-17
@@ -1,4 +1,5 @@
|
||||
#include "Game.h"
|
||||
#include "game_server/NetworkProtocol.h"
|
||||
#include "AnimatedModel.h"
|
||||
#include "Localization.h"
|
||||
#include "utils/Utils.h"
|
||||
@@ -89,7 +90,6 @@ namespace FRG
|
||||
, gameServer(internalGameServer)
|
||||
, gameServerHost(localGameServerHost)
|
||||
, menuManager(renderer, gameServer, *audioPlayer)
|
||||
, newTickCount(0)
|
||||
, lastTickCount(0)
|
||||
{
|
||||
}
|
||||
@@ -553,18 +553,29 @@ namespace FRG
|
||||
if (!campaign) return;
|
||||
#if defined(NETWORK)
|
||||
remoteCampaignActive = campaignId == "battle_zone_multiplayer";
|
||||
remoteInitialStateBound = false;
|
||||
if (remoteCampaignActive) {
|
||||
logger() << "[network] Campaign '" << campaignId
|
||||
<< "' selected; creating WebSocket client" << std::endl;
|
||||
websocketGameClient = std::make_unique<WebsocketGameClient>();
|
||||
remoteGameServerHost =
|
||||
std::make_unique<RemoteGameServerHost>(*websocketGameClient);
|
||||
gameServer.use(*websocketGameClient);
|
||||
gameServerHost.use(*remoteGameServerHost);
|
||||
logger() << "[network] Remote game server routes installed"
|
||||
<< std::endl;
|
||||
} else {
|
||||
logger() << "[network] Campaign '" << campaignId
|
||||
<< "' uses the in-process server" << std::endl;
|
||||
gameServer.use(internalGameServer);
|
||||
gameServerHost.use(localGameServerHost);
|
||||
}
|
||||
#else
|
||||
remoteCampaignActive = false;
|
||||
remoteInitialStateBound = false;
|
||||
logger() << "[network] Networking was not compiled into this client; "
|
||||
"campaign '" << campaignId << "' uses the in-process server"
|
||||
<< std::endl;
|
||||
#endif
|
||||
gameServerHost.initializeCampaign(
|
||||
campaignId, campaign->questsPath, CONST_ZIP_FILE);
|
||||
@@ -588,6 +599,7 @@ namespace FRG
|
||||
websocketGameClient.reset();
|
||||
#endif
|
||||
remoteCampaignActive = false;
|
||||
remoteInitialStateBound = false;
|
||||
renderer.textureManager.UnloadUnused(); // the player's textures
|
||||
}
|
||||
|
||||
@@ -1120,7 +1132,7 @@ namespace FRG
|
||||
|
||||
int64_t Game::getSyncTimeMs() {
|
||||
int64_t localNow = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
return localNow;
|
||||
}
|
||||
|
||||
@@ -1129,21 +1141,15 @@ namespace FRG
|
||||
|
||||
if (lastTickCount == 0) {
|
||||
lastTickCount = getSyncTimeMs();
|
||||
|
||||
lastTickCount = (lastTickCount / 50) * 50;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
newTickCount = getSyncTimeMs();
|
||||
|
||||
newTickCount = (newTickCount / 50) * 50;
|
||||
|
||||
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
|
||||
|
||||
int64_t delta = newTickCount - lastTickCount;
|
||||
|
||||
lastTickCount = newTickCount;
|
||||
const int64_t now = getSyncTimeMs();
|
||||
const int64_t delta =
|
||||
(std::min)(now - lastTickCount,
|
||||
static_cast<int64_t>(CONST_MAX_TIME_INTERVAL));
|
||||
lastTickCount = now;
|
||||
if (delta > 0) {
|
||||
|
||||
updateDarklandsFlash(delta);
|
||||
menuManager.uiManager.update(static_cast<float>(delta));
|
||||
@@ -1155,9 +1161,39 @@ namespace FRG
|
||||
|
||||
if (loadingCompleted)
|
||||
{
|
||||
// The authoritative implementation advances simulation; a future
|
||||
// network client can use the same call to poll/apply server updates.
|
||||
gameServerHost.update(delta);
|
||||
if (remoteCampaignActive) {
|
||||
// Remote authoritative time comes only from contiguous server ticks.
|
||||
gameServerHost.update(delta);
|
||||
#if defined(NETWORK)
|
||||
// The connection-assigned slot is known only after initial_state.
|
||||
// Always rebuild once: replica_.load() replaces authoritative
|
||||
// location state even when the assigned ID remains "player".
|
||||
if (websocketGameClient && websocketGameClient->initialized() &&
|
||||
(!remoteInitialStateBound || !player ||
|
||||
player->entityId() != server().localPlayerId())) {
|
||||
const auto assignedState = stateSnapshot();
|
||||
const std::string locationId = assignedState
|
||||
? assignedState->currentLocationId : std::string();
|
||||
unloadLocation(false);
|
||||
player = Location::createPlayer(
|
||||
renderer, audioPlayer.get(), server(),
|
||||
server().localPlayerId());
|
||||
if (player)
|
||||
player->onDeathAnimComplete =
|
||||
[this]() { startDarklandsTransition(); };
|
||||
if (!locationId.empty()) loadLocation(locationId);
|
||||
remoteInitialStateBound = true;
|
||||
logger() << "[network] Runtime rebound to assigned player '"
|
||||
<< server().localPlayerId() << "'" << std::endl;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
simulationAccumulatorMs += delta;
|
||||
while (simulationAccumulatorMs >= kSimulationTickMs) {
|
||||
gameServerHost.update(kSimulationTickMs);
|
||||
simulationAccumulatorMs -= kSimulationTickMs;
|
||||
}
|
||||
}
|
||||
#if defined(NETWORK)
|
||||
if (remoteCampaignActive && websocketGameClient &&
|
||||
websocketGameClient->connectionFailed()) {
|
||||
|
||||
Reference in new issue
Block a user