diff --git a/Adventures in Lestoria Tests/EnchantTests.cpp b/Adventures in Lestoria Tests/EnchantTests.cpp index 9521c8ad..939f051e 100644 --- a/Adventures in Lestoria Tests/EnchantTests.cpp +++ b/Adventures in Lestoria Tests/EnchantTests.cpp @@ -1118,5 +1118,37 @@ namespace EnchantTests while(BULLET_LIST.size()>0)Game::Update(1/30.f); Assert::AreEqual(962,newMonster.GetHealth(),L"Monster should have taken 150% more damage with Curse of Death."); } + TEST_METHOD(CurseOfDoomNoEnchantCheck){ + Game::ChangeClass(player,WITCH); + Monster&newMonster{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster2{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster3{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster4{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Game::Update(0.f); + Game::CastAbilityAtLocation(player->GetAbility3(),player->GetPos()+vf2d{30.f,0.f}); + newMonster.Hurt(1000,newMonster.OnUpperLevel(),newMonster.GetZ()); + Assert::AreEqual(true,newMonster.IsDead(),L"Monster 1 has died."); + Assert::AreEqual(size_t(1),newMonster.GetBuffs(BuffType::CURSE_OF_DEATH).size(),L"Monster 1 has a Curse of Death debuff."); + Assert::AreEqual(newMonster2.GetMaxHealth(),newMonster2.GetHealth(),L"Monster 2 is healthy and has not been hit by collateral damage."); + Assert::AreEqual(newMonster3.GetMaxHealth(),newMonster3.GetHealth(),L"Monster 3 is healthy and has not been hit by collateral damage."); + Assert::AreEqual(newMonster4.GetMaxHealth(),newMonster4.GetHealth(),L"Monster 4 is healthy and has not been hit by collateral damage."); + } + TEST_METHOD(CurseOfDoomEnchantCheck){ + Game::ChangeClass(player,WITCH); + Game::GiveAndEquipEnchantedRing("Curse of Doom"); + Monster&newMonster{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster2{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster3{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Monster&newMonster4{game->SpawnMonster({30.f,0.f},MONSTER_DATA["TestName"])}; + Game::Update(0.f); + newMonster.Hurt(750,newMonster.OnUpperLevel(),newMonster.GetZ()); + Game::CastAbilityAtLocation(player->GetAbility3(),player->GetPos()+vf2d{30.f,0.f}); + newMonster.Hurt(250,newMonster.OnUpperLevel(),newMonster.GetZ()); + Assert::AreEqual(true,newMonster.IsDead(),L"Monster 1 has died."); + Assert::AreEqual(size_t(1),newMonster.GetBuffs(BuffType::CURSE_OF_DEATH).size(),L"Monster 1 has a Curse of Death debuff."); + Assert::AreEqual(newMonster2.GetMaxHealth()-500,newMonster2.GetHealth(),L"Monster 2 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff"); + Assert::AreEqual(newMonster3.GetMaxHealth()-500,newMonster3.GetHealth(),L"Monster 3 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff."); + Assert::AreEqual(newMonster4.GetMaxHealth()-500,newMonster4.GetHealth(),L"Monster 4 takes collateral damage from Curse of Doom enchant based on the amount of damage dealt during Curse of Death debuff."); + } }; } diff --git a/Adventures in Lestoria/Buff.h b/Adventures in Lestoria/Buff.h index d0a0aeb7..ef764413 100644 --- a/Adventures in Lestoria/Buff.h +++ b/Adventures in Lestoria/Buff.h @@ -63,6 +63,7 @@ enum BuffType{ BURNING_ARROW_BURN, SWORD_ENCHANTMENT, CURSE_OF_PAIN, + CURSE_OF_DEATH, }; enum class BuffRestorationType{ ONE_OFF, //This is used as a hack fix for the RestoreDuringCast Item script since they require us to restore 1 tick immediately. Over time buffs do not apply a tick immediately. diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 13d9de2e..fcbfbc4f 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -452,6 +452,7 @@ void Monster::Draw()const{ if(HasBuff(BuffType::BURNING_ARROW_BURN))blendCol=GetBuffBlendCol(BuffType::BURNING_ARROW_BURN,1.f,{255,160,0}); else if(HasBuff(BuffType::DAMAGE_AMPLIFICATION))blendCol=GetBuffBlendCol(BuffType::DAMAGE_AMPLIFICATION,1.4f,{255,0,0}); + else if(HasBuff(BuffType::CURSE_OF_DEATH))blendCol=GetBuffBlendCol(BuffType::CURSE_OF_DEATH,1.4f,{255,0,0}); else if(HasBuff(BuffType::COLOR_MOD))blendCol=GetBuffBlendCol(BuffType::COLOR_MOD,1.4f,PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity),1.f); else if(HasBuff(BuffType::SLOWDOWN))blendCol=GetBuffBlendCol(BuffType::SLOWDOWN,1.4f,{255,255,128},0.5f); else if(glowPurpleBuff.has_value())blendCol=Pixel{uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().get().duration))),uint8_t(255*abs(sin(1.4*glowPurpleBuff.value().get().duration))),uint8_t(128+127*abs(sin(1.4*glowPurpleBuff.value().get().duration)))}; @@ -756,6 +757,8 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da hp=std::max(0,hp-int(mod_dmg)); + if(GetBuffs(BuffType::CURSE_OF_DEATH).size()>0)accumulatedCurseOfDeathDamage+=mod_dmg; + if(IsDOT){ if(lastDotTimer>0){ dotNumberPtr.get()->AddDamage(int(mod_dmg)); @@ -940,6 +943,12 @@ std::vectorMonster::GetBuffs(BuffType buff)const{ return filteredBuffs; } +std::vectorMonster::GetBuffs(std::vectorbuffs)const{ + std::vectorfilteredBuffs; + std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[&buffs](const Buff&b){return std::find(buffs.begin(),buffs.end(),b.type)!=buffs.end();}); + return filteredBuffs; +} + State::State Monster::GetState(){ return state; } @@ -1103,6 +1112,25 @@ void Monster::OnDeath(){ } #pragma endregion } + if(game->GetPlayer()->HasEnchant("Curse of Doom")&&accumulatedCurseOfDeathDamage>0){ + #pragma region Go Boom + float radius{"Curse of Doom"_ENC["EXPLODE RANGE"]/100.f*24}; + const HurtList list{game->Hurt(pos,radius,accumulatedCurseOfDeathDamage,OnUpperLevel(),GetZ(),HurtType::MONSTER,HurtFlag::PLAYER_ABILITY)}; + for(const auto&[targetPtr,wasHit]:list){ + if(wasHit){ + std::get(targetPtr)->ProximityKnockback(pos,"Curse of Doom"_ENC["EXPLODE KNOCKBACK AMOUNT"]); + std::get(targetPtr)->Knockup("Curse of Doom"_ENC["EXPLODE KNOCKUP AMOUNT"]); + } + } + + float targetScale{radius/24}; + float startingScale{GetCollisionRadius()/24}; + std::unique_ptrexplodeEffect{std::make_unique(pos,ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()-0.2f,"explosionframes.png",OnUpperLevel(),startingScale,0.2f,vf2d{0,-6.f},WHITE,util::random(2*PI),0.f)}; + explodeEffect->scaleSpd={(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration(),(targetScale-startingScale)/ANIMATION_DATA["explosionframes.png"].GetTotalAnimationDuration()}; + game->AddEffect(std::move(explodeEffect)); + SoundEffect::PlaySFX("Explosion",pos); + #pragma endregion + } SpawnDrops(); @@ -1533,7 +1561,7 @@ void Monster::SetWeakPointer(std::shared_ptr&sharedMonsterPtr){ const float Monster::GetDamageAmplificationMult(const bool backstabOccurred)const{ float damageAmpMult{1.f}; - const std::vector&buffList{GetBuffs(BuffType::DAMAGE_AMPLIFICATION)}; + const std::vector&buffList{GetBuffs({BuffType::DAMAGE_AMPLIFICATION,BuffType::CURSE_OF_DEATH})}; for(const Buff&buff:buffList){ damageAmpMult+=buff.intensity; } @@ -1579,4 +1607,7 @@ const bool Monster::FadeoutWhenStandingBehind()const{ } const bool Monster::FaceTarget()const{ return MONSTER_DATA.at(name).FaceTarget(); +} +void Monster::ResetCurseOfDeathDamage(){ + accumulatedCurseOfDeathDamage=0; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index c1f42293..41b61c05 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -145,6 +145,7 @@ public: void AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); void AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::MonsterBuffExpireCallbackFunction expireCallback); std::vectorGetBuffs(BuffType buff)const; + std::vectorGetBuffs(std::vectorbuffs)const; //Removes all buffs of a given type. void RemoveBuff(BuffType type); State::State GetState(); @@ -223,6 +224,7 @@ public: const float&GetRemainingStunDuration()const; const bool FadeoutWhenStandingBehind()const; const bool FaceTarget()const; + void ResetCurseOfDeathDamage(); private: //NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! // The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop. @@ -312,7 +314,8 @@ private: std::weak_ptrweakPtr; void LongLastingMarkEffect(float&out_markDuration); //Modifies a given duration. float stunTimer{}; -private: + int accumulatedCurseOfDeathDamage{}; + struct STRATEGY{ static std::string ERR; static int _GetInt(Monster&m,std::string param,std::string strategy,int index=0); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 45bc7566..21dbec4b 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 5 -#define VERSION_BUILD 11183 +#define VERSION_BUILD 11188 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Witch.cpp b/Adventures in Lestoria/Witch.cpp index 8fe02ca8..d78eeef1 100644 --- a/Adventures in Lestoria/Witch.cpp +++ b/Adventures in Lestoria/Witch.cpp @@ -181,7 +181,8 @@ void Witch::InitializeClassAbilities(){ const float curseDuration{"Witch.Ability 3.Curse Duration"_F}; float damageAmpMult{"Witch.Ability 3.Damage Amplification"_F/100.f}; if(p->HasEnchant("Expunge"))damageAmpMult+="Expunge"_ENC["BONUS DAMAGE"]/100.f; - curseTarget.value().lock()->AddBuff(BuffType::DAMAGE_AMPLIFICATION,curseDuration,damageAmpMult); + curseTarget.value().lock()->ResetCurseOfDeathDamage(); + curseTarget.value().lock()->AddBuff(BuffType::CURSE_OF_DEATH,curseDuration,damageAmpMult); const vf2d targetPos{curseTarget.value().lock()->GetPos()}; for(int i:std::ranges::iota_view(0,int(util::distance(p->GetPos(),targetPos)/8))){ float drawDist{i*8.f}; diff --git a/Adventures in Lestoria/assets/config/items/ItemEnchants.txt b/Adventures in Lestoria/assets/config/items/ItemEnchants.txt index 2310df34..1f204782 100644 --- a/Adventures in Lestoria/assets/config/items/ItemEnchants.txt +++ b/Adventures in Lestoria/assets/config/items/ItemEnchants.txt @@ -459,6 +459,8 @@ Item Enchants Affects = Ability 3 EXPLODE RANGE = 500 + EXPLODE KNOCKBACK AMOUNT = 400 + EXPLODE KNOCKUP AMOUNT = 0.6s # Stat, Lowest, Highest Value # Stat Modifier[0] = ..., 0, 0 diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index c565bbfd..d7483418 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