Implemented Pooling Poison enchant. Fix True Damage and Dot flags ignoring dead monsters and still applying damage. 191/191 Unit Tests passing. Release Build 11173.

pull/65/head
sigonasr2 3 months ago
parent b36e5b449d
commit e8ead7e07b
  1. 2
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 51
      Adventures in Lestoria Tests/MonsterTests.cpp
  3. 14
      Adventures in Lestoria/AdventuresInLestoria.cpp
  4. 3
      Adventures in Lestoria/AdventuresInLestoria.h
  5. 6
      Adventures in Lestoria/Effect.h
  6. 8
      Adventures in Lestoria/FadeInOutEffect.cpp
  7. 7
      Adventures in Lestoria/Monster.cpp
  8. 2
      Adventures in Lestoria/Monster.h
  9. 6
      Adventures in Lestoria/PoisonBottle.cpp
  10. 4
      Adventures in Lestoria/PoisonPool.cpp
  11. 1
      Adventures in Lestoria/SoundEffect.cpp
  12. 2
      Adventures in Lestoria/Version.h
  13. 4
      Adventures in Lestoria/assets/config/items/ItemEnchants.txt
  14. BIN
      x64/Release/Adventures in Lestoria.exe

@ -341,7 +341,7 @@ namespace EnchantTests
Assert::AreEqual(true,survivedAtLeastOnce,L"Player should have survived at least one time with Death Defiance.");
}
TEST_METHOD(ReaperOfSoulsCheck){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{},200.f};
MONSTER_DATA["TestName"]=testMonsterData;
Monster testMonster{{},MONSTER_DATA["TestName"]};
Monster testMonster2{{},MONSTER_DATA["TestName"]};

@ -41,6 +41,7 @@ All rights reserved.
#include "ItemDrop.h"
#include "Tutorial.h"
#include "DamageNumber.h"
#include "GameHelper.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
using namespace olc::utils;
@ -405,8 +406,7 @@ namespace MonsterTests
TEST_METHOD(TrapperMarkTest){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
game->SpawnMonster({},testMonsterData);
game->SetElapsedTime(0.1f);
game->OnUserUpdate(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
Game::Update(0.1f); //A monster that is spawned needs to be added to the monster list in the next tick.
std::weak_ptr<Monster>testMonster{MONSTER_LIST.front()};
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"Monster has 0 marks initially.");
@ -457,5 +457,52 @@ namespace MonsterTests
Assert::AreEqual(uint8_t(0),testMonster.lock()->GetMarkStacks(),L"The marks should have expired after 10 seconds.");
}
TEST_METHOD(HurtFailsWhenDead){
MonsterData testMonsterData{"TestName","Test Monster",30,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
Assert::IsFalse(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a dead monster, it should not take any damage and should not be triggering anything else.");
}
TEST_METHOD(HurtSucceedsWhenAlive){
MonsterData testMonsterData{"TestName","Test Monster",3000,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(!m.IsDead(),L"Monster is considered alive.");
Assert::IsTrue(m.IsAlive(),L"Monster is considered alive.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that is alive, it should take damage and should be triggering like normal.");
}
TEST_METHOD(HurtSucceedsWhenDyingWithExactHealth){
MonsterData testMonsterData{"TestName","Test Monster",50,10,5,{MonsterDropData{"Health Potion",100.f,1,1}},200.f};
Monster&m{game->SpawnMonster({},testMonsterData)};
m.Hurt(50,m.OnUpperLevel(),m.GetZ());
Assert::IsTrue(m.IsDead(),L"Monster is considered dead.");
Assert::IsTrue(!m.IsAlive(),L"Monster is considered dead.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ()),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m.Hurt(50,m.OnUpperLevel(),m.GetZ(),HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::DOT),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
m.Heal(50);
Assert::IsTrue(m._DealTrueDamage(50,HurtFlag::PLAYER_ABILITY),L"When hurting a monster that was alive and takes enough damage to die, it should still take damage and should be triggering like normal.");
}
};
}

@ -826,7 +826,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()));
if(InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
hitList.push_back({&*m,returnVal});
hitList.emplace_back(HurtListItem{&*m,returnVal});
}
}
}
@ -834,7 +834,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()));
if(InRange){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
hitList.push_back({GetPlayer(),returnVal});
hitList.emplace_back(HurtListItem{GetPlayer(),returnVal});
}
}
return hitList;
@ -848,7 +848,7 @@ const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperL
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius()));
if(m->GetName()==monsterName&&InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
hitList.push_back({&*m,returnVal});
hitList.emplace_back(HurtListItem{&*m,returnVal});
}
}
@ -867,7 +867,7 @@ const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweep
float angleDiff=util::angle_difference(angleToMonster,angle);
if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.push_back({&*m,returnVal});
affectedList.emplace_back(HurtListItem{&*m,returnVal});
hitList.insert(&*m);
}
}
@ -879,7 +879,7 @@ const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweep
float angleDiff=util::angle_difference(angleToPlayer,angle);
if(abs(angleDiff)<=sweepAngle){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.push_back({GetPlayer(),returnVal});
affectedList.emplace_back(HurtListItem{GetPlayer(),returnVal});
hitList.insert(GetPlayer());
}
}
@ -896,7 +896,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,
for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.push_back({&*m,returnVal});
affectedList.emplace_back(HurtListItem{&*m,returnVal});
hitList.insert(&*m);
}
}
@ -904,7 +904,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,
if(CheckForPlayerCollisions){
if(!hitList.count(GetPlayer())&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(GetPlayer()->GetPos(),12*GetPlayer()->GetSizeMult()))){
HurtReturnValue returnVal=GetPlayer()->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.push_back({GetPlayer(),returnVal});
affectedList.emplace_back(HurtListItem{GetPlayer(),returnVal});
hitList.insert(GetPlayer());
}
}

