134 lines
4.1 KiB
C++
134 lines
4.1 KiB
C++
#include "RemoteGameServerHost.h"
|
|
#include "WebsocketGameClient.h"
|
|
#include "../Campaign.h"
|
|
#include "../Location.h"
|
|
#include <stdexcept>
|
|
|
|
namespace FRG {
|
|
|
|
RemoteGameServerHost::RemoteGameServerHost(WebsocketGameClient& client)
|
|
: client_(client)
|
|
{
|
|
}
|
|
|
|
void RemoteGameServerHost::initializeCampaign(
|
|
const std::string& campaignId, const std::string&,
|
|
const std::string& zipFile)
|
|
{
|
|
localState_.clear();
|
|
const CampaignConfig* campaign =
|
|
CampaignRegistry::instance().find(campaignId);
|
|
if (!campaign)
|
|
throw std::runtime_error("Unknown remote campaign '" + campaignId + "'");
|
|
client_.expectCompatibility(
|
|
campaign->networkCompatibility.empty()
|
|
? "lockstep-v2:" + campaignId
|
|
: campaign->networkCompatibility);
|
|
client_.replica_.startCampaign(*campaign, zipFile, true, false);
|
|
}
|
|
|
|
void RemoteGameServerHost::clearCampaign()
|
|
{
|
|
runtimeLocations_.clear();
|
|
localState_.clear();
|
|
client_.replica_.clearCampaign();
|
|
}
|
|
|
|
void RemoteGameServerHost::resetCampaign(
|
|
const std::string&, const std::string&)
|
|
{
|
|
// The authoritative remote campaign cannot be reset by this client.
|
|
}
|
|
|
|
bool RemoteGameServerHost::prepareLocation(const LocationConfig& config)
|
|
{
|
|
auto existing = client_.replica_.state_.locations.find(config.id);
|
|
if (existing != client_.replica_.state_.locations.end() &&
|
|
existing->second.definition) {
|
|
// The location was prepared before initial_state. Loading the
|
|
// authoritative snapshot overlays mutable state while retaining this
|
|
// definition; preparing it again would reset the assigned player's
|
|
// position to LocationConfig::playerPosition.
|
|
return false;
|
|
}
|
|
return client_.replica_.prepareLocation(config);
|
|
}
|
|
|
|
void RemoteGameServerHost::setupRuntimeLocation(
|
|
const std::string& id, const std::shared_ptr<Location>& location,
|
|
LocationSetup params, AudioPlayerAsync* audioPlayer)
|
|
{
|
|
auto stored = client_.replica_.state_.locations.find(id);
|
|
if (stored == client_.replica_.state_.locations.end())
|
|
throw std::runtime_error("Remote location state was not prepared");
|
|
params.definition = stored->second.definition;
|
|
params.gameState = &stored->second;
|
|
params.localState = localState_.getOrCreate(id);
|
|
location->setup(params, audioPlayer);
|
|
}
|
|
|
|
Location* RemoteGameServerHost::runtimeLocation(const std::string& id) const
|
|
{
|
|
auto found = runtimeLocations_.find(id);
|
|
return found == runtimeLocations_.end() ? nullptr : found->second.get();
|
|
}
|
|
|
|
bool RemoteGameServerHost::hasRuntimeLocation(const std::string& id) const
|
|
{
|
|
return runtimeLocations_.count(id) > 0;
|
|
}
|
|
|
|
std::vector<std::string> RemoteGameServerHost::loadedRuntimeLocationIds() const
|
|
{
|
|
std::vector<std::string> result;
|
|
result.reserve(runtimeLocations_.size());
|
|
for (const auto& [id, location] : runtimeLocations_) result.push_back(id);
|
|
return result;
|
|
}
|
|
|
|
void RemoteGameServerHost::clearRuntimeLocations(bool)
|
|
{
|
|
runtimeLocations_.clear();
|
|
}
|
|
|
|
void RemoteGameServerHost::bindRuntimeLocation(
|
|
const std::string& id, std::shared_ptr<Location> location)
|
|
{
|
|
runtimeLocations_[id] = std::move(location);
|
|
auto stored = client_.replica_.state_.locations.find(id);
|
|
if (stored != client_.replica_.state_.locations.end() &&
|
|
stored->second.hasPersistentState) {
|
|
runtimeLocations_[id]->applyAuxiliaryState(stored->second);
|
|
}
|
|
}
|
|
|
|
void RemoteGameServerHost::update(std::int64_t deltaMs)
|
|
{
|
|
client_.update(deltaMs);
|
|
for (auto& completed : client_.takeCompletedSimulationTicks()) {
|
|
if (completed.locationId.empty()) continue;
|
|
auto runtime = runtimeLocations_.find(completed.locationId);
|
|
if (runtime == runtimeLocations_.end() || !runtime->second) continue;
|
|
runtime->second->update(kSimulationTickMs, completed.events);
|
|
}
|
|
}
|
|
|
|
void RemoteGameServerHost::save(nlohmann::json&)
|
|
{
|
|
// Persistence remains server-side.
|
|
}
|
|
|
|
void RemoteGameServerHost::load(const nlohmann::json& in)
|
|
{
|
|
// The authoritative half is loaded by the standalone server. Each client
|
|
// restores only its local presentation state from the shared start state.
|
|
nlohmann::json ignoredNetworkState;
|
|
ClientLocationState loadedLocalState;
|
|
loadSinglePlayerPersistence(in, ignoredNetworkState, loadedLocalState);
|
|
nlohmann::json localStateJson;
|
|
loadedLocalState.save(localStateJson);
|
|
localState_.load(localStateJson);
|
|
}
|
|
|
|
} // namespace FRG
|