Kelsidavis-WoWee/include/audio/npc_voice_manager.hpp
Kelsi 503f9ed650 fix: auto-detect CharSections.dbc layout and add Blood Elf/Draenei NPC voices
CharSections.dbc has different field layouts between stock WotLK (textures
at field 4-6) and Classic/TBC/Turtle/HD-textured WotLK (VariationIndex at
field 4). Add detectCharSectionsFields() that probes field-4 values at
runtime to determine the correct layout, so both stock and modded clients
work without JSON changes.

Also add BLOODELF_MALE/FEMALE and DRAENEI_MALE/FEMALE voice types to the
NPC voice system — previously all Blood Elf and Draenei NPCs fell through
to GENERIC (random dwarf/gnome/night elf/orc mix).
2026-03-23 11:00:49 -07:00

104 lines
3 KiB
C++

#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
#include <random>
#include <chrono>
#include <glm/glm.hpp>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace audio {
struct VoiceSample {
std::string path;
std::vector<uint8_t> data;
};
// NPC voice types (based on creature model/gender)
enum class VoiceType {
HUMAN_MALE,
HUMAN_FEMALE,
DWARF_MALE,
DWARF_FEMALE,
NIGHTELF_MALE,
NIGHTELF_FEMALE,
ORC_MALE,
ORC_FEMALE,
TAUREN_MALE,
TAUREN_FEMALE,
TROLL_MALE,
TROLL_FEMALE,
UNDEAD_MALE,
UNDEAD_FEMALE,
GNOME_MALE,
GNOME_FEMALE,
GOBLIN_MALE,
GOBLIN_FEMALE,
BLOODELF_MALE,
BLOODELF_FEMALE,
DRAENEI_MALE,
DRAENEI_FEMALE,
GENERIC, // Fallback
};
class NpcVoiceManager {
public:
NpcVoiceManager();
~NpcVoiceManager();
bool initialize(pipeline::AssetManager* assets);
void shutdown();
// Play NPC interaction sounds
void playGreeting(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
void playFarewell(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
void playVendor(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
void playPissed(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
// Play NPC combat sounds
void playAggro(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
void playFlee(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
void setVolumeScale(float scale) { volumeScale_ = scale; }
float getVolumeScale() const { return volumeScale_; }
private:
enum class SoundCategory {
GREETING,
FAREWELL,
VENDOR,
PISSED,
AGGRO,
FLEE
};
void loadVoiceSounds();
bool loadSound(const std::string& path, VoiceSample& sample);
void playSound(uint64_t npcGuid, VoiceType voiceType, SoundCategory category, const glm::vec3& position);
pipeline::AssetManager* assetManager_ = nullptr;
float volumeScale_ = 1.0f;
// Voice samples grouped by type and category
std::unordered_map<VoiceType, std::vector<VoiceSample>> greetingLibrary_;
std::unordered_map<VoiceType, std::vector<VoiceSample>> farewellLibrary_;
std::unordered_map<VoiceType, std::vector<VoiceSample>> vendorLibrary_;
std::unordered_map<VoiceType, std::vector<VoiceSample>> pissedLibrary_;
std::unordered_map<VoiceType, std::vector<VoiceSample>> aggroLibrary_;
std::unordered_map<VoiceType, std::vector<VoiceSample>> fleeLibrary_;
// Cooldown tracking (prevent spam clicking same NPC)
std::unordered_map<uint64_t, std::chrono::steady_clock::time_point> lastPlayTime_;
std::unordered_map<uint64_t, int> clickCount_; // Track clicks for pissed sounds
static constexpr float GREETING_COOLDOWN = 2.0f; // seconds
static constexpr int PISSED_CLICK_THRESHOLD = 5; // clicks before pissed
std::mt19937 rng_;
};
} // namespace audio
} // namespace wowee