@ -70,7 +70,8 @@ INCLUDE_BULLET_LIST
#define EndBullet )).get()))
using HurtReturnValue=bool;
using HurtList=std::vector<std::pair<std::variant<Monster*,Player*>,HurtReturnValue>>;
using HurtListItem=std::pair<std::variant<Monster*,Player*>,HurtReturnValue>;
using HurtList=std::vector<HurtListItem>;
using StackCount=uint8_t;
using MarkTime=float;

@ -166,10 +166,10 @@ public:
struct FadeInOutEffect:Effect{
//cycleSpd is how long it takes to get from fully opaque to fully transparent, and back to fully opaque
FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::optional<Effect>&particle={});
FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect()>&particleGenerator={});
virtual bool Update(float fElapsedTime)override;
virtual void Draw()const override;
std::optional<Effect>particle;
std::function<Effect()>particleGenerator;
const float particleSpawnFreq;
const float cycleSpd;
float particleSpawnTimer{};
@ -177,7 +177,7 @@ struct FadeInOutEffect:Effect{
};
struct PoisonPool:FadeInOutEffect{
PoisonPool(vf2d pos,const std::string&img,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::optional<Effect>&particle={});
PoisonPool(vf2d pos,const std::string&img,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending=false,float particleSpawnFreq=0.f,const std::function<Effect()>&particleGenerator={});
virtual bool Update(float fElapsedTime)override final;
const int damage;
float damageTimer{};

@ -42,14 +42,14 @@ All rights reserved.
INCLUDE_game
FadeInOutEffect::FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::optional<Effect>&particle)
:particleSpawnFreq(particleSpawnFreq),particle(particle),cycleSpd(cycleSpd),particleSpawnTimer(particleSpawnFreq),originalParticleSpawnTimer(particleSpawnTimer),Effect(pos,lifetime,img,onUpperLevel,size,0.25f,spd,col,rotation,rotationSpd,additiveBlending){}
FadeInOutEffect::FadeInOutEffect(vf2d pos,const std::string&img,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect()>&particleGenerator)
:particleSpawnFreq(particleSpawnFreq),particleGenerator(particleGenerator),cycleSpd(cycleSpd),particleSpawnTimer(particleSpawnFreq),originalParticleSpawnTimer(particleSpawnTimer),Effect(pos,lifetime,img,onUpperLevel,size,0.25f,spd,col,rotation,rotationSpd,additiveBlending){}
bool FadeInOutEffect::Update(float fElapsedTime){
if(particle){
if(particleGenerator){
particleSpawnTimer-=fElapsedTime;
if(particleSpawnTimer<=0.f){
particleSpawnTimer+=originalParticleSpawnTimer;
game->AddEffect(std::make_unique<Effect>(particle.value()));
game->AddEffect(std::make_unique<Effect>(particleGenerator()));
}
}
col.a=abs(sin(PI*game->GetRunTime()*cycleSpd))*255;

@ -694,6 +694,8 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z,HurtFlag::HurtFlag hurtF
return _Hurt(damage,onUpperLevel,z,TrueDamageFlag::NORMAL_DAMAGE,hurtFlags);
}
bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags){
if(IsDead())return false;
const bool HitByPlayerAbility{bool(hurtFlags&HurtFlag::PLAYER_ABILITY)};
const bool TrueDamage{damageRule==TrueDamageFlag::IGNORE_DAMAGE_RULES};
@ -703,6 +705,7 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
const bool PlayHitSoundEffect{!IsDOT};
if(!TrueDamage&&!IsDOT&&InUndamageableState(onUpperLevel,z))return false;
if(game->InBossEncounter()){
game->StartBossEncounter();
}
@ -1346,8 +1349,8 @@ const bool Monster::IsSolid()const{
return Immovable();
}
void Monster::_DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags){
_Hurt(damageAmt,OnUpperLevel(),GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES,hurtFlags);
const bool Monster::_DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags){
return _Hurt(damageAmt,OnUpperLevel(),GetZ(),TrueDamageFlag::IGNORE_DAMAGE_RULES,hurtFlags);
}
void Monster::Heal(const int healAmt){

@ -198,7 +198,7 @@ public:
const bool HasArrowIndicator()const;
const bool ReachedTargetPos(const float maxDistanceFromTarget=4.f)const;
const float GetHealthRatio()const;
void _DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE);
const bool _DealTrueDamage(const uint32_t damageAmt,HurtFlag::HurtFlag hurtFlags=HurtFlag::NONE);
void Heal(const int healAmt);
const float GetModdedStatBonuses(std::string_view stat)const;
//The collision rectangle is only used currently for determining the safe spots for the stone golem boss fight.

