#include "RemoteGameServerHost.h" #include "WebsocketGameClient.h" #include "../Campaign.h" #include "../Location.h" #include 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, 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 RemoteGameServerHost::loadedRuntimeLocationIds() const { std::vector 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) { 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