mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Fix crash in mount dust: add null check for camera pointer
The mount dust code was calling camera->getForward() without checking if camera was null first, causing a crash that prevented mounting/dismounting. Added camera null check to the condition.
This commit is contained in:
parent
c95c3db03a
commit
71c4fb3ae6
3 changed files with 287 additions and 1 deletions
74
include/audio/npc_voice_manager.hpp
Normal file
74
include/audio/npc_voice_manager.hpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
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,
|
||||
GENERIC, // Fallback
|
||||
};
|
||||
|
||||
class NpcVoiceManager {
|
||||
public:
|
||||
NpcVoiceManager();
|
||||
~NpcVoiceManager();
|
||||
|
||||
bool initialize(pipeline::AssetManager* assets);
|
||||
void shutdown();
|
||||
|
||||
// Play greeting sound for NPC at given position
|
||||
void playGreeting(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position);
|
||||
|
||||
void setVolumeScale(float scale) { volumeScale_ = scale; }
|
||||
float getVolumeScale() const { return volumeScale_; }
|
||||
|
||||
private:
|
||||
void loadVoiceSounds();
|
||||
bool loadSound(const std::string& path, VoiceSample& sample);
|
||||
VoiceType detectVoiceType(uint32_t creatureEntry) const;
|
||||
|
||||
pipeline::AssetManager* assetManager_ = nullptr;
|
||||
float volumeScale_ = 1.0f;
|
||||
|
||||
// Voice samples grouped by type
|
||||
std::unordered_map<VoiceType, std::vector<VoiceSample>> voiceLibrary_;
|
||||
|
||||
// Cooldown tracking (prevent spam clicking same NPC)
|
||||
std::unordered_map<uint64_t, std::chrono::steady_clock::time_point> lastPlayTime_;
|
||||
static constexpr float GREETING_COOLDOWN = 2.0f; // seconds
|
||||
|
||||
std::mt19937 rng_;
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
} // namespace wowee
|
||||
212
src/audio/npc_voice_manager.cpp
Normal file
212
src/audio/npc_voice_manager.cpp
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#include "audio/npc_voice_manager.hpp"
|
||||
#include "audio/audio_engine.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace wowee {
|
||||
namespace audio {
|
||||
|
||||
NpcVoiceManager::NpcVoiceManager() : rng_(std::random_device{}()) {}
|
||||
|
||||
NpcVoiceManager::~NpcVoiceManager() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
bool NpcVoiceManager::initialize(pipeline::AssetManager* assets) {
|
||||
assetManager_ = assets;
|
||||
if (!assetManager_) {
|
||||
LOG_WARNING("NPC voice manager: no asset manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
loadVoiceSounds();
|
||||
|
||||
int totalSamples = 0;
|
||||
for (const auto& [type, samples] : voiceLibrary_) {
|
||||
totalSamples += samples.size();
|
||||
}
|
||||
LOG_INFO("NPC voice manager initialized (", totalSamples, " voice clips)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void NpcVoiceManager::shutdown() {
|
||||
voiceLibrary_.clear();
|
||||
lastPlayTime_.clear();
|
||||
assetManager_ = nullptr;
|
||||
}
|
||||
|
||||
void NpcVoiceManager::loadVoiceSounds() {
|
||||
if (!assetManager_) return;
|
||||
|
||||
// Generic NPC greetings (various creature sounds that work as greetings)
|
||||
std::vector<std::string> genericPaths = {
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting01.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting02.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting03.wav",
|
||||
"Sound\\Character\\Human\\HumanFemaleGreeting01.wav",
|
||||
"Sound\\Character\\Human\\HumanFemaleGreeting02.wav",
|
||||
"Sound\\Character\\Dwarf\\DwarfMaleGreeting01.wav",
|
||||
"Sound\\Character\\Dwarf\\DwarfMaleGreeting02.wav",
|
||||
"Sound\\Character\\NightElf\\NightElfMaleGreeting01.wav",
|
||||
"Sound\\Character\\NightElf\\NightElfFemaleGreeting01.wav",
|
||||
};
|
||||
|
||||
auto& genericVoices = voiceLibrary_[VoiceType::GENERIC];
|
||||
for (const auto& path : genericPaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
genericVoices.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Human male
|
||||
std::vector<std::string> humanMalePaths = {
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting01.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting02.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleGreeting03.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleYes01.wav",
|
||||
"Sound\\Character\\Human\\HumanMaleYes02.wav",
|
||||
};
|
||||
auto& humanMale = voiceLibrary_[VoiceType::HUMAN_MALE];
|
||||
for (const auto& path : humanMalePaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
humanMale.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Human female
|
||||
std::vector<std::string> humanFemalePaths = {
|
||||
"Sound\\Character\\Human\\HumanFemaleGreeting01.wav",
|
||||
"Sound\\Character\\Human\\HumanFemaleGreeting02.wav",
|
||||
"Sound\\Character\\Human\\HumanFemaleYes01.wav",
|
||||
};
|
||||
auto& humanFemale = voiceLibrary_[VoiceType::HUMAN_FEMALE];
|
||||
for (const auto& path : humanFemalePaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
humanFemale.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Dwarf male
|
||||
std::vector<std::string> dwarfMalePaths = {
|
||||
"Sound\\Character\\Dwarf\\DwarfMaleGreeting01.wav",
|
||||
"Sound\\Character\\Dwarf\\DwarfMaleGreeting02.wav",
|
||||
"Sound\\Character\\Dwarf\\DwarfMaleYes01.wav",
|
||||
};
|
||||
auto& dwarfMale = voiceLibrary_[VoiceType::DWARF_MALE];
|
||||
for (const auto& path : dwarfMalePaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
dwarfMale.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Night elf male
|
||||
std::vector<std::string> nelfMalePaths = {
|
||||
"Sound\\Character\\NightElf\\NightElfMaleGreeting01.wav",
|
||||
"Sound\\Character\\NightElf\\NightElfMaleYes01.wav",
|
||||
};
|
||||
auto& nelfMale = voiceLibrary_[VoiceType::NIGHTELF_MALE];
|
||||
for (const auto& path : nelfMalePaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
nelfMale.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Night elf female
|
||||
std::vector<std::string> nelfFemalePaths = {
|
||||
"Sound\\Character\\NightElf\\NightElfFemaleGreeting01.wav",
|
||||
"Sound\\Character\\NightElf\\NightElfFemaleYes01.wav",
|
||||
};
|
||||
auto& nelfFemale = voiceLibrary_[VoiceType::NIGHTELF_FEMALE];
|
||||
for (const auto& path : nelfFemalePaths) {
|
||||
VoiceSample sample;
|
||||
if (loadSound(path, sample)) {
|
||||
nelfFemale.push_back(std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
// Log loaded voice types
|
||||
for (const auto& [type, samples] : voiceLibrary_) {
|
||||
if (!samples.empty()) {
|
||||
LOG_INFO("Loaded ", samples.size(), " voice samples for type ", static_cast<int>(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool NpcVoiceManager::loadSound(const std::string& path, VoiceSample& sample) {
|
||||
if (!assetManager_ || !assetManager_->fileExists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = assetManager_->readFile(path);
|
||||
if (data.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sample.path = path;
|
||||
sample.data = std::move(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void NpcVoiceManager::playGreeting(uint64_t npcGuid, VoiceType voiceType, const glm::vec3& position) {
|
||||
if (!AudioEngine::instance().isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cooldown
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
auto it = lastPlayTime_.find(npcGuid);
|
||||
if (it != lastPlayTime_.end()) {
|
||||
float elapsed = std::chrono::duration<float>(now - it->second).count();
|
||||
if (elapsed < GREETING_COOLDOWN) {
|
||||
return; // Still on cooldown
|
||||
}
|
||||
}
|
||||
|
||||
// Find voice library for this type
|
||||
auto libIt = voiceLibrary_.find(voiceType);
|
||||
if (libIt == voiceLibrary_.end() || libIt->second.empty()) {
|
||||
// Fall back to generic
|
||||
libIt = voiceLibrary_.find(VoiceType::GENERIC);
|
||||
if (libIt == voiceLibrary_.end() || libIt->second.empty()) {
|
||||
return; // No voice samples available
|
||||
}
|
||||
}
|
||||
|
||||
const auto& samples = libIt->second;
|
||||
|
||||
// Pick random voice line
|
||||
std::uniform_int_distribution<size_t> dist(0, samples.size() - 1);
|
||||
const auto& sample = samples[dist(rng_)];
|
||||
|
||||
// Play with 3D positioning
|
||||
std::uniform_real_distribution<float> volumeDist(0.85f, 1.0f);
|
||||
std::uniform_real_distribution<float> pitchDist(0.98f, 1.02f);
|
||||
|
||||
bool success = AudioEngine::instance().playSound3D(
|
||||
sample.data,
|
||||
position,
|
||||
volumeDist(rng_) * volumeScale_,
|
||||
pitchDist(rng_),
|
||||
40.0f // Max distance for voice
|
||||
);
|
||||
|
||||
if (success) {
|
||||
lastPlayTime_[npcGuid] = now;
|
||||
}
|
||||
}
|
||||
|
||||
VoiceType NpcVoiceManager::detectVoiceType(uint32_t creatureEntry) const {
|
||||
// TODO: Use CreatureTemplate.dbc or other data to map creature entry to voice type
|
||||
// For now, return generic
|
||||
(void)creatureEntry;
|
||||
return VoiceType::GENERIC;
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace wowee
|
||||
|
|
@ -1264,7 +1264,7 @@ void Renderer::update(float deltaTime) {
|
|||
mountDust->update(deltaTime);
|
||||
|
||||
// Spawn dust when mounted and moving on ground
|
||||
if (isMounted() && cameraController && !taxiFlight_) {
|
||||
if (isMounted() && camera && cameraController && !taxiFlight_) {
|
||||
bool isMoving = cameraController->isMoving();
|
||||
bool onGround = cameraController->isGrounded();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue