Working on the server
This commit is contained in:
1 parent
075750442f
commit
28c6e83eda
30 files changed
+646
-362
No files matched your search
+3
-3
@@ -42,11 +42,11 @@ real-time delay.
|
||||
Start the first multiplayer transport iteration:
|
||||
|
||||
```powershell
|
||||
.\server\build\Release\GameServer.exe --campaign battle_zone --location uni_exterior
|
||||
.\server\build\Release\GameServer.exe --campaign battle_zone_multiplayer --location uni_exterior
|
||||
```
|
||||
|
||||
The listener defaults to `ws://0.0.0.0:8080`. Launch the Windows graphical game
|
||||
afterward and select `battle_zone`; that campaign connects to
|
||||
afterward and select `battle_zone_multiplayer`; that campaign connects to
|
||||
`ws://127.0.0.1:8080`. Other campaigns continue to use the in-process server.
|
||||
The server broadcasts an initial full state and then one every five seconds.
|
||||
Client commands are intentionally discarded in this iteration.
|
||||
@@ -83,4 +83,4 @@ CTest covers:
|
||||
- repeated `LocationGameState::setup()` preserving mutable runtime state;
|
||||
- ticking a prepared location with zero players;
|
||||
- idle server startup;
|
||||
- headless loading of `battle_zone/uni_exterior`.
|
||||
- headless loading of `battle_zone_multiplayer/uni_exterior`.
|
||||
+29
-1
@@ -3,6 +3,8 @@
|
||||
#include "game_server/WebsocketGameServer.h"
|
||||
#endif
|
||||
#include "Campaign.h"
|
||||
#include "LocationState.h"
|
||||
#include "utils/Utils.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
@@ -88,7 +90,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
|
||||
//Temporary override:
|
||||
campaignId = "battle_zone";
|
||||
campaignId = "battle_zone_multiplayer";
|
||||
locationId = "uni_exterior";
|
||||
|
||||
std::signal(SIGINT, requestStop);
|
||||
@@ -98,6 +100,7 @@ int main(int argc, char** argv)
|
||||
if (!campaignId.empty()) {
|
||||
try {
|
||||
FRG::CampaignRegistry::instance().loadFromJson(campaignsPath);
|
||||
std::cout << "[server] Loading...\n" << std::flush;
|
||||
const FRG::CampaignConfig* campaign =
|
||||
FRG::CampaignRegistry::instance().find(campaignId);
|
||||
if (!campaign)
|
||||
@@ -113,7 +116,32 @@ int main(int argc, char** argv)
|
||||
"' has no location '" + selectedLocation + "'");
|
||||
server.startCampaign(*campaign, std::string(), true, false);
|
||||
server.prepareLocation(*location);
|
||||
const std::string startState =
|
||||
FRG::readTextFile(campaign->startStatePath);
|
||||
if (!startState.empty()) {
|
||||
try {
|
||||
nlohmann::json persisted =
|
||||
nlohmann::json::parse(startState);
|
||||
nlohmann::json networkState;
|
||||
FRG::ClientLocationState ignoredLocalState;
|
||||
FRG::loadSinglePlayerPersistence(
|
||||
persisted, networkState, ignoredLocalState);
|
||||
networkState["campaignId"] = campaignId;
|
||||
networkState["currentLocationId"] = selectedLocation;
|
||||
server.load(networkState);
|
||||
std::cout << "Loaded initial state '"
|
||||
<< campaign->startStatePath << "'\n";
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "Ignoring invalid initial state '"
|
||||
<< campaign->startStatePath << "': "
|
||||
<< error.what() << '\n';
|
||||
}
|
||||
}
|
||||
server.activateLocation(selectedLocation);
|
||||
if (!campaign->tutorial) {
|
||||
server.submit(FRG::SetTutorialStepCommand{
|
||||
FRG::TutorialStep::Completed});
|
||||
}
|
||||
std::cout << "Loaded campaign '" << campaignId
|
||||
<< "', location '" << selectedLocation
|
||||
<< "' with shared player '" << server.localPlayerId() << "'\n";
|
||||
|
||||
@@ -16,10 +16,69 @@ int fail(const char* message)
|
||||
|
||||
int main()
|
||||
{
|
||||
FRG::ClientLocationState localState;
|
||||
auto camera = localState.getOrCreate("test_location");
|
||||
camera->cameraAzimuth = 0.75f;
|
||||
camera->cameraInclination = 1.25f;
|
||||
nlohmann::json localJson;
|
||||
localState.save(localJson);
|
||||
FRG::ClientLocationState restoredLocalState;
|
||||
restoredLocalState.load(localJson);
|
||||
const auto restoredCamera =
|
||||
restoredLocalState.getOrCreate("test_location");
|
||||
if (restoredCamera->cameraAzimuth != 0.75f ||
|
||||
restoredCamera->cameraInclination != 1.25f) {
|
||||
return fail("Local camera state did not round trip");
|
||||
}
|
||||
|
||||
FRG::LocationNetworkState networkLocationState;
|
||||
nlohmann::json networkLocationJson;
|
||||
networkLocationState.save(networkLocationJson);
|
||||
if (networkLocationJson.contains("cameraAzimuth") ||
|
||||
networkLocationJson.contains("cameraInclination")) {
|
||||
return fail("Network location state contains local camera fields");
|
||||
}
|
||||
|
||||
const nlohmann::json authoritativeJson{
|
||||
{"schemaVersion", FRG::GameState::kSchemaVersion},
|
||||
{"campaignId", "test"}
|
||||
};
|
||||
nlohmann::json persistenceJson;
|
||||
FRG::saveSinglePlayerPersistence(
|
||||
authoritativeJson, localState, persistenceJson);
|
||||
nlohmann::json restoredAuthoritativeJson;
|
||||
FRG::ClientLocationState restoredPersistenceLocalState;
|
||||
FRG::loadSinglePlayerPersistence(
|
||||
persistenceJson, restoredAuthoritativeJson,
|
||||
restoredPersistenceLocalState);
|
||||
if (restoredAuthoritativeJson != authoritativeJson ||
|
||||
restoredPersistenceLocalState.getOrCreate("test_location")
|
||||
->cameraAzimuth != 0.75f) {
|
||||
return fail("Composed single-player state did not round trip");
|
||||
}
|
||||
if (persistenceJson.at("networkState").contains("localState")) {
|
||||
return fail("Local state leaked into authoritative state");
|
||||
}
|
||||
|
||||
const std::filesystem::path repositoryRoot =
|
||||
std::filesystem::path(__FILE__).parent_path().parent_path().parent_path();
|
||||
std::filesystem::current_path(repositoryRoot);
|
||||
|
||||
FRG::CampaignRegistry::instance().loadFromJson(
|
||||
"resources/config/campaigns.json");
|
||||
const FRG::CampaignConfig* singlePlayerBattleZone =
|
||||
FRG::CampaignRegistry::instance().find("battle_zone");
|
||||
const FRG::CampaignConfig* multiplayerBattleZoneConfig =
|
||||
FRG::CampaignRegistry::instance().find("battle_zone_multiplayer");
|
||||
if (FRG::CampaignRegistry::instance().all().size() != 3 ||
|
||||
!singlePlayerBattleZone || !multiplayerBattleZoneConfig) {
|
||||
return fail("Expected three campaigns including both Battle Zone modes");
|
||||
}
|
||||
if (singlePlayerBattleZone->startStatePath !=
|
||||
multiplayerBattleZoneConfig->startStatePath) {
|
||||
return fail("Battle Zone modes do not share their initial state");
|
||||
}
|
||||
|
||||
FRG::LocationDefinition definition;
|
||||
definition.config.id = "test_location";
|
||||
|
||||
@@ -87,8 +146,12 @@ int main()
|
||||
}
|
||||
|
||||
FRG::InternalGameServer networkServer;
|
||||
networkServer.startCampaign(battleZone, {}, true, false);
|
||||
FRG::CampaignConfig multiplayerBattleZone = battleZone;
|
||||
multiplayerBattleZone.id = "battle_zone_multiplayer";
|
||||
networkServer.startCampaign(multiplayerBattleZone, {}, true, false);
|
||||
const auto networkSnapshot = networkServer.snapshot();
|
||||
if (networkSnapshot->campaignId != "battle_zone_multiplayer")
|
||||
return fail("Network campaign ID was not retained");
|
||||
if (networkSnapshot->players.count("player") != 1)
|
||||
return fail("Network campaign shared player was not created");
|
||||
if (networkSnapshot->players.count("battle_zone_test_bot") != 0)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "game_server/NetworkProtocol.h"
|
||||
#include "LocationState.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
@@ -16,10 +17,14 @@ void require(bool condition, const char* message)
|
||||
|
||||
int main()
|
||||
{
|
||||
FRG::LocationNetworkState locationState;
|
||||
nlohmann::json savedLocationState;
|
||||
locationState.save(savedLocationState);
|
||||
const nlohmann::json original{
|
||||
{"schemaVersion", 1},
|
||||
{"campaignId", "battle_zone"},
|
||||
{"players", {{"player", {{"hp", 200.0}}}}}
|
||||
{"schemaVersion", 2},
|
||||
{"campaignId", "battle_zone_multiplayer"},
|
||||
{"players", {{"player", {{"hp", 200.0}}}}},
|
||||
{"locations", {{"uni_exterior", {{"state", savedLocationState}}}}}
|
||||
};
|
||||
const std::string message = FRG::makeStateMessage(original);
|
||||
nlohmann::json parsed;
|
||||
@@ -28,6 +33,10 @@ int main()
|
||||
FRG::parseStateMessage(message, parsed, error),
|
||||
"valid state envelope was rejected");
|
||||
require(parsed == original, "state payload did not round trip");
|
||||
require(
|
||||
!parsed["locations"]["uni_exterior"]["state"].contains("cameraAzimuth") &&
|
||||
!parsed["locations"]["uni_exterior"]["state"].contains("cameraInclination"),
|
||||
"local camera state leaked into the network payload");
|
||||
|
||||
require(
|
||||
!FRG::parseStateMessage(
|
||||
|
||||
Reference in new issue
Block a user