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

+67 -12
View File
@@ -8,6 +8,7 @@
#include <Eigen/Geometry>
#include "../Character.h"
#include <SDL.h>
#include <unordered_set>
namespace FRG {
@@ -91,6 +92,9 @@ namespace FRG {
for (const auto& item : j["objects"]) {
InteractiveObjectData data;
data.id = item.at("id").get<std::string>();
if (data.id.empty())
throw std::runtime_error("Interactive object id must not be empty in " + jsonPath);
data.base = parseGameObjectData(item);
data.interactionRadius = item.value("interactionRadius", 2.0f);
data.approachRadius = item.value("approachRadius", data.interactionRadius);
@@ -293,6 +297,7 @@ namespace FRG {
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
const std::string& jsonPath,
Renderer& renderer,
std::unordered_map<std::string, std::shared_ptr<InteractiveObjectState>>& states,
const std::string& zipPath)
{
std::vector<InteractiveObject> interactiveObjects;
@@ -300,12 +305,35 @@ namespace FRG {
if (jsonPath.empty())
return interactiveObjects;
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
const auto definitions = loadInteractiveFromJson(jsonPath, zipPath);
std::unordered_set<std::string> configuredIds;
for (const auto& data : definitions) {
if (!configuredIds.insert(data.id).second)
throw std::runtime_error(
"GameObjectLoader: duplicate interactive object id '" +
data.id + "' in " + jsonPath);
}
for (const auto& [id, state] : states)
if (configuredIds.count(id) == 0)
throw std::runtime_error(
"GameObjectLoader: saved interactive object id '" + id +
"' is not present in " + jsonPath);
for (const auto& data : definitions) {
SDL_PumpEvents();
logger() << "Loading interactive object: " << data.base.name << std::endl;
try {
InteractiveObject intObj = InteractiveObject::createFromState(
data.toInteractiveObjectState(), renderer, zipPath);
InteractiveObjectState definition = data.toInteractiveObjectState();
auto& state = states[data.id];
if (!state) state = std::make_shared<InteractiveObjectState>(definition);
else {
nlohmann::json runtime;
state->save(runtime);
*state = std::move(definition);
state->load(runtime);
}
InteractiveObject intObj =
InteractiveObject::createFromState(*state, renderer, zipPath);
intObj.id = data.id;
intObj.loadedObject.name = data.base.name;
if (!data.activateFunctionName.empty())
@@ -338,7 +366,9 @@ namespace FRG {
for (const auto& item : j["npcs"]) {
NpcData data;
data.id = item.value("id", "npc_unknown");
data.id = item.at("id").get<std::string>();
if (data.id.empty())
throw std::runtime_error("NPC id must not be empty in " + jsonPath);
data.name = item.value("name", "Unknown NPC");
data.texturePath = item.value("texturePath", "");
if (item.contains("meshTextures") && item["meshTextures"].is_object()) {
@@ -376,6 +406,7 @@ namespace FRG {
logger() << "Successfully loaded " << npcs.size() << " NPCs from " << jsonPath << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error loading NPCs from JSON: " << e.what() << std::endl;
throw;
}
return npcs;
@@ -385,9 +416,10 @@ namespace FRG {
CharacterState st;
// Identity + game state
st.npcId = id;
st.npcName = name;
st.position = Eigen::Vector3f(positionX, positionY, positionZ);
st.homePosition = Eigen::Vector3f(positionX, 0.f, positionZ);
st.stopInPlace();
st.facingAngle = facingAngle;
st.walkSpeed = walkSpeed;
st.rotationSpeed = rotationSpeed;
@@ -396,6 +428,7 @@ namespace FRG {
st.initialHp = hp;
st.visibleRange = visibleRange;
st.canAttack = canAttack;
if (canAttack) st.attackTargetId = "player";
st.enabled = enabled;
st.interactionRadius = interactionRadius;
@@ -433,29 +466,51 @@ namespace FRG {
std::vector<std::unique_ptr<Character>> GameObjectLoader::loadAndCreate_Npcs(
const std::string& jsonPath,
Renderer& renderer,
std::unordered_map<std::string, std::shared_ptr<CharacterState>>& states,
IGameServer& server,
const std::string& locationId,
const std::string& zipPath)
{
std::vector<std::unique_ptr<Character>> npcs;
int index = 0;
for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) {
const auto definitions = loadNpcsFromJson(jsonPath, zipPath);
std::unordered_set<std::string> seenIds;
for (const auto& definition : definitions) {
const std::string& id = definition.id;
if (id == "player" || !seenIds.insert(id).second)
throw std::runtime_error(
"GameObjectLoader: duplicate or reserved NPC id '" + id +
"' in " + jsonPath);
}
for (const auto& [id, state] : states)
if (seenIds.count(id) == 0)
throw std::runtime_error(
"GameObjectLoader: saved NPC id '" + id +
"' is not present in " + jsonPath);
for (const auto& npcData : definitions) {
SDL_PumpEvents();
logger() << "Loading NPC: " << npcData.name << std::endl;
try {
auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str());
CharacterState definition = npcData.toCharacterState();
auto& state = states[npcData.id];
if (!state) state = std::make_shared<CharacterState>(definition);
else {
nlohmann::json runtime;
state->save(runtime);
*state = std::move(definition);
state->load(runtime);
}
auto npc = Character::createFromState(
*state, server, locationId, npcData.id, renderer, zipPath.c_str());
if (npc) {
logger() << "Successfully loaded NPC: " << npcData.name << " at ("
<< npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl;
npc->index = index;
npcs.push_back(std::move(npc));
index = index + 1;
}
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load NPC '" << npcData.name << "': " << e.what() << std::endl;
}
}
logger() << "Total NPCs loaded: " << npcs.size() << std::endl;
return npcs;
}