From 161402234915671ae7202b15424a997b8eeaa263 Mon Sep 17 00:00:00 2001 From: sigonasr2 Date: Mon, 22 Jul 2024 00:01:20 -0500 Subject: [PATCH] Add unit testing for DOT effects, true damage effects, and applying mark tests to both player tests and monster tests. Fixed bugs related to DOTs not triggering proper damage events and not resetting DOT damage number timers over time. 105/105 unit tests passing. Release Build 10254. --- Adventures in Lestoria Tests/MonsterTests.cpp | 65 +++++++++++++++++++ Adventures in Lestoria Tests/PlayerTests.cpp | 43 ++++++++++-- Adventures in Lestoria/Monster.cpp | 47 +++++++++----- Adventures in Lestoria/Monster.h | 1 + Adventures in Lestoria/Player.cpp | 22 ++++--- Adventures in Lestoria/Version.h | 2 +- 6 files changed, 148 insertions(+), 32 deletions(-) diff --git a/Adventures in Lestoria Tests/MonsterTests.cpp b/Adventures in Lestoria Tests/MonsterTests.cpp index d8a250f9..0bd7b60f 100644 --- a/Adventures in Lestoria Tests/MonsterTests.cpp +++ b/Adventures in Lestoria Tests/MonsterTests.cpp @@ -40,12 +40,15 @@ All rights reserved. #include "config.h" #include "ItemDrop.h" #include "Tutorial.h" +#include "DamageNumber.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace olc::utils; INCLUDE_MONSTER_DATA INCLUDE_game +INCLUDE_GFX +INCLUDE_DAMAGENUMBER_LIST TEST_MODULE_INITIALIZE(AiLTestSuite) { @@ -74,8 +77,21 @@ namespace MonsterTests Menu::InitializeMenus(); Tutorial::Initialize(); Stats::InitializeDamageReductionTable(); + + GameState::Initialize(); + GameState::STATE=GameState::states.at(States::State::GAME_RUN); + + #pragma region Setup a fake test map + game->MAP_DATA["CAMPAIGN_1_1"]; + ItemDrop::drops.clear(); + #pragma endregion + MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f}; MONSTER_DATA["TestName"]=testMonsterData; + + Menu::themes.SetInitialized(); + GFX.SetInitialized(); + DAMAGENUMBER_LIST.clear(); } void SetupMockMap(){ game->MAP_DATA["CAMPAIGN_1_1"]; @@ -367,5 +383,54 @@ namespace MonsterTests Assert::Fail(L"Adding a Mana buff succeeded! This should NOT be allowed!"); }catch(std::exception&e){} } + TEST_METHOD(DOTTest){ + Monster testMonster{{},MONSTER_DATA["TestName"]}; + testMonster.AddBuff(BuffType::DAMAGE_REDUCTION,10.f,100._Pct); + testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT); + Assert::AreEqual(20,testMonster.GetHealth(),L"A DOT should go through all sources of damage reduction."); + testMonster.Update(1.f); //Let time pass so two DOT numbers don't stack up. + testMonster.ApplyIframes(0.5f); + testMonster.Hurt(10,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT); + Assert::AreEqual(10,testMonster.GetHealth(),L"A DOT should go through all sources of iframes."); + Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr&damageNumber){ + if(damageNumber->type==DamageNumberType::DOT)return count+1; + else return count; + }) + ,L"There should be 2 damage numbers of type DOT."); + } + TEST_METHOD(TrapperMarkTest){ + Monster testMonster{{},MONSTER_DATA["TestName"]}; + + Assert::AreEqual(0,testMonster.GetMarkStacks(),L"Monster has 0 marks initially."); + + testMonster.AddBuff(BuffType::TRAPPER_MARK,7.f,5); //Apply 5 marks of Trapper Mark. + + Assert::AreEqual(5,testMonster.GetMarkStacks(),L"Monster has 5 marks after receiving a buff."); + + testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ()); + + Assert::AreEqual(5,testMonster.GetMarkStacks(),L"Monster should still have 5 marks after taking damage from a normal attack."); + + testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::PLAYER_ABILITY); + + Assert::AreEqual(4,testMonster.GetMarkStacks(),L"Monster should have 4 marks remaining."); + Assert::AreEqual(22,testMonster.GetHealth(),L"Mark deals 60% of the player's attack. And 2 damage already taken from earlier."); + + testMonster.Hurt(1,testMonster.OnUpperLevel(),testMonster.GetZ(),HurtFlag::DOT); + + Assert::AreEqual(4,testMonster.GetMarkStacks(),L"Monster should still have 4 marks remaining (DOTs don't remove a mark)."); + + testMonster._DealTrueDamage(10); + + Assert::AreEqual(4,testMonster.GetMarkStacks(),L"Monster should still have 4 marks remaining after taking true damage from something not marked as a player ability."); + + testMonster._DealTrueDamage(10,HurtFlag::PLAYER_ABILITY); + + Assert::AreEqual(3,testMonster.GetMarkStacks(),L"Monster should have 3 marks remaining after taking true damage."); + + testMonster._DealTrueDamage(10,HurtFlag::DOT|HurtFlag::PLAYER_ABILITY); + + Assert::AreEqual(2,testMonster.GetMarkStacks(),L"Monster should have 2 marks remaining after taking true damage, even though it's classified as a DOT. This is an edge case, but it's really meaningless here..."); + } }; } \ No newline at end of file diff --git a/Adventures in Lestoria Tests/PlayerTests.cpp b/Adventures in Lestoria Tests/PlayerTests.cpp index f087f006..2278bd22 100644 --- a/Adventures in Lestoria Tests/PlayerTests.cpp +++ b/Adventures in Lestoria Tests/PlayerTests.cpp @@ -41,12 +41,14 @@ All rights reserved. #include #include #include "ItemDrop.h" +#include "DamageNumber.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace olc::utils; INCLUDE_GFX INCLUDE_ITEM_DATA +INCLUDE_DAMAGENUMBER_LIST extern std::mt19937 rng; @@ -79,10 +81,10 @@ namespace PlayerTests GameState::STATE=GameState::states.at(States::State::GAME_RUN); #pragma region Setup a fake test map and test monster - game->MAP_DATA["CAMPAIGN_1_1"]; - ItemDrop::drops.clear(); - MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f}; - MONSTER_DATA["TestName"]=testMonsterData; + game->MAP_DATA["CAMPAIGN_1_1"]; + ItemDrop::drops.clear(); + MonsterData testMonsterData{"TestName","Test Monster",1000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f}; + MONSTER_DATA["TestName"]=testMonsterData; #pragma endregion player=testGame->GetPlayer(); @@ -92,6 +94,8 @@ namespace PlayerTests testGame->olc_UpdateKeyFocus(true); //Force the game to be "focused" for tests. Required if we want keyboard inputs to work. Menu::themes.SetInitialized(); GFX.SetInitialized(); + + DAMAGENUMBER_LIST.clear(); } TEST_METHOD_CLEANUP(CleanupTests){ testGame->EndGame(); @@ -430,7 +434,7 @@ namespace PlayerTests player->CheckAndPerformAbility(player->GetAbility4(),testKeyboardInput); game->SetElapsedTime(0.01f); //Force elapsed time value. game->OnUserUpdate(0.01f); //Let 0.01 seconds go by. All abilities should be off cooldown. - Assert::AreEqual(0.f,player->GetRightClickAbility().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); //Defensive is currently not affected by CDR. + Assert::AreEqual(0.f,player->GetRightClickAbility().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility1().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility2().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); Assert::AreEqual(0.f,player->GetAbility3().cooldown,L"Using an ability with 100% CDR should result in a 0 second cooldown."); @@ -573,5 +577,34 @@ namespace PlayerTests Assert::AreEqual(1.1f,player->GetMoveSpdMult(),L"Move Speed Multiplier increased by 10% to x1.1"); Assert::AreEqual(0.105f,player->GetAttackRecoveryRateReduction(),L"Attack Recovery Rate reduced by 30%, or 0.105s"); } + TEST_METHOD(TrueDamageCheck){ + std::weak_ptrsetArmor{Inventory::AddItem("Test Armor3"s)}; + Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction. + player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES); + Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through true damage."); + player->ApplyIframes(0.5f); + player->Hurt(20,player->OnUpperLevel(),player->GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES); + Assert::AreEqual(60,player->GetHealth(),L"Player should take 20 damage through true damage even when iframes are on."); + } + TEST_METHOD(DOTDamageCheck){ + std::weak_ptrsetArmor{Inventory::AddItem("Test Armor3"s)}; + Inventory::EquipItem(setArmor,EquipSlot::ARMOR); //Equip an item with 100% damage reduction. + player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT); + Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT."); + Assert::AreEqual(1,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr&damageNumber){ + if(damageNumber->type==DamageNumberType::DOT)return count+1; + else return count; + }) + ,L"There should be a damage number of type DOT."); + player->Update(1.0f);//Let some time pass so two DOT numbers don't stack up. + player->ApplyIframes(0.5f); + player->Hurt(20,player->OnUpperLevel(),player->GetZ(),HurtFlag::DOT); + Assert::AreEqual(80,player->GetHealth(),L"Player should take 20 damage through a DOT even when iframes are on."); //HP Recovery/4s set effect has restored the health of the player to 100. So it should go back down to 80. + Assert::AreEqual(2,std::accumulate(DAMAGENUMBER_LIST.begin(),DAMAGENUMBER_LIST.end(),0,[](int count,const std::shared_ptr&damageNumber){ + if(damageNumber->type==DamageNumberType::DOT)return count+1; + else return count; + }) + ,L"There should be 2 damage numbers of type DOT."); + } }; } diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index a678ff5c..4e855364 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -260,6 +260,7 @@ bool Monster::SetY(float y){ bool Monster::Update(float fElapsedTime){ lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime); + lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime); iframe_timer=std::max(0.f,iframe_timer-fElapsedTime); monsterHurtSoundCooldown=std::max(0.f,monsterHurtSoundCooldown-fElapsedTime); lastHitPlayer=std::max(0.f,lastHitPlayer-fElapsedTime); @@ -367,7 +368,7 @@ bool Monster::Update(float fElapsedTime){ if(GetState()==State::NORMAL){ UpdateFacingDirection(game->GetPlayer()->GetPos()); } - Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime); + if(!game->TestingModeEnabled())Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime); } if(!IsAlive()){ deathTimer+=fElapsedTime; @@ -617,7 +618,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage}; const bool PlayHitSoundEffect{!IsDOT}; - if(!TrueDamage&&(Invulnerable()||!IsAlive()||onUpperLevel!=OnUpperLevel()||AttackAvoided(z)))return false; + if(!TrueDamage&&!IsDOT&&(Invulnerable()||!IsAlive()||onUpperLevel!=OnUpperLevel()||AttackAvoided(z)))return false; if(game->InBossEncounter()){ game->StartBossEncounter(); } @@ -636,7 +637,10 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da mod_dmg-=mod_dmg*GetDamageReductionFromBuffs(); } - if(TriggersMark&&GetMarkStacks()>0)Hurt(game->GetPlayer()->GetAttack()*"Trapper.Ability 1.Damage Increase Bonus"_F/100.f,OnUpperLevel(),GetZ(),HurtFlag::DOT); + if(TriggersMark&&GetMarkStacks()>0){ + Hurt(game->GetPlayer()->GetAttack()*"Trapper.Ability 1.Damage Increase Bonus"_F/100.f,OnUpperLevel(),GetZ(),HurtFlag::DOT); + RemoveMarkStack(); + } mod_dmg=std::ceil(mod_dmg); @@ -651,21 +655,22 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da DAMAGENUMBER_LIST.push_back(dotNumberPtr); } lastDotTimer=0.05f; - }else - if(lastHitTimer>0){ - damageNumberPtr.get()->damage+=int(mod_dmg); - damageNumberPtr.get()->pauseTime=0.4f; - damageNumberPtr.get()->RecalculateSize(); }else{ - damageNumberPtr=std::make_shared(pos,int(mod_dmg)); - DAMAGENUMBER_LIST.push_back(damageNumberPtr); - } - #pragma region Change Label to Crit - if(crit){ - damageNumberPtr.get()->type=DamageNumberType::CRIT; + if(lastHitTimer>0){ + damageNumberPtr.get()->damage+=int(mod_dmg); + damageNumberPtr.get()->pauseTime=0.4f; + damageNumberPtr.get()->RecalculateSize(); + }else{ + damageNumberPtr=std::make_shared(pos,int(mod_dmg)); + DAMAGENUMBER_LIST.push_back(damageNumberPtr); } - #pragma endregion - lastHitTimer=0.05f; + #pragma region Change Label to Crit + if(crit){ + damageNumberPtr.get()->type=DamageNumberType::CRIT; + } + #pragma endregion + lastHitTimer=0.05f; + } attackedByPlayer=true; if(!IsAlive()){ @@ -1203,4 +1208,14 @@ const int Monster::GetMarkStacks()const{ stackCount+=b.intensity; } return stackCount; +} + +void Monster::RemoveMarkStack(){ + if(GetMarkStacks()<=0)ERR("WARNING! Tried to remove a mark stack, but no stacks exist. THIS SHOULD NOT BE HAPPENING!"); + for(Buff&b:buffList){ + if(b.type==BuffType::TRAPPER_MARK&&b.intensity>0){ + b.intensity--; + break; + } + } } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 91e2f739..af8b9ab4 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -281,6 +281,7 @@ private: bool markedForDeletion{false}; //DO NOT MODIFY DIRECTLY. Use MarkForDeletion() if this monster needs to be marked. NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! float solidFadeTimer{0.f}; bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags); + void RemoveMarkStack(); private: struct STRATEGY{ static std::string ERR; diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 2fa441d3..2b87332f 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -340,6 +340,7 @@ void Player::Update(float fElapsedTime){ notEnoughManaDisplay.second=std::max(0.f,notEnoughManaDisplay.second-fElapsedTime); notificationDisplay.second=std::max(0.f,notificationDisplay.second-fElapsedTime); lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime); + lastDotTimer=std::max(0.f,lastDotTimer-fElapsedTime); lastPathfindingCooldown=std::max(0.f,lastPathfindingCooldown-fElapsedTime); lowHealthSoundPlayedTimer=std::max(0.f,lowHealthSoundPlayedTimer-fElapsedTime); if(hurtRumbleTime>0.f){ @@ -813,7 +814,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama const bool NormalDamageCalculationRequired{!IsDOT&&!TrueDamage}; const bool PlayHitSoundEffect{!IsDOT}; - if(!TrueDamage&&(!IsAlive()||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1))return false; + if(!TrueDamage&&!IsDOT&&(!IsAlive()||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1))return false; float mod_dmg=float(damage); if(NormalDamageCalculationRequired){ if(GetState()==State::BLOCK){ @@ -849,7 +850,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama if(Menu::IsMenuOpen()&&mod_dmg>0)Menu::CloseAllMenus(); - if(mod_dmg>0)game->ShowDamageVignetteOverlay(); + if(!IsDOT&&mod_dmg>0)game->ShowDamageVignetteOverlay(); hp=std::max(0,hp-int(mod_dmg)); @@ -868,16 +869,17 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama DAMAGENUMBER_LIST.push_back(dotNumberPtr); } lastDotTimer=0.05f; - }else - if(lastHitTimer>0){ - damageNumberPtr.get()->damage+=int(mod_dmg); - damageNumberPtr.get()->pauseTime=0.4f; - damageNumberPtr.get()->RecalculateSize(); }else{ - damageNumberPtr=std::make_shared(pos,int(mod_dmg),true); - DAMAGENUMBER_LIST.push_back(damageNumberPtr); + if(lastHitTimer>0){ + damageNumberPtr.get()->damage+=int(mod_dmg); + damageNumberPtr.get()->pauseTime=0.4f; + damageNumberPtr.get()->RecalculateSize(); + }else{ + damageNumberPtr=std::make_shared(pos,int(mod_dmg),true); + DAMAGENUMBER_LIST.push_back(damageNumberPtr); + } + lastHitTimer=0.05f; } - lastHitTimer=0.05f; if(!lowHealthSoundPlayed&&lowHealthSoundPlayedTimer==0.f&&GetHealthRatio()<="Player.Health Warning Pct"_F/100.f){ SoundEffect::PlaySFX("Health Warning",SoundEffect::CENTERED); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index a29a3e7f..9276cce9 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 1 #define VERSION_MINOR 2 #define VERSION_PATCH 3 -#define VERSION_BUILD 10248 +#define VERSION_BUILD 10254 #define stringify(a) stringify_(a) #define stringify_(a) #a