@ -66,6 +66,12 @@ void PoisonBottle::Update(float fElapsedTime){
col.a=util::random_range(60,200);
game->AddEffect(std::make_unique<Effect>(pos+vf2d{util::random(16)*poisonCircleScale,util::random(2*PI)}.cart(),util::random_range(1.f,4.f),"circle.png",game->GetPlayer()->OnUpperLevel(),vf2d{size,size},util::random_range(0.2f,0.5f),vf2d{util::random_range(-6.f,6.f),util::random_range(-12.f,-4.f)},col));
}
if(game->GetPlayer()->HasEnchant("Pooling Poison")){
game->AddEffect(std::make_unique<PoisonPool>(pos,"poison_pool.png",explodeRadius,game->GetPlayer()->GetAttack()*"Pooling Poison"_ENC["POISON POOL DAMAGE PCT"]/100.f,"Pooling Poison"_ENC["POISON POOL DAMAGE FREQUENCY"],friendly?HurtType::MONSTER:HurtType::PLAYER,"Pooling Poison"_ENC["SPLASH LINGER TIME"],2.f,OnUpperLevel(),poisonCircleScale,vf2d{},WHITE,0.f,0.f,false,0.05f,[pos=pos,col=col,poisonCircleScale](){
float size{util::random_range(0.4f,0.8f)};
return Effect{pos+vf2d{util::random(16)*poisonCircleScale,util::random(2*PI)}.cart(),util::random_range(1.f,4.f),"circle.png",game->GetPlayer()->OnUpperLevel(),vf2d{size,size},util::random_range(0.2f,0.5f),vf2d{util::random_range(-6.f,6.f),util::random_range(-12.f,-4.f)},col};
}),true);
}
const HurtList hurtList{game->Hurt(pos,explodeRadius,damage,OnUpperLevel(),z,HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)};
for(const auto&[targetPtr,wasHit]:hurtList){
if(wasHit){

@ -42,8 +42,8 @@ All rights reserved.
INCLUDE_game
PoisonPool::PoisonPool(vf2d pos,const std::string&img,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::optional<Effect>&particle)
:damage(damage),damageTimer(damageFreq),originalDamageTimer(damageTimer),radius(radius),friendly(friendly),poisonPoolSFXID(SoundEffect::PlayLoopingSFX("Poison Pool",pos)),FadeInOutEffect(pos,img,lifetime,cycleSpd,onUpperLevel,size,spd,col,rotation,rotationSpd,additiveBlending,particleSpawnFreq,particle){}
PoisonPool::PoisonPool(vf2d pos,const std::string&img,float radius,int damage,float damageFreq,const HurtType friendly,float lifetime,float cycleSpd,bool onUpperLevel,float size,vf2d spd,Pixel col,float rotation,float rotationSpd,bool additiveBlending,float particleSpawnFreq,const std::function<Effect()>&particleGenerator)
:damage(damage),damageTimer(damageFreq),originalDamageTimer(damageTimer),radius(radius),friendly(friendly),poisonPoolSFXID(SoundEffect::PlayLoopingSFX("Poison Pool",pos)),FadeInOutEffect(pos,img,lifetime,cycleSpd,onUpperLevel,size,spd,col,rotation,rotationSpd,additiveBlending,particleSpawnFreq,particleGenerator){}
bool PoisonPool::Update(float fElapsedTime){
damageTimer-=fElapsedTime;

@ -113,6 +113,7 @@ void SoundEffect::PlaySFX(const std::string_view eventName,const vf2d&pos){
}
size_t SoundEffect::PlayLoopingSFX(const std::string_view eventName,const vf2d&pos){
if(game->TestingModeEnabled()||eventName.length()==0)return 0U;
const SoundEffect&sfx=GetRandomSFXFromFile(eventName);
const size_t id=Audio::Engine().LoadSound(operator""_SFX(sfx.filename.c_str(),sfx.filename.length()));
RepeatingSoundEffect::playingSoundEffects.insert(id);

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 5
#define VERSION_BUILD 11160
#define VERSION_BUILD 11173
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -421,11 +421,11 @@ Item Enchants
}
Pooling Poison
{
Description = "Throw Poison causes the splash effect to linger for {SPLASH LINGER TIME} seconds, dealing {POISON POOL DAMAGE}% attack every {POISON POOL DAMAGE FREQUENCY} seconds."
Description = "Throw Poison causes the splash effect to linger for {SPLASH LINGER TIME} seconds, dealing {POISON POOL DAMAGE PCT}% attack every {POISON POOL DAMAGE FREQUENCY} seconds."
Affects = Ability 2
SPLASH LINGER TIME = 10s
POISON POOL DAMAGE = 100%
POISON POOL DAMAGE PCT = 100%
POISON POOL DAMAGE FREQUENCY = 2s
# Stat, Lowest, Highest Value

Loading…
Cancel
Save