Implemented Sound effect playing unit tests for taking combat damage for monsters and the player. Added GetVolume() to miniaudio PGEX. Added appropriate sound effects to Blizzard and Freeze Ground. Release Build 12914.
All checks were successful
Emscripten Build / Build_and_Deploy_Web_Build (push) Successful in 7m23s

This commit is contained in:
sigonasr2 2026-03-05 16:56:32 -06:00
parent b872b7335c
commit 451c248f7d
25 changed files with 107 additions and 27 deletions

View File

@ -35,13 +35,14 @@ Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
All rights reserved.
*/
#pragma endregion
#include "CppUnitTest.h"
#include "AdventuresInLestoria.h"
#include "config.h"
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
#include"CppUnitTest.h"
#include"AdventuresInLestoria.h"
#include"config.h"
#include"ItemDrop.h"
#include"Tutorial.h"
#include"DamageNumber.h"
#include"GameHelper.h"
#include"SoundEffect.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -96,6 +97,7 @@ namespace MonsterTests
#pragma endregion
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
testMonsterData.hurtSound="Monster Hurt";
MONSTER_DATA["TestName"]=testMonsterData;
Menu::themes.SetInitialized();
@ -144,6 +146,18 @@ namespace MonsterTests
testMonster.Hurt(0,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
Assert::AreEqual(size_t(2),DAMAGENUMBER_LIST.size(),L"Flag should suppress a damage number being generated");
}
TEST_METHOD(Deal5DamageAndDamageSoundEffectsPlay)
{
Monster&testMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ());
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Monster Hurt"],L"Monster hit sound should have played");
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Monster Hurt"],L"Monster hit sound should not play if there is no visible damage number.");
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
testMonster.Hurt(5,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT);
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Monster Hurt"],L"Monster hit sound should not play if a DoT tick occurs.");
}
TEST_METHOD(ZeroDamageResultsInTrue)
{
Monster testMonster{{},MONSTER_DATA["TestName"]};

View File

@ -44,6 +44,7 @@ All rights reserved.
#include "DamageNumber.h"
#include <ranges>
#include "GameHelper.h"
#include"SoundEffect.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -122,6 +123,16 @@ namespace PlayerTests
player->Hurt(0,player->OnUpperLevel(),player->GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
Assert::AreEqual(size_t(2),DAMAGENUMBER_LIST.size(),L"Flag should suppress a damage number being generated");
}
TEST_METHOD(PlayerTakesDamageSoundEffectsWork){
player->Hurt(30,player->OnUpperLevel(),player->GetZ());
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Player Hit"],L"Player hit sound should have played");
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::NO_DAMAGE_NUMBER);
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Player Hit"],L"Player hit sound should not play if there is no visible damage number.");
Game::Update(0.1f); //Prevent stacking up damage all on one damage number.
player->Hurt(5,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT);
Assert::AreEqual(1,SoundEffect::soundsPlayedLog["Player Hit"],L"Player hit sound should not play if a DoT tick occurs.");
}
TEST_METHOD(PlayerBlockingTakesNoDamage){
player->SetState(State::BLOCK);
player->Hurt(30,player->OnUpperLevel(),player->GetZ());

View File

@ -38,21 +38,27 @@ All rights reserved.
#include"Effect.h"
#include"AdventuresInLestoria.h"
#include"BlizzardSnowEmitter.h"
#include"SoundEffect.h"
#include<ranges>
INCLUDE_game
Blizzard::Blizzard(const vf2d pos,const float radius, const float lifetime,const int damage,const float tickRate,const std::pair<MinScale,MaxScale>snowSizeRange,const float emitterFreq,const bool upperLevel,const FriendlyType friendly)
:damage(damage),tickRate(tickRate),tickTimer(tickRate),radius(radius),friendly(friendly), Effect(pos,lifetime,"aiming_target.png",upperLevel,(radius*2)*vf2d{1.f,1.f}/24.f,0.5f,{},{199,247,239,128},0.f,0.f,true)
:damage(damage),tickRate(tickRate),tickTimer(tickRate),radius(radius),friendly(friendly),blizzardSFX("Blizzard",pos),Effect(pos,lifetime,"aiming_target.png",upperLevel,(radius*2)*vf2d{1.f,1.f}/24.f,0.5f,{},{199,247,239,128},0.f,0.f,true)
,snow(std::make_unique<BlizzardSnowEmitter>(pos,"circle.png",radius,snowSizeRange,emitterFreq,lifetime,upperLevel)){
for(int i:std::ranges::iota_view(0,100))snow->Emit();
}
bool Blizzard::Update(float fElapsedTime){
tickTimer-=fElapsedTime;
if(tickTimer<=0.f){
tickTimer+=tickRate;
game->Hurt(pos,radius*0.9f,damage,OnUpperLevel(),z,friendly?HurtType::MONSTER:HurtType::PLAYER);
if(!IsDead()){
blizzardSFX.Update();
tickTimer-=fElapsedTime;
const float blizzardAdjustedVol{Audio::Engine().GetVolume(blizzardSFX.soundInstance)};
if(lifetime<=1.f)Audio::Engine().SetVolume(blizzardSFX.soundInstance,std::max(0.f,util::lerp(0.f,blizzardAdjustedVol,lifetime/1.f)));
if(tickTimer<=0.f){
tickTimer+=tickRate;
game->Hurt(pos,radius*0.9f,damage,OnUpperLevel(),z,friendly?HurtType::MONSTER:HurtType::PLAYER);
}
snow->Update(fElapsedTime);
}
snow->Update(fElapsedTime);
return Effect::Update(fElapsedTime);
}

View File

@ -31,4 +31,7 @@ Nb Pixel Font Bundle 2: https://nimblebeastscollective.itch.io/nb-pixel-font-bun
Ogg Vorbis audio decoder - v1.22 - public domain http://nothings.org/stb_vorbis/
miniaudio library Copyright© 2024 by David Reid under the MIT No Attribution License
olcPGEX_MiniAudio Copyright© 2024 by Moros Smith under the OLC-3 License
olcPGEX_MiniAudio Copyright© 2024 by Moros Smith under the OLC-3 License
Blizzard by TanwerAman <https://pixabay.com/sound-effects/nature-blizzard-445020/>
Ice freezing by TanwerAman <https://pixabay.com/sound-effects/nature-ice-freezing-445024/>

View File

@ -138,4 +138,8 @@ void Effect::SetType(const EffectType type){
const bool&Effect::OnUpperLevel()const{
return upperLevel;
}
const bool Effect::IsDead()const{
return dead;
}

View File

@ -44,6 +44,7 @@ All rights reserved.
#include "Entity.h"
#include"FriendlyType.h"
#include"BlizzardSnowEmitter.h"
#include"EnvironmentalAudio.h"
class Monster;
class Player;
using HitList=std::unordered_set<std::variant<Monster*,Player*>>;
@ -88,6 +89,7 @@ public:
const float GetZ()const;
const bool&OnUpperLevel()const;
void SetType(const EffectType type);
const bool IsDead()const;
protected:
float original_fadeOutTime;
float original_fadeInTime{};
@ -273,6 +275,7 @@ private:
const float radius;
const FriendlyType friendly;
const std::unique_ptr<BlizzardSnowEmitter>snow;
EnvironmentalAudio blizzardSFX;
};
struct FreezeGround:Effect{

View File

@ -60,6 +60,9 @@ EnvironmentalAudio::EnvironmentalAudio()
:audioName(""),pos({}){}
EnvironmentalAudio::EnvironmentalAudio(const std::string_view audioName,const vf2d pos)
:audioName(audioName),pos(pos){}
EnvironmentalAudio::~EnvironmentalAudio(){
Deactivate();
}
void EnvironmentalAudio::SetPos(const vf2d&pos){
this->pos=pos;
}
@ -75,6 +78,7 @@ void EnvironmentalAudio::Activate(){
void EnvironmentalAudio::Deactivate(){
if(!activated)return;
if(soundInstance!=std::numeric_limits<size_t>::max()){
if(Audio::Engine().IsPlaying(soundInstance))Audio::Engine().Stop(soundInstance);
Audio::Engine().UnloadSound(soundInstance);
}
soundInstance=std::numeric_limits<size_t>::max();

View File

@ -49,6 +49,7 @@ struct EnvironmentalAudioData{
class EnvironmentalAudio{
friend class AiL;
friend struct Blizzard;
vf2d pos;
std::string audioName;
static float ACTIVATION_RANGE;
@ -59,6 +60,7 @@ public:
static void Initialize();
EnvironmentalAudio();
EnvironmentalAudio(const std::string_view audioName,const vf2d pos);
~EnvironmentalAudio();
void SetPos(const vf2d&pos);
void SetAudioName(const std::string_view audioName);
void Activate();

View File

@ -50,6 +50,7 @@ FireBolt::FireBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,Fri
:Bullet(pos,vel,radius,damage,
"energy_bolt.png",upperLevel,false,INFINITE,true,friendly,col){
if(game->GetPlayer()->HasEnchant("Trail of Fire")&&friendly)flameTrail=dynamic_cast<TrailEffect&>(game->AddEffect(std::make_unique<TrailEffect>(pos,"Trail of Fire"_ENC["TRAIL DURATION"],"FlamesTexture.png","Trail of Fire"_ENC["TRAIL DAMAGE"]/100.f*game->GetPlayer()->GetAttack(),"Trail of Fire"_ENC["TRAIL TICK FREQUENCY"],upperLevel,1.f,vf2d{1.f,2.f},30000.f,Oscillator<Pixel>{{255,0,0,128},Pixel(0xE74F30),2.f},EffectType::TRAIL_OF_FIRE,true),true));
SoundEffect::PlaySFX("Wizard Fire Bolt Shoot",SoundEffect::CENTERED);
}
void FireBolt::Update(float fElapsedTime){

View File

@ -1,12 +1,16 @@
#include"Effect.h"
#include"AdventuresInLestoria.h"
#include"SoundEffect.h"
INCLUDE_GFX
INCLUDE_game
FreezeGround::FreezeGround(const vf2d pos,const float lifetime,const int damage,const FreezeGroundSettings settings,const bool upperLevel,const FriendlyType friendly)
:damage(damage),friendly(friendly),settings(settings),Effect(pos,lifetime,"freezeground.png",upperLevel,settings.radius/100.f/(GFX["freezeground.png"].Sprite()->Size().x/24.f)*2.35f,0.5f,{},{199,247,239,32},0.f,0.f,true)
{}
{
SoundEffect::PlaySFX("Freezing Ice",pos);
}
bool FreezeGround::Update(float fElapsedTime){
tickTimer+=fElapsedTime;
while(tickTimer>=settings.slowdownFrequency){

View File

@ -48,7 +48,9 @@ INCLUDE_MONSTER_LIST
INCLUDE_EMITTER_LIST
LightningBolt::LightningBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,FriendlyType friendly,Pixel col)
:Bullet(pos,vel,radius,damage,"lightning_bolt.png",upperLevel,false,INFINITE,true,friendly,col){}
:Bullet(pos,vel,radius,damage,"lightning_bolt.png",upperLevel,false,INFINITE,true,friendly,col){
SoundEffect::PlaySFX("Wizard Lightning Bolt Shoot",SoundEffect::CENTERED);
}
void LightningBolt::Update(float fElapsedTime){
lastParticleSpawn=std::max(0.f,lastParticleSpawn-fElapsedTime);

View File

@ -801,7 +801,8 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
const bool IsDOT{bool(hurtFlags&HurtFlag::DOT)};
const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage};
const bool PlayHitSoundEffect{!IsDOT};
const bool DisplayDamageNumber{!(hurtFlags&HurtFlag::NO_DAMAGE_NUMBER)};
const bool PlayHitSoundEffect{!IsDOT&&DisplayDamageNumber};
if(!TrueDamage&&!IsDOT&&InUndamageableState(onUpperLevel,z))return false;
@ -856,7 +857,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
if(GetBuffs(BuffType::CURSE_OF_DEATH).size()>0)accumulatedCurseOfDeathDamage+=mod_dmg;
if(!(hurtFlags&HurtFlag::NO_DAMAGE_NUMBER)){
if(DisplayDamageNumber){
if(IsDOT){
if(lastDotTimer>0){
dotNumberPtr.get()->AddDamage(int(mod_dmg));

View File

@ -914,7 +914,8 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama
const bool TrueDamage{damageRule==TrueDamageFlag::IGNORE_DAMAGE_RULES};
const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage};
const bool PlayHitSoundEffect{!IsDOT};
const bool DisplayDamageNumber{!(hurtFlags&HurtFlag::NO_DAMAGE_NUMBER)};
const bool PlayHitSoundEffect{!IsDOT&&DisplayDamageNumber};
if(!TrueDamage&&!IsDOT&&(!IsAlive()||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1))return false;
float mod_dmg=float(damage);
@ -985,7 +986,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama
hurtRumbleTime="Player.Hurt Rumble Time"_F;
Input::StartVibration();
Input::SetLightbar(PixelLerp(DARK_RED,GREEN,GetHealthRatio()));
if(!(hurtFlags&HurtFlag::NO_DAMAGE_NUMBER)){
if(DisplayDamageNumber){
if(IsDOT){
if(lastDotTimer>0){
dotNumberPtr.get()->AddDamage(int(mod_dmg));

View File

@ -91,7 +91,11 @@ void SoundEffect::Initialize(){
}
void SoundEffect::PlaySFX(const std::string&eventName,const vf2d&pos){
if(game->TestingModeEnabled()||eventName.length()==0)return;
if(game->TestingModeEnabled()){
soundsPlayedLog[eventName]++;
return;
}
if(eventName.length()==0)return;
const SoundEffect&sfx=GetRandomSFXFromFile(eventName);
if(GameState::STATE==GameState::states[States::MAIN_MENU]&&sfx.combatSound)return; //Do not play combat sounds on the main menu.

View File

@ -51,7 +51,13 @@ private:
static std::unordered_set<size_t>playingSoundEffects;
};
namespace MonsterTests{
class MonsterTest;
};
class SoundEffect{
friend class PlayerTests::PlayerTest;
friend class MonsterTests::MonsterTest;
public:
SoundEffect(const std::string_view filename,const float&vol,const float&minPitch=0.9f,const float&maxPitch=1.1f,const bool combatSound=false,const bool treatAsBGM=false);
static void PlaySFX(const std::string&eventName,const vf2d&pos);
@ -71,4 +77,5 @@ private:
bool combatSound=false;
float minPitch=0.9f;
float maxPitch=1.1f;
inline static std::unordered_map<std::string,int>soundsPlayedLog{};
};

View File

@ -20,7 +20,6 @@ Cherry pick 6355054d6c8e76c6aa4b18760a293e3d1a020752 from master
Cherry pick 5914938b709927bb8a8557795d6d46fcbfcfb4ea from SkeletonFireMage
Cherry pick 66a10cee9f874f024633efdc84ebb4432752ab15 from SkeletonFireMage
Monster damage test should check for no damage number flag.
Hurt sound plays for Freeze Ground without max damage stacks
The Start button on the overworld map should progress to the item loadout screen.

View File

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 12893
#define VERSION_BUILD 12914
#define stringify(a) stringify_(a)
#define stringify_(a) #a

View File

@ -184,7 +184,6 @@ void Wizard::InitializeClassAbilities(){
[](Player*p,vf2d pos={}){
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
CreateBullet(FireBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 1.BulletSpeed"_F},"Wizard.Ability 1.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 1.InitialDamageMult"_F),p->upperLevel,FRIENDLY,"Wizard.Ability 1.BulletColor"_Pixel)EndBullet;
SoundEffect::PlaySFX("Wizard Fire Bolt Shoot",SoundEffect::CENTERED);
return true;
};
#pragma endregion
@ -193,7 +192,6 @@ void Wizard::InitializeClassAbilities(){
[](Player*p,vf2d pos={}){
float angleToCursor=atan2(p->GetWorldAimingLocation().y-p->GetPos().y,p->GetWorldAimingLocation().x-p->GetPos().x);
CreateBullet(LightningBolt)(p->GetPos(),{cos(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F,sin(angleToCursor)*"Wizard.Ability 2.BulletSpeed"_F},"Wizard.Ability 2.Radius"_F/100*12,int(p->GetAttack()*"Wizard.Ability 2.DamageMult"_F),p->upperLevel,FRIENDLY,"Wizard.Ability 2.BulletColor"_Pixel)EndBullet;
SoundEffect::PlaySFX("Wizard Lightning Bolt Shoot",SoundEffect::CENTERED);
return true;
};
#pragma endregion

View File

@ -2239,8 +2239,8 @@ Monsters
Abilities
{
#Ability Name = Mana cost, % chance to cast (every second), Cast Time (Instant = 0.0s), [Variable representing the cast radius]
# Blizzard = 60mp, 60%, 1.5sec, MonsterStrategy.Skeleton Mage.Blizzard Radius
Freeze Ground = 30mp, 50%, 0.0sec
Blizzard = 60mp, 60%, 1.5sec, MonsterStrategy.Skeleton Mage.Blizzard Radius
# Freeze Ground = 30mp, 20%, 0.0sec
}
Strategy = Skeleton Mage

View File

@ -25,6 +25,11 @@ Environmental Audio
File = birds4.ogg
Volume = 100%
}
Blizzard
{
File = blizzard.ogg
Volume = 100%
}
Campfire
{
File = campfire.ogg

View File

@ -195,6 +195,11 @@ Events
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = footsteps_wet.ogg, 100%
}
Freezing Ice
{
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = ice_freeze.ogg, 70%
}
Glass Break
{
CombatSound = True

Binary file not shown.

Binary file not shown.

View File

@ -115,6 +115,7 @@ namespace olc
public: // MISC CONTROLS
// set volume of a sound, 0.0f is mute, 1.0f is full
void SetVolume(const int id, const float& volume);
float GetVolume(const int id);
// set pan of a sound, -1.0f is left, 1.0f is right, 0.0f is center
void SetPan(const int id, const float& pan);
// set pitch of a sound, 1.0f is normal
@ -457,6 +458,11 @@ namespace olc
ma_sound_set_volume(vecSounds.at(id), volume);
}
float MiniAudio::GetVolume(const int id)
{
return ma_sound_get_volume(vecSounds.at(id));
}
void MiniAudio::SetPan(const int id, const float& pan)
{
ma_sound_set_pan(vecSounds.at(id), pan);