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.

mac-build
sigonasr2 4 months ago
parent 3830ba8840
commit 1614022349
  1. 65
      Adventures in Lestoria Tests/MonsterTests.cpp
  2. 35
      Adventures in Lestoria Tests/PlayerTests.cpp
  3. 23
      Adventures in Lestoria/Monster.cpp
  4. 1
      Adventures in Lestoria/Monster.h
  5. 8
      Adventures in Lestoria/Player.cpp
  6. 2
      Adventures in Lestoria/Version.h

@ -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>&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...");
}
};
}

@ -41,12 +41,14 @@ All rights reserved.
#include <random>
#include <format>
#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;
@ -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_ptr<Item>setArmor{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_ptr<Item>setArmor{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>&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>&damageNumber){
if(damageNumber->type==DamageNumberType::DOT)return count+1;
else return count;
})
,L"There should be 2 damage numbers of type DOT.");
}
};
}

@ -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,7 +655,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
DAMAGENUMBER_LIST.push_back(dotNumberPtr);
}
lastDotTimer=0.05f;
}else
}else{
if(lastHitTimer>0){
damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4f;
@ -666,6 +670,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
}
#pragma endregion
lastHitTimer=0.05f;
}
attackedByPlayer=true;
if(!IsAlive()){
@ -1204,3 +1209,13 @@ const int Monster::GetMarkStacks()const{
}
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;
}
}
}

@ -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;

@ -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,7 +869,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama
DAMAGENUMBER_LIST.push_back(dotNumberPtr);
}
lastDotTimer=0.05f;
}else
}else{
if(lastHitTimer>0){
damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4f;
@ -878,6 +879,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama
DAMAGENUMBER_LIST.push_back(damageNumberPtr);
}
lastHitTimer=0.05f;
}
if(!lowHealthSoundPlayed&&lowHealthSoundPlayedTimer==0.f&&GetHealthRatio()<="Player.Health Warning Pct"_F/100.f){
SoundEffect::PlaySFX("Health Warning",SoundEffect::CENTERED);

@ -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

Loading…
Cancel
Save