#include "GameObjectLoader.h" #include #include #include #include "render/Renderer.h" #include "render/TextureManager.h" #include "utils/Utils.h" #include #include "../Character.h" namespace ZL { void set_Texture(Character& npc, const TextureDataStruct& texture); void set_Texture(Character& npc, const std::string& meshName, const TextureDataStruct& texture); using json = nlohmann::json; std::vector GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector objects; std::string content; try { if (zipPath.empty()) { content = readTextFile(jsonPath); } else { auto buf = readFileFromZIP(jsonPath, zipPath); if (buf.empty()) { std::cerr << "UiManager: failed to read " << jsonPath << " from zip " << zipPath << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } content.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { std::cerr << "UiManager: failed to open " << jsonPath << " : " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } json j; try { j = json::parse(content); } catch (const std::exception& e) { std::cerr << "UiManager: json parse error: " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } //json j; try { /*std::ifstream file(jsonPath); if (!file.is_open()) { throw std::runtime_error("Could not open file: " + jsonPath); } if (file.peek() == std::ifstream::traits_type::eof()) { throw std::runtime_error("JSON file is empty: " + jsonPath); } file >> j;*/ if (!j.contains("objects") || !j["objects"].is_array()) { std::cerr << "Warning: 'objects' array not found in " << jsonPath << std::endl; return objects; } for (const auto& item : j["objects"]) { GameObjectData data; data.name = item.value("name", "Unknown"); data.texturePath = item.value("texturePath", ""); data.meshPath = item.value("meshPath", ""); data.rotationX = item.value("rotationX", 0.0f); data.rotationY = item.value("rotationY", 0.0f); data.rotationZ = item.value("rotationZ", 0.0f); data.positionX = item.value("positionX", 0.0f); data.positionY = item.value("positionY", 0.0f); data.positionZ = item.value("positionZ", 0.0f); data.scale = item.value("scale", 1.0f); // Interactive object properties data.interactive = item.value("interactive", false); if (data.interactive && item.contains("item") && item["item"].is_object()) { const auto& itemData = item["item"]; data.itemId = itemData.value("id", ""); data.itemName = itemData.value("name", "Unknown Item"); data.itemDescription = itemData.value("description", ""); data.itemIcon = itemData.value("icon", ""); data.interactionRadius = itemData.value("radius", 2.0f); } data.activateFunctionName = item.value("activateFunction", ""); if (!data.meshPath.empty()) { objects.push_back(data); } } std::cout << "Successfully loaded " << objects.size() << " game objects from " << jsonPath << std::endl; } catch (const std::exception& e) { std::cerr << "Error loading JSON: " << e.what() << std::endl; } return objects; } std::unordered_map GameObjectLoader::loadAndCreateGameObjects( const std::string& jsonPath, Renderer& renderer, const std::string& zipPath) { std::unordered_map gameObjects; std::vector objectsData = loadFromJson(jsonPath, zipPath); for (const auto& objData : objectsData) { if (objData.interactive) continue; // Skip interactive objects, they're handled separately std::cout << "Loading game object: " << objData.name << std::endl; LoadedGameObject gameObj; gameObj.name = objData.name; // Load texture try { gameObj.texture = std::make_shared(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str())); } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load texture for '" << objData.name << "': " << e.what() << std::endl; continue; } // Load mesh try { gameObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath); } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load mesh for '" << objData.name << "': " << e.what() << std::endl; continue; } // Apply rotation Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity(); if (objData.rotationX != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat; } if (objData.rotationY != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat; } if (objData.rotationZ != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat; } gameObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix()); // Apply scale if (objData.scale != 1.0f) { gameObj.mesh.data.Scale(objData.scale); } // Apply position if (objData.positionX != 0.0f || objData.positionY != 0.0f || objData.positionZ != 0.0f) { gameObj.mesh.data.Move({ objData.positionX, objData.positionY, objData.positionZ }); } gameObj.mesh.RefreshVBO(); gameObjects[objData.name] = gameObj; std::cout << "Successfully loaded: " << objData.name << std::endl; } std::cout << "Total game objects loaded: " << gameObjects.size() << std::endl; return gameObjects; } std::vector GameObjectLoader::loadAndCreateInteractiveObjects( const std::string& jsonPath, Renderer& renderer, const std::string& zipPath) { std::vector interactiveObjects; std::vector objectsData = loadFromJson(jsonPath, zipPath); for (const auto& objData : objectsData) { if (!objData.interactive) continue; std::cout << "Loading interactive object: " << objData.name << std::endl; InteractiveObject intObj; //intObj.id = objData.name; intObj.id = !objData.itemId.empty() ? objData.itemId : objData.name; intObj.name = objData.name; intObj.interactionRadius = objData.interactionRadius; intObj.activateFunctionName = objData.activateFunctionName; // Load texture try { intObj.texture = std::make_shared(CreateTextureDataFromPng(objData.texturePath, zipPath.c_str())); } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl; continue; } // Load mesh try { intObj.mesh.data = LoadFromTextFile02(objData.meshPath, zipPath); } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load mesh for interactive '" << objData.name << "': " << e.what() << std::endl; continue; } // Apply rotation Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity(); if (objData.rotationX != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat; } if (objData.rotationY != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationY, Eigen::Vector3f::UnitY())) * rotationQuat; } if (objData.rotationZ != 0.0f) { rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationZ, Eigen::Vector3f::UnitZ())) * rotationQuat; } intObj.mesh.data.RotateByMatrix(rotationQuat.toRotationMatrix()); // Apply scale if (objData.scale != 1.0f) { intObj.mesh.data.Scale(objData.scale); } intObj.mesh.RefreshVBO(); // Calculate mesh bounds to properly offset position if (!intObj.mesh.data.PositionData.empty()) { Eigen::Vector3f meshMin = intObj.mesh.data.PositionData[0]; Eigen::Vector3f meshMax = intObj.mesh.data.PositionData[0]; for (const auto& vert : intObj.mesh.data.PositionData) { meshMin = meshMin.cwiseMin(vert); meshMax = meshMax.cwiseMax(vert); } Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f; std::cout << "[LOADER] Mesh bounds:" << std::endl; std::cout << " Min: (" << meshMin.x() << ", " << meshMin.y() << ", " << meshMin.z() << ")" << std::endl; std::cout << " Max: (" << meshMax.x() << ", " << meshMax.y() << ", " << meshMax.z() << ")" << std::endl; std::cout << " Center: (" << meshCenter.x() << ", " << meshCenter.y() << ", " << meshCenter.z() << ")" << std::endl; // Translate mesh so it's centered at origin intObj.mesh.data.Move(-meshCenter); intObj.mesh.RefreshVBO(); // Adjust position to account for mesh offset intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ) + meshCenter; std::cout << "[LOADER] Corrected position: (" << intObj.position.x() << ", " << intObj.position.y() << ", " << intObj.position.z() << ")" << std::endl; } else { // Fallback if no vertices intObj.position = Eigen::Vector3f(objData.positionX, objData.positionY, objData.positionZ); } // Create item intObj.dropItem = Item(objData.itemId, objData.itemName, objData.itemDescription, objData.itemIcon); interactiveObjects.push_back(intObj); std::cout << "Successfully loaded interactive: " << objData.name << " (item: " << objData.itemName; if (!objData.activateFunctionName.empty()) { std::cout << ", function: " << objData.activateFunctionName; } std::cout << ")" << std::endl; } std::cout << "Total interactive objects loaded: " << interactiveObjects.size() << std::endl; return interactiveObjects; } std::vector GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath) { std::vector npcs; std::string content; try { if (zipPath.empty()) { content = readTextFile(jsonPath); } else { auto buf = readFileFromZIP(jsonPath, zipPath); if (buf.empty()) { std::cerr << "UiManager: failed to read " << jsonPath << " from zip " << zipPath << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } content.assign(buf.begin(), buf.end()); } } catch (const std::exception& e) { std::cerr << "UiManager: failed to open " << jsonPath << " : " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } json j; try { j = json::parse(content); } catch (const std::exception& e) { std::cerr << "UiManager: json parse error: " << e.what() << std::endl; throw std::runtime_error("Failed to load UI file: " + jsonPath); } //json j; try { /*std::ifstream file(jsonPath); if (!file.is_open()) { throw std::runtime_error("Could not open file: " + jsonPath); } if (file.peek() == std::ifstream::traits_type::eof()) { throw std::runtime_error("JSON file is empty: " + jsonPath); } file >> j;*/ if (!j.contains("npcs") || !j["npcs"].is_array()) { std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl; return npcs; } for (const auto& item : j["npcs"]) { NpcData data; data.id = item.value("id", "npc_unknown"); data.name = item.value("name", "Unknown NPC"); data.texturePath = item.value("texturePath", ""); if (item.contains("meshTextures") && item["meshTextures"].is_object()) { for (auto it = item["meshTextures"].begin(); it != item["meshTextures"].end(); ++it) { if (it.value().is_string()) { data.meshTextures[it.key()] = it.value().get(); } } } data.interactionRadius = item.value("interactionRadius", 0.0f); 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.0f); data.positionY = item.value("positionY", 0.0f); data.positionZ = item.value("positionZ", 0.0f); data.walkSpeed = item.value("walkSpeed", 1.5f); data.rotationSpeed = item.value("rotationSpeed", 8.0f); data.modelScale = item.value("modelScale", 0.01f); // Model correction rotation (in degrees, will convert to radians) data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast(M_PI) / 180.0f; data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast(M_PI) / 180.0f; data.facingAngle = item.value("facingAngle", 0.0f); data.hp = item.value("hp", 100.0f); data.canAttack = item.value("canAttack", false); // Load gift data if available if (item.contains("gift") && item["gift"].is_object()) { const auto& giftData = item["gift"]; data.gift.id = giftData.value("id", ""); data.gift.name = giftData.value("name", "Gift"); data.gift.description = giftData.value("description", "A gift from an NPC"); data.gift.icon = giftData.value("icon", ""); } npcs.push_back(data); } std::cout << "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; } return npcs; } std::vector> GameObjectLoader::loadAndCreate_Npcs( const std::string& jsonPath, const std::string& zipPath) { std::vector> npcs; std::vector npcsData = loadNpcsFromJson(jsonPath, zipPath); for (const auto& npcData : npcsData) { std::cout << "Loading NPC: " << npcData.name << std::endl; auto npc = std::make_unique(); // Load animations 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; } // Optional combat/death slots — empty path means "skip this slot". auto loadOptionalAnim = [&](AnimationState state, const std::string& path, const char* label) { if (path.empty()) return; try { if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim") { npc->loadBinaryAnimation(state, path, zipPath); } else { npc->loadAnimation(state, 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; } }; 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"); // Load textures: per-mesh map takes precedence; fall back to single texturePath. try { if (!npcData.meshTextures.empty()) { std::unordered_map> cache; for (const auto& entry : npcData.meshTextures) { const std::string& meshName = entry.first; const std::string& path = entry.second; /*auto cit = cache.find(path); if (cit == cache.end()) { auto tex = std::make_shared(CreateTextureDataFromPng(path, zipPath.c_str())); cit = cache.emplace(path, std::move(tex)).first; std::cout << " Loaded texture: " << path << std::endl; }*/ //npc->setTexture(meshName, std::make_shared(CreateTextureDataFromPng(path, zipPath.c_str()))); set_Texture(*npc, meshName, CreateTextureDataFromPng(path, zipPath.c_str())); //std::cout << xxa(*npc, CreateTextureDataFromPng(path, zipPath.c_str())) << std::endl; std::cout << " -> mesh '" << meshName << "'" << std::endl; } } else { //auto texture = std::make_shared(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); set_Texture(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())); //std::cout << xxa(*npc, CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str())) << std::endl; std::cout << " Loaded texture: " << npcData.texturePath << std::endl; } } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load texture for '" << npcData.name << "': " << e.what() << std::endl; continue; } // Set NPC properties npc->walkSpeed = npcData.walkSpeed; npc->rotationSpeed = npcData.rotationSpeed; npc->modelScale = npcData.modelScale; npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ); npc->facingAngle = npcData.facingAngle; npc->hp = npcData.hp; npc->canAttack = npcData.canAttack; npc->interactionRadius = npcData.interactionRadius; // Set NPC metadata npc->npcId = npcData.id; npc->npcName = npcData.name; // Set gift if (!npcData.gift.id.empty()) { npc->giftItem = Item(npcData.gift.id, npcData.gift.name, npcData.gift.description, npcData.gift.icon); std::cout << " Gift: " << npcData.gift.name << std::endl; } Eigen::Quaternionf corrRotQuat = Eigen::Quaternionf::Identity(); if (npcData.modelCorrectionRotX != 0.0f) { corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotX, Eigen::Vector3f::UnitX())) * corrRotQuat; } if (npcData.modelCorrectionRotY != 0.0f) { corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotY, Eigen::Vector3f::UnitY())) * corrRotQuat; } if (npcData.modelCorrectionRotZ != 0.0f) { corrRotQuat = Eigen::Quaternionf(Eigen::AngleAxisf(npcData.modelCorrectionRotZ, Eigen::Vector3f::UnitZ())) * corrRotQuat; } npc->modelCorrectionRotation = corrRotQuat; // Set initial target to current position npc->setTarget(npc->position); npcs.push_back(std::move(npc)); std::cout << "Successfully loaded NPC: " << npcData.name << " at position (" << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; } std::cout << "Total NPCs loaded: " << npcs.size() << std::endl; return npcs; } } // namespace ZL