diff --git a/Adventures in Lestoria Tests/EnchantTests.cpp b/Adventures in Lestoria Tests/EnchantTests.cpp index 96e8a2a4..7a10b31e 100644 --- a/Adventures in Lestoria Tests/EnchantTests.cpp +++ b/Adventures in Lestoria Tests/EnchantTests.cpp @@ -682,13 +682,13 @@ namespace EnchantTests Assert::AreEqual(uint8_t(1),Thief::ability2.charges,L"Deadly Dash should be available to use again."); Assert::AreEqual(0.f,Thief::ability2.cooldown,L"Deadly Dash should no longer be on cooldown."); Assert::AreEqual(0,Thief::ability2.manaCost,L"Deadly Dash's mana cost should be zero."); - Assert::AreEqual("Deadly Mirage"_ENC["REACTIVATE TIMING WINDOW"],player->GetTimer(Player::TimerType::DEADLY_MIRAGE_SECOND_CAST).RemainingTime(),L"Deadly Mirage's second cast timer should have started."); + Assert::AreEqual("Deadly Mirage"_ENC["REACTIVATE TIMING WINDOW"],player->GetTimer(PlayerTimerType::DEADLY_MIRAGE_SECOND_CAST).RemainingTime(),L"Deadly Mirage's second cast timer should have started."); player->SetState(State::NORMAL); player->CheckAndPerformAbility(player->GetAbility2(),testKeyboardInput); Assert::AreEqual(uint8_t(0),Thief::ability2.charges,L"Deadly Dash should have been used up."); Assert::AreEqual("Thief.Ability 2.Cooldown"_F,Thief::ability2.cooldown,L"Deadly Dash should now be on cooldown"); Assert::AreEqual("Thief.Ability 2.Mana Cost"_I,Thief::ability2.manaCost,L"Deadly Dash's mana cost is back to normal."); - Assert::AreEqual(true,player->GetTimer(Player::TimerType::DEADLY_MIRAGE_SECOND_CAST).IsDone(),L"Deadly Mirage's second cast timer should have been cancelled."); + Assert::AreEqual(true,player->GetTimer(PlayerTimerType::DEADLY_MIRAGE_SECOND_CAST).IsDone(),L"Deadly Mirage's second cast timer should have been cancelled."); Thief::ability2.charges=1; Thief::ability2.cooldown=0.f; player->RestoreMana(100); @@ -750,5 +750,19 @@ namespace EnchantTests Assert::AreEqual(110,player->GetAttack(),L"Bloodlust's bonus attacks stack only to 10."); Assert::AreEqual("Thief.Ability 3.Duration"_F+"Bloodlust"_ENC["BUFF TIMER INCREASE"]*30,player->GetBuffs(BuffType::ADRENALINE_RUSH)[0].duration,L"Bloodlust's duration increases per kill."); } + TEST_METHOD(EvasiveMovementCheck){ + testKey->bHeld=true; //Force the key to be held down for testing purposes. + game->ChangePlayerClass(THIEF); + player=game->GetPlayer(); + player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput); + Assert::AreEqual(size_t(0),player->GetBuffs(BuffType::DAMAGE_REDUCTION).size(),L"Roll doesn't give a damage reduction buff."); + player->GetRightClickAbility().charges=1; + std::weak_ptrnullRing{Inventory::AddItem("Null Ring"s)}; + Inventory::EquipItem(nullRing,EquipSlot::RING1); + nullRing.lock()->EnchantItem("Evasive Movement"); + player->CheckAndPerformAbility(player->GetRightClickAbility(),testKeyboardInput); + 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."); + } }; } \ No newline at end of file diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj b/Adventures in Lestoria/Adventures in Lestoria.vcxproj index 585e7a89..04552b80 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj @@ -582,6 +582,10 @@ + + + + diff --git a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters index fa9c24b4..29eb6ab4 100644 --- a/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters +++ b/Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters @@ -231,9 +231,6 @@ Header Files\Interface - - Source Files - Header Files @@ -678,8 +675,14 @@ Header Files + + Header Files + + + Header Files + - Source Files + Header Files diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index e55f7bbf..3fbb3452 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -1118,11 +1118,14 @@ void AiL::RenderWorld(float fElapsedTime){ const std::vectorattackBuffs{player->GetStatBuffs({"Attack","Attack %"})}; const std::vectormovespeedBuffs{player->GetBuffs(BuffType::SPEEDBOOST)}; const std::vectoradrenalineRushBuffs{player->GetBuffs(BuffType::ADRENALINE_RUSH)}; + const std::vectordamageReductionBuffs{player->GetBuffs(BuffType::DAMAGE_REDUCTION)}; Pixel playerCol{WHITE}; if(attackBuffs.size()>0)playerCol={255,uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration))),uint8_t(255*abs(sin(1.4f*attackBuffs[0].duration)))}; else if(adrenalineRushBuffs.size()>0)playerCol={uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration))),255,uint8_t(255*abs(sin(6.f*adrenalineRushBuffs[0].duration)))}; else if(movespeedBuffs.size()>0)playerCol={uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration))),255,uint8_t(255*abs(sin(2.f*movespeedBuffs[0].duration)))}; + + if(damageReductionBuffs.size()>0)view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->playerOutline.Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale+0.1f,{1.f,player->ySquishFactor},{210,210,210,uint8_t(util::lerp(0,255,abs(sin((PI*GetRunTime())/1.25f))))}); view.DrawPartialSquishedRotatedDecal(pos+vf2d{0,-player->GetZ()*(std::signbit(scale.y)?-1:1)},player->GetFrame().GetSourceImage()->Decal(),player->GetSpinAngle(),{12,12},player->GetFrame().GetSourceRect().pos,player->GetFrame().GetSourceRect().size,playerScale*scale,{1.f,player->ySquishFactor},playerCol); DrawAfterImage:view.DrawRotatedDecal(player->afterImagePos,player->afterImage.Decal(),0.f,player->afterImage.Sprite()->Size()/2,{player->GetSizeMult(),player->GetSizeMult()},{0xFFDCDA}); SetDecalMode(DecalMode::NORMAL); diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 8367e72f..1ba4b553 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -120,6 +120,9 @@ void Player::Initialize(){ p.a=0; } afterImage.Decal()->Update(); + playerOutline.Create(24,24); + + AddTimer(PlayerTimerType::PLAYER_OUTLINE_TIMER,Timer{"Player Outline Update Timer",0.05f,[&](){UpdatePlayerOutline();},Timer::REPEAT}); } void Player::InitializeMinimapImage(){ @@ -1083,21 +1086,26 @@ void Player::UpdateIdleAnimation(Key direction){ } void Player::AddBuff(BuffType type,float duration,float intensity){ - buffList.push_back(Buff{this,type,duration,intensity}); + Buff&newBuff{buffList.emplace_back(Buff{this,type,duration,intensity})}; + OnBuffAdd(newBuff); } void Player::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 %"&&attr.ActualName()!="Defense"&&attr.ActualName()!="Defense %"&&attr.ActualName()!="CDR"&&attr.ActualName()!="Move Spd %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr.ActualName()));}); - buffList.push_back(Buff{this,type,duration,intensity,attr}); + Buff&newBuff{buffList.emplace_back(Buff{this,type,duration,intensity,attr})}; + OnBuffAdd(newBuff); } void Player::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 %"&&attr!="Defense"&&attr!="Defense %"&&attr!="CDR"&&attr!="Move Spd %")ERR(std::format("WARNING! Stat Up Attribute type {} is NOT IMPLEMENTED!",attr));}); - buffList.push_back(Buff{this,type,duration,intensity,attr}); + Buff&newBuff{buffList.emplace_back(Buff{this,type,duration,intensity,attr})}; + OnBuffAdd(newBuff); } void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks){ - buffList.push_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks}); + Buff&newBuff{buffList.emplace_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks})}; + OnBuffAdd(newBuff); } void Player::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks,Buff::PlayerBuffExpireCallbackFunction expireCallbackFunc){ - buffList.push_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc}); + Buff&newBuff{buffList.emplace_back(Buff{this,type,overTimeType,duration,intensity,timeBetweenTicks,expireCallbackFunc})}; + OnBuffAdd(newBuff); } bool Player::OnUpperLevel(){ @@ -1408,7 +1416,8 @@ const float Player::GetDamageReductionFromBuffs()const{ for(const Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){ dmgReduction+=b.intensity; } - dmgReduction+=GetDamageReductionPct(); + dmgReduction+=GetEquipStat("Damage Reduction")/100.f; + if(LastReserveEnchantConditionsMet())dmgReduction+="Last Reserve"_ENC["DAMAGE REDUCTION PCT"]/100.f; return std::min(0.75f,dmgReduction); }; @@ -1575,13 +1584,6 @@ void Player::PerformHPRecovery(){ } } -const float Player::GetDamageReductionPct()const{ - float modDmgReductionPct=0; - modDmgReductionPct+=GetEquipStat("Damage Reduction")/100.f; - if(LastReserveEnchantConditionsMet())modDmgReductionPct+="Last Reserve"_ENC["DAMAGE REDUCTION PCT"]/100.f; - return modDmgReductionPct; -} - void Player::AddXP(const uint64_t xpGain){ currentLevelXP+=xpGain; totalXPEarned+=xpGain; @@ -1982,7 +1984,7 @@ void Player::OnAbilityUse(const Ability&ability){ Thief::ability2.charges++; Thief::ability2.manaCost=0; Thief::ability2.cooldown=0.f; - AddTimer(TimerType::DEADLY_MIRAGE_SECOND_CAST,Timer{"Deadly Mirage Second Cast","Deadly Mirage"_ENC["REACTIVATE TIMING WINDOW"],[](){ + AddTimer(PlayerTimerType::DEADLY_MIRAGE_SECOND_CAST,Timer{"Deadly Mirage Second Cast","Deadly Mirage"_ENC["REACTIVATE TIMING WINDOW"],[](){ Thief::ability2.manaCost="Thief.Ability 2.Mana Cost"_I; Thief::ability2.cooldown="Thief.Ability 2.Cooldown"_F; Thief::ability2.charges--; @@ -1991,7 +1993,7 @@ void Player::OnAbilityUse(const Ability&ability){ if(!IsFirstDash){ Thief::ability2.manaCost="Thief.Ability 2.Mana Cost"_I; Thief::ability2.cooldown="Thief.Ability 2.Cooldown"_F; - CancelTimer(TimerType::DEADLY_MIRAGE_SECOND_CAST); + CancelTimer(PlayerTimerType::DEADLY_MIRAGE_SECOND_CAST); } } } @@ -2056,18 +2058,18 @@ const bool Player::HasEnchantWithAbilityAffected(const std::string_view abilityN return enchantAbilityList.count(std::string(abilityName)); } -Timer&Player::AddTimer(const TimerType type,const Timer&timer){ +Timer&Player::AddTimer(const PlayerTimerType type,const Timer&timer){ auto timerReturnResult{timers.insert({type,timer})}; Timer&newTimer{(*(timerReturnResult.first)).second}; newTimer=timer; return newTimer; } -Timer&Player::GetTimer(const TimerType type){ +Timer&Player::GetTimer(const PlayerTimerType type){ return timers.at(type); } -void Player::CancelTimer(const TimerType type){ +void Player::CancelTimer(const PlayerTimerType type){ GetTimer(type).Cancel(); } @@ -2079,4 +2081,22 @@ void Player::RunTimers(){ void Player::ResetTimers(){ timers.clear(); +} + +void Player::UpdatePlayerOutline(){ + if(GetBuffs(BuffType::DAMAGE_REDUCTION).size()>0){ + game->SetDrawTarget(playerOutline.Sprite()); + game->Clear(BLANK); + for(int32_t y:std::ranges::iota_view(0,game->GetDrawTargetHeight())){ + for(int32_t x:std::ranges::iota_view(0,game->GetDrawTargetWidth())){ + if(animation.GetFrame(internal_animState).GetSourceImage()->Sprite()->GetPixel(x,y)!=BLANK)game->GetDrawTarget()->SetPixel(x,y,WHITE); + } + } + playerOutline.Decal()->Update(); + game->SetDrawTarget(nullptr); + } +} + +void Player::OnBuffAdd(Buff&newBuff){ + if(newBuff.type==DAMAGE_REDUCTION)UpdatePlayerOutline(); } \ No newline at end of file diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index 28b6e7d2..56b079a1 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -49,6 +49,7 @@ All rights reserved. #include "Item.h" #include "AttributableStat.h" #include "Timer.h" +#include "PlayerTimerType.h" #undef GetClassName @@ -107,10 +108,6 @@ class Player{ friend class ItemInfo; friend void ItemOverlay::Draw(); public: - enum class TimerType{ - DEADLY_MIRAGE_SECOND_CAST, - ADRENALINE_STIM, - }; Player(); //So this is rather fascinating and only exists because we have the ability to change classes which means we need to initialize a class @@ -144,7 +141,6 @@ public: const float GetHPRecoveryPct()const; const float GetHP6RecoveryPct()const; const float GetHP4RecoveryPct()const; - const float GetDamageReductionPct()const; const float GetAttackRecoveryRateReduction()const; void SetSizeMult(float size); float GetAttackRange(); //Returns the player's attack range in units. Divide by 100 for number of tiles. @@ -315,9 +311,9 @@ public: const int RemainingRapidFireShots()const; const std::vector>GetAbilities();//Returns player defensive, core abilities (1-4) and item abilities. const bool HasEnchantWithAbilityAffected(const std::string_view abilityName)const; //Returns whether or not the player has an enchant that affects the provided name. - Timer&AddTimer(const TimerType type,const Timer&timer); - Timer&GetTimer(const TimerType type); - void CancelTimer(const TimerType type); + Timer&AddTimer(const PlayerTimerType type,const Timer&timer); + Timer&GetTimer(const PlayerTimerType type); + void CancelTimer(const PlayerTimerType type); void ResetTimers(); private: int hp="Warrior.BaseHealth"_I; @@ -401,9 +397,12 @@ private: const bool LastReserveEnchantConditionsMet()const; float poisonArrowLastParticleTimer{}; const vf2d GetAimingLocation(bool useWalkDir=false,bool invert=false); - std::unordered_maptimers; + std::unordered_maptimers; void RunTimers(); float base_attack_range="Warrior.Auto Attack.Range"_F/100.f; + Renderable playerOutline; + void UpdatePlayerOutline(); + void OnBuffAdd(Buff&newBuff); protected: const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F; const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F; diff --git a/Adventures in Lestoria/PlayerTimerType.h b/Adventures in Lestoria/PlayerTimerType.h new file mode 100644 index 00000000..34718fd2 --- /dev/null +++ b/Adventures in Lestoria/PlayerTimerType.h @@ -0,0 +1,44 @@ +#pragma region License +/* +License (OLC-3) +~~~~~~~~~~~~~~~ + +Copyright 2024 Joshua Sigona + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions or derivations of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions or derivative works in binary form must reproduce the above +copyright notice. This list of conditions and the following disclaimer must be +reproduced in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may +be used to endorse or promote products derived from this software without specific +prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +Portions of this software are copyright © 2024 The FreeType +Project (www.freetype.org). Please see LICENSE_FT.txt for more information. +All rights reserved. +*/ +#pragma endregion +#pragma once + +enum class PlayerTimerType{ + DEADLY_MIRAGE_SECOND_CAST, + ADRENALINE_STIM, + PLAYER_OUTLINE_TIMER, +}; \ No newline at end of file diff --git a/Adventures in Lestoria/Thief.cpp b/Adventures in Lestoria/Thief.cpp index 65838c1f..541817c0 100644 --- a/Adventures in Lestoria/Thief.cpp +++ b/Adventures in Lestoria/Thief.cpp @@ -128,6 +128,8 @@ void Thief::InitializeClassAbilities(){ float iframeTime{originalIframeTime}; if(p->HasEnchant("Tumble"))iframeTime+=originalIframeTime*"Tumble"_ENC["BOOST PERCENTAGE"]/100.f; + if(p->HasEnchant("Evasive Movement"))p->AddBuff(BuffType::DAMAGE_REDUCTION,"Evasive Movement"_ENC["DAMAGE REDUCTION DURATION"],"Evasive Movement"_ENC["DAMAGE REDUCTION PCT"]/100.f); + p->ApplyIframes(iframeTime); p->footstepTimer=0.f; return true; @@ -175,7 +177,7 @@ void Thief::InitializeClassAbilities(){ if(p->HasEnchant("Adrenaline Stim"))adrenalineRushDuration="Adrenaline Stim"_ENC["NEW ADRENALINE RUSH DURATION"]; p->AddBuff(BuffType::ADRENALINE_RUSH,adrenalineRushDuration,0.f); - p->AddTimer(TimerType::ADRENALINE_STIM,Timer{"Adrenaline Stim Extra Sound Timer",0.3f,[](){SoundEffect::PlaySFX("Adrenaline Rush High Pitch",SoundEffect::CENTERED);},Timer::MANUAL}); + p->AddTimer(PlayerTimerType::ADRENALINE_STIM,Timer{"Adrenaline Stim Extra Sound Timer",0.3f,[](){SoundEffect::PlaySFX("Adrenaline Rush High Pitch",SoundEffect::CENTERED);},Timer::MANUAL}); int healthCost{}; if(p->HasEnchant("Adrenaline Stim"))healthCost=p->GetMaxHealth()*("Adrenaline Stim"_ENC["HEALTH PCT COST"]/100.f); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 19c904a8..b8f9ef7d 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 10981 +#define VERSION_BUILD 10989 #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 3467e1da..714d44c8 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