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
@@ -0,0 +1,427 @@
|
||||
#include "LocationDefinition.h"
|
||||
#include "../external/nlohmann/json.hpp"
|
||||
#include "../utils/Utils.h"
|
||||
#include <Eigen/Geometry>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace FRG {
|
||||
namespace {
|
||||
|
||||
using json = nlohmann::json;
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
json loadJson(const std::string& path, const std::string& zipFile)
|
||||
{
|
||||
if (path.empty()) return json::object();
|
||||
std::string content;
|
||||
if (zipFile.empty()) {
|
||||
content = readTextFile(path);
|
||||
} else {
|
||||
const auto bytes = readFileFromZIP(path, zipFile);
|
||||
content.assign(bytes.begin(), bytes.end());
|
||||
}
|
||||
if (content.empty()) throw std::runtime_error("Failed to read " + path);
|
||||
try {
|
||||
return json::parse(content);
|
||||
} catch (const std::exception& error) {
|
||||
throw std::runtime_error(
|
||||
"JSON parse error in " + path + ": " + error.what());
|
||||
}
|
||||
}
|
||||
|
||||
GameObjectData parseGameObject(const json& item)
|
||||
{
|
||||
GameObjectData data;
|
||||
data.name = item.value("name", "Unknown");
|
||||
data.texturePath = item.value("texturePath", "");
|
||||
data.textureDarkandsPath = item.value("textureDarkandsPath", "");
|
||||
data.meshPath = item.value("meshPath", "");
|
||||
data.rotationX = item.value("rotationX", 0.f);
|
||||
data.rotationY = item.value("rotationY", 0.f);
|
||||
data.rotationZ = item.value("rotationZ", 0.f);
|
||||
data.positionX = item.value("positionX", 0.f);
|
||||
data.positionY = item.value("positionY", 0.f);
|
||||
data.positionZ = item.value("positionZ", 0.f);
|
||||
data.scale = item.value("scale", 1.f);
|
||||
data.instanced = item.value("instanced", true);
|
||||
return data;
|
||||
}
|
||||
|
||||
Eigen::Vector3f vector(
|
||||
const json& item, const char* x, const char* y, const char* z,
|
||||
const Eigen::Vector3f& fallback = Eigen::Vector3f::Zero())
|
||||
{
|
||||
return {item.value(x, fallback.x()), item.value(y, fallback.y()),
|
||||
item.value(z, fallback.z())};
|
||||
}
|
||||
|
||||
std::pair<int, int> readBinaryAnimationMetadata(
|
||||
const std::string& path, const std::string& zipFile)
|
||||
{
|
||||
const std::vector<char> bytes = zipFile.empty()
|
||||
? readFile(path) : readFileFromZIP(path, zipFile);
|
||||
if (bytes.size() < 8)
|
||||
throw std::runtime_error("Invalid animation file " + path);
|
||||
const char* cursor = bytes.data();
|
||||
const char* end = cursor + bytes.size();
|
||||
auto skip = [&](std::size_t size) {
|
||||
if (size > static_cast<std::size_t>(end - cursor))
|
||||
throw std::runtime_error("Unexpected EOF in " + path);
|
||||
cursor += size;
|
||||
};
|
||||
auto readU32 = [&]() {
|
||||
std::uint32_t value = 0;
|
||||
if (sizeof(value) > static_cast<std::size_t>(end - cursor))
|
||||
throw std::runtime_error("Unexpected EOF in " + path);
|
||||
std::memcpy(&value, cursor, sizeof(value));
|
||||
cursor += sizeof(value);
|
||||
return value;
|
||||
};
|
||||
auto readI32 = [&]() {
|
||||
std::int32_t value = 0;
|
||||
if (sizeof(value) > static_cast<std::size_t>(end - cursor))
|
||||
throw std::runtime_error("Unexpected EOF in " + path);
|
||||
std::memcpy(&value, cursor, sizeof(value));
|
||||
cursor += sizeof(value);
|
||||
return value;
|
||||
};
|
||||
auto skipString = [&]() { skip(readU32()); };
|
||||
|
||||
if (std::memcmp(cursor, "BSMF", 4) != 0)
|
||||
throw std::runtime_error("Invalid animation file " + path);
|
||||
skip(4);
|
||||
if (readU32() != 2)
|
||||
throw std::runtime_error("Unsupported animation version in " + path);
|
||||
skip(16 * sizeof(float));
|
||||
const std::uint32_t boneCount = readU32();
|
||||
for (std::uint32_t bone = 0; bone < boneCount; ++bone) {
|
||||
skip(3 * sizeof(float) + sizeof(float) + 9 * sizeof(float));
|
||||
skip(sizeof(std::int32_t));
|
||||
skip(static_cast<std::size_t>(readU32()) * sizeof(std::int32_t));
|
||||
}
|
||||
for (std::uint32_t bone = 0; bone < boneCount; ++bone) skipString();
|
||||
|
||||
const std::uint32_t meshCount = readU32();
|
||||
for (std::uint32_t mesh = 0; mesh < meshCount; ++mesh) {
|
||||
skipString();
|
||||
const std::uint32_t vertexCount = readU32();
|
||||
skip(static_cast<std::size_t>(vertexCount) * 3 * sizeof(float));
|
||||
const std::uint32_t faceCount = readU32();
|
||||
skip(static_cast<std::size_t>(faceCount) * 3 * 2 * sizeof(float));
|
||||
skip(static_cast<std::size_t>(vertexCount) * 3 * sizeof(float));
|
||||
const std::uint32_t triangleCount = readU32();
|
||||
skip(static_cast<std::size_t>(triangleCount) * 3 * sizeof(std::int32_t));
|
||||
for (std::uint32_t vertex = 0; vertex < vertexCount; ++vertex)
|
||||
skip(static_cast<std::size_t>(readU32()) *
|
||||
(sizeof(std::int32_t) + sizeof(float)));
|
||||
}
|
||||
|
||||
const std::uint32_t keyframeCount = readU32();
|
||||
int lastFrame = 0;
|
||||
for (std::uint32_t frame = 0; frame < keyframeCount; ++frame) {
|
||||
lastFrame = readI32();
|
||||
skip(static_cast<std::size_t>(boneCount) *
|
||||
(3 * sizeof(float) + 16 * sizeof(float)));
|
||||
}
|
||||
return {0, keyframeCount == 0 ? 1 : lastFrame + 1};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
LoadedGameObjectState GameObjectData::toLoadedGameObjectState() const
|
||||
{
|
||||
LoadedGameObjectState state;
|
||||
state.texturePath = texturePath;
|
||||
state.textureDarkandsPath = textureDarkandsPath;
|
||||
state.meshPath = meshPath;
|
||||
state.meshRotationX = rotationX;
|
||||
state.meshRotationY = rotationY;
|
||||
state.meshRotationZ = rotationZ;
|
||||
state.meshScale = scale;
|
||||
return state;
|
||||
}
|
||||
|
||||
InteractiveObjectState InteractiveObjectData::toInteractiveObjectState() const
|
||||
{
|
||||
InteractiveObjectState state;
|
||||
state.objectInfo = base.toLoadedGameObjectState();
|
||||
state.interactionRadius = interactionRadius;
|
||||
state.approachRadius = approachRadius;
|
||||
state.castShadow = castShadow;
|
||||
state.castShadowNight = castShadowNight;
|
||||
state.isActive = isActive;
|
||||
state.activateFunctionName = activateFunctionName;
|
||||
state.pivot = pivot;
|
||||
state.boundsMin = boundsMin;
|
||||
state.boundsMax = boundsMax;
|
||||
state.hasInteractionPosition = hasInteractionPosition;
|
||||
state.interactionPosition = interactionPosition;
|
||||
state.jsonPositionX = base.positionX;
|
||||
state.jsonPositionY = base.positionY;
|
||||
state.jsonPositionZ = base.positionZ;
|
||||
state.position =
|
||||
Eigen::Vector3f(base.positionX, base.positionY, base.positionZ);
|
||||
return state;
|
||||
}
|
||||
|
||||
CharacterState NpcData::toCharacterState() const
|
||||
{
|
||||
CharacterState state;
|
||||
state.npcName = name;
|
||||
state.position = {positionX, positionY, positionZ};
|
||||
state.homePosition = {positionX, 0.f, positionZ};
|
||||
state.stopInPlace();
|
||||
state.facingAngle = facingAngle;
|
||||
state.walkSpeed = walkSpeed;
|
||||
state.rotationSpeed = rotationSpeed;
|
||||
state.modelScale = modelScale;
|
||||
state.setHp(hp);
|
||||
state.initialHp = hp;
|
||||
state.visibleRange = visibleRange;
|
||||
state.canAttack = canAttack;
|
||||
if (canAttack) state.attackTargetId = "player";
|
||||
state.enabled = enabled;
|
||||
state.interactionRadius = interactionRadius;
|
||||
|
||||
Eigen::Quaternionf correction = Eigen::Quaternionf::Identity();
|
||||
if (modelCorrectionRotX != 0.f)
|
||||
correction = Eigen::Quaternionf(Eigen::AngleAxisf(
|
||||
modelCorrectionRotX, Eigen::Vector3f::UnitX())) * correction;
|
||||
if (modelCorrectionRotY != 0.f)
|
||||
correction = Eigen::Quaternionf(Eigen::AngleAxisf(
|
||||
modelCorrectionRotY, Eigen::Vector3f::UnitY())) * correction;
|
||||
if (modelCorrectionRotZ != 0.f)
|
||||
correction = Eigen::Quaternionf(Eigen::AngleAxisf(
|
||||
modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * correction;
|
||||
state.modelCorrectionRotation = correction;
|
||||
|
||||
auto addAnimation = [&](AnimationState animation, const std::string& path) {
|
||||
if (!path.empty())
|
||||
state.creationInfo.animationPaths.emplace_back(animation, path);
|
||||
};
|
||||
addAnimation(AnimationState::STAND, animationIdlePath);
|
||||
addAnimation(AnimationState::WALK, animationWalkPath);
|
||||
addAnimation(AnimationState::ACTION_IDLE, animationActionIdlePath);
|
||||
addAnimation(AnimationState::ACTION_ATTACK, animationActionAttackPath);
|
||||
addAnimation(AnimationState::STAND_TO_ACTION, animationStandToActionPath);
|
||||
addAnimation(AnimationState::ACTION_TO_STAND, animationActionToStandPath);
|
||||
addAnimation(AnimationState::ACTION_TO_DEATH, animationActionToDeathPath);
|
||||
addAnimation(AnimationState::DEATH_IDLE, animationDeathIdlePath);
|
||||
state.creationInfo.meshTexturePaths = {
|
||||
meshTextures.begin(), meshTextures.end()};
|
||||
state.creationInfo.singleTexturePath = texturePath;
|
||||
return state;
|
||||
}
|
||||
|
||||
void loadCharacterAnimationMetadata(
|
||||
CharacterState& state, const std::string& zipFile)
|
||||
{
|
||||
for (const auto& [animation, path] : state.creationInfo.animationPaths) {
|
||||
if (path.size() < 5 || path.substr(path.size() - 5) != ".anim")
|
||||
continue;
|
||||
try {
|
||||
const auto [startFrame, frameCount] =
|
||||
readBinaryAnimationMetadata(path, zipFile);
|
||||
state.setAnimationMetadata(animation, startFrame, frameCount);
|
||||
} catch (const std::exception& error) {
|
||||
logger() << "[content] Animation metadata failed for " << path
|
||||
<< ": " << error.what() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocationDefinition LocationContentLoader::load(
|
||||
const LocationConfig& config, const std::string& zipFile)
|
||||
{
|
||||
LocationDefinition result;
|
||||
result.config = config;
|
||||
|
||||
if (!config.gameObjectsJsonPath.empty()) {
|
||||
const json root = loadJson(config.gameObjectsJsonPath, zipFile);
|
||||
for (const auto& item : root.value("objects", json::array())) {
|
||||
GameObjectData data = parseGameObject(item);
|
||||
if (!data.meshPath.empty())
|
||||
result.staticObjects.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.interactiveObjectsJsonPath.empty()) {
|
||||
const json root = loadJson(config.interactiveObjectsJsonPath, zipFile);
|
||||
std::unordered_set<std::string> ids;
|
||||
for (const auto& item : root.value("objects", json::array())) {
|
||||
InteractiveObjectData data;
|
||||
data.id = item.at("id").get<std::string>();
|
||||
if (data.id.empty() || !ids.insert(data.id).second)
|
||||
throw std::runtime_error(
|
||||
"Missing or duplicate interactive-object id in " +
|
||||
config.interactiveObjectsJsonPath);
|
||||
data.base = parseGameObject(item);
|
||||
data.interactionRadius = item.value("interactionRadius", 2.f);
|
||||
data.approachRadius =
|
||||
item.value("approachRadius", data.interactionRadius);
|
||||
data.activateFunctionName =
|
||||
item.value("activateFunction", "");
|
||||
data.pivot = vector(item, "pivotX", "pivotY", "pivotZ");
|
||||
data.boundsMin =
|
||||
vector(item, "boundsMinX", "boundsMinY", "boundsMinZ");
|
||||
data.boundsMax =
|
||||
vector(item, "boundsMaxX", "boundsMaxY", "boundsMaxZ");
|
||||
data.hasInteractionPosition =
|
||||
item.contains("interactionPositionX");
|
||||
if (data.hasInteractionPosition) {
|
||||
data.interactionPosition = vector(
|
||||
item, "interactionPositionX", "interactionPositionY",
|
||||
"interactionPositionZ");
|
||||
}
|
||||
data.castShadow = item.value("castShadow", true);
|
||||
data.castShadowNight = item.value("castShadowNight", true);
|
||||
data.isActive = item.value("isActive", true);
|
||||
result.interactiveObjects.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.npcsJsonPath.empty()) {
|
||||
const json root = loadJson(config.npcsJsonPath, zipFile);
|
||||
std::unordered_set<std::string> ids;
|
||||
for (const auto& item : root.value("npcs", json::array())) {
|
||||
NpcData data;
|
||||
data.id = item.at("id").get<std::string>();
|
||||
if (data.id.empty() || data.id == "player" ||
|
||||
!ids.insert(data.id).second) {
|
||||
throw std::runtime_error(
|
||||
"Missing, duplicate, or reserved NPC id in " +
|
||||
config.npcsJsonPath);
|
||||
}
|
||||
data.name = item.value("name", "Unknown NPC");
|
||||
data.texturePath = item.value("texturePath", "");
|
||||
if (item.contains("meshTextures") &&
|
||||
item["meshTextures"].is_object()) {
|
||||
for (const auto& [name, path] :
|
||||
item["meshTextures"].items()) {
|
||||
if (path.is_string())
|
||||
data.meshTextures[name] = path.get<std::string>();
|
||||
}
|
||||
}
|
||||
data.interactionRadius =
|
||||
item.value("interactionRadius", 0.f);
|
||||
data.animationIdlePath =
|
||||
item.value("animationIdlePath", "");
|
||||
data.animationWalkPath =
|
||||
item.value("animationWalkPath", "");
|
||||
data.animationActionIdlePath =
|
||||
item.value("animationActionIdlePath", "");
|
||||
data.animationActionAttackPath =
|
||||
item.value("animationActionAttackPath", "");
|
||||
data.animationStandToActionPath =
|
||||
item.value("animationStandToActionPath", "");
|
||||
data.animationActionToStandPath =
|
||||
item.value("animationActionToStandPath", "");
|
||||
data.animationActionToDeathPath =
|
||||
item.value("animationActionToDeathPath", "");
|
||||
data.animationDeathIdlePath =
|
||||
item.value("animationDeathIdlePath", "");
|
||||
data.positionX = item.value("positionX", 0.f);
|
||||
data.positionY = item.value("positionY", 0.f);
|
||||
data.positionZ = item.value("positionZ", 0.f);
|
||||
data.walkSpeed = item.value("walkSpeed", 1.5f);
|
||||
data.rotationSpeed = item.value("rotationSpeed", 8.f);
|
||||
data.modelScale = item.value("modelScale", 0.01f);
|
||||
const float degrees = kPi / 180.f;
|
||||
data.modelCorrectionRotX =
|
||||
item.value("modelCorrectionRotX", 0.f) * degrees;
|
||||
data.modelCorrectionRotY =
|
||||
item.value("modelCorrectionRotY", 0.f) * degrees;
|
||||
data.modelCorrectionRotZ =
|
||||
item.value("modelCorrectionRotZ", 0.f) * degrees;
|
||||
data.facingAngle =
|
||||
item.value("facingAngle", 0.f) * degrees;
|
||||
data.hp = item.value("hp", 100.f);
|
||||
data.visibleRange =
|
||||
item.value("visibleRange", VISIBLE_RANGE);
|
||||
data.canAttack = item.value("canAttack", false);
|
||||
data.enabled = item.value("enabled", true);
|
||||
result.npcs.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.teleportsJsonPath.empty()) {
|
||||
const json root = loadJson(config.teleportsJsonPath, zipFile);
|
||||
std::unordered_set<std::string> ids;
|
||||
for (const auto& item : root.value("teleports", json::array())) {
|
||||
TeleportDefinition data;
|
||||
data.id = item.at("id").get<std::string>();
|
||||
if (data.id.empty() || !ids.insert(data.id).second)
|
||||
throw std::runtime_error(
|
||||
"Missing or duplicate teleport id in " +
|
||||
config.teleportsJsonPath);
|
||||
data.active = item.value("active", false);
|
||||
data.position =
|
||||
vector(item, "positionX", "positionY", "positionZ");
|
||||
data.radius = item.value("radius", 0.f);
|
||||
data.automatic = item.value("automatic", false);
|
||||
data.automaticRadius =
|
||||
item.value("automaticRadius", data.radius);
|
||||
data.destinationLocation =
|
||||
item.value("destinationLocation", "");
|
||||
data.destinationPosition = vector(
|
||||
item, "destinationPositionX", "destinationPositionY",
|
||||
"destinationPositionZ");
|
||||
data.destinationRotationY =
|
||||
item.value("destinationRotationY", 0.f);
|
||||
result.teleports.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.triggerZonesJsonPath.empty()) {
|
||||
const json root = loadJson(config.triggerZonesJsonPath, zipFile);
|
||||
std::unordered_set<std::string> ids;
|
||||
for (const auto& item :
|
||||
root.value("trigger_zones", json::array())) {
|
||||
TriggerZoneDefinition data;
|
||||
data.id = item.at("id").get<std::string>();
|
||||
if (data.id.empty() || !ids.insert(data.id).second)
|
||||
throw std::runtime_error(
|
||||
"Missing or duplicate trigger-zone id in " +
|
||||
config.triggerZonesJsonPath);
|
||||
data.enabled = item.value("enabled", true);
|
||||
data.radius = item.value("radius", 1.5f);
|
||||
data.hysteresis = item.value("hysteresis", 0.3f);
|
||||
data.position =
|
||||
vector(item, "positionX", "positionY", "positionZ");
|
||||
result.triggerZones.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.lightsJsonPath.empty()) {
|
||||
const json root = loadJson(config.lightsJsonPath, zipFile);
|
||||
for (const auto& item : root.value("lights", json::array())) {
|
||||
PointLightDefinition data;
|
||||
data.id = item.value("id", "");
|
||||
data.position =
|
||||
vector(item, "positionX", "positionY", "positionZ");
|
||||
data.direction = vector(
|
||||
item, "directionX", "directionY", "directionZ",
|
||||
Eigen::Vector3f(0.f, -1.f, 0.f));
|
||||
data.color = vector(
|
||||
item, "colorR", "colorG", "colorB",
|
||||
Eigen::Vector3f::Ones());
|
||||
data.autoLight = item.value("autoLight", false);
|
||||
data.autoLightDistance =
|
||||
item.value("autoLightDistance", -1.f);
|
||||
data.limitX = item.value("limitX", 99999.f);
|
||||
data.limitY = item.value("limitY", 99999.f);
|
||||
data.autoLightLimitX =
|
||||
item.value("autoLightLimitX", -1.f);
|
||||
data.autoLightLimitY =
|
||||
item.value("autoLightLimitY", -1.f);
|
||||
result.pointLights.push_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace FRG
|
||||
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Campaign.h"
|
||||
#include "../CharacterState.h"
|
||||
#include "../items/InteractiveObjectState.h"
|
||||
#include <Eigen/Core>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace FRG {
|
||||
|
||||
struct GameObjectData {
|
||||
std::string name;
|
||||
std::string texturePath;
|
||||
std::string textureDarkandsPath;
|
||||
std::string meshPath;
|
||||
float rotationX = 0.f;
|
||||
float rotationY = 0.f;
|
||||
float rotationZ = 0.f;
|
||||
float positionX = 0.f;
|
||||
float positionY = 0.f;
|
||||
float positionZ = 0.f;
|
||||
float scale = 1.f;
|
||||
bool instanced = true;
|
||||
|
||||
LoadedGameObjectState toLoadedGameObjectState() const;
|
||||
};
|
||||
|
||||
struct InteractiveObjectData {
|
||||
std::string id;
|
||||
GameObjectData base;
|
||||
float interactionRadius = 2.f;
|
||||
float approachRadius = 2.f;
|
||||
std::string activateFunctionName;
|
||||
Eigen::Vector3f pivot = Eigen::Vector3f::Zero();
|
||||
Eigen::Vector3f boundsMin = Eigen::Vector3f::Zero();
|
||||
Eigen::Vector3f boundsMax = Eigen::Vector3f::Zero();
|
||||
bool hasInteractionPosition = false;
|
||||
Eigen::Vector3f interactionPosition = Eigen::Vector3f::Zero();
|
||||
bool castShadow = true;
|
||||
bool castShadowNight = true;
|
||||
bool isActive = true;
|
||||
|
||||
InteractiveObjectState toInteractiveObjectState() const;
|
||||
};
|
||||
|
||||
struct NpcData {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string texturePath;
|
||||
std::map<std::string, std::string> meshTextures;
|
||||
std::string animationIdlePath;
|
||||
std::string animationWalkPath;
|
||||
std::string animationActionIdlePath;
|
||||
std::string animationActionAttackPath;
|
||||
std::string animationStandToActionPath;
|
||||
std::string animationActionToStandPath;
|
||||
std::string animationActionToDeathPath;
|
||||
std::string animationDeathIdlePath;
|
||||
float positionX = 0.f;
|
||||
float positionY = 0.f;
|
||||
float positionZ = 0.f;
|
||||
float walkSpeed = 1.5f;
|
||||
float rotationSpeed = 8.f;
|
||||
float modelScale = 0.01f;
|
||||
float modelCorrectionRotX = 0.f;
|
||||
float modelCorrectionRotY = 0.f;
|
||||
float modelCorrectionRotZ = 0.f;
|
||||
float facingAngle = 0.f;
|
||||
float hp = 100.f;
|
||||
float visibleRange = VISIBLE_RANGE;
|
||||
bool canAttack = false;
|
||||
bool enabled = true;
|
||||
float interactionRadius = 0.f;
|
||||
|
||||
CharacterState toCharacterState() const;
|
||||
};
|
||||
|
||||
struct TeleportDefinition {
|
||||
std::string id;
|
||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||
float radius = 0.f;
|
||||
bool active = false;
|
||||
std::string destinationLocation;
|
||||
Eigen::Vector3f destinationPosition = Eigen::Vector3f::Zero();
|
||||
float destinationRotationY = 0.f;
|
||||
bool automatic = false;
|
||||
float automaticRadius = 0.f;
|
||||
};
|
||||
|
||||
struct TriggerZoneDefinition {
|
||||
std::string id;
|
||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||
float radius = 1.5f;
|
||||
float hysteresis = 0.3f;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
struct PointLightDefinition {
|
||||
std::string id;
|
||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||
Eigen::Vector3f direction = Eigen::Vector3f(0.f, -1.f, 0.f);
|
||||
Eigen::Vector3f color = Eigen::Vector3f::Ones();
|
||||
bool autoLight = false;
|
||||
float autoLightDistance = -1.f;
|
||||
float limitX = 99999.f;
|
||||
float limitY = 99999.f;
|
||||
float autoLightLimitX = -1.f;
|
||||
float autoLightLimitY = -1.f;
|
||||
};
|
||||
|
||||
struct LocationDefinition {
|
||||
LocationConfig config;
|
||||
std::vector<GameObjectData> staticObjects;
|
||||
std::vector<InteractiveObjectData> interactiveObjects;
|
||||
std::vector<NpcData> npcs;
|
||||
std::vector<TeleportDefinition> teleports;
|
||||
std::vector<TriggerZoneDefinition> triggerZones;
|
||||
std::vector<PointLightDefinition> pointLights;
|
||||
};
|
||||
|
||||
class LocationContentLoader {
|
||||
public:
|
||||
static LocationDefinition load(
|
||||
const LocationConfig& config, const std::string& zipFile = {});
|
||||
};
|
||||
|
||||
void loadCharacterAnimationMetadata(
|
||||
CharacterState& state, const std::string& zipFile = {});
|
||||
|
||||
} // namespace FRG
|
||||
Reference in new issue
Block a user