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