Code works more or less good

This commit is contained in:
Vladislav Khorev 2026-04-25 21:11:07 +03:00
parent 3495d28edf
commit 348aa632dd
17 changed files with 4180 additions and 56 deletions

View File

@ -22,6 +22,78 @@
"description": "A mysterious essence from the Ghost realm", "description": "A mysterious essence from the Ghost realm",
"icon": "resources/w/red.png" "icon": "resources/w/red.png"
} }
},
{
"id": "ghost_01x",
"name": "Опасный Призрак",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": 0.0,
"positionY": 0.0,
"positionZ": -20.0,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 35,
"canAttack": true
},
{
"id": "ghost_02x",
"name": "Злой призрак",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": -5.91548,
"positionY": 0.0,
"positionZ": -25.1861,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 25,
"canAttack": true
},
{
"id": "ghost_02x",
"name": "Злой дух",
"texturePath": "resources/w/ghost_skin002.png",
"animationIdlePath": "resources/w/default_float001.anim",
"animationWalkPath": "resources/w/default_float001.anim",
"animationActionIdlePath": "resources/w/float_attack003_cut.anim",
"animationActionAttackPath": "resources/w/float_attack003.anim",
"animationStandToActionPath": "resources/w/default_float001_cut.anim",
"animationActionToStandPath": "resources/w/default_float001_cut.anim",
"animationActionToDeathPath": "resources/w/default_float001_cut.anim",
"animationDeathIdlePath": "resources/w/default_float001_cut.anim",
"positionX": 6.64604,
"positionY": 0.0,
"positionZ": -16.0176,
"walkSpeed": 1.5,
"rotationSpeed": 8.0,
"modelScale": 0.01,
"modelCorrectionRotX": 0.0,
"modelCorrectionRotY": 180.0,
"modelCorrectionRotZ": 0.0,
"hp": 15,
"canAttack": true
} }
] ]
} }

View File

