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
@@ -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_;
|
||||
|
||||
Reference in new issue
Block a user