diff --git a/src/Location.cpp b/src/Location.cpp index 1ee4661..f1aeed7 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -108,7 +108,7 @@ namespace ZL // Load NPCs from JSON. Aggressive (canAttack) NPCs need a player target // and hit sparks; peaceful NPCs don't take damage from the player so // they don't need either. - npcs = GameObjectLoader::loadAndCreate_Npcs(params.npcsJsonPath, CONST_ZIP_FILE); + npcs = GameObjectLoader::loadAndCreate_Npcs(params.npcsJsonPath, renderer, CONST_ZIP_FILE); for (auto& npc : npcs) { if (!npc) continue; npc->state.homePosition = Eigen::Vector3f(npc->state.position.x(), 0.f, npc->state.position.z()); diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index 44a7da7..5983ffa 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -312,97 +312,73 @@ namespace ZL { return npcs; } + CharacterState NpcData::toCharacterState() const { + CharacterState st; + + // Identity + game state + st.npcId = id; + st.npcName = name; + st.position = Eigen::Vector3f(positionX, positionY, positionZ); + st.facingAngle = facingAngle; + st.walkSpeed = walkSpeed; + st.rotationSpeed = rotationSpeed; + st.modelScale = modelScale; + st.setHp(hp); + st.initialHp = hp; + st.canAttack = canAttack; + 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(meshTextures.begin(), meshTextures.end()); + info.singleTexturePath = texturePath; + + return st; + } + std::vector> GameObjectLoader::loadAndCreate_Npcs( const std::string& jsonPath, + Renderer& renderer, const std::string& zipPath) { std::vector> npcs; for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) { std::cout << "Loading NPC: " << npcData.name << std::endl; - - auto npc = std::make_unique(); - try { - if (npcData.animationIdlePath.substr(npcData.animationIdlePath.size() - 5) == ".anim") - npc->loadBinaryAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath); - else - npc->loadAnimation(AnimationState::STAND, npcData.animationIdlePath, zipPath); - std::cout << " Loaded IDLE animation: " << npcData.animationIdlePath << std::endl; - } catch (const std::exception& e) { - std::cerr << "GameObjectLoader: Failed to load IDLE animation for '" << npcData.name << "': " << e.what() << std::endl; - continue; - } - - try { - if (npcData.animationWalkPath.substr(npcData.animationWalkPath.size() - 5) == ".anim") - npc->loadBinaryAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath); - else - npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath); - std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl; - } catch (const std::exception& e) { - std::cerr << "GameObjectLoader: Failed to load WALK animation for '" << npcData.name << "': " << e.what() << std::endl; - continue; - } - - auto loadOptionalAnim = [&](AnimationState animState, const std::string& path, const char* label) { - if (path.empty()) return; - try { - if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim") - npc->loadBinaryAnimation(animState, path, zipPath); - else - npc->loadAnimation(animState, path, zipPath); - std::cout << " Loaded " << label << " animation: " << path << std::endl; - } catch (const std::exception& e) { - std::cerr << "GameObjectLoader: Failed to load " << label - << " animation for '" << npcData.name << "': " << e.what() << std::endl; + auto npc = Character::createFromState(npcData.toCharacterState(), renderer, zipPath.c_str()); + if (npc) { + std::cout << "Successfully loaded NPC: " << npcData.name << " at (" + << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; + npcs.push_back(std::move(npc)); } - }; - loadOptionalAnim(AnimationState::ACTION_IDLE, npcData.animationActionIdlePath, "ACTION_IDLE"); - loadOptionalAnim(AnimationState::ACTION_ATTACK, npcData.animationActionAttackPath, "ACTION_ATTACK"); - loadOptionalAnim(AnimationState::STAND_TO_ACTION, npcData.animationStandToActionPath, "STAND_TO_ACTION"); - loadOptionalAnim(AnimationState::ACTION_TO_STAND, npcData.animationActionToStandPath, "ACTION_TO_STAND"); - loadOptionalAnim(AnimationState::ACTION_TO_DEATH, npcData.animationActionToDeathPath, "ACTION_TO_DEATH"); - loadOptionalAnim(AnimationState::DEATH_IDLE, npcData.animationDeathIdlePath, "DEATH_IDLE"); - - try { - - for (const auto& entry : npcData.meshTextures) { - npc->setTexture(entry.first, std::make_shared(CreateTextureDataFromPng(entry.second, zipPath.c_str()))); - std::cout << " -> mesh '" << entry.first << "'" << std::endl; - } } catch (const std::exception& e) { - std::cerr << "GameObjectLoader: Failed to load texture for '" << npcData.name << "': " << e.what() << std::endl; - continue; + std::cerr << "GameObjectLoader: Failed to load NPC '" << npcData.name << "': " << e.what() << std::endl; } - - npc->state.walkSpeed = npcData.walkSpeed; - npc->state.rotationSpeed = npcData.rotationSpeed; - npc->state.modelScale = npcData.modelScale; - npc->state.position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ); - npc->state.facingAngle = npcData.facingAngle; - npc->state.setHp(npcData.hp); - npc->state.initialHp = npcData.hp; - npc->state.canAttack = npcData.canAttack; - npc->state.enabled = npcData.enabled; - npc->state.interactionRadius = npcData.interactionRadius; - npc->state.npcId = npcData.id; - npc->state.npcName = npcData.name; - - Eigen::Quaternionf corrRot = Eigen::Quaternionf::Identity(); - if (npcData.modelCorrectionRotX != 0.0f) - corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRot; - if (npcData.modelCorrectionRotY != 0.0f) - corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRot; - if (npcData.modelCorrectionRotZ != 0.0f) - corrRot = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRot; - npc->state.modelCorrectionRotation = corrRot; - - npc->setTarget(npc->state.position); - npcs.push_back(std::move(npc)); - - std::cout << "Successfully loaded NPC: " << npcData.name << " at (" - << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; } std::cout << "Total NPCs loaded: " << npcs.size() << std::endl; diff --git a/src/items/GameObjectLoader.h b/src/items/GameObjectLoader.h index 88503cb..73f6e4b 100644 --- a/src/items/GameObjectLoader.h +++ b/src/items/GameObjectLoader.h @@ -7,6 +7,7 @@ #include "external/nlohmann/json.hpp" #include "TextModel.h" #include "InteractiveObject.h" +#include "../CharacterState.h" namespace ZL { @@ -71,6 +72,10 @@ namespace ZL { bool canAttack = false; bool enabled = true; float interactionRadius = 0.0f; + + // Converts this JSON-parsed descriptor into a fully-populated CharacterState + // (including creationInfo) ready for Character::createFromState(). + CharacterState toCharacterState() const; }; class GameObjectLoader { @@ -89,6 +94,7 @@ namespace ZL { static std::vector> loadAndCreate_Npcs( const std::string& jsonPath, + Renderer& renderer, const std::string& zipPath = "" );