Implemented Curse of Doom. Release Build 11188.

pull/65/head
sigonasr2 3 months ago
parent a81dd94182
commit 6d4de7940b
  1. 32
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 1
      Adventures in Lestoria/Buff.h
  3. 33
      Adventures in Lestoria/Monster.cpp
  4. 5
      Adventures in Lestoria/Monster.h
  5. 2
      Adventures in Lestoria/Version.h
  6. 3
      Adventures in Lestoria/Witch.cpp
  7. 2
      Adventures in Lestoria/assets/config/items/ItemEnchants.txt
  8. BIN
      x64/Release/Adventures in Lestoria.exe

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

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

@ -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::vector<Buff>Monster::GetBuffs(BuffType buff)const{
return filteredBuffs;
}
std::vector<Buff>Monster::GetBuffs(std::vector<BuffType>buffs)const{
std::vector<Buff>filteredBuffs;
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<Monster*>(targetPtr)->ProximityKnockback(pos,"Curse of Doom"_ENC["EXPLODE KNOCKBACK AMOUNT"]);
std::get<Monster*>(targetPtr)->Knockup("Curse of Doom"_ENC["EXPLODE KNOCKUP AMOUNT"]);
}
}
float targetScale{radius/24};
float startingScale{GetCollisionRadius()/24};
std::unique_ptr<Effect>explodeEffect{std::make_unique<Effect>(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<Monster>&sharedMonsterPtr){
const float Monster::GetDamageAmplificationMult(const bool backstabOccurred)const{
float damageAmpMult{1.f};
const std::vector<Buff>&buffList{GetBuffs(BuffType::DAMAGE_AMPLIFICATION)};
const std::vector<Buff>&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;
}

@ -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::vector<Buff>GetBuffs(BuffType buff)const;
std::vector<Buff>GetBuffs(std::vector<BuffType>buffs)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_ptr<Monster>weakPtr;
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);

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

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

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

Loading…
Cancel
Save