Implemented Curse of Doom. Release Build 11188.

This commit is contained in:
sigonasr2 2024-09-01 02:15:12 -05:00
parent a81dd94182
commit 6d4de7940b
8 changed files with 74 additions and 4 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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