Code works more or less good

This commit is contained in:
Vladislav Khorev committed 2026-04-25 21:11:07 +03:00
1 parent 3495d28edf
commit 348aa632dd
17 files changed
+4179 -55

No files matched your search

+72
View File
@@ -22,6 +22,78 @@
"description": "A mysterious essence from the Ghost realm",
"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
}
]
}
+1 -1
View File
@@ -168,7 +168,7 @@
"id": "choice_1",
"type": "Choice",
"speaker": "Hero",
"portrait": "resources/hero.png",
"portrait": "resources/w/gg/gg2_s_podsvetkoy5.png",
"text": "",
"choices": [
{
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -99,7 +99,7 @@ public:
std::string npcId;
std::string npcName;
bool giftReceived = false;
float hp = 100.f;
float hp = 200.f;
// 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.
float initialHp = 0.f;
+3 -3
View File
@@ -15,12 +15,12 @@ namespace ZL {
using std::max;
#endif
constexpr float DEFAULT_ZOOM = 36.f;
constexpr float DEFAULT_ZOOM = 24.f;
class Environment {
public:
static constexpr int CONST_DEFAULT_WIDTH = 1280;
static constexpr int CONST_DEFAULT_HEIGHT = 720;
static constexpr int CONST_DEFAULT_WIDTH = 1280;
static constexpr int CONST_DEFAULT_HEIGHT = 720;
static int windowHeaderHeight;
static int width;
static int height;
+3 -4
View File
@@ -160,7 +160,7 @@ namespace ZL
std::cout << "Load resurces step 4" << std::endl;
LocationSetup params1;
params1.roomMeshPath = "resources/w/room001.txt";
params1.roomMeshPath = "resources/w/room003.txt";
params1.roomTexturePath = "resources/w/room005.png";
params1.gameObjectsJsonPath = "resources/config2/gameobjects.json";
params1.npcsJsonPath = "resources/config2/npcs.json";
@@ -168,7 +168,6 @@ namespace ZL
params1.navigationJsonPath = "resources/config2/navigation.json";
params1.scriptPath = "resources/start.lua";
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.teleportRadius = 1.5f;
@@ -185,7 +184,6 @@ namespace ZL
params2.playerPosition = Eigen::Vector3f(7.f, 0.f, 27.f);
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.teleportRadius = 1.5f;
location2 = std::make_shared<Location>(renderer, inventory);
@@ -270,6 +268,7 @@ namespace ZL
this->menuManager.uiManager.setText("inventory_items_text", itemText);
});
menuManager.uiManager.setTextButtonCallback("close_inventory_button", [this](const std::string& name) {
std::cout << "[UI] Close button clicked" << std::endl;
menuManager.uiManager.setNodeVisible("inventory_items_panel", false);
@@ -531,7 +530,7 @@ namespace ZL
case SDLK_o:
//y = y + 0.002;
currentLocation->player->hp = 5;
currentLocation->player->hp = 200;
break;
case SDLK_k:
+12 -35
View File
@@ -103,43 +103,19 @@ namespace ZL
player->setupHitSparks(sparkTexture);
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);
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));
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));
auto sparkTexture2 = std::make_shared<Texture>(CreateTextureDataFromPng("resources/w/star.png", CONST_ZIP_FILE));
// Teleport zone visuals: an upward fountain of sparks parked at the
// teleport position, so the player can spot the zone from a distance.
@@ -150,12 +126,13 @@ namespace ZL
std::vector<Vector3f> teleportEmitPoints;
teleportEmitPoints.push_back(Vector3f{ teleportPosition.x(), teleportPosition.y(), teleportPosition.z() });
teleportSparks->setEmissionPoints(teleportEmitPoints);
teleportSparks->setTexture(sparkTexture);
teleportSparks->setTexture(sparkTexture2);
teleportSparks->setEmissionRate(50.0f);
teleportSparks->setMaxParticles(80);
teleportSparks->setParticleSize(0.15f);
teleportSparks->setParticleSize(0.05f);
teleportSparks->setBiasX(0.0f);
teleportSparks->setEmissionDirection(Vector3f{ 0.0f, 1.0f, 0.0f });
teleportSparks->setEmissionRadius(teleportRadius);
teleportSparks->setSpeedRange(0.5f, 1.0f);
teleportSparks->setZSpeedRange(0.5f, 1.5f);
teleportSparks->setScaleRange(0.5f, 1.0f);
-1
View File
@@ -26,7 +26,6 @@ namespace ZL
std::string navigationJsonPath;
std::string scriptPath;
Eigen::Vector3f playerPosition = Eigen::Vector3f::Zero();
Eigen::Vector3f ghostPosition = Eigen::Vector3f::Zero();
Eigen::Vector3f teleportPosition = Eigen::Vector3f::Zero();
float teleportRadius = 0.0f;
};
+22 -6
View File
@@ -16,8 +16,8 @@ namespace ZL {
SparkEmitter::SparkEmitter()
: emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
useWorldSpace(false) {
shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
configured(false), useWorldSpace(false) {
particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6);
@@ -38,6 +38,7 @@ namespace ZL {
scaleRange(copyFrom.scaleRange),
lifeTimeRange(copyFrom.lifeTimeRange),
shaderProgramName(copyFrom.shaderProgramName),
emissionRadius(copyFrom.emissionRadius),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace),
emissionDirection(copyFrom.emissionDirection)
{
@@ -49,8 +50,8 @@ namespace ZL {
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
: emissionPoints(positions), emissionRate(rate), isActive(true),
drawDataDirty(true), maxParticles(positions.size() * 100),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
useWorldSpace(false) {
shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
configured(false), useWorldSpace(false) {
particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6);
@@ -64,8 +65,8 @@ namespace ZL {
float rate)
: emissionPoints(positions), texture(tex), emissionRate(rate),
isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100),
shaderProgramName("default"), particleSize(0.04f), biasX(0.3f), configured(false),
useWorldSpace(false) {
shaderProgramName("default"), emissionRadius(0.0f), particleSize(0.04f), biasX(0.3f),
configured(false), useWorldSpace(false) {
particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6);
@@ -320,12 +321,27 @@ namespace ZL {
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
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) {
Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) {
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;
for (auto& particle : particles) {
+7
View File
@@ -50,6 +50,10 @@ namespace ZL {
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;
// Rebuilds the CPU-side quad/UV arrays. If cameraViewMatrix is identity,
// 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 setLifeTimeRange(float minVal, float maxVal) { lifeTimeRange.min = minVal; lifeTimeRange.max = maxVal; }
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; }
bool isConfigured() const { return configured; }
+3 -2
View File
@@ -112,7 +112,8 @@ void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel&
// const float H = Environment::projectionHeight;
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 };
lastCutsceneAdvanceRect = {};
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 });
}
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 });
lastChoiceRects.clear();
+35
View File
@@ -354,6 +354,12 @@ namespace ZL {
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);
@@ -370,6 +376,9 @@ namespace ZL {
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"];
@@ -437,6 +446,30 @@ namespace ZL {
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()) {
@@ -474,6 +507,8 @@ namespace ZL {
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
+9
View File
@@ -51,6 +51,13 @@ namespace ZL {
std::map<std::string, std::string> meshTextures;
std::string animationIdlePath;
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 positionY = 0.0f;
float positionZ = 0.0f;
@@ -61,6 +68,8 @@ namespace ZL {
float modelCorrectionRotY = 0.0f;
float modelCorrectionRotZ = 0.0f;
float facingAngle = 0.0f;
float hp = 100.0f;
bool canAttack = false;
GiftData gift;
float interactionRadius = 0.0f;
};
+15 -2
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_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(
"Bishkek Witcher Game",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
CONST_WIDTH, CONST_HEIGHT,
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
ZL::Environment::width, ZL::Environment::height,
windowFlags
);
SDL_GLContext ctx = SDL_GL_CreateContext(ZL::Environment::window);