Major refactoring for game objects

This commit is contained in:
Vladislav Khorev committed 2026-05-12 21:19:29 +03:00
1 parent efb85f7328
commit 2cae9998a0
18 files changed
+547 -716

No files matched your search

+202 -386
View File
@@ -7,6 +7,7 @@
#include "utils/Utils.h"
#include <Eigen/Geometry>
#include "../Character.h"
namespace ZL {
void set_Texture(Character& npc, const TextureDataStruct& texture);
@@ -14,169 +15,144 @@ namespace ZL {
using json = nlohmann::json;
std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
// -----------------------------------------------------------------------
// Private helpers
// -----------------------------------------------------------------------
json GameObjectLoader::loadJson(const std::string& jsonPath, const std::string& zipPath)
{
std::vector<GameObjectData> objects;
std::string content;
try {
if (zipPath.empty()) {
content = readTextFile(jsonPath);
}
else {
} 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);
}
if (buf.empty())
throw std::runtime_error("empty result from zip");
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;
throw std::runtime_error("Failed to read " + jsonPath + ": " + e.what());
}
try {
return json::parse(content);
} catch (const std::exception& e) {
throw std::runtime_error("JSON parse error in " + jsonPath + ": " + e.what());
}
}
static GameObjectData parseGameObjectData(const json& item)
{
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);
return data;
}
std::vector<GameObjectData> GameObjectLoader::loadFromJson(const std::string& jsonPath, const std::string& zipPath)
{
std::vector<GameObjectData> objects;
json j = loadJson(jsonPath, zipPath);
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 = parseGameObjectData(item);
if (!data.meshPath.empty())
objects.push_back(std::move(data));
}
std::cout << "Loaded " << objects.size() << " static objects from " << jsonPath << std::endl;
return objects;
}
std::vector<InteractiveObjectData> GameObjectLoader::loadInteractiveFromJson(const std::string& jsonPath, const std::string& zipPath)
{
std::vector<InteractiveObjectData> objects;
json j = loadJson(jsonPath, zipPath);
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"]) {
InteractiveObjectData data;
data.base = parseGameObjectData(item);
data.interactionRadius = item.value("interactionRadius", 2.0f);
data.activateFunctionName = item.value("activateFunction", "");
if (!data.base.meshPath.empty())
objects.push_back(std::move(data));
}
std::cout << "Loaded " << objects.size() << " interactive objects from " << jsonPath << std::endl;
return objects;
}
LoadedGameObject GameObjectLoader::buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath)
{
LoadedGameObject obj;
obj.name = data.name;
obj.texture = renderer.textureManager.LoadFromPng(data.texturePath, zipPath);
if (data.meshPath.size() > 4 && data.meshPath.compare(data.meshPath.size() - 4, 4, ".bin") == 0)
obj.mesh.data = LoadModelFromBinFile(data.meshPath, zipPath);
else
obj.mesh.data = LoadFromTextFile02(data.meshPath, zipPath);
Eigen::Quaternionf rot = Eigen::Quaternionf::Identity();
if (data.rotationX != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationX, Eigen::Vector3f::UnitX())) * rot;
if (data.rotationY != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationY, Eigen::Vector3f::UnitY())) * rot;
if (data.rotationZ != 0.0f)
rot = Eigen::Quaternionf(Eigen::AngleAxisf(data.rotationZ, Eigen::Vector3f::UnitZ())) * rot;
obj.mesh.data.RotateByMatrix(rot.toRotationMatrix());
if (data.scale != 1.0f)
obj.mesh.data.Scale(data.scale);
return obj;
}
// -----------------------------------------------------------------------
// Public API
// -----------------------------------------------------------------------
std::unordered_map<std::string, LoadedGameObject> GameObjectLoader::loadAndCreateGameObjects(
const std::string& jsonPath,
Renderer& renderer,
const std::string& zipPath)
{
std::unordered_map<std::string, LoadedGameObject> gameObjects;
std::vector<GameObjectData> 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
for (const auto& data : loadFromJson(jsonPath, zipPath)) {
std::cout << "Loading game object: " << data.name << std::endl;
try {
gameObj.texture = renderer.textureManager.LoadFromPng(objData.texturePath, zipPath);
}
catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load texture for '" << objData.name << "': " << e.what() << std::endl;
continue;
}
LoadedGameObject obj = buildLoadedObject(data, renderer, zipPath);
// Load mesh
try {
if (objData.meshPath.size() > 4 && objData.meshPath.compare(objData.meshPath.size() - 4, 4, ".bin") == 0)
gameObj.mesh.data = LoadModelFromBinFile(objData.meshPath, zipPath);
else
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;
}
if (data.positionX != 0.0f || data.positionY != 0.0f || data.positionZ != 0.0f)
obj.mesh.data.Move({ data.positionX, data.positionY, data.positionZ });
// Apply rotation
Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();
if (objData.rotationX != 0.0f) {
rotationQuat = Eigen::Quaternionf(Eigen::AngleAxisf(objData.rotationX, Eigen::Vector3f::UnitX())) * rotationQuat;
obj.mesh.RefreshVBO();
gameObjects[data.name] = std::move(obj);
std::cout << "Successfully loaded: " << data.name << std::endl;
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load '" << data.name << "': " << e.what() << std::endl;
}
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;
@@ -189,103 +165,49 @@ namespace ZL {
const std::string& zipPath)
{
std::vector<InteractiveObject> interactiveObjects;
std::vector<GameObjectData> objectsData = loadFromJson(jsonPath, zipPath);
for (const auto& objData : objectsData) {
if (!objData.interactive) continue;
if (jsonPath.empty())
return interactiveObjects;
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
for (const auto& data : loadInteractiveFromJson(jsonPath, zipPath)) {
std::cout << "Loading interactive object: " << data.base.name << std::endl;
try {
intObj.texture = renderer.textureManager.LoadFromPng(objData.texturePath, zipPath);
}
catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load texture for interactive '" << objData.name << "': " << e.what() << std::endl;
continue;
}
InteractiveObject intObj;
intObj.loadedObject = buildLoadedObject(data.base, renderer, zipPath);
intObj.interactionRadius = data.interactionRadius;
intObj.activateFunctionName = data.activateFunctionName;
// Load mesh
try {
if (objData.meshPath.size() > 4 && objData.meshPath.compare(objData.meshPath.size() - 4, 4, ".bin") == 0)
intObj.mesh.data = LoadModelFromBinFile(objData.meshPath, zipPath);
else
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;
}
intObj.loadedObject.mesh.RefreshVBO();
// Apply rotation
Eigen::Quaternionf rotationQuat = Eigen::Quaternionf::Identity();
// Center mesh at origin; store corrected world position separately.
if (!intObj.loadedObject.mesh.data.PositionData.empty()) {
Eigen::Vector3f meshMin = intObj.loadedObject.mesh.data.PositionData[0];
Eigen::Vector3f meshMax = intObj.loadedObject.mesh.data.PositionData[0];
for (const auto& v : intObj.loadedObject.mesh.data.PositionData) {
meshMin = meshMin.cwiseMin(v);
meshMax = meshMax.cwiseMax(v);
}
Eigen::Vector3f meshCenter = (meshMin + meshMax) * 0.5f;
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.loadedObject.mesh.data.Move(-meshCenter);
intObj.loadedObject.mesh.RefreshVBO();
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);
intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ) + meshCenter;
} else {
intObj.position = Eigen::Vector3f(data.base.positionX, data.base.positionY, data.base.positionZ);
}
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);
interactiveObjects.push_back(std::move(intObj));
if (!data.activateFunctionName.empty())
std::cout << "Successfully loaded interactive: " << data.base.name
<< " (function: " << data.activateFunctionName << ")" << std::endl;
else
std::cout << "Successfully loaded interactive: " << data.base.name << std::endl;
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load interactive '" << data.base.name << "': " << e.what() << std::endl;
}
// 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;
@@ -295,111 +217,52 @@ namespace ZL {
std::vector<NpcData> GameObjectLoader::loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath)
{
std::vector<NpcData> npcs;
json j = loadJson(jsonPath, zipPath);
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());
}
if (!j.contains("npcs") || !j["npcs"].is_array()) {
std::cerr << "Warning: 'npcs' array not found in " << jsonPath << std::endl;
return npcs;
}
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.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()) {
if (it.value().is_string())
data.meshTextures[it.key()] = it.value().get<std::string>();
}
}
}
data.interactionRadius = item.value("interactionRadius", 0.0f);
data.animationIdlePath = item.value("animationIdlePath", "");
data.animationWalkPath = item.value("animationWalkPath", "");
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.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.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);
data.modelCorrectionRotX = item.value("modelCorrectionRotX", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.modelCorrectionRotY = item.value("modelCorrectionRotY", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.modelCorrectionRotZ = item.value("modelCorrectionRotZ", 0.0f) * static_cast<float>(M_PI) / 180.0f;
data.facingAngle = item.value("facingAngle", 0.0f);
data.hp = item.value("hp", 100.0f);
data.canAttack = item.value("canAttack", false);
data.enabled = item.value("enabled", true);
data.facingAngle = item.value("facingAngle", 0.0f);
data.hp = item.value("hp", 100.0f);
data.canAttack = item.value("canAttack", false);
data.enabled = item.value("enabled", true);
// 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);
npcs.push_back(std::move(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;
}
@@ -412,62 +275,45 @@ namespace ZL {
const std::string& zipPath)
{
std::vector<std::unique_ptr<Character>> npcs;
std::vector<NpcData> npcsData = loadNpcsFromJson(jsonPath, zipPath);
for (const auto& npcData : npcsData) {
for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) {
std::cout << "Loading NPC: " << npcData.name << std::endl;
auto npc = std::make_unique<Character>();
// 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) {
} 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) {
} 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") {
if (path.size() >= 5 && path.substr(path.size() - 5) == ".anim")
npc->loadBinaryAnimation(state, path, zipPath);
}
else {
else
npc->loadAnimation(state, path, zipPath);
}
std::cout << " Loaded " << label << " animation: " << path << std::endl;
}
catch (const std::exception& e) {
} catch (const std::exception& e) {
std::cerr << "GameObjectLoader: Failed to load " << label
<< " animation for '" << npcData.name << "': " << e.what() << std::endl;
<< " animation for '" << npcData.name << "': " << e.what() << std::endl;
}
};
loadOptionalAnim(AnimationState::ACTION_IDLE, npcData.animationActionIdlePath, "ACTION_IDLE");
@@ -477,81 +323,51 @@ namespace ZL {
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<std::string, std::shared_ptr<Texture>> 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<Texture>(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<Texture>(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;
set_Texture(*npc, entry.first, CreateTextureDataFromPng(entry.second, zipPath.c_str()));
std::cout << " -> mesh '" << entry.first << "'" << std::endl;
}
}
else {
//auto texture = std::make_shared<Texture>(CreateTextureDataFromPng(npcData.texturePath, zipPath.c_str()));
} else {
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) {
} 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->enabled = npcData.enabled;
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->enabled = npcData.enabled;
npc->interactionRadius = npcData.interactionRadius;
npc->npcId = npcData.id;
npc->npcName = npcData.name;
// Set NPC metadata
npc->npcId = npcData.id;
npc->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->modelCorrectionRotation = corrRot;
// 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 << "Successfully loaded NPC: " << npcData.name << " at ("
<< npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl;
}
std::cout << "Total NPCs loaded: " << npcs.size() << std::endl;
return npcs;
}
} // namespace ZL
} // namespace ZL
+10 -22
View File
@@ -23,20 +23,12 @@ namespace ZL {
float positionY = 0.0f;
float positionZ = 0.0f;
float scale = 1.0f;
bool interactive = false;
std::string itemId;
std::string itemName;
std::string itemDescription;
std::string itemIcon;
float interactionRadius = 2.0f;
std::string activateFunctionName;
};
struct GiftData {
std::string id;
std::string name;
std::string description;
std::string icon;
struct InteractiveObjectData {
GameObjectData base;
float interactionRadius = 2.0f;
std::string activateFunctionName;
};
struct NpcData {
@@ -66,20 +58,11 @@ namespace ZL {
float hp = 100.0f;
bool canAttack = false;
bool enabled = true;
GiftData gift;
float interactionRadius = 0.0f;
};
struct LoadedGameObject {
std::shared_ptr<Texture> texture;
VertexRenderStruct mesh;
std::string name;
};
class GameObjectLoader {
public:
static std::vector<GameObjectData> loadFromJson(const std::string& jsonPath, const std::string& zipPath = "");
static std::unordered_map<std::string, LoadedGameObject> loadAndCreateGameObjects(
const std::string& jsonPath,
Renderer& renderer,
@@ -98,7 +81,12 @@ namespace ZL {
);
static std::vector<NpcData> loadNpcsFromJson(const std::string& jsonPath, const std::string& zipPath = "");
private:
static nlohmann::json loadJson(const std::string& jsonPath, const std::string& zipPath);
static std::vector<GameObjectData> loadFromJson(const std::string& jsonPath, const std::string& zipPath);
static std::vector<InteractiveObjectData> loadInteractiveFromJson(const std::string& jsonPath, const std::string& zipPath);
static LoadedGameObject buildLoadedObject(const GameObjectData& data, Renderer& renderer, const std::string& zipPath);
};
} // namespace ZL
+3 -17
View File
@@ -11,26 +11,12 @@ namespace ZL {
namespace ZL {
void InteractiveObject::draw(Renderer& renderer) const {
if (!isActive || !texture) return;
/*
std::cout << "[DRAW] InteractiveObject::draw() called" << std::endl;
std::cout << "[DRAW] Object: " << name << std::endl;
std::cout << "[DRAW] Position: (" << position.x() << ", " << position.y() << ", " << position.z() << ")" << std::endl;
// Check mesh bounds
if (!mesh.data.PositionData.empty()) {
std::cout << "[DRAW] First vertex: (" << mesh.data.PositionData[0].x() << ", "
<< mesh.data.PositionData[0].y() << ", " << mesh.data.PositionData[0].z() << ")" << std::endl;
}
*/
// Apply position transformation
if (!isActive || !loadedObject.texture) return;
renderer.PushMatrix();
renderer.TranslateMatrix(position);
renderer.RenderUniform1i(textureUniformName, 0);
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
renderer.DrawVertexRenderStruct(mesh);
glBindTexture(GL_TEXTURE_2D, loadedObject.texture->getTexID());
renderer.DrawVertexRenderStruct(loadedObject.mesh);
renderer.PopMatrix();
}
+10 -25
View File
@@ -2,43 +2,28 @@
#include <string>
#include <memory>
#include <Eigen/Core>
#include "Item.h"
#include "render/Renderer.h"
#include "render/Renderer.h"
namespace ZL {
struct Texture;
struct VertexRenderStruct;
class Renderer;
struct InteractiveObject {
std::string id;
std::string name;
Eigen::Vector3f position;
float interactionRadius;
struct LoadedGameObject {
std::shared_ptr<Texture> texture;
VertexRenderStruct mesh;
Item dropItem;
std::string name;
};
struct InteractiveObject {
LoadedGameObject loadedObject; // name, texture, mesh
Eigen::Vector3f position;
float interactionRadius;
bool isActive = true;
bool isNpc = false;
std::string npcInteractCallback;
float walkSpeed = 1.5f;
float rotationSpeed = 8.0f;
float modelScale = 0.01f;
std::string animationIdlePath;
std::string animationWalkPath;
std::string texturePath;
std::string activateFunctionName;
InteractiveObject() : interactionRadius(2.0f) {}
bool isInRange(const Eigen::Vector3f& playerPos) const {
return (playerPos - position).norm() <= interactionRadius;
}
void draw(Renderer& renderer) const;
};
} // namespace ZL
} // namespace ZL
+68
View File
@@ -0,0 +1,68 @@
#include "ItemRegistry.h"
#include "utils/Utils.h"
#include "external/nlohmann/json.hpp"
#include <iostream>
namespace ZL {
ItemRegistry& ItemRegistry::instance() {
static ItemRegistry reg;
return reg;
}
void ItemRegistry::loadFromJson(const std::string& jsonPath, const std::string& zipPath) {
std::string content;
try {
if (zipPath.empty()) {
content = readTextFile(jsonPath);
} else {
auto buf = readFileFromZIP(jsonPath, zipPath);
if (buf.empty()) {
std::cerr << "[ItemRegistry] Failed to read " << jsonPath << " from zip\n";
return;
}
content.assign(buf.begin(), buf.end());
}
} catch (const std::exception& e) {
std::cerr << "[ItemRegistry] Failed to open " << jsonPath << ": " << e.what() << "\n";
return;
}
using json = nlohmann::json;
json j;
try {
j = json::parse(content);
} catch (const std::exception& e) {
std::cerr << "[ItemRegistry] JSON parse error in " << jsonPath << ": " << e.what() << "\n";
return;
}
if (!j.contains("items") || !j["items"].is_array()) {
std::cerr << "[ItemRegistry] No 'items' array in " << jsonPath << "\n";
return;
}
items_.clear();
for (const auto& entry : j["items"]) {
std::string id = entry.value("id", "");
if (id.empty()) continue;
Item item(
id,
entry.value("name", "Unknown"),
entry.value("description", ""),
entry.value("icon", "")
);
items_[id] = std::move(item);
}
std::cout << "[ItemRegistry] Loaded " << items_.size() << " items from " << jsonPath << "\n";
}
const Item* ItemRegistry::findById(const std::string& id) const {
auto it = items_.find(id);
return it != items_.end() ? &it->second : nullptr;
}
} // namespace ZL
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <unordered_map>
#include "Item.h"
namespace ZL {
class ItemRegistry {
public:
static ItemRegistry& instance();
void loadFromJson(const std::string& jsonPath, const std::string& zipPath = "");
// Returns nullptr if not found.
const Item* findById(const std::string& id) const;
private:
ItemRegistry() = default;
std::unordered_map<std::string, Item> items_;
};
} // namespace ZL