More work on states, added bot
This commit is contained in:
1 parent
66e93d46f4
commit
5e9a663812
25 files changed
+1595
-740
No files matched your search
@@ -94,8 +94,51 @@ struct AppendChatMessageCommand {
|
||||
};
|
||||
struct SetTaxiCalledCommand { bool called; };
|
||||
struct StartTaxiCommand { std::string locationId; };
|
||||
struct ResetTaxiCommand { std::string locationId; };
|
||||
struct SetLocationCameraCommand {
|
||||
std::string locationId;
|
||||
float azimuth;
|
||||
float inclination;
|
||||
};
|
||||
struct SetLocationTargetsCommand {
|
||||
std::string locationId;
|
||||
std::string interactiveObjectId;
|
||||
std::string npcId;
|
||||
std::string teleportZoneId;
|
||||
};
|
||||
struct SwitchLocationNavigationCommand {
|
||||
std::string locationId;
|
||||
int index;
|
||||
};
|
||||
struct SetNavigationAreaAvailableCommand {
|
||||
std::string locationId;
|
||||
std::string areaName;
|
||||
bool available;
|
||||
};
|
||||
struct AddNavigationObstacleCommand {
|
||||
std::string locationId;
|
||||
std::string name;
|
||||
std::vector<Eigen::Vector2f> polygon;
|
||||
};
|
||||
struct AddInventoryItemCommand { std::string itemId; };
|
||||
struct RemoveInventoryItemCommand { std::string itemId; };
|
||||
struct UnlockQuestCommand { std::string questId; };
|
||||
struct CompleteQuestCommand { std::string questId; };
|
||||
struct FailQuestCommand { std::string questId; };
|
||||
struct SetQuestObjectiveCompletedCommand {
|
||||
std::string questId;
|
||||
std::string objectiveId;
|
||||
bool completed;
|
||||
};
|
||||
struct SetQuestObjectiveVisibleCommand {
|
||||
std::string questId;
|
||||
std::string objectiveId;
|
||||
bool visible;
|
||||
};
|
||||
struct SetQuestActiveObjectiveCommand {
|
||||
std::string questId;
|
||||
int objectiveIndex;
|
||||
};
|
||||
struct SetGlobalIntCommand { std::string key; int value; };
|
||||
struct SetGlobalFloatCommand { std::string key; float value; };
|
||||
struct SetCharacterTransformCommand {
|
||||
@@ -188,8 +231,20 @@ using GameCommand = std::variant<
|
||||
AppendChatMessageCommand,
|
||||
SetTaxiCalledCommand,
|
||||
StartTaxiCommand,
|
||||
ResetTaxiCommand,
|
||||
SetLocationCameraCommand,
|
||||
SetLocationTargetsCommand,
|
||||
SwitchLocationNavigationCommand,
|
||||
SetNavigationAreaAvailableCommand,
|
||||
AddNavigationObstacleCommand,
|
||||
AddInventoryItemCommand,
|
||||
RemoveInventoryItemCommand,
|
||||
UnlockQuestCommand,
|
||||
CompleteQuestCommand,
|
||||
FailQuestCommand,
|
||||
SetQuestObjectiveCompletedCommand,
|
||||
SetQuestObjectiveVisibleCommand,
|
||||
SetQuestActiveObjectiveCommand,
|
||||
SetGlobalIntCommand,
|
||||
SetGlobalFloatCommand,
|
||||
SetCharacterTransformCommand,
|
||||
@@ -209,13 +264,24 @@ using GameCommand = std::variant<
|
||||
SetTriggerZoneEnabledCommand,
|
||||
ReloadQuestDefinitionsCommand>;
|
||||
|
||||
struct InventoryItemAddedEvent { std::string itemId; };
|
||||
struct InventoryItemRemovedEvent { std::string itemId; };
|
||||
|
||||
using GameServerEvent = std::variant<
|
||||
InventoryItemAddedEvent,
|
||||
InventoryItemRemovedEvent,
|
||||
Quest::QuestEvent>;
|
||||
|
||||
class IGameServer {
|
||||
public:
|
||||
virtual ~IGameServer() = default;
|
||||
|
||||
// Each returned object is immutable and remains valid after later commands.
|
||||
virtual std::shared_ptr<const GameSnapshot> snapshot() const = 0;
|
||||
// Client-relative identity assigned by this server connection.
|
||||
virtual const std::string& localPlayerId() const = 0;
|
||||
virtual void submit(const GameCommand& command) = 0;
|
||||
virtual std::vector<GameServerEvent> drainEvents() = 0;
|
||||
virtual void update(std::int64_t deltaMs) = 0;
|
||||
|
||||
// Persistence stays server-side. The JSON shape is GameState's established
|
||||
|
||||
@@ -1,12 +1,192 @@
|
||||
#include "InternalGameServer.h"
|
||||
#include "../Campaign.h"
|
||||
#include "../Location.h"
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <type_traits>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
InternalGameServer::InternalGameServer() = default;
|
||||
|
||||
void InternalGameServer::ensureBattleZoneTestBot()
|
||||
{
|
||||
if (state_.campaignId != kBattleZoneCampaignId) return;
|
||||
if (!kEnableBattleZoneTestBot) {
|
||||
state_.players.erase(kBattleZoneTestBotId);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& bot = state_.players[kBattleZoneTestBotId];
|
||||
if (!bot) bot = std::make_shared<CharacterState>();
|
||||
bot->configureAsPlayer();
|
||||
bot->npcName = "Test Bot";
|
||||
}
|
||||
|
||||
void InternalGameServer::updateBattleZoneTestBot(std::int64_t deltaMs)
|
||||
{
|
||||
if (!kEnableBattleZoneTestBot ||
|
||||
state_.campaignId != kBattleZoneCampaignId ||
|
||||
state_.currentLocationName.empty() ||
|
||||
state_.players.find(kBattleZoneTestBotId) == state_.players.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
battleZoneTestBotElapsedMs_ += deltaMs;
|
||||
if (battleZoneTestBotElapsedMs_ < kBattleZoneTestBotIntervalMs) return;
|
||||
battleZoneTestBotElapsedMs_ %= kBattleZoneTestBotIntervalMs;
|
||||
|
||||
static std::mt19937 randomEngine(std::random_device{}());
|
||||
static std::uniform_real_distribution<float> coordinate(-10.0f, 10.0f);
|
||||
submit(SetCharacterDestinationCommand{
|
||||
state_.currentLocationName,
|
||||
kBattleZoneTestBotId,
|
||||
Eigen::Vector3f(coordinate(randomEngine), 0.f, coordinate(randomEngine)),
|
||||
std::string()});
|
||||
}
|
||||
|
||||
void InternalGameServer::initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
const std::string& zipFile)
|
||||
{
|
||||
state_.campaignId = campaignId;
|
||||
state_.currentLocationName.clear();
|
||||
state_.locations.clear();
|
||||
state_.players.clear();
|
||||
state_.questJournal.loadFromFile(questsPath, zipFile);
|
||||
state_.globalFloats["player_hp"] = 200.f;
|
||||
|
||||
auto player = std::make_shared<CharacterState>();
|
||||
player->configureAsPlayer();
|
||||
state_.players.emplace(localPlayerId_, std::move(player));
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
ensureBattleZoneTestBot();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::clearCampaign()
|
||||
{
|
||||
runtimeLocations_.clear();
|
||||
state_.currentLocationName.clear();
|
||||
state_.players.clear();
|
||||
state_.locations.clear();
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::resetCampaign(
|
||||
const std::string& questsPath, const std::string& zipFile)
|
||||
{
|
||||
pendingEvents_.clear();
|
||||
state_.currentLocationName.clear();
|
||||
state_.isDarklands = false;
|
||||
state_.isNight = false;
|
||||
state_.isDawn = false;
|
||||
state_.playerHp = 200.f;
|
||||
state_.playerMaxHp = 200.f;
|
||||
state_.money = 5500;
|
||||
state_.globalInts.clear();
|
||||
state_.globalFloats.clear();
|
||||
state_.globalFloats["player_hp"] = 200.f;
|
||||
state_.tutorialStep = TutorialStep::AdvanceDialogue;
|
||||
state_.tutorialPhoneChatScreenOpened = false;
|
||||
state_.tutorialJournalScreenOpened = false;
|
||||
state_.tutorialNeedOpenTaxiScreen = false;
|
||||
state_.tutorialTaxiHintDone = false;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
state_.chatUnread[i] = true;
|
||||
state_.chatPreviewMsg[i].clear();
|
||||
state_.chatHistory[i].clear();
|
||||
}
|
||||
state_.inventory.clear();
|
||||
state_.questJournal.loadFromFile(questsPath, zipFile);
|
||||
state_.locations.clear();
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
ensureBattleZoneTestBot();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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;
|
||||
@@ -85,7 +265,11 @@ std::shared_ptr<const GameSnapshot> InternalGameServer::snapshot() const
|
||||
|
||||
void InternalGameServer::submit(const GameCommand& command)
|
||||
{
|
||||
std::visit([this](const auto& value) {
|
||||
auto enqueueQuestEvents = [this](Quest::QuestMutationResult result) {
|
||||
for (Quest::QuestEvent& event : result.events)
|
||||
pendingEvents_.emplace_back(std::move(event));
|
||||
};
|
||||
std::visit([this, &enqueueQuestEvents](const auto& value) {
|
||||
using T = std::decay_t<decltype(value)>;
|
||||
if constexpr (std::is_same_v<T, SetWorldModeCommand>) {
|
||||
state_.isDarklands = value.darklands;
|
||||
@@ -126,10 +310,83 @@ void InternalGameServer::submit(const GameCommand& command)
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end())
|
||||
location->second.startTaxi();
|
||||
} else if constexpr (std::is_same_v<T, ResetTaxiCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end())
|
||||
location->second.resetTaxi();
|
||||
} else if constexpr (std::is_same_v<T, SetLocationCameraCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end() && location->second.location) {
|
||||
location->second.location->cameraAzimuth = value.azimuth;
|
||||
location->second.location->cameraInclination = value.inclination;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, SetLocationTargetsCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end() && location->second.location) {
|
||||
location->second.location->targetInteractiveObjectId =
|
||||
value.interactiveObjectId;
|
||||
location->second.location->targetInteractNpcId = value.npcId;
|
||||
location->second.location->targetTeleportZoneId =
|
||||
value.teleportZoneId;
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, SwitchLocationNavigationCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end() && location->second.location &&
|
||||
value.index >= 0 &&
|
||||
value.index < static_cast<int>(
|
||||
location->second.location->navigationMaps.size())) {
|
||||
LocationState& locationState = *location->second.location;
|
||||
locationState.activeNavigationIndex = value.index;
|
||||
for (const auto& [id, player] : state_.players)
|
||||
if (player) player->forceReplan(locationState, id);
|
||||
for (const auto& [id, npc] : location->second.npcs)
|
||||
if (npc) npc->forceReplan(locationState, id);
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, SetNavigationAreaAvailableCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end() && location->second.location) {
|
||||
LocationState& locationState = *location->second.location;
|
||||
const int index = locationState.activeNavigationIndex;
|
||||
if (index >= 0 &&
|
||||
index < static_cast<int>(locationState.navigationMaps.size())) {
|
||||
locationState.navigationMaps[index].setAreaAvailable(
|
||||
value.areaName, value.available);
|
||||
}
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, AddNavigationObstacleCommand>) {
|
||||
auto location = state_.locations.find(value.locationId);
|
||||
if (location != state_.locations.end() && location->second.location) {
|
||||
LocationState& locationState = *location->second.location;
|
||||
const int index = locationState.activeNavigationIndex;
|
||||
if (index >= 0 &&
|
||||
index < static_cast<int>(locationState.navigationMaps.size())) {
|
||||
PathFinder::ObstaclePolygon polygon;
|
||||
polygon.name = value.name;
|
||||
polygon.polygon = value.polygon;
|
||||
locationState.navigationMaps[index].addObstaclePolygon(polygon);
|
||||
}
|
||||
}
|
||||
} else if constexpr (std::is_same_v<T, AddInventoryItemCommand>) {
|
||||
state_.inventory.addItem(value.itemId);
|
||||
if (state_.inventory.addItem(value.itemId))
|
||||
pendingEvents_.emplace_back(InventoryItemAddedEvent{value.itemId});
|
||||
} else if constexpr (std::is_same_v<T, RemoveInventoryItemCommand>) {
|
||||
state_.inventory.removeItem(value.itemId);
|
||||
if (state_.inventory.removeItem(value.itemId))
|
||||
pendingEvents_.emplace_back(InventoryItemRemovedEvent{value.itemId});
|
||||
} else if constexpr (std::is_same_v<T, UnlockQuestCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.unlockQuest(value.questId));
|
||||
} else if constexpr (std::is_same_v<T, CompleteQuestCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.completeQuest(value.questId));
|
||||
} else if constexpr (std::is_same_v<T, FailQuestCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.failQuest(value.questId));
|
||||
} else if constexpr (std::is_same_v<T, SetQuestObjectiveCompletedCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.setObjectiveCompleted(
|
||||
value.questId, value.objectiveId, value.completed));
|
||||
} else if constexpr (std::is_same_v<T, SetQuestObjectiveVisibleCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.setObjectiveVisible(
|
||||
value.questId, value.objectiveId, value.visible));
|
||||
} else if constexpr (std::is_same_v<T, SetQuestActiveObjectiveCommand>) {
|
||||
enqueueQuestEvents(state_.questJournal.setActiveObjective(
|
||||
value.questId, value.objectiveIndex));
|
||||
} else if constexpr (std::is_same_v<T, SetGlobalIntCommand>) {
|
||||
state_.globalInts[value.key] = value.value;
|
||||
} else if constexpr (std::is_same_v<T, SetGlobalFloatCommand>) {
|
||||
@@ -225,7 +482,14 @@ void InternalGameServer::submit(const GameCommand& command)
|
||||
state_.questJournal.reloadDefinitionsPreservingState(value.path, value.zipFile);
|
||||
}
|
||||
}, command);
|
||||
markHostMutation();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
std::vector<GameServerEvent> InternalGameServer::drainEvents()
|
||||
{
|
||||
std::vector<GameServerEvent> result;
|
||||
result.swap(pendingEvents_);
|
||||
return result;
|
||||
}
|
||||
|
||||
void InternalGameServer::update(std::int64_t deltaMs)
|
||||
@@ -234,11 +498,15 @@ void InternalGameServer::update(std::int64_t deltaMs)
|
||||
if (runtime == runtimeLocations_.end() || !runtime->second) return;
|
||||
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 =
|
||||
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();
|
||||
}
|
||||
@@ -251,9 +519,12 @@ void InternalGameServer::save(nlohmann::json& out) const
|
||||
|
||||
void InternalGameServer::load(const nlohmann::json& in)
|
||||
{
|
||||
pendingEvents_.clear();
|
||||
state_.load(in);
|
||||
battleZoneTestBotElapsedMs_ = 0;
|
||||
ensureBattleZoneTestBot();
|
||||
applyStateToRuntime();
|
||||
markHostMutation();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::bindRuntimeLocation(const std::string& id, std::shared_ptr<Location> location)
|
||||
@@ -265,7 +536,7 @@ void InternalGameServer::bindRuntimeLocation(const std::string& id, std::shared_
|
||||
} else {
|
||||
runtimeLocations_[id]->captureAuxiliaryState(state_.locations[id]);
|
||||
}
|
||||
markHostMutation();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::synchronizeRuntimeAuxiliaryState()
|
||||
@@ -274,7 +545,7 @@ void InternalGameServer::synchronizeRuntimeAuxiliaryState()
|
||||
if (!location) continue;
|
||||
location->captureAuxiliaryState(state_.locations[id]);
|
||||
}
|
||||
markHostMutation();
|
||||
publishMutation();
|
||||
}
|
||||
|
||||
void InternalGameServer::applyStateToRuntime()
|
||||
@@ -286,7 +557,7 @@ void InternalGameServer::applyStateToRuntime()
|
||||
}
|
||||
}
|
||||
|
||||
void InternalGameServer::markHostMutation()
|
||||
void InternalGameServer::publishMutation()
|
||||
{
|
||||
++revision_;
|
||||
cachedSnapshot_.reset();
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
#include "IGameServer.h"
|
||||
|
||||
class AudioPlayerAsync;
|
||||
|
||||
namespace FRG {
|
||||
|
||||
class Game;
|
||||
class Character;
|
||||
class Location;
|
||||
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.
|
||||
@@ -15,7 +19,9 @@ public:
|
||||
InternalGameServer();
|
||||
|
||||
std::shared_ptr<const GameSnapshot> snapshot() const override;
|
||||
const std::string& localPlayerId() const override { return localPlayerId_; }
|
||||
void submit(const GameCommand& command) override;
|
||||
std::vector<GameServerEvent> drainEvents() override;
|
||||
void update(std::int64_t deltaMs) override;
|
||||
void save(nlohmann::json& out) const override;
|
||||
void load(const nlohmann::json& in) override;
|
||||
@@ -23,25 +29,38 @@ public:
|
||||
private:
|
||||
friend class Game;
|
||||
|
||||
// Transitional host operations are intentionally available only to Game,
|
||||
// which owns location loading and old-save migration. They are never part of
|
||||
// IGameServer and therefore cannot leak mutable state to UI/network clients.
|
||||
GameState& hostState() { return state_; }
|
||||
const GameState& hostState() const { return state_; }
|
||||
std::unordered_map<std::string, std::shared_ptr<Location>>& runtimeLocations() { return runtimeLocations_; }
|
||||
const std::unordered_map<std::string, std::shared_ptr<Location>>& runtimeLocations() const { return runtimeLocations_; }
|
||||
// Host-only lifecycle operations. They never expose authoritative state and
|
||||
// are intentionally absent from IGameServer.
|
||||
void initializeCampaign(
|
||||
const std::string& campaignId, const std::string& questsPath,
|
||||
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 markHostMutation();
|
||||
void publishMutation();
|
||||
void ensureBattleZoneTestBot();
|
||||
void updateBattleZoneTestBot(std::int64_t deltaMs);
|
||||
|
||||
GameSnapshot buildSnapshot() const;
|
||||
|
||||
GameState state_;
|
||||
std::string localPlayerId_ = "player";
|
||||
std::int64_t battleZoneTestBotElapsedMs_ = 0;
|
||||
std::unordered_map<std::string, std::shared_ptr<Location>> runtimeLocations_;
|
||||
std::uint64_t revision_ = 1;
|
||||
mutable std::uint64_t cachedRevision_ = 0;
|
||||
mutable std::shared_ptr<const GameSnapshot> cachedSnapshot_;
|
||||
std::vector<GameServerEvent> pendingEvents_;
|
||||
};
|
||||
|
||||
} // namespace FRG
|
||||
Reference in new issue
Block a user