diff --git a/Adventures in Lestoria Tests/EnchantTests.cpp b/Adventures in Lestoria Tests/EnchantTests.cpp index 7a10b31e..5a0d4ba6 100644 --- a/Adventures in Lestoria Tests/EnchantTests.cpp +++ b/Adventures in Lestoria Tests/EnchantTests.cpp @@ -51,6 +51,7 @@ INCLUDE_GFX INCLUDE_ITEM_DATA INCLUDE_DAMAGENUMBER_LIST INCLUDE_INITIALIZEGAMECONFIGURATIONS +INCLUDE_MONSTER_LIST extern std::mt19937 rng; @@ -204,16 +205,16 @@ namespace EnchantTests TEST_METHOD(StoneskinCheck){ std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; std::weak_ptrnullRing2{Inventory::AddItem("Null Ring"s)}; - Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% Damage Reduction."); + Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Player starts with 0% Damage Reduction."); Inventory::EquipItem(nullRing,EquipSlot::RING1); for(int i:std::ranges::iota_view(0,1000)){ nullRing.lock()->EnchantItem("Stoneskin"); - Test::InRange(player->GetDamageReductionPct(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range."); + Test::InRange(player->GetDamageReductionFromBuffs(),{3.0_Pct,5.0_Pct},L"Damage Reduction not in expected range."); } Inventory::EquipItem(nullRing2,EquipSlot::RING2); for(int i:std::ranges::iota_view(0,1000)){ nullRing2.lock()->EnchantItem("Stoneskin"); - Test::InRange(player->GetDamageReductionPct(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings."); + Test::InRange(player->GetDamageReductionFromBuffs(),{6.0_Pct,10.0_Pct},L"Damage Reduction not in expected range with two rings."); } } TEST_METHOD(ManaPoolCheck){ @@ -235,14 +236,14 @@ namespace EnchantTests std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; std::weak_ptrnullRing2{Inventory::AddItem("Null Ring"s)}; Assert::AreEqual(100,player->GetMaxHealth(),L"Player starts with 100 health."); - Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Player starts with 0% damage reduction."); + Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Player starts with 0% damage reduction."); Assert::AreEqual(100.0_Pct,player->GetMoveSpdMult(),L"Player starts with 100% move speed."); Assert::AreEqual(0.0_Pct,player->GetHP6RecoveryPct(),L"Player starts with 0% HP/6 recovery."); Inventory::EquipItem(nullRing,EquipSlot::RING1); for(int i:std::ranges::iota_view(0,1000)){ nullRing.lock()->EnchantItem("Magical Protection"); Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range."); - Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range."); + Test::InRange(player->GetDamageReductionFromBuffs(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range."); Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range."); Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range."); } @@ -250,7 +251,7 @@ namespace EnchantTests for(int i:std::ranges::iota_view(0,1000)){ nullRing2.lock()->EnchantItem("Magical Protection"); Test::InRange(player->GetMaxHealth(),{102,103},L"Max Health not in expected range with two rings."); - Test::InRange(player->GetDamageReductionPct(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range with two rings."); + Test::InRange(player->GetDamageReductionFromBuffs(),{2.0_Pct,3.0_Pct},L"Damage Reduction not in expected range with two rings."); Test::InRange(player->GetMoveSpdMult(),{102.0_Pct,103.0_Pct},L"Move Speed % not in expected range with two rings."); Test::InRange(player->GetHP6RecoveryPct(),{1.0_Pct,1.0_Pct},L"HP/6 Recovery not in expected range with two rings."); } @@ -486,17 +487,17 @@ namespace EnchantTests } TEST_METHOD(LastReserveCheck){ player->SetBaseStat("Attack",100.f); - Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage reduction starts at 0%"); + Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction starts at 0%"); Assert::AreEqual(100,player->GetAttack(),L"Attack damage starts at 100."); Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction starts at 0%"); std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; Inventory::EquipItem(nullRing,EquipSlot::RING1); nullRing.lock()->EnchantItem("Last Reserve"); - Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage reduction is still 0%"); + Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction is still 0%"); Assert::AreEqual(100,player->GetAttack(),L"Attack damage still 100."); Assert::AreEqual(0.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction is still 0%"); player->Hurt(80,player->OnUpperLevel(),player->GetZ()); - Assert::AreEqual(30.0_Pct,player->GetDamageReductionPct(),L"Damage reduction increased to 30%"); + Assert::AreEqual(30.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage reduction increased to 30%"); Assert::AreEqual(110,player->GetAttack(),L"Attack damage is now 110."); Assert::AreEqual(10.0_Pct,player->GetCooldownReductionPct(),L"Cooldown reduction increased to 10%"); } @@ -764,5 +765,82 @@ namespace EnchantTests Assert::AreEqual(size_t(1),player->GetBuffs(BuffType::DAMAGE_REDUCTION).size(),L"Roll gives a damage reduction buff."); Assert::AreEqual("Evasive Movement"_ENC["DAMAGE REDUCTION PCT"]/100.f,player->GetDamageReductionFromBuffs(),L"Evasive Movement provides 50% damage reduction."); } + TEST_METHOD(BurningArrowCheck){ + testKey->bHeld=true; //Force the key to be held down for testing purposes. + game->ChangePlayerClass(TRAPPER); + player=game->GetPlayer(); + player->AutoAttack(); + Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist."); + Monster testMonster{{},MONSTER_DATA["TestName"]}; + BULLET_LIST.back()->MonsterHit(testMonster,testMonster.GetMarkStacks()); + game->SetElapsedTime(0.4f); + game->OnUserUpdate(0.4f); + Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist."); + Assert::AreEqual(size_t(0),testMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target is not burned."); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + Inventory::EquipItem(nullRing,EquipSlot::RING1); + nullRing.lock()->EnchantItem("Burning Arrow"); + Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist."); + BULLET_LIST.back()->MonsterHit(testMonster,testMonster.GetMarkStacks()); + game->SetElapsedTime(0.4f); + game->OnUserUpdate(0.4f); + Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist."); + Assert::AreEqual(size_t(0),testMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target should still not be burned since there are no marks on the target."); + player->ReduceAutoAttackTimer(INFINITE); + Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])}; + game->SetElapsedTime(0.01f); + game->OnUserUpdate(0.01f); + player->_ForceCastSpell(player->GetAbility1()); + game->SetElapsedTime("Trapper.Ability 1.Precast Time"_F); + game->OnUserUpdate("Trapper.Ability 1.Precast Time"_F); + game->SetElapsedTime(0.6f); //Wait for mark lock-on + game->OnUserUpdate(0.6f); //Wait for mark lock-on + player->AutoAttack(); + Assert::AreEqual(size_t(1),BULLET_LIST.size(),L"Arrow projectile should now exist."); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + game->SetElapsedTime(0.4f); + game->OnUserUpdate(0.4f); + Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist."); + Assert::AreEqual(size_t(1),newMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Target should be burned since there are marks on the target and we have the Burning Arrow enchant."); + } + TEST_METHOD(BurningArrowTripleStackCheck){ + testKey->bHeld=true; //Force the key to be held down for testing purposes. + game->ChangePlayerClass(TRAPPER); + player=game->GetPlayer(); + std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + Inventory::EquipItem(nullRing,EquipSlot::RING1); + nullRing.lock()->EnchantItem("Burning Arrow"); + Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])}; + game->SetElapsedTime(0.01f); + game->OnUserUpdate(0.01f); + player->_ForceCastSpell(player->GetAbility1()); + game->SetElapsedTime("Trapper.Ability 1.Precast Time"_F); + game->OnUserUpdate("Trapper.Ability 1.Precast Time"_F); + game->SetElapsedTime(0.6f); //Wait for mark lock-on + game->OnUserUpdate(0.6f); //Wait for mark lock-on + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + BULLET_LIST.back()->MonsterHit(newMonster,newMonster.GetMarkStacks()); + game->SetElapsedTime(0.4f); + game->OnUserUpdate(0.4f); + Assert::AreEqual(size_t(0),BULLET_LIST.size(),L"No more projectiles exist."); + Assert::AreEqual(size_t("Burning Arrow"_ENC["MAX BURN STACK"]),newMonster.GetBuffs(BuffType::BURNING_ARROW_BURN).size(),L"Only 3 burn ticks maximum are allowed."); + player->ReduceAutoAttackTimer(INFINITE); + player->AutoAttack(); + } }; } \ No newline at end of file diff --git a/Adventures in Lestoria Tests/PlayerTests.cpp b/Adventures in Lestoria Tests/PlayerTests.cpp index 3a9f3b7a..5240e1f9 100644 --- a/Adventures in Lestoria Tests/PlayerTests.cpp +++ b/Adventures in Lestoria Tests/PlayerTests.cpp @@ -502,10 +502,10 @@ namespace PlayerTests } TEST_METHOD(DamageReductionStatEquipCheck){ std::weak_ptrsetArmor{Inventory::AddItem("Test Armor3"s)}; - Assert::AreEqual(0.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 0%"); + Assert::AreEqual(0.0_Pct,player->GetDamageReductionFromBuffs(),L"Damage Reduction is 0%"); Inventory::EquipItem(setArmor,EquipSlot::ARMOR); - Assert::AreEqual(100.0_Pct,player->GetDamageReductionPct(),L"Damage Reduction is 100%"); + Assert::AreEqual(75.0_Pct,player->GetDamageReductionFromBuffs(),L"Max Damage Reduction is 75%, even if a piece has 100% damage reduction."); } TEST_METHOD(HPRecoveryStatEquipCheck){ std::weak_ptrsetArmor{Inventory::AddItem("Test Armor4"s)}; diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index 04552b80..da376161 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -242,7 +242,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true - stdcpplatest + stdcpp20 C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\LabUser\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\LabUser\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\niconiconii\OneDrive\Documents\include;C:\Users\niconiconii\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\steam;C:\Users\sigon\source\repos\AdventuresInLestoria\Adventures in Lestoria\discord-files;C:\Users\sigon\OneDrive\Documents\include;%(AdditionalIncludeDirectories) 4099;5030;4715;4172;4834 true diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 3fbb3452..64a7cb31 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -3399,11 +3399,11 @@ void AiL::InitializeLevels(){ } Monster&AiL::SpawnMonster(vf2d pos,MonsterData&data,bool upperLevel,bool isBossSpawn){ - monstersToBeSpawned.push_back(Monster(pos,data,upperLevel,isBossSpawn)); + std::shared_ptrnewMonster{monstersToBeSpawned.emplace_back(std::make_shared(pos,data,upperLevel,isBossSpawn))}; if(isBossSpawn){ totalBossEncounterMobs++; } - return monstersToBeSpawned.back(); + return *newMonster; } void AiL::DrawPie(vf2d center,float radius,float degreesCut,Pixel col){ @@ -4365,11 +4365,10 @@ void AiL::UpdateMonsters(){ } [[likely]]if(m->isBoss||m->GetDistanceFrom(GetPlayer()->GetPos())<40.f*24)m->Update(game->GetElapsedTime()); } - for(Monster&m:game->monstersToBeSpawned){ + for(std::shared_ptr&m:game->monstersToBeSpawned){ size_t prevCapacity=MONSTER_LIST.capacity(); - std::shared_ptrnewMonster{std::make_shared(m)}; - newMonster->weakPtr=newMonster; - MONSTER_LIST.emplace_back(std::move(newMonster)); + m->weakPtr=m; + MONSTER_LIST.emplace_back(std::move(m)); 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/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index 968d69b3..8615ee90 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -187,7 +187,7 @@ private: float fadeOutDuration=0; States::State transitionState=States::State::GAME_RUN; bool gameEnd=false; - std::vectormonstersToBeSpawned; + std::vector>monstersToBeSpawned; bool aMonsterIsMarkedForDeletion=false; //DO NOT MODIFY DIRECTLY! Use AMonsterIsMarkedForDeletion() instead! time_t gameStarted; std::functionresponseCallback; diff --git a/Adventures in Lestoria/Arrow.cpp b/Adventures in Lestoria/Arrow.cpp index 3b06c5bb..c35deb42 100644 --- a/Adventures in Lestoria/Arrow.cpp +++ b/Adventures in Lestoria/Arrow.cpp @@ -104,6 +104,7 @@ BulletDestroyState Arrow::MonsterHit(Monster&monster,const uint8_t markStacksBef fadeOutTime=0.2f; game->AddEffect(std::make_unique(monster.GetPos(),0,"splash_effect.png",upperLevel,monster.GetSizeMult(),0.25)); if(poisonArrow)monster.AddBuff(BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Poisonous Arrow"_ENC["POISON DURATION"],damage*("Poisonous Arrow"_ENC["POISON TICK DAMAGE"]/100.f),1.f); + if(game->GetPlayer()->HasEnchant("Burning Arrow")&&markStacksBeforeHit>0&&monster.GetBuffs(BuffType::BURNING_ARROW_BURN).size()<"Burning Arrow"_ENC["MAX BURN STACK"])monster.AddBuff(BuffType::BURNING_ARROW_BURN,BuffRestorationType::OVER_TIME,BuffOverTimeType::HP_DAMAGE_OVER_TIME,"Burning Arrow"_ENC["BURN DURATION"],game->GetPlayer()->GetAttack()*"Burning Arrow"_ENC["BURN DAMAGE"]/100.f,2.f); return BulletDestroyState::KEEP_ALIVE; } diff --git a/Adventures in Lestoria/Buff.cpp b/Adventures in Lestoria/Buff.cpp index d7242133..e9aad0bd 100644 --- a/Adventures in Lestoria/Buff.cpp +++ b/Adventures in Lestoria/Buff.cpp @@ -51,19 +51,41 @@ Buff::Buff(std::variant>attachedTarget,BuffType t } } -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){ +Buff::Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){ playerBuffCallbackFunc=expireCallbackFunc; + switch(restorationType){ + case BuffRestorationType::ONE_OFF:{ + this->type=ONE_OFF; + }break; + case BuffRestorationType::OVER_TIME:{ + this->type=OVER_TIME; + }break; + case BuffRestorationType::OVER_TIME_DURING_CAST:{ + this->type=OVER_TIME_DURING_CAST; + }break; + } } -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){ +Buff::Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){ monsterBuffCallbackFunc=expireCallbackFunc; + switch(restorationType){ + case BuffRestorationType::ONE_OFF:{ + this->type=ONE_OFF; + }break; + case BuffRestorationType::OVER_TIME:{ + this->type=OVER_TIME; + }break; + case BuffRestorationType::OVER_TIME_DURING_CAST:{ + this->type=OVER_TIME_DURING_CAST; + }break; + } } -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){ +Buff::Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks) + :Buff(attachedTarget,ONE_OFF,restorationType,overTimeType,duration,intensity,timeBetweenTicks){ + switch(restorationType){ case BuffRestorationType::ONE_OFF:{ this->type=ONE_OFF; }break; @@ -76,12 +98,26 @@ Buff::Buff(std::variant>attachedTarget,BuffRestor } } +Buff::Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){ + playerBuffCallbackFunc=expireCallbackFunc; +} + +Buff::Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc) +:Buff(attachedTarget,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks){ + monsterBuffCallbackFunc=expireCallbackFunc; +} + +Buff::Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks) + :attachedTarget(attachedTarget),type(type),restorationType(restorationType),duration(duration),intensity(intensity),nextTick(timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType){} + void Buff::Update(AiL*game,float fElapsedTime){ duration-=fElapsedTime; - if(enabled&&overTimeType.has_value()&&nextTick>0&&duration=nextTick){ BuffTick(game,fElapsedTime); - nextTick-=timeBetweenTicks; - if(type==ONE_OFF)enabled=false; + nextTick+=timeBetweenTicks; + if(restorationType==BuffRestorationType::ONE_OFF)enabled=false; } } diff --git a/Adventures in Lestoria/Buff.h b/Adventures in Lestoria/Buff.h index bded5aa8..a855c9c3 100644 --- a/Adventures in Lestoria/Buff.h +++ b/Adventures in Lestoria/Buff.h @@ -53,15 +53,16 @@ enum BuffType{ ADRENALINE_RUSH, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack. TRAPPER_MARK, OVER_TIME, - ONE_OFF, + 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. OVER_TIME_DURING_CAST, GLOW_PURPLE, COLOR_MOD, DAMAGE_AMPLIFICATION, //Multiplies all incoming damage by this amount. LETHAL_TEMPO, + BURNING_ARROW_BURN, }; enum class BuffRestorationType{ - ONE_OFF, + 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. OVER_TIME, OVER_TIME_DURING_CAST, }; @@ -83,10 +84,12 @@ struct Buff{ using MonsterBuffExpireCallbackFunction=std::functionattachedTarget,Buff&b)>; BuffType type; + BuffRestorationType restorationType; float duration=1; float timeBetweenTicks=1; float intensity=1; float nextTick=0; + float lifetime{}; std::set attr; std::variant>attachedTarget; //Who has this buff. PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){}; @@ -95,13 +98,16 @@ struct Buff{ 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); + Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); + Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc); + Buff(std::variant>attachedTarget,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc); + Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); + Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,PlayerBuffExpireCallbackFunction expireCallbackFunc); + Buff(std::variant>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,MonsterBuffExpireCallbackFunction expireCallbackFunc); void Update(AiL*game,float fElapsedTime); private: - bool enabled{true}; + bool enabled{true}; //This is only turned off because the ONE_OFF effect. See BuffType::ONE_OFF for more details. std::optionaloverTimeType; void BuffTick(AiL*game,float fElapsedTime); }; \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 4786ddfe..645287aa 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -443,9 +443,14 @@ void Monster::Draw()const{ if(GetBuffs(BuffType::GLOW_PURPLE).size()>0)glowPurpleBuff=GetBuffs(BuffType::GLOW_PURPLE)[0]; - if(GetBuffs(BuffType::DAMAGE_AMPLIFICATION).size()>0)blendCol=Pixel{uint8_t(128+127*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration))),uint8_t(0*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration))),uint8_t(0*abs(sin(1.4*PI*GetBuffs(BuffType::DAMAGE_AMPLIFICATION)[0].duration)))}; - else if(GetBuffs(BuffType::COLOR_MOD).size()>0)blendCol=Pixel{uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).r*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration))),uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).g*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration))),uint8_t(PixelRaw(GetBuffs(BuffType::COLOR_MOD)[0].intensity).b*abs(sin(1.4*GetBuffs(BuffType::COLOR_MOD)[0].duration)))}; - else if(GetBuffs(BuffType::SLOWDOWN).size()>0)blendCol=Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}; + const auto HasBuff=[&](const BuffType buff){return GetBuffs(buff).size()>0;}; + //The lerpCutAmount is how much to divide the initial color by, which is used as the lerp oscillation amount. 0.5 means half the color is always active, and the other half linearly oscillates. 0.1 would mean 90% of the color is normal and 10% of the color oscillates. + const auto GetBuffBlendCol=[&](const BuffType buff,const float oscillationTime_s,const Pixel blendCol,const float lerpCutAmount=0.5f){return Pixel{uint8_t(blendCol.r*(1-lerpCutAmount)+blendCol.r*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration))),uint8_t(blendCol.g*(1-lerpCutAmount)+blendCol.g*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration))),uint8_t(blendCol.b*(1-lerpCutAmount)+blendCol.b*lerpCutAmount*abs(sin(oscillationTime_s*PI*GetBuffs(buff)[0].duration)))};}; + + 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::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)))}; const vf2d hitTimerOffset=vf2d{sin(20*PI*lastHitTimer+randomFrameOffset),0.f}*2.f*GetSizeMult(); @@ -819,6 +824,15 @@ void Monster::AddBuff(BuffType type,float duration,float intensity,std::setattr); void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::MonsterBuffExpireCallbackFunction expireCallback); + 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; //Removes all buffs of a given type. void RemoveBuff(BuffType type); diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 1ba4b553..15f852ef 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -880,7 +880,7 @@ bool Player::Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag dama SoundEffect::PlaySFX("Warrior Block Hit",SoundEffect::CENTERED); }else{ float otherDmgTaken=1-GetDamageReductionFromBuffs(); - float armorDmgTaken=1-GetDamageReductionFromArmor(); + float armorDmgTaken=1-GetDefenseDamageReductionLookup(); lastCombatTime=0; float finalPctDmgTaken=armorDmgTaken*otherDmgTaken; @@ -1107,6 +1107,14 @@ void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType Buff&newBuff{buffList.emplace_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc})}; OnBuffAdd(newBuff); } +void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){ + Buff&newBuff{buffList.emplace_back(Buff{this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks})}; + OnBuffAdd(newBuff); +} +void Player::AddBuff(BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){ + Buff&newBuff{buffList.emplace_back(Buff{this,type,restorationType,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc})}; + OnBuffAdd(newBuff); +} bool Player::OnUpperLevel(){ return upperLevel; @@ -1421,7 +1429,7 @@ const float Player::GetDamageReductionFromBuffs()const{ return std::min(0.75f,dmgReduction); }; -const float Player::GetDamageReductionFromArmor()const{ +const float Player::GetDefenseDamageReductionLookup()const{ float dmgReduction=0; dmgReduction+=Stats::GetDamageReductionPct(GetDefense()); return std::min(0.75f,dmgReduction); @@ -2099,4 +2107,11 @@ void Player::UpdatePlayerOutline(){ void Player::OnBuffAdd(Buff&newBuff){ if(newBuff.type==DAMAGE_REDUCTION)UpdatePlayerOutline(); +} + +void Player::_ForceCastSpell(Ability&ability){ + if(game->TestingModeEnabled()){ + PrepareCast(ability); + CastSpell(ability); + } } \ No newline at end of file diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index 56b079a1..25cb0619 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -132,7 +132,7 @@ public: const int GetAttack()const; const int GetDefense()const; const float GetDamageReductionFromBuffs()const; - const float GetDamageReductionFromArmor()const; + const float GetDefenseDamageReductionLookup()const; float GetMoveSpdMult(); float GetSizeMult()const; const float GetCooldownReductionPct()const; @@ -182,6 +182,8 @@ public: void AddBuff(BuffType type,float duration,float intensity,std::setattr); void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks); void AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc); + 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::PlayerBuffExpireCallbackFunction expireCallbackFunc); const std::vectorGetBuffs(BuffType buff)const; const std::vectorGetStatBuffs(const std::vector&attr)const; @@ -315,6 +317,7 @@ public: Timer&GetTimer(const PlayerTimerType type); void CancelTimer(const PlayerTimerType type); void ResetTimers(); + void _ForceCastSpell(Ability&ability); 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 b8f9ef7d..f6d18cb8 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 10989 +#define VERSION_BUILD 11008 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 714d44c8..8a9a079e 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