diff --git a/src/Character.cpp b/src/Character.cpp index 3a18464..ca3b04b 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -330,6 +330,7 @@ void Character::update(int64_t deltaMs, const CharacterResolver& resolver) { state.currentState != AnimationState::DEATH_IDLE) { state.currentState = AnimationState::ACTION_TO_DEATH; state.resetAnim = true; + state.attackTargetIndex = CharacterState::kNoTarget; } } else { if (state.battle_state == 1) { @@ -433,7 +434,7 @@ void Character::update(int64_t deltaMs, const CharacterResolver& resolver) { (state.currentState == AnimationState::ACTION_ATTACK || state.currentState == AnimationState::ACTION_ATTACK_2)) { if (attackTarget != nullptr) { - attackTarget->applyDamage(10.f, attackDirectionToTarget()); + attackTarget->applyDamage(10.f, attackDirectionToTarget(), index); if (audioPlayer) { if (state.isPlayer) @@ -1214,12 +1215,12 @@ void Character::setupHitSparks(std::shared_ptr sparkTexture) { hitSparkEmitter.markConfigured(); } -void Character::applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection) { +void Character::applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection, int attackerIndex) { std::cout << "Character::applyDamage" << std::endl; state.hp -= damageAmount; if (state.hp < 0) state.hp = 0; - if (state.onHpChanged) state.onHpChanged(state.hp, state.initialHp); + if (state.onHpChanged) state.onHpChanged(state.hp, state.initialHp, attackerIndex); if (!hitSparkEmitter.isConfigured()) return; diff --git a/src/Character.h b/src/Character.h index 6f9bbd9..f7dd281 100644 --- a/src/Character.h +++ b/src/Character.h @@ -31,6 +31,8 @@ public: // All serializable game state (position, hp, flags, pathfinding, etc.) CharacterState state; + int index = CharacterState::kNoTarget; + // ---- Asset loading ---- void loadAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = ""); void loadBinaryAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = ""); @@ -64,7 +66,7 @@ public: void resetPlayerAfterDeath(); // ---- Damage ---- - void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero()); + void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero(), int attackerIndex = CharacterState::kNoTarget); // ---- Spark setup ---- void setupHitSparks(std::shared_ptr sparkTexture); diff --git a/src/CharacterState.cpp b/src/CharacterState.cpp index f5cea4a..ca7e2bd 100644 --- a/src/CharacterState.cpp +++ b/src/CharacterState.cpp @@ -27,7 +27,7 @@ void CharacterState::stopInPlace() { void CharacterState::setHp(float newHp) { hp = newHp; if (hp < 0.f) hp = 0.f; - if (onHpChanged) onHpChanged(hp, initialHp); + if (onHpChanged) onHpChanged(hp, initialHp, CharacterState::kNoTarget); } void CharacterState::save(nlohmann::json& out) const diff --git a/src/CharacterState.h b/src/CharacterState.h index a334044..7ec2db6 100644 --- a/src/CharacterState.h +++ b/src/CharacterState.h @@ -114,7 +114,7 @@ public: // --- Non-serializable callbacks (re-wired after each load) --- std::function onArrivedCallback; std::function onDeathAnimComplete; - std::function onHpChanged; + std::function onHpChanged; // Global Lua function name backing onArrivedCallback, if it was sourced from a // script (see ScriptEngine::rehydrateNamedCallback). Serialized so the callback diff --git a/src/Game.cpp b/src/Game.cpp index c06ff1c..b4fee37 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -744,8 +744,25 @@ namespace ZL // Wire HP-change callbacks so all player instances update the health bar HUD. for (auto& [name, loc] : gameState.locations) { if (loc->player) { - loc->player->state.onHpChanged = [this](float hp, float maxHp) { + loc->player->state.onHpChanged = [this, loc](float hp, float maxHp, int attackerIndex) { menuManager.updateHealthBar(hp, maxHp); + + if (attackerIndex != CharacterState::kNoTarget) { + //Someone attacked, cancel dialogue and agro the player + + if (loc->dialogueSystem.isDialogueActive()) { + loc->dialogueSystem.stopDialogue(); + } + + if ((loc->player->state.attackTargetIndex == CharacterState::kNoTarget || + ((loc->player->state.attackTargetIndex >= 0) && + (loc->npcs[loc->player->state.attackTargetIndex]->state.hp <= 0))) + && + loc->player->isMoving() == false) { + loc->player->state.attackTargetIndex = attackerIndex; + } + + } }; } } diff --git a/src/dialogue/DialogueSystem.h b/src/dialogue/DialogueSystem.h index b4af404..d9a72e3 100644 --- a/src/dialogue/DialogueSystem.h +++ b/src/dialogue/DialogueSystem.h @@ -52,6 +52,7 @@ public: void load(const nlohmann::json& in) override { loadDialogueState(in); } bool isActive() const { return dialogueRuntime.isActive() || cutsceneRuntime.isActive(); } + bool isDialogueActive() const { return dialogueRuntime.isActive(); } bool blocksGameplayInput() const { return isActive(); } void setFlag(const std::string& name, int value) { dialogueRuntime.setFlag(name, value); } diff --git a/src/items/GameObjectLoader.cpp b/src/items/GameObjectLoader.cpp index 9e6c20e..1f64a7c 100644 --- a/src/items/GameObjectLoader.cpp +++ b/src/items/GameObjectLoader.cpp @@ -363,6 +363,8 @@ namespace ZL { { std::vector> npcs; + int index = 0; + for (const auto& npcData : loadNpcsFromJson(jsonPath, zipPath)) { SDL_PumpEvents(); std::cout << "Loading NPC: " << npcData.name << std::endl; @@ -371,7 +373,9 @@ namespace ZL { if (npc) { std::cout << "Successfully loaded NPC: " << npcData.name << " at (" << npcData.positionX << ", " << npcData.positionY << ", " << npcData.positionZ << ")" << std::endl; + npc->index = index; npcs.push_back(std::move(npc)); + index = index + 1; } } catch (const std::exception& e) { std::cerr << "GameObjectLoader: Failed to load NPC '" << npcData.name << "': " << e.what() << std::endl;