diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 086ac72a..099fdd95 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -4235,7 +4235,9 @@ void AiL::UpdateMonsters(){ } for(Monster&m:game->monstersToBeSpawned){ size_t prevCapacity=MONSTER_LIST.capacity(); - MONSTER_LIST.emplace_back(std::make_shared(m)); + std::shared_ptrnewMonster{std::make_shared(m)}; + newMonster->weakPtr=newMonster; + MONSTER_LIST.emplace_back(std::move(newMonster)); if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity())); } if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::shared_ptr&m){return m->markedForDeletion;}); diff --git a/Adventures in Lestoria/BearTrap.cpp b/Adventures in Lestoria/BearTrap.cpp index 83863e74..866800ba 100644 --- a/Adventures in Lestoria/BearTrap.cpp +++ b/Adventures in Lestoria/BearTrap.cpp @@ -70,7 +70,7 @@ BulletDestroyState BearTrap::MonsterHit(Monster&monster,const uint8_t markStacks const uint8_t resetStackCount{uint8_t("Trapper.Ability 2.Marked Target Stack Count Reset"_I)+1U}; //Add an additional stack because we know the target hit is about to lose one stack. const uint8_t numberOfStacksToReplenish{uint8_t(resetStackCount-monster.GetMarkStacks())}; monster.ApplyMark("Trapper.Ability 2.Marked Target Reset Time"_F,numberOfStacksToReplenish); - monster.AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,bleedDuration,bleedDamage,timeBetweenTicks); + monster.ApplyDot(bleedDuration,bleedDamage,timeBetweenTicks); } monster.AddBuff(BuffType::SLOWDOWN,"Trapper.Ability 2.Slowdown Time"_F,"Trapper.Ability 2.Slowdown Amount"_F/100.f); diff --git a/Adventures in Lestoria/Buff.cpp b/Adventures in Lestoria/Buff.cpp index 96ffb343..8c523ac7 100644 --- a/Adventures in Lestoria/Buff.cpp +++ b/Adventures in Lestoria/Buff.cpp @@ -40,18 +40,28 @@ All rights reserved. #include "Player.h" #include "Monster.h" - -Buff::Buff(std::variantattachedTarget,BuffType type,float duration,float intensity) +Buff::Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity) :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){} -Buff::Buff(std::variantattachedTarget,BuffType type,float duration,float intensity,std::set attr) +Buff::Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity,std::set attr) :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr){} -Buff::Buff(std::variantattachedTarget,BuffType type,float duration,float intensity,std::set attr) +Buff::Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity,std::set attr) :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){ for(const std::string&s:attr){ this->attr.insert(ItemAttribute::attributes.at(s)); } } -Buff::Buff(std::variantattachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks) + +Buff::Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){ + playerBuffCallbackFunc=expireCallbackFunc; +} + +Buff::Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,type,overTimeType,duration,intensity,timeBetweenTicks){ + monsterBuffCallbackFunc=expireCallbackFunc; +} + +Buff::Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks) :attachedTarget(attachedTarget),duration(duration),intensity(intensity),nextTick(duration-timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType){ switch(type){ case BuffRestorationType::ONE_OFF:{ @@ -78,35 +88,42 @@ void Buff::Update(AiL*game,float fElapsedTime){ void Buff::BuffTick(AiL*game,float fElapsedTime){ if(!overTimeType.has_value())ERR("WARNING! Trying to run BuffTick without a valid over time type provided! THIS SHOULD NOT BE HAPPENING!"); using enum BuffOverTimeType::BuffOverTimeType; + + const auto OwnerIsPlayer=[&](){return std::holds_alternative(attachedTarget);}; + const auto OwnerIsMonster=[&](){return std::holds_alternative>(attachedTarget);}; + const auto MonsterIsValid=[&](){return !std::get>(attachedTarget).expired();}; + const auto GetPlayer=[&](){return std::get(attachedTarget);}; + const auto GetMonster=[&](){return std::get>(attachedTarget).lock();}; + switch(int(overTimeType.value())){ case HP_RESTORATION:{ - IfEntity(Player)GetEntity(Player)->Heal(intensity); - else IfEntity(Monster)GetEntity(Monster)->Heal(intensity); + if(OwnerIsPlayer())GetPlayer()->Heal(intensity); + else if(OwnerIsMonster()){if(MonsterIsValid())GetMonster()->Heal(intensity);} else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; case MP_RESTORATION:{ - IfEntity(Player)GetEntity(Player)->RestoreMana(intensity); - else IfEntity(Monster)ERR("WARNING! Monsters don't have mana, this functionality is not supported!") + if(OwnerIsPlayer())GetPlayer()->RestoreMana(intensity); + else if(OwnerIsMonster())ERR("WARNING! Monsters don't have mana, this functionality is not supported!") else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; case HP_PCT_RESTORATION:{ - IfEntity(Player)GetEntity(Player)->Heal(GetEntity(Player)->GetMaxHealth()*intensity/100.f); - else IfEntity(Monster)GetEntity(Monster)->Heal(GetEntity(Monster)->GetMaxHealth()*intensity/100.f); + if(OwnerIsPlayer())GetPlayer()->Heal(GetPlayer()->GetMaxHealth()*intensity/100.f); + else if(OwnerIsMonster())GetMonster()->Heal(GetMonster()->GetMaxHealth()*intensity/100.f); else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; case MP_PCT_RESTORATION:{ - IfEntity(Player)GetEntity(Player)->RestoreMana(GetEntity(Player)->GetMaxMana()*intensity/100.f); - else IfEntity(Monster)ERR("WARNING! Monsters don't have mana, this functionality is not supported!") + if(OwnerIsPlayer())GetPlayer()->RestoreMana(GetPlayer()->GetMaxMana()*intensity/100.f); + else if(OwnerIsMonster())ERR("WARNING! Monsters don't have mana, this functionality is not supported!") else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; case HP_DAMAGE_OVER_TIME:{ - IfEntity(Player)GetEntity(Player)->Hurt(intensity,GetEntity(Player)->OnUpperLevel(),GetEntity(Player)->GetZ(),HurtFlag::DOT); - else IfEntity(Monster)GetEntity(Monster)->Hurt(intensity,GetEntity(Monster)->OnUpperLevel(),GetEntity(Monster)->GetZ(),HurtFlag::DOT); + if(OwnerIsPlayer())GetPlayer()->Hurt(intensity,GetPlayer()->OnUpperLevel(),GetPlayer()->GetZ(),HurtFlag::DOT); + else if(OwnerIsMonster())GetMonster()->Hurt(intensity,GetMonster()->OnUpperLevel(),GetMonster()->GetZ(),HurtFlag::DOT); else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; case HP_PCT_DAMAGE_OVER_TIME:{ - IfEntity(Player)GetEntity(Player)->Hurt(GetEntity(Player)->GetMaxHealth()*intensity/100.f,GetEntity(Player)->OnUpperLevel(),GetEntity(Player)->GetZ(),HurtFlag::DOT); - else IfEntity(Monster)GetEntity(Monster)->Hurt(GetEntity(Monster)->GetMaxHealth()*intensity/100.f,GetEntity(Monster)->OnUpperLevel(),GetEntity(Monster)->GetZ(),HurtFlag::DOT); + if(OwnerIsPlayer())GetPlayer()->Hurt(GetPlayer()->GetMaxHealth()*intensity/100.f,GetPlayer()->OnUpperLevel(),GetPlayer()->GetZ(),HurtFlag::DOT); + else if(OwnerIsMonster())GetMonster()->Hurt(GetMonster()->GetMaxHealth()*intensity/100.f,GetMonster()->OnUpperLevel(),GetMonster()->GetZ(),HurtFlag::DOT); else ERR("WARNING! Buff Over Time attached Target is somehow not a Player nor a Monster Pointer! THIS SHOULD NOT BE HAPPENING!") }break; } diff --git a/Adventures in Lestoria/Buff.h b/Adventures in Lestoria/Buff.h index ef36fdf3..b9d8317e 100644 --- a/Adventures in Lestoria/Buff.h +++ b/Adventures in Lestoria/Buff.h @@ -52,7 +52,6 @@ enum BuffType{ SELF_INFLICTED_SLOWDOWN, //Used for monsters and can't be applied by any player abilities. ADRENALINE_RUSH, TRAPPER_MARK, - DAMAGE_OVER_TIME, OVER_TIME, ONE_OFF, OVER_TIME_DURING_CAST, @@ -73,24 +72,29 @@ namespace BuffOverTimeType{ }; }; -#define IfEntity(type) if(std::holds_alternative(attachedTarget)) -#define GetEntity(type) std::get(attachedTarget) - class AiL; struct Buff{ + + using PlayerBuffExpireCallbackFunction=std::function; + using MonsterBuffExpireCallbackFunction=std::functionattachedTarget,Buff&b)>; + BuffType type; float duration=1; float timeBetweenTicks=1; float intensity=1; float nextTick=0; std::set attr; - std::variantattachedTarget; //Who has this buff. + std::variant>attachedTarget; //Who has this buff. + PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){}; + MonsterBuffExpireCallbackFunction monsterBuffCallbackFunc=[](std::weak_ptrm,Buff&b){}; - Buff(std::variantattachedTarget,BuffType type,float duration,float intensity); - Buff(std::variantattachedTarget,BuffType type,float duration,float intensity,std::set attr); - Buff(std::variantattachedTarget,BuffType type,float duration,float intensity,std::set attr); - Buff(std::variantattachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); + Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity); + Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity,std::set attr); + Buff(std::variant>attachedTarget,BuffType type,float duration,float intensity,std::set attr); + Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); + Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc); + Buff(std::variant>attachedTarget,BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc); void Update(AiL*game,float fElapsedTime); private: diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index f373ecb7..5babb617 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -329,7 +329,14 @@ bool Monster::Update(float fElapsedTime){ if(IsAlive()){ std::for_each(buffList.begin(),buffList.end(),[&](Buff&b){b.Update(game,fElapsedTime);}); - std::erase_if(buffList,[](Buff&b){return b.duration<=0;}); + std::erase_if(buffList,[](Buff&b){ + if(b.duration<=0.f){ + if(!std::holds_alternative>(b.attachedTarget))ERR(std::format("WARNING! Somehow removed buff of type {} is inside the player buff list but is not attached to the Player? THIS SHOULD NOT BE HAPPENING!",int(b.type))); + b.monsterBuffCallbackFunc(std::get>(b.attachedTarget),b); + return true; + } + return false; + }); if(!HasIframes()){ for(std::shared_ptr&m:MONSTER_LIST){ const float monsterRadius{GetCollisionRadius()}; @@ -753,17 +760,17 @@ const bool Monster::OnUpperLevel()const{ } void Monster::AddBuff(BuffType type,float duration,float intensity){ - buffList.push_back(Buff{this,type,duration,intensity}); + buffList.push_back(Buff{GetWeakPointer(),type,duration,intensity}); } void Monster::AddBuff(BuffType type,float duration,float intensity,std::setattr){ if(type==STAT_UP)std::for_each(attr.begin(),attr.end(),[](const ItemAttribute&attr){if(attr.ActualName()!="Health"&&attr.ActualName()!="Health %"&&attr.ActualName()!="Attack"&&attr.ActualName()!="Attack %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr.ActualName()));}); - buffList.emplace_back(this,type,duration,intensity,attr); + buffList.emplace_back(GetWeakPointer(),type,duration,intensity,attr); } void Monster::AddBuff(BuffType type,float duration,float intensity,std::setattr){ if(type==STAT_UP)std::for_each(attr.begin(),attr.end(),[](const std::string&attr){if(attr!="Health"&&attr!="Health %"&&attr!="Attack"&&attr!="Attack %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr));}); - buffList.emplace_back(this,type,duration,intensity,attr); + buffList.emplace_back(GetWeakPointer(),type,duration,intensity,attr); } void Monster::RemoveBuff(BuffType type){ @@ -1297,7 +1304,7 @@ const bool Monster::InUndamageableState(const bool onUpperLevel,const float z)co } void Monster::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){ - buffList.push_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks}); + buffList.push_back(Buff{GetWeakPointer(),type,overTimeType,duration,intensity,timeBetweenTicks}); } const bool Monster::CanMove()const{ @@ -1305,5 +1312,13 @@ const bool Monster::CanMove()const{ } const std::weak_ptrMonster::GetWeakPointer()const{ - return *std::find_if(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr&ptr){return &*ptr==this;}); + return weakPtr; +} + +void Monster::ApplyDot(float duration,int damage,float timeBetweenTicks){ + AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,duration,damage,timeBetweenTicks); +} + +void Monster::SetWeakPointer(std::shared_ptr&sharedMonsterPtr){ + weakPtr=sharedMonsterPtr; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 3a111299..3dbd2189 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -208,6 +208,7 @@ public: const bool InUndamageableState(const bool onUpperLevel,const float z)const; const bool CanMove()const; const std::weak_ptrGetWeakPointer()const; + void ApplyDot(float duration,int damage,float timeBetweenTicks); 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. @@ -291,6 +292,8 @@ private: bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags); void RemoveMarkStack(); float markApplicationTimer{}; //Used for animations involving the mark being applied to the target. + void SetWeakPointer(std::shared_ptr&sharedMonsterPtr); + std::weak_ptrweakPtr; private: struct STRATEGY{ static std::string ERR; diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 465c2a03..0aae268b 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -388,7 +388,14 @@ void Player::Update(float fElapsedTime){ RestoreMana(1,true); } std::for_each(buffList.begin(),buffList.end(),[&](Buff&b){b.Update(game,fElapsedTime);}); - std::erase_if(buffList,[](Buff&b){return b.duration<=0;}); + std::erase_if(buffList,[](Buff&b){ + if(b.duration<=0.f){ + if(!std::holds_alternative(b.attachedTarget))ERR(std::format("WARNING! Somehow removed buff of type {} is inside the player buff list but is not attached to the Player? THIS SHOULD NOT BE HAPPENING!",int(b.type))); + b.playerBuffCallbackFunc(std::get(b.attachedTarget),b); + return true; + } + return false; + }); //Class-specific update events. OnUpdate(fElapsedTime); switch(state){ @@ -1822,4 +1829,8 @@ void Player::SetupAfterImage(){ afterImage.Decal()->Update(); removeLineTimer=TIME_BETWEEN_LINE_REMOVALS; scanLine=1U; +} + +void Player::ApplyDot(float duration,int damage,float timeBetweenTicks){ + AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,duration,damage,timeBetweenTicks); } \ No newline at end of file diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index 9a8da115..b51c3813 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -290,6 +290,7 @@ public: const bool IsUsingAdditiveBlending()const; vf2d MoveUsingPathfinding(vf2d targetPos); const std::unordered_set&GetMyClass()const; + void ApplyDot(float duration,int damage,float timeBetweenTicks); private: int hp="Warrior.BaseHealth"_I; int mana="Player.BaseMana"_I; diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 4fcb88bc..c5a20553 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 3 -#define VERSION_BUILD 10356 +#define VERSION_BUILD 10359 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Witch.cpp b/Adventures in Lestoria/Witch.cpp index 21c687fd..34281ee5 100644 --- a/Adventures in Lestoria/Witch.cpp +++ b/Adventures in Lestoria/Witch.cpp @@ -122,13 +122,18 @@ void Witch::InitializeClassAbilities(){ p->SetAnimationBasedOnTargetingDirection("TRANSFORM",p->transformTargetDir); p->ApplyIframes("Witch.Right Click Ability.Leap Max Range Time"_F+0.1f); p->SetState(State::LEAP); + SoundEffect::PlaySFX("Meow",p->GetPos()); return true; }; #pragma endregion - #pragma region Witch Ability 1 (???) + #pragma region Witch Ability 1 (Curse of Pain) Witch::ability1.action= [](Player*p,vf2d pos={}){ - return false; + std::optional>curseTarget{Monster::GetNearestMonster(pos,"Witch.Ability 1.Casting Range"_F/100.f*24,p->OnUpperLevel(),p->GetZ())}; + if(curseTarget.has_value()&&!curseTarget.value().expired()){ + curseTarget.value().lock()->ApplyDot("Witch.Ability 1.Curse Debuff"_f[2],p->GetAttack()*"Witch.Ability 1.Curse Debuff"_f[0],"Witch.Ability 1.Curse Debuff"_f[1]); + } + return true; }; #pragma endregion #pragma region Witch Ability 2 (???) diff --git a/Adventures in Lestoria/assets/config/audio/events.txt b/Adventures in Lestoria/assets/config/audio/events.txt index cbdbba6a..c65bb919 100644 --- a/Adventures in Lestoria/assets/config/audio/events.txt +++ b/Adventures in Lestoria/assets/config/audio/events.txt @@ -203,6 +203,12 @@ Events # Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) File[0] = menu_navigate.ogg, 0% } + Meow + { + CombatSound = True + # Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) + File[0] = kitten_mew.ogg, 70% + } Monster Hurt { CombatSound = True diff --git a/Adventures in Lestoria/assets/sounds/kitten_mew.ogg b/Adventures in Lestoria/assets/sounds/kitten_mew.ogg new file mode 100644 index 00000000..248e4587 Binary files /dev/null and b/Adventures in Lestoria/assets/sounds/kitten_mew.ogg differ diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 4bb87f68..9a5f555d 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