Separating state out of the location

This commit is contained in:
Vladislav Khorev committed 2026-10-30 14:56:24 +06:00
1 parent 5e9a663812
commit 297bd7377f
45 files changed
+2114 -778

No files matched your search

+68 -158
View File
@@ -102,20 +102,21 @@ namespace FRG {
data.castShadowNight = item.value("castShadowNight", true);
data.isActive = item.value("isActive", true);
data.activateFunctionName = item.value("activateFunction", "");
data.pivotX = item.value("pivotX", 0.0f);
data.pivotY = item.value("pivotY", 0.0f);
data.pivotZ = item.value("pivotZ", 0.0f);
data.boundsMinX = item.value("boundsMinX", 0.0f);
data.boundsMinY = item.value("boundsMinY", 0.0f);
data.boundsMinZ = item.value("boundsMinZ", 0.0f);
data.boundsMaxX = item.value("boundsMaxX", 0.0f);
data.boundsMaxY = item.value("boundsMaxY", 0.0f);
data.boundsMaxZ = item.value("boundsMaxZ", 0.0f);
data.pivot = {
item.value("pivotX", 0.0f), item.value("pivotY", 0.0f),
item.value("pivotZ", 0.0f)};
data.boundsMin = {
item.value("boundsMinX", 0.0f), item.value("boundsMinY", 0.0f),
item.value("boundsMinZ", 0.0f)};
data.boundsMax = {
item.value("boundsMaxX", 0.0f), item.value("boundsMaxY", 0.0f),
item.value("boundsMaxZ", 0.0f)};
if (item.contains("interactionPositionX")) {
data.hasInteractionPosition = true;
data.interactionPositionX = item.value("interactionPositionX", 0.0f);
data.interactionPositionY = item.value("interactionPositionY", 0.0f);
data.interactionPositionZ = item.value("interactionPositionZ", 0.0f);
data.interactionPosition = {
item.value("interactionPositionX", 0.0f),
item.value("interactionPositionY", 0.0f),
item.value("interactionPositionZ", 0.0f)};
}
if (!data.base.meshPath.empty())
objects.push_back(std::move(data));
@@ -125,40 +126,6 @@ namespace FRG {
return objects;
}
LoadedGameObjectState GameObjectData::toLoadedGameObjectState() const {
LoadedGameObjectState st;
st.texturePath = texturePath;
st.textureDarkandsPath = textureDarkandsPath;
st.meshPath = meshPath;
st.meshRotationX = rotationX;
st.meshRotationY = rotationY;
st.meshRotationZ = rotationZ;
st.meshScale = scale;
return st;
}
InteractiveObjectState InteractiveObjectData::toInteractiveObjectState() const {
InteractiveObjectState st;
st.objectInfo = base.toLoadedGameObjectState();
st.interactionRadius = interactionRadius;
st.approachRadius = approachRadius;
st.castShadow = castShadow;
st.castShadowNight = castShadowNight;
st.isActive = isActive;
st.activateFunctionName = activateFunctionName;
st.pivot = Eigen::Vector3f(pivotX, pivotY, pivotZ);
st.boundsMin = Eigen::Vector3f(boundsMinX, boundsMinY, boundsMinZ);
st.boundsMax = Eigen::Vector3f(boundsMaxX, boundsMaxY, boundsMaxZ);
st.hasInteractionPosition = hasInteractionPosition;
if (hasInteractionPosition)
st.interactionPosition = Eigen::Vector3f(interactionPositionX, interactionPositionY, interactionPositionZ);
st.jsonPositionX = base.positionX;
st.jsonPositionY = base.positionY;
st.jsonPositionZ = base.positionZ;
// state.position is set by InteractiveObject::createFromState() after mesh-center computation
return st;
}
static VertexDataStruct loadMeshData(const std::string& meshPath, const std::string& zipPath)
{
if (meshPath.size() > 4 && meshPath.compare(meshPath.size() - 4, 4, ".bin") == 0)
@@ -213,20 +180,18 @@ namespace FRG {
// Public API
// -----------------------------------------------------------------------
StaticObjectSet GameObjectLoader::loadAndCreateGameObjects(
const std::string& jsonPath,
StaticObjectSet GameObjectLoader::createGameObjects(
const std::vector<GameObjectData>& definitions,
Renderer& renderer,
const std::string& zipPath)
{
StaticObjectSet result;
std::vector<GameObjectData> allData = loadFromJson(jsonPath, zipPath);
// Group objects sharing mesh + textures (in file order) so they can be drawn instanced.
std::vector<std::vector<const GameObjectData*>> groups;
std::unordered_map<std::string, size_t> groupIndexByKey;
for (const auto& data : allData) {
for (const auto& data : definitions) {
if (!data.instanced) {
groups.push_back({ &data });
continue;
@@ -294,45 +259,32 @@ namespace FRG {
return result;
}
std::vector<InteractiveObject> GameObjectLoader::loadAndCreateInteractiveObjects(
const std::string& jsonPath,
StaticObjectSet GameObjectLoader::loadAndCreateGameObjects(
const std::string& jsonPath, Renderer& renderer,
const std::string& zipPath)
{
return createGameObjects(
loadFromJson(jsonPath, zipPath), renderer, zipPath);
}
std::vector<InteractiveObject> GameObjectLoader::createInteractiveObjects(
const std::vector<InteractiveObjectData>& definitions,
Renderer& renderer,
std::unordered_map<std::string, std::shared_ptr<InteractiveObjectState>>& states,
const std::string& zipPath)
{
std::vector<InteractiveObject> interactiveObjects;
if (jsonPath.empty())
return interactiveObjects;
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 {
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);
}
auto state = states.find(data.id);
if (state == states.end() || !state->second)
throw std::runtime_error(
"Authoritative state missing for '" + data.id + "'");
InteractiveObject intObj =
InteractiveObject::createFromState(*state, renderer, zipPath);
InteractiveObject::createFromState(
*state->second, renderer, zipPath);
intObj.id = data.id;
intObj.loadedObject.name = data.base.name;
@@ -352,6 +304,18 @@ namespace FRG {
return interactiveObjects;
}
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)
{
return createInteractiveObjects(
loadInteractiveFromJson(jsonPath, zipPath), renderer, states,
zipPath);
}
std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
{
std::vector<NpcData> npcs;
@@ -412,96 +376,29 @@ namespace FRG {
return npcs;
}
CharacterState NpcData::toCharacterState() const {
CharacterState st;
// Identity + game state
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;
st.modelScale = modelScale;
st.setHp(hp);
st.initialHp = hp;
st.visibleRange = visibleRange;
st.canAttack = canAttack;
if (canAttack) st.attackTargetId = "player";
st.enabled = enabled;
st.interactionRadius = interactionRadius;
Eigen::Quaternionf corrRot = Eigen::Quaternionf::Identity();
if (modelCorrectionRotX != 0.0f)
corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRot;
if (modelCorrectionRotY != 0.0f)
corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRot;
if (modelCorrectionRotZ != 0.0f)
corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRot;
st.modelCorrectionRotation = corrRot;
// Creation info: animation paths
CharacterCreationInfo& info = st.creationInfo;
auto addAnim = [&](AnimationState animState, const std::string& path) {
if (!path.empty())
info.animationPaths.emplace_back(animState, path);
};
addAnim(AnimationState::STAND, animationIdlePath);
addAnim(AnimationState::WALK, animationWalkPath);
addAnim(AnimationState::ACTION_IDLE, animationActionIdlePath);
addAnim(AnimationState::ACTION_ATTACK, animationActionAttackPath);
addAnim(AnimationState::STAND_TO_ACTION, animationStandToActionPath);
addAnim(AnimationState::ACTION_TO_STAND, animationActionToStandPath);
addAnim(AnimationState::ACTION_TO_DEATH, animationActionToDeathPath);
addAnim(AnimationState::DEATH_IDLE, animationDeathIdlePath);
// Creation info: textures
info.meshTexturePaths = std::unordered_map<std::string, std::string>(meshTextures.begin(), meshTextures.end());
info.singleTexturePath = texturePath;
return st;
}
std::vector<std::unique_ptr<Character>> GameObjectLoader::loadAndCreate_Npcs(
const std::string& jsonPath,
std::vector<std::unique_ptr<Character>> GameObjectLoader::createNpcs(
const std::vector<NpcData>& definitions,
Renderer& renderer,
std::unordered_map<std::string, std::shared_ptr<CharacterState>>& states,
const 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;
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 {
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 state = states.find(npcData.id);
if (state == states.end() || !state->second)
throw std::runtime_error(
"Authoritative state missing for '" +
npcData.id + "'");
auto npc = Character::createFromState(
*state, server, locationId, npcData.id, renderer, zipPath.c_str());
*state->second, 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;
@@ -515,4 +412,17 @@ namespace FRG {
return npcs;
}
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)
{
return createNpcs(
loadNpcsFromJson(jsonPath, zipPath), renderer, states, server,
locationId, zipPath);
}
} // namespace FRG