@ -168,7 +168,7 @@
"id": "choice_1", "id": "choice_1",
"type": "Choice", "type": "Choice",
"speaker": "Hero", "speaker": "Hero",
"portrait": "resources/hero.png", "portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "", "text": "",
"choices": [ "choices": [
{ {

BIN
resources/w/ghost_skin002.png (Stored with Git LFS) Normal file

Binary file not shown.

1995
resources/w/room002.txt Normal file

File diff suppressed because it is too large Load Diff

1995
resources/w/room003.txt Normal file

File diff suppressed because it is too large Load Diff

BIN
resources/w/star.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -99,7 +99,7 @@ public:
std::string npcId; std::string npcId;
std::string npcName; std::string npcName;
bool giftReceived = false; bool giftReceived = false;
float hp = 100.f; float hp = 200.f;
// Captured lazily from `hp` on the first update() tick if left at zero; // Captured lazily from `hp` on the first update() tick if left at zero;
// set explicitly if you need a different reference for the health bar. // set explicitly if you need a different reference for the health bar.
float initialHp = 0.f; float initialHp = 0.f;

View File

@ -15,12 +15,12 @@ namespace ZL {
using std::max; using std::max;
#endif #endif
constexpr float DEFAULT_ZOOM = 36.f; constexpr float DEFAULT_ZOOM = 24.f;
class Environment { class Environment {
public: public:
static constexpr int CONST_DEFAULT_WIDTH = 1280; static constexpr int CONST_DEFAULT_WIDTH = 1280;
static constexpr int CONST_DEFAULT_HEIGHT = 720; static constexpr int CONST_DEFAULT_HEIGHT = 720;
static int windowHeaderHeight; static int windowHeaderHeight;
static int width; static int width;
static int height; static int height;

View File

@ -160,7 +160,7 @@ namespace ZL
std::cout << "Load resurces step 4" << std::endl; std::cout << "Load resurces step 4" << std::endl;
LocationSetup params1; LocationSetup params1;
params1.roomMeshPath = "resources/w/room001.txt"; params1.roomMeshPath = "resources/w/room003.txt";
params1.roomTexturePath = "resources/w/room005.png"; params1.roomTexturePath = "resources/w/room005.png";
params1.gameObjectsJsonPath = "resources/config2/gameobjects.json"; params1.gameObjectsJsonPath = "resources/config2/gameobjects.json";
params1.npcsJsonPath = "resources/config2/npcs.json"; params1.npcsJsonPath = "resources/config2/npcs.json";
@ -168,7 +168,6 @@ namespace ZL
params1.navigationJsonPath = "resources/config2/navigation.json"; params1.navigationJsonPath = "resources/config2/navigation.json";
params1.scriptPath = "resources/start.lua"; params1.scriptPath = "resources/start.lua";
params1.playerPosition = Eigen::Vector3f::Zero(); params1.playerPosition = Eigen::Vector3f::Zero();
params1.ghostPosition = Eigen::Vector3f(0.f, 0.f, -20.f);
params1.teleportPosition = Eigen::Vector3f(-2.03001f, 0.f, -4.95618f); params1.teleportPosition = Eigen::Vector3f(-2.03001f, 0.f, -4.95618f);
params1.teleportRadius = 1.5f; params1.teleportRadius = 1.5f;
@ -185,7 +184,6 @@ namespace ZL
params2.playerPosition = Eigen::Vector3f(7.f, 0.f, 27.f); params2.playerPosition = Eigen::Vector3f(7.f, 0.f, 27.f);
params2.npcsJsonPath = "resources/config2/npcs2.json"; params2.npcsJsonPath = "resources/config2/npcs2.json";
params2.ghostPosition = Eigen::Vector3f(700.f, 0.f, 700.f);
params2.teleportPosition = Eigen::Vector3f(-6.45, 0.0, 7.82); params2.teleportPosition = Eigen::Vector3f(-6.45, 0.0, 7.82);
params2.teleportRadius = 1.5f; params2.teleportRadius = 1.5f;
location2 = std::make_shared<Location>(renderer, inventory); location2 = std::make_shared<Location>(renderer, inventory);
@ -270,6 +268,7 @@ namespace ZL
this->menuManager.uiManager.setText("inventory_items_text", itemText); this->menuManager.uiManager.setText("inventory_items_text", itemText);
}); });
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) { menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
std::cout << "[UI] Close button clicked" << std::endl; std::cout << "[UI] Close button clicked" << std::endl;
menuManager.uiManager.setNodeVisible("inventory_items_panel", false); menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
@ -531,7 +530,7 @@ namespace ZL
case SDLK_o: case SDLK_o:
//y = y + 0.002; //y = y + 0.002;
currentLocation->player->hp = 5; currentLocation->player->hp = 200;
break; break;
case SDLK_k: case SDLK_k:

View File

@ -103,43 +103,19 @@ namespace ZL
player->setupHitSparks(sparkTexture); player->setupHitSparks(sparkTexture);
std::cout << "Load resurces step 9" << std::endl; std::cout << "Load resurces step 9" << std::endl;
// Load NPCs from JSON // 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, CONST_ZIP_FILE);
for (auto& npc : npcs) { for (auto& npc : npcs) {
if (npc) npc->setupHitSparks(sparkTexture); if (!npc) continue;
if (npc->canAttack) {
npc->setupHitSparks(sparkTexture);
npc->attackTarget = player.get();
}
} }
auto ghostTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/ghost_skin001.png", CONST_ZIP_FILE)); auto sparkTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/star.png", CONST_ZIP_FILE));
std::cout << "Load resurces step 11" << std::endl;
auto npc02 = std::make_unique<Character>();
npc02->loadBinaryAnimation(AnimationState::STAND, "resources/w/default_float001.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::WALK, "resources/w/default_float001.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_IDLE, "resources/w/float_attack003_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_ATTACK, "resources/w/float_attack003.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::STAND_TO_ACTION, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_STAND, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::ACTION_TO_DEATH, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->loadBinaryAnimation(AnimationState::DEATH_IDLE, "resources/w/default_float001_cut.anim", CONST_ZIP_FILE);
npc02->npcId = "ghost_01x";
npc02->npcName = "Evil Ghost";
npc02->setTexture(ghostTexture);
npc02->walkSpeed = 1.5f;
npc02->rotationSpeed = 8.0f;
npc02->modelScale = 0.01f;
npc02->hp = 30;
//npc02->modelScale = 0.1f;
npc02->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY()));
npc02->position = params.ghostPosition;
npc02->setTarget(npc02->position);
npc02->canAttack = true;
npc02->attackTarget = player.get();
npc02->setupHitSparks(sparkTexture);
npcs.push_back(std::move(npc02));
// Teleport zone visuals: an upward fountain of sparks parked at the // Teleport zone visuals: an upward fountain of sparks parked at the
// teleport position, so the player can spot the zone from a distance. // teleport position, so the player can spot the zone from a distance.
@ -150,12 +126,13 @@ namespace ZL
std::vector<Vector3f> teleportEmitPoints; std::vector<Vector3f> teleportEmitPoints;
teleportEmitPoints.push_back(Vector3f{ teleportPosition.x(), teleportPosition.y(), teleportPosition.z() }); teleportEmitPoints.push_back(Vector3f{ teleportPosition.x(), teleportPosition.y(), teleportPosition.z() });
teleportSparks->setEmissionPoints(teleportEmitPoints); teleportSparks->setEmissionPoints(teleportEmitPoints);
teleportSparks->setTexture(sparkTexture); teleportSparks->setTexture(sparkTexture2);
teleportSparks->setEmissionRate(50.0f); teleportSparks->setEmissionRate(50.0f);
teleportSparks->setMaxParticles(80); teleportSparks->setMaxParticles(80);
teleportSparks->setParticleSize(0.15f); teleportSparks->setParticleSize(0.05f);
teleportSparks->setBiasX(0.0f); teleportSparks->setBiasX(0.0f);
teleportSparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f }); teleportSparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f });
teleportSparks->setEmissionRadius(teleportRadius);
teleportSparks->setSpeedRange(0.5f, 1.0f); teleportSparks->setSpeedRange(0.5f, 1.0f);
teleportSparks->setZSpeedRange(0.5f, 1.5f); teleportSparks->setZSpeedRange(0.5f, 1.5f);
teleportSparks->setScaleRange(0.5f, 1.0f); teleportSparks->setScaleRange(0.5f, 1.0f);

