Major refactoring - internal server state machine implemented

This commit is contained in:
Vladislav Khorev committed 2026-10-29 00:09:16 +06:00
1 parent 71c654e5b6
commit 66e93d46f4
48 files changed
+3948 -3696

No files matched your search

+295
View File
@@ -0,0 +1,295 @@
#include "InternalGameServer.h"
#include "../Location.h"
#include <algorithm>
#include <type_traits>
namespace FRG {
InternalGameServer::InternalGameServer() = default;
GameSnapshot InternalGameServer::buildSnapshot() const
{
GameSnapshot result;
result.revision = revision_;
result.campaignId = state_.campaignId;
result.currentLocationId = state_.currentLocationName;
result.isDarklands = state_.isDarklands;
result.isNight = state_.isNight;
result.isDawn = state_.isDawn;
result.playerHp = state_.playerHp;
result.playerMaxHp = state_.playerMaxHp;
result.money = state_.money;
result.tutorialStep = state_.tutorialStep;
result.tutorialPhoneChatScreenOpened = state_.tutorialPhoneChatScreenOpened;
result.tutorialJournalScreenOpened = state_.tutorialJournalScreenOpened;
result.tutorialNeedOpenTaxiScreen = state_.tutorialNeedOpenTaxiScreen;
result.tutorialTaxiHintDone = state_.tutorialTaxiHintDone;
result.taxiIsCalled = state_.taxiIsCalled;
for (int i = 0; i < 3; ++i) {
result.chatUnread[i] = state_.chatUnread[i];
result.chatPreview[i] = state_.chatPreviewMsg[i];
result.chatHistory[i].reserve(state_.chatHistory[i].size());
for (const StoredChatMessage& message : state_.chatHistory[i])
result.chatHistory[i].push_back({message.text, message.incoming});
}
nlohmann::json inventoryJson;
state_.inventory.save(inventoryJson);
if (inventoryJson.contains("items") && inventoryJson["items"].is_array()) {
for (const auto& item : inventoryJson["items"])
if (item.is_string()) result.inventoryItemIds.push_back(item.get<std::string>());
}
state_.questJournal.save(result.questJournal);
result.globalInts = state_.globalInts;
result.globalFloats = state_.globalFloats;
for (const Quest::QuestState* quest : state_.questJournal.getVisibleQuests())
if (quest) result.quests.push_back(*quest);
for (const auto& [playerId, source] : state_.players) {
if (!source) continue;
CharacterState clean = *source;
result.players.emplace(playerId, CharacterSnapshot{playerId, std::move(clean)});
}
for (const auto& [locationId, source] : state_.locations) {
LocationSnapshot location;
location.id = locationId;
location.state = *source.location;
for (const auto& [id, sourceNpc] : source.npcs) {
if (!sourceNpc) continue;
CharacterState clean = *sourceNpc;
location.npcs.emplace(
id, CharacterSnapshot{id, std::move(clean)});
}
for (const auto& [id, sourceObject] : source.interactiveObjects) {
if (!sourceObject) continue;
InteractiveObjectState clean = *sourceObject;
location.interactiveObjects.emplace(
id, InteractiveObjectSnapshot{id, std::move(clean)});
}
location.triggerZones = source.triggerZones;
location.teleportZones = source.teleportZones;
location.dialogue = source.dialogue;
location.scriptGlobals = source.scriptGlobals;
result.locations.emplace(locationId, std::move(location));
}
return result;
}
std::shared_ptr<const GameSnapshot> InternalGameServer::snapshot() const
{
if (!cachedSnapshot_ || cachedRevision_ != revision_) {
cachedSnapshot_ = std::make_shared<const GameSnapshot>(buildSnapshot());
cachedRevision_ = revision_;
}
return cachedSnapshot_;
}
void InternalGameServer::submit(const GameCommand& command)
{
std::visit([this](const auto& value) {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, SetWorldModeCommand>) {
state_.isDarklands = value.darklands;
state_.isNight = value.night;
state_.isDawn = value.dawn;
} else if constexpr (std::is_same_v<T, SetCurrentLocationCommand>) {
state_.currentLocationName = value.locationId;
} else if constexpr (std::is_same_v<T, SetPlayerHealthCommand>) {
state_.playerHp = value.hp;
state_.playerMaxHp = value.maxHp;
} else if constexpr (std::is_same_v<T, SetMoneyCommand>) {
state_.money = value.amount;
} else if constexpr (std::is_same_v<T, SpendMoneyCommand>) {
state_.money -= value.amount;
} else if constexpr (std::is_same_v<T, CompleteTutorialStepCommand>) {
state_.completeTutorialStep(value.step);
} else if constexpr (std::is_same_v<T, SetTutorialStepCommand>) {
state_.tutorialStep = value.step;
} else if constexpr (std::is_same_v<T, SetTutorialFlagsCommand>) {
state_.tutorialPhoneChatScreenOpened = value.phoneChatOpened;
state_.tutorialJournalScreenOpened = value.journalOpened;
state_.tutorialNeedOpenTaxiScreen = value.needTaxiHint;
state_.tutorialTaxiHintDone = value.taxiHintDone;
} else if constexpr (std::is_same_v<T, SetChatCommand>) {
if (value.index >= 0 && value.index < 3) {
state_.chatUnread[value.index] = value.unread;
state_.chatPreviewMsg[value.index] = value.preview;
}
} else if constexpr (std::is_same_v<T, AppendChatMessageCommand>) {
if (value.index >= 0 && value.index < 3) {
if (state_.chatHistory[value.index].size() >= 5)
state_.chatHistory[value.index].erase(state_.chatHistory[value.index].begin());
state_.chatHistory[value.index].push_back({value.text, value.incoming});
}
} else if constexpr (std::is_same_v<T, SetTaxiCalledCommand>) {
state_.taxiIsCalled = value.called;
} else if constexpr (std::is_same_v<T, StartTaxiCommand>) {
auto location = state_.locations.find(value.locationId);
if (location != state_.locations.end())
location->second.startTaxi();
} else if constexpr (std::is_same_v<T, AddInventoryItemCommand>) {
state_.inventory.addItem(value.itemId);
} else if constexpr (std::is_same_v<T, RemoveInventoryItemCommand>) {
state_.inventory.removeItem(value.itemId);
} else if constexpr (std::is_same_v<T, SetGlobalIntCommand>) {
state_.globalInts[value.key] = value.value;
} else if constexpr (std::is_same_v<T, SetGlobalFloatCommand>) {
state_.globalFloats[value.key] = value.value;
} else if constexpr (std::is_same_v<T, SetCharacterTransformCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) {
character->position = value.position;
character->facingAngle = value.facingAngle;
character->targetFacingAngle = value.facingAngle;
}
} else if constexpr (std::is_same_v<T, SetCharacterTargetCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->attackTargetId = value.targetId;
} else if constexpr (std::is_same_v<T, ClearCharacterAttackTargetCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->attackTargetId.clear();
} else if constexpr (std::is_same_v<T, SetCharacterDestinationCommand>) {
auto location = state_.locations.find(value.locationId);
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character && location != state_.locations.end() &&
location->second.location) {
character->setTarget(
value.target, value.callbackName,
*location->second.location, value.characterId);
}
} else if constexpr (std::is_same_v<T, StopCharacterCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->stopInPlace();
} else if constexpr (std::is_same_v<T, FaceCharacterCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character)
character->stopAndRotateToTarget(value.targetId);
} else if constexpr (std::is_same_v<T, ReplanCharacterCommand>) {
auto location = state_.locations.find(value.locationId);
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character && location != state_.locations.end() &&
location->second.location) {
character->forceReplan(
*location->second.location, value.characterId);
}
} else if constexpr (std::is_same_v<T, ResetCharacterAfterDeathCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->resetPlayerAfterDeath();
} else if constexpr (std::is_same_v<T, TryCharacterAttackComboCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->tryAttackCombo();
} else if constexpr (std::is_same_v<T, SetCharacterHealthCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->setHp(value.hp);
} else if constexpr (std::is_same_v<T, SetCharacterAnimationMetadataCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) {
character->setAnimationMetadata(
value.animationState, value.startFrame, value.frameCount);
}
} else if constexpr (std::is_same_v<T, DamageCharacterCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) character->hp = (std::max)(0.f, character->hp - (std::max)(0.f, value.amount));
} else if constexpr (std::is_same_v<T, CommitCharacterStateCommand>) {
CharacterState* character =
state_.findCharacter(value.locationId, value.characterId);
if (character) *character = value.state;
} else if constexpr (std::is_same_v<T, SetInteractiveObjectActiveCommand>) {
auto location = state_.locations.find(value.locationId);
if (location == state_.locations.end()) return;
auto object = location->second.interactiveObjects.find(value.objectId);
if (object != location->second.interactiveObjects.end() && object->second)
object->second->isActive = value.active;
} else if constexpr (std::is_same_v<T, SetTriggerZoneEnabledCommand>) {
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;
}
}
} else if constexpr (std::is_same_v<T, ReloadQuestDefinitionsCommand>) {
state_.questJournal.reloadDefinitionsPreservingState(value.path, value.zipFile);
}
}, command);
markHostMutation();
}
void InternalGameServer::update(std::int64_t deltaMs)
{
auto runtime = runtimeLocations_.find(state_.currentLocationName);
if (runtime == runtimeLocations_.end() || !runtime->second) return;
auto stored = state_.locations.find(state_.currentLocationName);
if (stored == state_.locations.end()) return;
const bool interactiveObjectsLocked =
stored->second.lockInteractiveObjectsDuringTutorial &&
state_.tutorialStep < TutorialStep::PickUpItem;
LocationSimulationEvents events =
stored->second.update(deltaMs, state_.players, interactiveObjectsLocked);
runtime->second->update(deltaMs, events);
synchronizeRuntimeAuxiliaryState();
}
void InternalGameServer::save(nlohmann::json& out) const
{
const_cast<InternalGameServer*>(this)->synchronizeRuntimeAuxiliaryState();
state_.save(out);
}
void InternalGameServer::load(const nlohmann::json& in)
{
state_.load(in);
applyStateToRuntime();
markHostMutation();
}
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]);
}
markHostMutation();
}
void InternalGameServer::synchronizeRuntimeAuxiliaryState()
{
for (const auto& [id, location] : runtimeLocations_) {
if (!location) continue;
location->captureAuxiliaryState(state_.locations[id]);
}
markHostMutation();
}
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::markHostMutation()
{
++revision_;
cachedSnapshot_.reset();
}
} // namespace FRG