mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Add comprehensive spell sound manager with 35+ magic sounds
Implemented complete spell casting audio system with all magic schools: Magic schools supported: - Fire: Precast (Low/Medium/High), Cast, Fireball impacts - Frost: Precast (Low/Medium/High), Cast, Blizzard impacts - Holy: Precast (Low/Medium/High), Cast, Holy impacts (4 levels) - Nature: Precast (Low/Medium/High), Cast - Shadow: Precast (Low/Medium/High), Cast - Arcane: Precast, Arcane Missile impacts - Physical: Non-magical abilities Spell phases: - Precast: Channeling/preparation sounds (before cast) - Cast: Spell release sounds (when spell fires) - Impact: Spell hit sounds (when spell hits target) Power levels: - Low: Weak spells, low level abilities - Medium: Standard power spells - High: Powerful high-level spells Sound coverage (35+ sounds): - 16 precast sounds (Fire/Frost/Holy/Nature/Shadow × Low/Med/High + Arcane) - 5 cast sounds (one per school) - 16 impact sounds (Fireball ×3, Blizzard ×6, Holy ×4, Arcane Missile ×3) Technical details: - Loads 35+ sound files from Sound\Spells directory - Simple API: playPrecast(school, power), playCast(school), playImpact(school, power) - Convenience methods: playFireball(), playFrostbolt(), playHeal(), etc. - Random variation selection for impacts - Volume at 0.75 with global scale control - Ready for integration with spell casting system Usage examples: ```cpp // Full spell sequence spellSoundManager->playPrecast(MagicSchool::FIRE, SpellPower::HIGH); // ... cast time ... spellSoundManager->playCast(MagicSchool::FIRE); // ... projectile travel ... spellSoundManager->playImpact(MagicSchool::FIRE, SpellPower::HIGH); // Convenience methods spellSoundManager->playFireball(); spellSoundManager->playHeal(); ``` This adds essential magic feedback for spell casting gameplay!
This commit is contained in:
parent
3a3e9f3c79
commit
f1e7f75141
3 changed files with 402 additions and 0 deletions
|
|
@ -108,6 +108,7 @@ set(WOWEE_SOURCES
|
|||
src/audio/ambient_sound_manager.cpp
|
||||
src/audio/ui_sound_manager.cpp
|
||||
src/audio/combat_sound_manager.cpp
|
||||
src/audio/spell_sound_manager.cpp
|
||||
|
||||
# Pipeline (asset loaders)
|
||||
src/pipeline/mpq_manager.cpp
|
||||
|
|
|
|||
106
include/audio/spell_sound_manager.hpp
Normal file
106
include/audio/spell_sound_manager.hpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
class AssetManager;
|
||||
}
|
||||
|
||||
namespace audio {
|
||||
|
||||
class SpellSoundManager {
|
||||
public:
|
||||
SpellSoundManager() = default;
|
||||
~SpellSoundManager() = default;
|
||||
|
||||
// Initialization
|
||||
bool initialize(pipeline::AssetManager* assets);
|
||||
void shutdown();
|
||||
|
||||
// Volume control
|
||||
void setVolumeScale(float scale);
|
||||
|
||||
// Magic school types
|
||||
enum class MagicSchool {
|
||||
FIRE,
|
||||
FROST,
|
||||
HOLY,
|
||||
NATURE,
|
||||
SHADOW,
|
||||
ARCANE,
|
||||
PHYSICAL // Non-magical abilities
|
||||
};
|
||||
|
||||
// Spell power level
|
||||
enum class SpellPower {
|
||||
LOW, // Weak spells, low level
|
||||
MEDIUM, // Standard spells
|
||||
HIGH // Powerful spells
|
||||
};
|
||||
|
||||
// Spell casting sounds
|
||||
void playPrecast(MagicSchool school, SpellPower power); // Channeling/preparation
|
||||
void playCast(MagicSchool school); // When spell fires
|
||||
void playImpact(MagicSchool school, SpellPower power); // When spell hits target
|
||||
|
||||
// Specific spell sounds
|
||||
void playFireball();
|
||||
void playFrostbolt();
|
||||
void playLightningBolt();
|
||||
void playHeal();
|
||||
void playShadowBolt();
|
||||
|
||||
private:
|
||||
struct SpellSample {
|
||||
std::string path;
|
||||
std::vector<uint8_t> data;
|
||||
bool loaded;
|
||||
};
|
||||
|
||||
// Precast sound libraries (channeling)
|
||||
std::vector<SpellSample> precastFireLowSounds_;
|
||||
std::vector<SpellSample> precastFireMediumSounds_;
|
||||
std::vector<SpellSample> precastFireHighSounds_;
|
||||
std::vector<SpellSample> precastFrostLowSounds_;
|
||||
std::vector<SpellSample> precastFrostMediumSounds_;
|
||||
std::vector<SpellSample> precastFrostHighSounds_;
|
||||
std::vector<SpellSample> precastHolyLowSounds_;
|
||||
std::vector<SpellSample> precastHolyMediumSounds_;
|
||||
std::vector<SpellSample> precastHolyHighSounds_;
|
||||
std::vector<SpellSample> precastNatureLowSounds_;
|
||||
std::vector<SpellSample> precastNatureMediumSounds_;
|
||||
std::vector<SpellSample> precastNatureHighSounds_;
|
||||
std::vector<SpellSample> precastShadowLowSounds_;
|
||||
std::vector<SpellSample> precastShadowMediumSounds_;
|
||||
std::vector<SpellSample> precastShadowHighSounds_;
|
||||
std::vector<SpellSample> precastArcaneSounds_;
|
||||
|
||||
// Cast sound libraries (spell release)
|
||||
std::vector<SpellSample> castFireSounds_;
|
||||
std::vector<SpellSample> castFrostSounds_;
|
||||
std::vector<SpellSample> castHolySounds_;
|
||||
std::vector<SpellSample> castNatureSounds_;
|
||||
std::vector<SpellSample> castShadowSounds_;
|
||||
|
||||
// Impact sound libraries (spell hits)
|
||||
std::vector<SpellSample> impactFireballSounds_;
|
||||
std::vector<SpellSample> impactBlizzardSounds_;
|
||||
std::vector<SpellSample> impactHolySounds_;
|
||||
std::vector<SpellSample> impactArcaneMissileSounds_;
|
||||
|
||||
// State tracking
|
||||
float volumeScale_ = 1.0f;
|
||||
bool initialized_ = false;
|
||||
|
||||
// Helper methods
|
||||
bool loadSound(const std::string& path, SpellSample& sample, pipeline::AssetManager* assets);
|
||||
void playSound(const std::vector<SpellSample>& library, float volumeMultiplier = 1.0f);
|
||||
void playRandomSound(const std::vector<SpellSample>& library, float volumeMultiplier = 1.0f);
|
||||
};
|
||||
|
||||
} // namespace audio
|
||||
} // namespace wowee
|
||||
295
src/audio/spell_sound_manager.cpp
Normal file
295
src/audio/spell_sound_manager.cpp
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
#include "audio/spell_sound_manager.hpp"
|
||||
#include "audio/audio_engine.hpp"
|
||||
#include "pipeline/asset_manager.hpp"
|
||||
#include "core/logger.hpp"
|
||||
#include <random>
|
||||
|
||||
namespace wowee {
|
||||
namespace audio {
|
||||
|
||||
namespace {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
}
|
||||
|
||||
bool SpellSoundManager::initialize(pipeline::AssetManager* assets) {
|
||||
if (!assets) {
|
||||
LOG_ERROR("SpellSoundManager: AssetManager is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO("SpellSoundManager: Initializing...");
|
||||
|
||||
// Load Fire precast sounds
|
||||
precastFireLowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFireLow.wav", precastFireLowSounds_[0], assets);
|
||||
|
||||
precastFireMediumSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFireMedium.wav", precastFireMediumSounds_[0], assets);
|
||||
|
||||
precastFireHighSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFireHigh.wav", precastFireHighSounds_[0], assets);
|
||||
|
||||
// Load Frost precast sounds
|
||||
precastFrostLowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFrostMagicLow.wav", precastFrostLowSounds_[0], assets);
|
||||
|
||||
precastFrostMediumSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFrostMagicMedium.wav", precastFrostMediumSounds_[0], assets);
|
||||
|
||||
precastFrostHighSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastFrostMagicHigh.wav", precastFrostHighSounds_[0], assets);
|
||||
|
||||
// Load Holy precast sounds
|
||||
precastHolyLowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastHolyMagicLow.wav", precastHolyLowSounds_[0], assets);
|
||||
|
||||
precastHolyMediumSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastHolyMagicMedium.wav", precastHolyMediumSounds_[0], assets);
|
||||
|
||||
precastHolyHighSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastHolyMagicHigh.wav", precastHolyHighSounds_[0], assets);
|
||||
|
||||
// Load Nature precast sounds
|
||||
precastNatureLowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastNatureMagicLow.wav", precastNatureLowSounds_[0], assets);
|
||||
|
||||
precastNatureMediumSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastNatureMagicMedium.wav", precastNatureMediumSounds_[0], assets);
|
||||
|
||||
precastNatureHighSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastNatureMagicHigh.wav", precastNatureHighSounds_[0], assets);
|
||||
|
||||
// Load Shadow precast sounds
|
||||
precastShadowLowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastShadowMagicLow.wav", precastShadowLowSounds_[0], assets);
|
||||
|
||||
precastShadowMediumSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastShadowMagicMedium.wav", precastShadowMediumSounds_[0], assets);
|
||||
|
||||
precastShadowHighSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\PreCastShadowMagicHigh.wav", precastShadowHighSounds_[0], assets);
|
||||
|
||||
// Load Arcane precast sounds
|
||||
precastArcaneSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Arcane_Form_Precast.wav", precastArcaneSounds_[0], assets);
|
||||
|
||||
// Load Cast sounds (when spell fires)
|
||||
castFireSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Cast\\FireCast.wav", castFireSounds_[0], assets);
|
||||
|
||||
castFrostSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Cast\\IceCast.wav", castFrostSounds_[0], assets);
|
||||
|
||||
castHolySounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Cast\\HolyCast.wav", castHolySounds_[0], assets);
|
||||
|
||||
castNatureSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Cast\\NatureCast.wav", castNatureSounds_[0], assets);
|
||||
|
||||
castShadowSounds_.resize(1);
|
||||
loadSound("Sound\\Spells\\Cast\\ShadowCast.wav", castShadowSounds_[0], assets);
|
||||
|
||||
// Load Impact sounds
|
||||
impactFireballSounds_.resize(3);
|
||||
loadSound("Sound\\Spells\\FireBallImpactA.wav", impactFireballSounds_[0], assets);
|
||||
loadSound("Sound\\Spells\\FireBallImpactB.wav", impactFireballSounds_[1], assets);
|
||||
loadSound("Sound\\Spells\\FireBallImpactC.wav", impactFireballSounds_[2], assets);
|
||||
|
||||
impactBlizzardSounds_.resize(6);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1a.wav", impactBlizzardSounds_[0], assets);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1b.wav", impactBlizzardSounds_[1], assets);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1c.wav", impactBlizzardSounds_[2], assets);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1d.wav", impactBlizzardSounds_[3], assets);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1e.wav", impactBlizzardSounds_[4], assets);
|
||||
loadSound("Sound\\Spells\\BlizzardImpact1f.wav", impactBlizzardSounds_[5], assets);
|
||||
|
||||
impactHolySounds_.resize(4);
|
||||
loadSound("Sound\\Spells\\DirectDamage\\HolyImpactDDLow.wav", impactHolySounds_[0], assets);
|
||||
loadSound("Sound\\Spells\\DirectDamage\\HolyImpactDDMedium.wav", impactHolySounds_[1], assets);
|
||||
loadSound("Sound\\Spells\\DirectDamage\\HolyImpactDDHigh.wav", impactHolySounds_[2], assets);
|
||||
loadSound("Sound\\Spells\\DirectDamage\\HolyImpactDDUber.wav", impactHolySounds_[3], assets);
|
||||
|
||||
impactArcaneMissileSounds_.resize(3);
|
||||
loadSound("Sound\\Spells\\ArcaneMissileImpact1a.wav", impactArcaneMissileSounds_[0], assets);
|
||||
loadSound("Sound\\Spells\\ArcaneMissileImpact1b.wav", impactArcaneMissileSounds_[1], assets);
|
||||
loadSound("Sound\\Spells\\ArcaneMissileImpact1c.wav", impactArcaneMissileSounds_[2], assets);
|
||||
|
||||
LOG_INFO("SpellSoundManager: Precast sounds - Fire: ", precastFireLowSounds_[0].loaded ? "YES" : "NO",
|
||||
", Frost: ", precastFrostLowSounds_[0].loaded ? "YES" : "NO",
|
||||
", Holy: ", precastHolyLowSounds_[0].loaded ? "YES" : "NO");
|
||||
LOG_INFO("SpellSoundManager: Cast sounds - Fire: ", castFireSounds_[0].loaded ? "YES" : "NO",
|
||||
", Frost: ", castFrostSounds_[0].loaded ? "YES" : "NO",
|
||||
", Shadow: ", castShadowSounds_[0].loaded ? "YES" : "NO");
|
||||
LOG_INFO("SpellSoundManager: Impact sounds - Fireball: ", impactFireballSounds_[0].loaded ? "YES" : "NO",
|
||||
", Blizzard: ", impactBlizzardSounds_[0].loaded ? "YES" : "NO",
|
||||
", Holy: ", impactHolySounds_[0].loaded ? "YES" : "NO");
|
||||
|
||||
initialized_ = true;
|
||||
LOG_INFO("SpellSoundManager: Initialization complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpellSoundManager::shutdown() {
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
bool SpellSoundManager::loadSound(const std::string& path, SpellSample& sample, pipeline::AssetManager* assets) {
|
||||
sample.path = path;
|
||||
sample.loaded = false;
|
||||
|
||||
try {
|
||||
sample.data = assets->readFile(path);
|
||||
if (!sample.data.empty()) {
|
||||
sample.loaded = true;
|
||||
return true;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
// Silently fail - not all sounds may exist
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpellSoundManager::playSound(const std::vector<SpellSample>& library, float volumeMultiplier) {
|
||||
if (!initialized_ || library.empty() || !library[0].loaded) return;
|
||||
|
||||
float volume = 0.75f * volumeScale_ * volumeMultiplier;
|
||||
AudioEngine::instance().playSound2D(library[0].data, volume, 1.0f);
|
||||
}
|
||||
|
||||
void SpellSoundManager::playRandomSound(const std::vector<SpellSample>& library, float volumeMultiplier) {
|
||||
if (!initialized_ || library.empty()) return;
|
||||
|
||||
// Count loaded sounds
|
||||
std::vector<const SpellSample*> loadedSounds;
|
||||
for (const auto& sample : library) {
|
||||
if (sample.loaded) {
|
||||
loadedSounds.push_back(&sample);
|
||||
}
|
||||
}
|
||||
|
||||
if (loadedSounds.empty()) return;
|
||||
|
||||
// Pick random sound
|
||||
std::uniform_int_distribution<size_t> dist(0, loadedSounds.size() - 1);
|
||||
size_t index = dist(gen);
|
||||
|
||||
float volume = 0.75f * volumeScale_ * volumeMultiplier;
|
||||
AudioEngine::instance().playSound2D(loadedSounds[index]->data, volume, 1.0f);
|
||||
}
|
||||
|
||||
void SpellSoundManager::setVolumeScale(float scale) {
|
||||
volumeScale_ = std::max(0.0f, std::min(1.0f, scale));
|
||||
}
|
||||
|
||||
void SpellSoundManager::playPrecast(MagicSchool school, SpellPower power) {
|
||||
const std::vector<SpellSample>* library = nullptr;
|
||||
|
||||
switch (school) {
|
||||
case MagicSchool::FIRE:
|
||||
library = (power == SpellPower::LOW) ? &precastFireLowSounds_ :
|
||||
(power == SpellPower::MEDIUM) ? &precastFireMediumSounds_ :
|
||||
&precastFireHighSounds_;
|
||||
break;
|
||||
case MagicSchool::FROST:
|
||||
library = (power == SpellPower::LOW) ? &precastFrostLowSounds_ :
|
||||
(power == SpellPower::MEDIUM) ? &precastFrostMediumSounds_ :
|
||||
&precastFrostHighSounds_;
|
||||
break;
|
||||
case MagicSchool::HOLY:
|
||||
library = (power == SpellPower::LOW) ? &precastHolyLowSounds_ :
|
||||
(power == SpellPower::MEDIUM) ? &precastHolyMediumSounds_ :
|
||||
&precastHolyHighSounds_;
|
||||
break;
|
||||
case MagicSchool::NATURE:
|
||||
library = (power == SpellPower::LOW) ? &precastNatureLowSounds_ :
|
||||
(power == SpellPower::MEDIUM) ? &precastNatureMediumSounds_ :
|
||||
&precastNatureHighSounds_;
|
||||
break;
|
||||
case MagicSchool::SHADOW:
|
||||
library = (power == SpellPower::LOW) ? &precastShadowLowSounds_ :
|
||||
(power == SpellPower::MEDIUM) ? &precastShadowMediumSounds_ :
|
||||
&precastShadowHighSounds_;
|
||||
break;
|
||||
case MagicSchool::ARCANE:
|
||||
library = &precastArcaneSounds_;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (library) {
|
||||
playSound(*library);
|
||||
}
|
||||
}
|
||||
|
||||
void SpellSoundManager::playCast(MagicSchool school) {
|
||||
switch (school) {
|
||||
case MagicSchool::FIRE:
|
||||
playSound(castFireSounds_);
|
||||
break;
|
||||
case MagicSchool::FROST:
|
||||
playSound(castFrostSounds_);
|
||||
break;
|
||||
case MagicSchool::HOLY:
|
||||
playSound(castHolySounds_);
|
||||
break;
|
||||
case MagicSchool::NATURE:
|
||||
playSound(castNatureSounds_);
|
||||
break;
|
||||
case MagicSchool::SHADOW:
|
||||
playSound(castShadowSounds_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellSoundManager::playImpact(MagicSchool school, SpellPower power) {
|
||||
switch (school) {
|
||||
case MagicSchool::FIRE:
|
||||
playRandomSound(impactFireballSounds_);
|
||||
break;
|
||||
case MagicSchool::FROST:
|
||||
playRandomSound(impactBlizzardSounds_);
|
||||
break;
|
||||
case MagicSchool::HOLY:
|
||||
if (power == SpellPower::LOW) {
|
||||
playSound(impactHolySounds_); // Use first (low)
|
||||
} else if (power == SpellPower::MEDIUM && impactHolySounds_.size() > 1) {
|
||||
playSound({impactHolySounds_[1]});
|
||||
} else if (power == SpellPower::HIGH && impactHolySounds_.size() > 2) {
|
||||
playSound({impactHolySounds_[2]});
|
||||
}
|
||||
break;
|
||||
case MagicSchool::ARCANE:
|
||||
playRandomSound(impactArcaneMissileSounds_);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellSoundManager::playFireball() {
|
||||
playPrecast(MagicSchool::FIRE, SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
void SpellSoundManager::playFrostbolt() {
|
||||
playPrecast(MagicSchool::FROST, SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
void SpellSoundManager::playLightningBolt() {
|
||||
playPrecast(MagicSchool::NATURE, SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
void SpellSoundManager::playHeal() {
|
||||
playPrecast(MagicSchool::HOLY, SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
void SpellSoundManager::playShadowBolt() {
|
||||
playPrecast(MagicSchool::SHADOW, SpellPower::MEDIUM);
|
||||
}
|
||||
|
||||
} // namespace audio
|
||||
} // namespace wowee
|
||||
Loading…
Add table
Add a link
Reference in a new issue