View File

@ -26,7 +26,6 @@ namespace ZL
std::string navigationJsonPath; std::string navigationJsonPath;
std::string scriptPath; std::string scriptPath;
Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero(); Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
Eigen::Vector3f ghostPosition = Eigen::Vector3f::Zero();
Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero(); Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero();
float teleportRadius = 0.0f; float teleportRadius = 0.0f;
}; };

View File

@ -16,8 +16,8 @@ namespace ZL {
SparkEmitter::SparkEmitter() SparkEmitter::SparkEmitter()
: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200), : emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false), shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
useWorldSpace(false) { configured(false), useWorldSpace(false) {
particles.resize(maxParticles); particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6); drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6);
@ -38,6 +38,7 @@ namespace ZL {
scaleRange(copyFrom.scaleRange), scaleRange(copyFrom.scaleRange),
lifeTimeRange(copyFrom.lifeTimeRange), lifeTimeRange(copyFrom.lifeTimeRange),
shaderProgramName(copyFrom.shaderProgramName), shaderProgramName(copyFrom.shaderProgramName),
emissionRadius(copyFrom.emissionRadius),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace), configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace),
emissionDirection(copyFrom.emissionDirection) emissionDirection(copyFrom.emissionDirection)
{ {
@ -49,8 +50,8 @@ namespace ZL {
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate) SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
: emissionPoints(positions), emissionRate(rate), isActive(true), : emissionPoints(positions), emissionRate(rate), isActive(true),
drawDataDirty(true), maxParticles(positions.size() * 100), drawDataDirty(true), maxParticles(positions.size() * 100),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false), shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
useWorldSpace(false) { configured(false), useWorldSpace(false) {
particles.resize(maxParticles); particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6); drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6);
@ -64,8 +65,8 @@ namespace ZL {
float rate) float rate)
: emissionPoints(positions), texture(tex), emissionRate(rate), : emissionPoints(positions), texture(tex), emissionRate(rate),
isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100), isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false), shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
useWorldSpace(false) { configured(false), useWorldSpace(false) {
particles.resize(maxParticles); particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6); drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6);
@ -320,12 +321,27 @@ namespace ZL {
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая) // lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
bool canInterp = (prevEmissionPoints.size() == emissionPoints.size()); bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());
// Reusable RNG for the radius perturbation. Rejection-sampled in a unit
// cube so the resulting offset is uniformly distributed in the unit
// ball, then scaled to emissionRadius.
static std::random_device rrd;
static std::mt19937 rgen(rrd());
std::uniform_real_distribution<float> radiusDist(-1.0f, 1.0f);
for (int i = 0; i < (int)emissionPoints.size(); ++i) { for (int i = 0; i < (int)emissionPoints.size(); ++i) {
Vector3f birthPos = emissionPoints[i]; Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) { if (canInterp && lerpT > 0.0f) {
birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT; birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
} }
if (emissionRadius > 0.0f) {
Vector3f offset;
do {
offset = Vector3f{ radiusDist(rgen), radiusDist(rgen), radiusDist(rgen) };
} while (offset.squaredNorm() > 1.0f);
birthPos = birthPos + offset * emissionRadius;
}
bool particleFound = false; bool particleFound = false;
for (auto& particle : particles) { for (auto& particle : particles) {

View File

@ -50,6 +50,10 @@ namespace ZL {
std::string shaderProgramName; std::string shaderProgramName;
// Sparks spawn within this radius of each emission point. 0 means
// "spawn exactly at the point" (legacy behavior).
float emissionRadius;
bool configured; bool configured;
// Rebuilds the CPU-side quad/UV arrays. If cameraViewMatrix is identity, // Rebuilds the CPU-side quad/UV arrays. If cameraViewMatrix is identity,
// particles are rendered as axis-aligned quads in world XY and sorted by // particles are rendered as axis-aligned quads in world XY and sorted by
@ -90,6 +94,9 @@ namespace ZL {
void setScaleRange(float minVal, float maxVal) { scaleRange.min = minVal; scaleRange.max = maxVal; } void setScaleRange(float minVal, float maxVal) { scaleRange.min = minVal; scaleRange.max = maxVal; }
void setLifeTimeRange(float minVal, float maxVal) { lifeTimeRange.min = minVal; lifeTimeRange.max = maxVal; } void setLifeTimeRange(float minVal, float maxVal) { lifeTimeRange.min = minVal; lifeTimeRange.max = maxVal; }
void setEmissionDirection(const Vector3f& dir) { emissionDirection = dir; } void setEmissionDirection(const Vector3f& dir) { emissionDirection = dir; }
// 0 = spawn exactly at the emission point; >0 = spawn at a uniform random
// point inside a 3D ball of this radius around the emission point.
void setEmissionRadius(float r) { emissionRadius = r; }
void markConfigured() { configured = true; } void markConfigured() { configured = true; }
bool isConfigured() const { return configured; } bool isConfigured() const { return configured; }

View File

@ -112,7 +112,8 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
// const float H = Environment::projectionHeight; // const float H = Environment::projectionHeight;
const UiRect portraitRect{ 24.0f, 24.0f, 182.0f, 182.0f }; const UiRect portraitRect{ 24.0f, 24.0f, 182.0f, 182.0f };
const UiRect textboxRect{ 220.0f, 24.0f, max(300.0f, W - 244.0f), 182.0f }; const UiRect textboxRect{ 220.0f, 24.0f, max(200.0f, W - 244.0f), 182.0f };
lastDialogueAdvanceRect = { portraitRect.x, portraitRect.y, textboxRect.x + textboxRect.w - portraitRect.x, textboxRect.h }; lastDialogueAdvanceRect = { portraitRect.x, portraitRect.y, textboxRect.x + textboxRect.w - portraitRect.x, textboxRect.h };
lastCutsceneAdvanceRect = {}; lastCutsceneAdvanceRect = {};
cutsceneAdvanceEnabled = false; cutsceneAdvanceEnabled = false;
@ -150,7 +151,7 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f }); nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
} }
const std::string wrappedBody = wrapText(model.visibleText, 56); const std::string wrappedBody = wrapText(model.visibleText, 90);
bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f }); bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });
lastChoiceRects.clear(); lastChoiceRects.clear();

