Separating state out of the location
This commit is contained in:
1 parent
5e9a663812
commit
297bd7377f
45 files changed
+2114
-778
No files matched your search
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGameServer.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class AudioPlayerAsync;
|
||||
|
||||
namespace FRG {
|
||||
|
||||
class Location;
|
||||
struct LocationConfig;
|
||||
struct LocationSetup;
|
||||
|
||||
// Client-only lifecycle and presentation integration for the in-process server.
|
||||
// The public gameplay contract remains IGameServer and is graphics-free.
|
||||
class IGameServerHost {
|
||||
public:
|
||||
virtual ~IGameServerHost() = default;
|
||||
|
||||
virtual void initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
const std::string& zipFile) = 0;
|
||||
virtual void clearCampaign() = 0;
|
||||
virtual void resetCampaign(
|
||||
const std::string& questsPath, const std::string& zipFile) = 0;
|
||||
virtual bool prepareLocation(const LocationConfig& config) = 0;
|
||||
|
||||
virtual void setupRuntimeLocation(
|
||||
const std::string& id, const std::shared_ptr<Location>& location,
|
||||
LocationSetup params, AudioPlayerAsync* audioPlayer) = 0;
|
||||
virtual Location* runtimeLocation(const std::string& id) const = 0;
|
||||
virtual bool hasRuntimeLocation(const std::string& id) const = 0;
|
||||
virtual std::vector<std::string> loadedRuntimeLocationIds() const = 0;
|
||||
virtual void clearRuntimeLocations(bool retainState) = 0;
|
||||
virtual void bindRuntimeLocation(
|
||||
const std::string& id, std::shared_ptr<Location> location) = 0;
|
||||
|
||||
virtual void update(std::int64_t deltaMs) = 0;
|
||||
virtual void save(nlohmann::json& out) = 0;
|
||||
virtual void load(const nlohmann::json& in) = 0;
|
||||
};
|
||||
|
||||
} // namespace FRG
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace FRG {
|
||||
|
||||
struct CampaignConfig;
|
||||
struct LocationConfig;
|
||||
|
||||
// Graphics-free administrative lifecycle used by local and standalone hosts.
|
||||
class IGameServerLifecycle {
|
||||
public:
|
||||
virtual ~IGameServerLifecycle() = default;
|
||||
|
||||
virtual void startCampaign(
|
||||
const CampaignConfig& campaign, const std::string& zipFile,
|
||||
bool createLocalPlayer, bool enableTestBot) = 0;
|
||||
virtual bool prepareLocation(const LocationConfig& config) = 0;
|
||||
virtual void activateLocation(const std::string& locationId) = 0;
|
||||
};
|
||||
|
||||
} // namespace FRG
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "InternalGameServer.h"
|
||||
#include "../Campaign.h"
|
||||
#include "../Location.h"
|
||||
#include "../content/LocationDefinition.h"
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
@@ -8,8 +8,6 @@
|
||||
namespace FRG {
|
||||
|
||||
namespace {
|
||||
// Set this to false to disable the battle-zone remote-player test bot.
|
||||
constexpr bool kEnableBattleZoneTestBot = true;
|
||||
constexpr const char* kBattleZoneCampaignId = "battle_zone";
|
||||
constexpr const char* kBattleZoneTestBotId = "battle_zone_test_bot";
|
||||
constexpr std::int64_t kBattleZoneTestBotIntervalMs = 5000;
|
||||
@@ -20,7 +18,7 @@ InternalGameServer::InternalGameServer() = default;
|
||||
void InternalGameServer::ensureBattleZoneTestBot()
|
||||
{
|
||||
if (state_.campaignId != kBattleZoneCampaignId) return;
|
||||
if (!kEnableBattleZoneTestBot) {
|
||||
if (!battleZoneTestBotEnabled_) {
|
||||
state_.players.erase(kBattleZoneTestBotId);
|
||||
return;
|
||||
}
|
||||
@@ -33,7 +31,7 @@ void InternalGameServer::ensureBattleZoneTestBot()
|
||||
|
||||
void InternalGameServer::updateBattleZoneTestBot(std::int64_t deltaMs)
|
||||
{
|
||||
if (!kEnableBattleZoneTestBot ||
|
||||
if (!battleZoneTestBotEnabled_ ||
|
||||
state_.campaignId != kBattleZoneCampaignId ||
|
||||
state_.currentLocationName.empty() ||
|
||||
state_.players.find(kBattleZoneTestBotId) == state_.players.end()) {
|
||||
@@ -57,24 +55,66 @@ void InternalGameServer::initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
const std::string& zipFile)
|
||||
{
|
||||
state_.campaignId = campaignId;
|
||||
CampaignConfig campaign;
|
||||
campaign.id = campaignId;
|
||||
campaign.questsPath = questsPath;
|
||||
startCampaign(campaign, zipFile, true, true);
|
||||
}
|
||||
|
||||
void InternalGameServer::startCampaign(
|
||||
const CampaignConfig& campaign, const std::string& zipFile,
|
||||
bool createLocalPlayer, bool enableTestBot)
|
||||
{
|
||||
resourceZipFile_ = zipFile;
|
||||
battleZoneTestBotEnabled_ = enableTestBot;
|
||||
state_.campaignId = campaign.id;
|
||||
state_.currentLocationName.clear();
|
||||
state_.locations.clear();
|
||||
state_.players.clear();
|
||||
state_.questJournal.loadFromFile(questsPath, zipFile);
|
||||
state_.questJournal.loadFromFile(campaign.questsPath, zipFile);
|
||||
state_.globalFloats["player_hp"] = 200.f;
|
||||
|
||||
auto player = std::make_shared<CharacterState>();
|
||||
player->configureAsPlayer();
|
||||
state_.players.emplace(localPlayerId_, std::move(player));
|
||||
if (createLocalPlayer) {
|
||||
auto player = std::make_shared<CharacterState>();
|
||||
player->configureAsPlayer();
|
||||
player->creationInfo.animationPaths = {
|
||||
{AnimationState::STAND,
|
||||
"resources/w/gg/new2/gg_stand_idle001_small.anim"},
|
||||
{AnimationState::WALK,
|
||||
"resources/w/gg/new2/gg_walk001_small.anim"},
|
||||
{AnimationState::STAND_TO_ACTION,
|
||||
"resources/w/gg/new2/gg_stand_to_action001_small.anim"},
|
||||
{AnimationState::ACTION_ATTACK,
|
||||
"resources/w/gg/new2/gg_action_chop001_small.anim"},
|
||||
{AnimationState::ACTION_ATTACK_2,
|
||||
"resources/w/gg/new2/gg_action_stab001_small.anim"},
|
||||
{AnimationState::ACTION_IDLE,
|
||||
"resources/w/gg/new2/gg_action_idle002_small.anim"},
|
||||
{AnimationState::ACTION_TO_STAND,
|
||||
"resources/w/gg/new2/gg_action_to_stand001_small.anim"},
|
||||
{AnimationState::ACTION_TO_DEATH,
|
||||
"resources/w/gg/new2/gg_die001_small.anim"},
|
||||
{AnimationState::DEATH_IDLE,
|
||||
"resources/w/gg/new2/gg_die_idle001_small.anim"}};
|
||||
loadCharacterAnimationMetadata(*player, zipFile);
|
||||
state_.players.emplace(localPlayerId_, std::move(player));
|
||||
}
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
ensureBattleZoneTestBot();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::activateLocation(const std::string& locationId)
|
||||
{
|
||||
if (state_.locations.count(locationId) == 0)
|
||||
throw std::runtime_error(
|
||||
"Cannot activate unprepared location '" + locationId + "'");
|
||||
state_.currentLocationName = locationId;
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::clearCampaign()
|
||||
{
|
||||
runtimeLocations_.clear();
|
||||
state_.currentLocationName.clear();
|
||||
state_.players.clear();
|
||||
state_.locations.clear();
|
||||
@@ -117,76 +157,20 @@ void InternalGameServer::resetCampaign(
|
||||
bool InternalGameServer::prepareLocation(const LocationConfig& config)
|
||||
{
|
||||
LocationGameState& authoritative = state_.locations[config.id];
|
||||
const bool initializeLocation = !authoritative.hasPersistentState;
|
||||
if (!authoritative.location)
|
||||
authoritative.location = std::make_shared<LocationState>();
|
||||
if (authoritative.location->navigationMaps.empty())
|
||||
authoritative.location->initNavigation(config.navigationJsonPaths);
|
||||
|
||||
authoritative.lockInteractiveObjectsDuringTutorial =
|
||||
config.lockInteractiveObjectsDuringTutorial;
|
||||
if (config.taxi) {
|
||||
const LocationConfig::Taxi& taxi = *config.taxi;
|
||||
LocationState& locationState = *authoritative.location;
|
||||
locationState.taxiCarTriggerPosition = taxi.triggerPosition;
|
||||
locationState.taxiCarWaypoints = taxi.waypoints;
|
||||
locationState.taxiCarInitialState.position = taxi.initialPosition;
|
||||
locationState.taxiCarInitialState.rotation = taxi.initialRotation;
|
||||
locationState.taxiCarInitialState.mode = NpcCar::Mode::NONE;
|
||||
locationState.taxiCarInitialState.velocity = 0.f;
|
||||
locationState.taxiCarInitialState.steeringAngle = 0.f;
|
||||
locationState.taxiCarInitialState.waypointReachRadius =
|
||||
taxi.waypointReachRadius;
|
||||
locationState.taxiCar.waypointReachRadius = taxi.waypointReachRadius;
|
||||
if (initializeLocation) authoritative.resetTaxi();
|
||||
const bool initializeLocation = authoritative.setup(
|
||||
LocationContentLoader::load(config, resourceZipFile_),
|
||||
resourceZipFile_);
|
||||
auto player = state_.players.find(localPlayerId_);
|
||||
if (player != state_.players.end() && player->second) {
|
||||
player->second->position = config.playerPosition;
|
||||
player->second->setTarget(
|
||||
config.playerPosition, std::string(), *authoritative.location,
|
||||
localPlayerId_);
|
||||
}
|
||||
publishMutation();
|
||||
return initializeLocation;
|
||||
}
|
||||
|
||||
void InternalGameServer::setupRuntimeLocation(
|
||||
const std::string& id, const std::shared_ptr<Location>& location,
|
||||
LocationSetup params, AudioPlayerAsync* audioPlayer)
|
||||
{
|
||||
auto stored = state_.locations.find(id);
|
||||
if (stored == state_.locations.end())
|
||||
throw std::runtime_error("Location state was not prepared");
|
||||
params.gameState = &stored->second;
|
||||
location->setup(params, audioPlayer);
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
Location* InternalGameServer::runtimeLocation(const std::string& id) const
|
||||
{
|
||||
auto it = runtimeLocations_.find(id);
|
||||
return it != runtimeLocations_.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
bool InternalGameServer::hasRuntimeLocation(const std::string& id) const
|
||||
{
|
||||
return runtimeLocations_.count(id) > 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> InternalGameServer::loadedRuntimeLocationIds() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(runtimeLocations_.size());
|
||||
for (const auto& [id, location] : runtimeLocations_) result.push_back(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
void InternalGameServer::clearRuntimeLocations(bool retainState)
|
||||
{
|
||||
if (retainState) {
|
||||
synchronizeRuntimeAuxiliaryState();
|
||||
} else {
|
||||
for (const auto& [id, location] : runtimeLocations_)
|
||||
state_.locations.erase(id);
|
||||
publishMutation();
|
||||
}
|
||||
runtimeLocations_.clear();
|
||||
}
|
||||
|
||||
GameSnapshot InternalGameServer::buildSnapshot() const
|
||||
{
|
||||
GameSnapshot result;
|
||||
@@ -472,11 +456,8 @@ void InternalGameServer::submit(const GameCommand& command)
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end()) {
|
||||
location->second.triggerZones[value.zoneId].enabled = value.enabled;
|
||||
auto runtime = runtimeLocations_.find(value.locationId);
|
||||
if (runtime != runtimeLocations_.end() && runtime->second) {
|
||||
for (auto& zone : runtime->second->triggerZones)
|
||||
if (zone.id == value.zoneId) zone.enabled = value.enabled;
|
||||
}
|
||||
pendingTriggerZoneUpdates_.emplace_back(
|
||||
value.locationId, value.zoneId);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, ReloadQuestDefinitionsCommand>) {
|
||||
state_.questJournal.reloadDefinitionsPreservingState(value.path, value.zipFile);
|
||||
@@ -494,26 +475,22 @@ std::vector<GameServerEvent> InternalGameServer::drainEvents()
|
||||
|
||||
void InternalGameServer::update(std::int64_t deltaMs)
|
||||
{
|
||||
auto runtime = runtimeLocations_.find(state_.currentLocationName);
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second) return;
|
||||
pendingSimulationLocationId_.clear();
|
||||
pendingSimulationEvents_ = {};
|
||||
auto stored = state_.locations.find(state_.currentLocationName);
|
||||
if (stored == state_.locations.end()) return;
|
||||
updateBattleZoneTestBot(deltaMs);
|
||||
const bool interactiveObjectsLocked =
|
||||
stored->second.lockInteractiveObjectsDuringTutorial &&
|
||||
state_.tutorialStep < TutorialStep::PickUpItem;
|
||||
LocationSimulationEvents events =
|
||||
pendingSimulationLocationId_ = state_.currentLocationName;
|
||||
pendingSimulationEvents_ =
|
||||
stored->second.update(deltaMs, state_.players, interactiveObjectsLocked);
|
||||
// Runtime presentation reads through snapshots, so publish the simulation
|
||||
// tick before forwarding its value-only events to the loaded location.
|
||||
publishMutation();
|
||||
runtime->second->update(deltaMs, events);
|
||||
synchronizeRuntimeAuxiliaryState();
|
||||
}
|
||||
|
||||
void InternalGameServer::save(nlohmann::json& out) const
|
||||
{
|
||||
const_cast<InternalGameServer*>(this)->synchronizeRuntimeAuxiliaryState();
|
||||
state_.save(out);
|
||||
}
|
||||
|
||||
@@ -523,40 +500,9 @@ void InternalGameServer::load(const nlohmann::json& in)
|
||||
state_.load(in);
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
ensureBattleZoneTestBot();
|
||||
applyStateToRuntime();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::bindRuntimeLocation(const std::string& id, std::shared_ptr<Location> location)
|
||||
{
|
||||
runtimeLocations_[id] = std::move(location);
|
||||
auto stored = state_.locations.find(id);
|
||||
if (stored != state_.locations.end() && stored->second.hasPersistentState) {
|
||||
runtimeLocations_[id]->applyAuxiliaryState(stored->second);
|
||||
} else {
|
||||
runtimeLocations_[id]->captureAuxiliaryState(state_.locations[id]);
|
||||
}
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::synchronizeRuntimeAuxiliaryState()
|
||||
{
|
||||
for (const auto& [id, location] : runtimeLocations_) {
|
||||
if (!location) continue;
|
||||
location->captureAuxiliaryState(state_.locations[id]);
|
||||
}
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::applyStateToRuntime()
|
||||
{
|
||||
for (const auto& [id, stored] : state_.locations) {
|
||||
auto runtime = runtimeLocations_.find(id);
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second) continue;
|
||||
runtime->second->applyAuxiliaryState(stored);
|
||||
}
|
||||
}
|
||||
|
||||
void InternalGameServer::publishMutation()
|
||||
{
|
||||
++revision_;
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGameServer.h"
|
||||
|
||||
class AudioPlayerAsync;
|
||||
#include "IGameServerLifecycle.h"
|
||||
|
||||
namespace FRG {
|
||||
|
||||
class Game;
|
||||
class Character;
|
||||
class Location;
|
||||
class LocalGameServerHost;
|
||||
struct LocationConfig;
|
||||
struct LocationSetup;
|
||||
|
||||
// In-process implementation used by single-player. GameState exists exactly
|
||||
// once, here; clients see immutable snapshots and mutate it with typed commands.
|
||||
class InternalGameServer final : public IGameServer {
|
||||
class InternalGameServer final
|
||||
: public IGameServer, public IGameServerLifecycle {
|
||||
public:
|
||||
InternalGameServer();
|
||||
|
||||
@@ -25,9 +22,14 @@ public:
|
||||
void update(std::int64_t deltaMs) override;
|
||||
void save(nlohmann::json& out) const override;
|
||||
void load(const nlohmann::json& in) override;
|
||||
void startCampaign(
|
||||
const CampaignConfig& campaign, const std::string& zipFile,
|
||||
bool createLocalPlayer, bool enableTestBot) override;
|
||||
bool prepareLocation(const LocationConfig& config) override;
|
||||
void activateLocation(const std::string& locationId) override;
|
||||
|
||||
private:
|
||||
friend class Game;
|
||||
friend class LocalGameServerHost;
|
||||
|
||||
// Host-only lifecycle operations. They never expose authoritative state and
|
||||
// are intentionally absent from IGameServer.
|
||||
@@ -36,17 +38,6 @@ private:
|
||||
const std::string& zipFile);
|
||||
void clearCampaign();
|
||||
void resetCampaign(const std::string& questsPath, const std::string& zipFile);
|
||||
bool prepareLocation(const LocationConfig& config);
|
||||
void setupRuntimeLocation(
|
||||
const std::string& id, const std::shared_ptr<Location>& location,
|
||||
LocationSetup params, AudioPlayerAsync* audioPlayer);
|
||||
Location* runtimeLocation(const std::string& id) const;
|
||||
bool hasRuntimeLocation(const std::string& id) const;
|
||||
std::vector<std::string> loadedRuntimeLocationIds() const;
|
||||
void clearRuntimeLocations(bool retainState);
|
||||
void bindRuntimeLocation(const std::string& id, std::shared_ptr<Location> location);
|
||||
void synchronizeRuntimeAuxiliaryState();
|
||||
void applyStateToRuntime();
|
||||
void publishMutation();
|
||||
void ensureBattleZoneTestBot();
|
||||
void updateBattleZoneTestBot(std::int64_t deltaMs);
|
||||
@@ -55,8 +46,12 @@ private:
|
||||
|
||||
GameState state_;
|
||||
std::string localPlayerId_ = "player";
|
||||
std::string resourceZipFile_;
|
||||
std::int64_t battleZoneTestBotElapsedMs_ = 0;
|
||||
std::unordered_map<std::string, std::shared_ptr<Location>> runtimeLocations_;
|
||||
bool battleZoneTestBotEnabled_ = false;
|
||||
std::string pendingSimulationLocationId_;
|
||||
LocationSimulationEvents pendingSimulationEvents_;
|
||||
std::vector<std::pair<std::string, std::string>> pendingTriggerZoneUpdates_;
|
||||
std::uint64_t revision_ = 1;
|
||||
mutable std::uint64_t cachedRevision_ = 0;
|
||||
mutable std::shared_ptr<const GameSnapshot> cachedSnapshot_;
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "LocalGameServerHost.h"
|
||||
#include "InternalGameServer.h"
|
||||
#include "../Campaign.h"
|
||||
#include "../Location.h"
|
||||
#include <stdexcept>
|
||||
|
||||
namespace FRG {
|
||||
|
||||
LocalGameServerHost::LocalGameServerHost(InternalGameServer& server)
|
||||
: server_(server)
|
||||
{
|
||||
}
|
||||
|
||||
void LocalGameServerHost::initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
const std::string& zipFile)
|
||||
{
|
||||
server_.initializeCampaign(campaignId, questsPath, zipFile);
|
||||
}
|
||||
|
||||
void LocalGameServerHost::clearCampaign()
|
||||
{
|
||||
runtimeLocations_.clear();
|
||||
server_.clearCampaign();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::resetCampaign(
|
||||
const std::string& questsPath, const std::string& zipFile)
|
||||
{
|
||||
server_.resetCampaign(questsPath, zipFile);
|
||||
}
|
||||
|
||||
bool LocalGameServerHost::prepareLocation(const LocationConfig& config)
|
||||
{
|
||||
return server_.prepareLocation(config);
|
||||
}
|
||||
|
||||
void LocalGameServerHost::setupRuntimeLocation(
|
||||
const std::string& id, const std::shared_ptr<Location>& location,
|
||||
LocationSetup params, AudioPlayerAsync* audioPlayer)
|
||||
{
|
||||
auto stored = server_.state_.locations.find(id);
|
||||
if (stored == server_.state_.locations.end())
|
||||
throw std::runtime_error("Location state was not prepared");
|
||||
params.definition = stored->second.definition;
|
||||
params.gameState = &stored->second;
|
||||
location->setup(params, audioPlayer);
|
||||
server_.publishMutation();
|
||||
}
|
||||
|
||||
Location* LocalGameServerHost::runtimeLocation(const std::string& id) const
|
||||
{
|
||||
auto it = runtimeLocations_.find(id);
|
||||
return it != runtimeLocations_.end() ? it->second.get() : nullptr;
|
||||
}
|
||||
|
||||
bool LocalGameServerHost::hasRuntimeLocation(const std::string& id) const
|
||||
{
|
||||
return runtimeLocations_.count(id) > 0;
|
||||
}
|
||||
|
||||
std::vector<std::string> LocalGameServerHost::loadedRuntimeLocationIds() const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(runtimeLocations_.size());
|
||||
for (const auto& [id, location] : runtimeLocations_) result.push_back(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
void LocalGameServerHost::clearRuntimeLocations(bool retainState)
|
||||
{
|
||||
if (retainState) {
|
||||
synchronizeRuntimeAuxiliaryState();
|
||||
} else {
|
||||
for (const auto& [id, location] : runtimeLocations_)
|
||||
server_.state_.locations.erase(id);
|
||||
server_.publishMutation();
|
||||
}
|
||||
runtimeLocations_.clear();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::bindRuntimeLocation(
|
||||
const std::string& id, std::shared_ptr<Location> location)
|
||||
{
|
||||
runtimeLocations_[id] = std::move(location);
|
||||
auto stored = server_.state_.locations.find(id);
|
||||
if (stored != server_.state_.locations.end() &&
|
||||
stored->second.hasPersistentState) {
|
||||
runtimeLocations_[id]->applyAuxiliaryState(stored->second);
|
||||
} else {
|
||||
runtimeLocations_[id]->captureAuxiliaryState(server_.state_.locations[id]);
|
||||
}
|
||||
server_.publishMutation();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::update(std::int64_t deltaMs)
|
||||
{
|
||||
server_.update(deltaMs);
|
||||
|
||||
for (const auto& [locationId, zoneId] : server_.pendingTriggerZoneUpdates_) {
|
||||
auto runtime = runtimeLocations_.find(locationId);
|
||||
auto stored = server_.state_.locations.find(locationId);
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second ||
|
||||
stored == server_.state_.locations.end()) {
|
||||
continue;
|
||||
}
|
||||
auto zoneState = stored->second.triggerZones.find(zoneId);
|
||||
if (zoneState == stored->second.triggerZones.end()) continue;
|
||||
for (auto& zone : runtime->second->triggerZones)
|
||||
if (zone.id == zoneId) zone.enabled = zoneState->second.enabled;
|
||||
}
|
||||
server_.pendingTriggerZoneUpdates_.clear();
|
||||
|
||||
if (server_.pendingSimulationLocationId_.empty()) return;
|
||||
auto runtime =
|
||||
runtimeLocations_.find(server_.pendingSimulationLocationId_);
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second) return;
|
||||
runtime->second->update(deltaMs, server_.pendingSimulationEvents_);
|
||||
synchronizeRuntimeAuxiliaryState();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::save(nlohmann::json& out)
|
||||
{
|
||||
synchronizeRuntimeAuxiliaryState();
|
||||
server_.save(out);
|
||||
}
|
||||
|
||||
void LocalGameServerHost::load(const nlohmann::json& in)
|
||||
{
|
||||
server_.load(in);
|
||||
applyStateToRuntime();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::synchronizeRuntimeAuxiliaryState()
|
||||
{
|
||||
for (const auto& [id, location] : runtimeLocations_) {
|
||||
if (!location) continue;
|
||||
location->captureAuxiliaryState(server_.state_.locations[id]);
|
||||
}
|
||||
server_.publishMutation();
|
||||
}
|
||||
|
||||
void LocalGameServerHost::applyStateToRuntime()
|
||||
{
|
||||
for (const auto& [id, stored] : server_.state_.locations) {
|
||||
auto runtime = runtimeLocations_.find(id);
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second) continue;
|
||||
runtime->second->applyAuxiliaryState(stored);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace FRG
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "IGameServerHost.h"
|
||||
#include <unordered_map>
|
||||
|
||||
namespace FRG {
|
||||
|
||||
class InternalGameServer;
|
||||
|
||||
// Adapts the headless authoritative server to the graphical in-process client.
|
||||
class LocalGameServerHost final : public IGameServerHost {
|
||||
public:
|
||||
explicit LocalGameServerHost(InternalGameServer& server);
|
||||
|
||||
void initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
const std::string& zipFile) override;
|
||||
void clearCampaign() override;
|
||||
void resetCampaign(
|
||||
const std::string& questsPath, const std::string& zipFile) override;
|
||||
bool prepareLocation(const LocationConfig& config) override;
|
||||
|
||||
void setupRuntimeLocation(
|
||||
const std::string& id, const std::shared_ptr<Location>& location,
|
||||
LocationSetup params, AudioPlayerAsync* audioPlayer) override;
|
||||
Location* runtimeLocation(const std::string& id) const override;
|
||||
bool hasRuntimeLocation(const std::string& id) const override;
|
||||
std::vector<std::string> loadedRuntimeLocationIds() const override;
|
||||
void clearRuntimeLocations(bool retainState) override;
|
||||
void bindRuntimeLocation(
|
||||
const std::string& id, std::shared_ptr<Location> location) override;
|
||||
|
||||
void update(std::int64_t deltaMs) override;
|
||||
void save(nlohmann::json& out) override;
|
||||
void load(const nlohmann::json& in) override;
|
||||
|
||||
private:
|
||||
void synchronizeRuntimeAuxiliaryState();
|
||||
void applyStateToRuntime();
|
||||
|
||||
InternalGameServer& server_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Location>> runtimeLocations_;
|
||||
};
|
||||
|
||||
} // namespace FRG
|
||||
Reference in new issue
Block a user