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
+68
-158
@@ -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
|
||||
Reference in new issue
Block a user