View File

@ -354,6 +354,12 @@ namespace ZL {
data.interactionRadius = item.value("interactionRadius", 0.0f); data.interactionRadius = item.value("interactionRadius", 0.0f);
data.animationIdlePath = item.value("animationIdlePath", ""); data.animationIdlePath = item.value("animationIdlePath", "");
data.animationWalkPath = item.value("animationWalkPath", ""); 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.positionX = item.value("positionX", 0.0f);
data.positionY = item.value("positionY", 0.0f); data.positionY = item.value("positionY", 0.0f);
@ -370,6 +376,9 @@ namespace ZL {
data.facingAngle = item.value("facingAngle", 0.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 // Load gift data if available
if (item.contains("gift") && item["gift"].is_object()) { if (item.contains("gift") && item["gift"].is_object()) {
const auto& giftData = item["gift"]; const auto& giftData = item["gift"];
@ -429,7 +438,7 @@ namespace ZL {
{ {
npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath); npc->loadAnimation(AnimationState::WALK, npcData.animationWalkPath, zipPath);
} }
std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl; std::cout << " Loaded WALK animation: " << npcData.animationWalkPath << std::endl;
} }
catch (const std::exception& e) { catch (const std::exception& e) {
@ -437,6 +446,30 @@ namespace ZL {
continue; 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. // Load textures: per-mesh map takes precedence; fall back to single texturePath.
try { try {
if (!npcData.meshTextures.empty()) { if (!npcData.meshTextures.empty()) {
@ -474,6 +507,8 @@ namespace ZL {
npc->modelScale = npcData.modelScale; npc->modelScale = npcData.modelScale;
npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ); npc->position = Eigen::Vector3f(npcData.positionX, npcData.positionY, npcData.positionZ);
npc->facingAngle = npcData.facingAngle; npc->facingAngle = npcData.facingAngle;
npc->hp = npcData.hp;
npc->canAttack = npcData.canAttack;
npc->interactionRadius = npcData.interactionRadius; npc->interactionRadius = npcData.interactionRadius;
// Set NPC metadata // Set NPC metadata

View File

@ -51,6 +51,13 @@ namespace ZL {
std::map<std::string, std::string> meshTextures; std::map<std::string, std::string> meshTextures;
std::string animationIdlePath; std::string animationIdlePath;
std::string animationWalkPath; std::string animationWalkPath;
// Optional combat / death animations. Empty path = slot not loaded.
std::string animationActionIdlePath;
std::string animationActionAttackPath;
std::string animationStandToActionPath;
std::string animationActionToStandPath;
std::string animationActionToDeathPath;
std::string animationDeathIdlePath;
float positionX = 0.0f; float positionX = 0.0f;
float positionY = 0.0f; float positionY = 0.0f;
float positionZ = 0.0f; float positionZ = 0.0f;
@ -61,6 +68,8 @@ namespace ZL {
float modelCorrectionRotY = 0.0f; float modelCorrectionRotY = 0.0f;
float modelCorrectionRotZ = 0.0f; float modelCorrectionRotZ = 0.0f;
float facingAngle = 0.0f; float facingAngle = 0.0f;
float hp = 100.0f;
bool canAttack = false;
GiftData gift; GiftData gift;
float interactionRadius = 0.0f; float interactionRadius = 0.0f;
}; };

View File

@ -249,11 +249,24 @@ int main(int argc, char *argv[]) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
#ifdef FULLSCREEN
// Match the desktop's current resolution so the framebuffer matches
// the screen and we don't force a video mode change.
/*SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) == 0) {
ZL::Environment::width = dm.w;
ZL::Environment::height = dm.h;
}*/
const Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
const Uint32 windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
#endif
ZL::Environment::window = SDL_CreateWindow( ZL::Environment::window = SDL_CreateWindow(
"Bishkek Witcher Game", "Bishkek Witcher Game",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
CONST_WIDTH, CONST_HEIGHT, ZL::Environment::width, ZL::Environment::height,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN windowFlags
); );
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window); SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window);