audio_event_handler.cpp 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #include "audio_event_handler.h"
  2. #include "../core/component.h"
  3. #include "../core/entity.h"
  4. #include "../core/world.h"
  5. #include "audio_system.h"
  6. #include "core/event_manager.h"
  7. #include "units/spawn_type.h"
  8. #include <chrono>
  9. #include <random>
  10. #include <string>
  11. namespace Game::Audio {
  12. namespace {
  13. thread_local std::mt19937 g_audio_rng(std::random_device{}());
  14. auto get_volume_variation() -> float {
  15. std::uniform_real_distribution<float> dist(0.85F, 1.0F);
  16. return dist(g_audio_rng);
  17. }
  18. auto get_hit_sound_for_type(Game::Units::SpawnType type) -> std::string {
  19. switch (type) {
  20. case Game::Units::SpawnType::Knight:
  21. case Game::Units::SpawnType::MountedKnight:
  22. return "combat_hit_sword";
  23. case Game::Units::SpawnType::Spearman:
  24. case Game::Units::SpawnType::HorseSpearman:
  25. return "combat_hit_spear";
  26. case Game::Units::SpawnType::Archer:
  27. case Game::Units::SpawnType::HorseArcher:
  28. return "combat_hit_arrow";
  29. case Game::Units::SpawnType::Catapult:
  30. case Game::Units::SpawnType::Ballista:
  31. return "combat_hit_siege";
  32. default:
  33. return "combat_hit_generic";
  34. }
  35. }
  36. } // namespace
  37. AudioEventHandler::AudioEventHandler(Engine::Core::World *world)
  38. : m_world(world) {}
  39. AudioEventHandler::~AudioEventHandler() { shutdown(); }
  40. auto AudioEventHandler::initialize() -> bool {
  41. if (m_initialized) {
  42. return true;
  43. }
  44. m_unitSelectedSub =
  45. Engine::Core::ScopedEventSubscription<Engine::Core::UnitSelectedEvent>(
  46. [this](const Engine::Core::UnitSelectedEvent &event) {
  47. onUnitSelected(event);
  48. });
  49. m_ambientChangedSub = Engine::Core::ScopedEventSubscription<
  50. Engine::Core::AmbientStateChangedEvent>(
  51. [this](const Engine::Core::AmbientStateChangedEvent &event) {
  52. onAmbientStateChanged(event);
  53. });
  54. m_audioTriggerSub =
  55. Engine::Core::ScopedEventSubscription<Engine::Core::AudioTriggerEvent>(
  56. [this](const Engine::Core::AudioTriggerEvent &event) {
  57. onAudioTrigger(event);
  58. });
  59. m_musicTriggerSub =
  60. Engine::Core::ScopedEventSubscription<Engine::Core::MusicTriggerEvent>(
  61. [this](const Engine::Core::MusicTriggerEvent &event) {
  62. onMusicTrigger(event);
  63. });
  64. m_combatHitSub =
  65. Engine::Core::ScopedEventSubscription<Engine::Core::CombatHitEvent>(
  66. [](const Engine::Core::CombatHitEvent &event) {
  67. onCombatHit(event);
  68. });
  69. m_initialized = true;
  70. return true;
  71. }
  72. void AudioEventHandler::shutdown() {
  73. if (!m_initialized) {
  74. return;
  75. }
  76. m_unitSelectedSub.unsubscribe();
  77. m_ambientChangedSub.unsubscribe();
  78. m_audioTriggerSub.unsubscribe();
  79. m_musicTriggerSub.unsubscribe();
  80. m_combatHitSub.unsubscribe();
  81. m_unitVoiceMap.clear();
  82. m_ambientMusicMap.clear();
  83. m_initialized = false;
  84. }
  85. void AudioEventHandler::loadUnitVoiceMapping(const std::string &unit_type,
  86. const std::string &sound_id) {
  87. m_unitVoiceMap[unit_type] = sound_id;
  88. }
  89. void AudioEventHandler::loadAmbientMusic(Engine::Core::AmbientState state,
  90. const std::string &music_id) {
  91. m_ambientMusicMap[state] = music_id;
  92. }
  93. void AudioEventHandler::setVoiceSoundCategory(bool useVoiceCategory) {
  94. m_useVoiceCategory = useVoiceCategory;
  95. }
  96. void AudioEventHandler::onUnitSelected(
  97. const Engine::Core::UnitSelectedEvent &event) {
  98. if (m_world == nullptr) {
  99. return;
  100. }
  101. auto *entity = m_world->get_entity(event.unit_id);
  102. if (entity == nullptr) {
  103. return;
  104. }
  105. auto *unit_component = entity->get_component<Engine::Core::UnitComponent>();
  106. if (unit_component == nullptr) {
  107. return;
  108. }
  109. std::string const unit_type_str =
  110. Game::Units::spawn_typeToString(unit_component->spawn_type);
  111. auto it = m_unitVoiceMap.find(unit_type_str);
  112. if (it != m_unitVoiceMap.end()) {
  113. auto now = std::chrono::steady_clock::now();
  114. auto time_since_last_sound =
  115. std::chrono::duration_cast<std::chrono::milliseconds>(
  116. now - m_lastSelectionSoundTime)
  117. .count();
  118. bool const should_play =
  119. (time_since_last_sound >= SELECTION_SOUND_COOLDOWN_MS) ||
  120. (unit_type_str != m_lastSelectionUnitType);
  121. if (should_play) {
  122. AudioCategory const category =
  123. m_useVoiceCategory ? AudioCategory::VOICE : AudioCategory::SFX;
  124. AudioSystem::getInstance().playSound(it->second, UNIT_SELECTION_VOLUME,
  125. false, UNIT_SELECTION_PRIORITY,
  126. category);
  127. m_lastSelectionSoundTime = now;
  128. m_lastSelectionUnitType = unit_type_str;
  129. }
  130. }
  131. }
  132. void AudioEventHandler::onAmbientStateChanged(
  133. const Engine::Core::AmbientStateChangedEvent &event) {
  134. auto it = m_ambientMusicMap.find(event.new_state);
  135. if (it != m_ambientMusicMap.end()) {
  136. AudioSystem::getInstance().playMusic(it->second);
  137. }
  138. }
  139. void AudioEventHandler::onAudioTrigger(
  140. const Engine::Core::AudioTriggerEvent &event) {
  141. AudioSystem::getInstance().playSound(event.sound_id, event.volume, event.loop,
  142. event.priority);
  143. }
  144. void AudioEventHandler::onMusicTrigger(
  145. const Engine::Core::MusicTriggerEvent &event) {
  146. AudioSystem::getInstance().playMusic(event.music_id, event.volume,
  147. event.crossfade);
  148. }
  149. void AudioEventHandler::onCombatHit(const Engine::Core::CombatHitEvent &event) {
  150. std::string const sound_id = get_hit_sound_for_type(event.attacker_type);
  151. float const volume = COMBAT_HIT_VOLUME * get_volume_variation();
  152. AudioSystem::getInstance().playSound(sound_id, volume, false,
  153. COMBAT_HIT_PRIORITY, AudioCategory::SFX);
  154. if (event.is_killing_blow) {
  155. AudioSystem::getInstance().playSound("combat_death", volume * 0.9F, false,
  156. COMBAT_HIT_PRIORITY + 1,
  157. AudioCategory::SFX);
  158. }
  159. }
  160. } // namespace Game::Audio