Implemented Evasive Movement enchant. Added a player outline decal on a repeating timer. Release Build 10989.

mac-build
sigonasr2 4 months ago
parent 012f5de2a1
commit 9df7016f2d
  1. 18
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 4
      Adventures in Lestoria/Adventures in Lestoria.vcxproj
  3. 11
      Adventures in Lestoria/Adventures in Lestoria.vcxproj.filters
  4. 3
      Adventures in Lestoria/AdventuresInLestoria.cpp
  5. 56
      Adventures in Lestoria/Player.cpp
  6. 17
      Adventures in Lestoria/Player.h
  7. 44
      Adventures in Lestoria/PlayerTimerType.h
  8. 4
      Adventures in Lestoria/Thief.cpp
  9. 2
      Adventures in Lestoria/Version.h
  10. BIN
      x64/Release/Adventures in Lestoria.exe

@ -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_ptr<Item>nullRing{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.");
}
};
}

@ -582,6 +582,10 @@
<ClInclude Include="olcUTIL_Geometry2D.h" />
<ClInclude Include="Pathfinding.h" />
<ClInclude Include="Player.h" />
<ClInclude Include="PlayerTimerType.h">
<SubType>
</SubType>
</ClInclude>
<ClInclude Include="PopupMenuLabel.h">
<SubType>
</SubType>

@ -231,9 +231,6 @@
<ClInclude Include="MenuAnimatedIconToggleButton.h">
<Filter>Header Files\Interface</Filter>
</ClInclude>
<ClInclude Include="Toggleable.h">
<Filter>Source Files</Filter>
</ClInclude>
<ClInclude Include="ConnectionPoint.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -678,8 +675,14 @@
<ClInclude Include="ItemEnchant.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="PlayerTimerType.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Toggleable.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Timer.h">
<Filter>Source Files</Filter>
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>

@ -1118,11 +1118,14 @@ void AiL::RenderWorld(float fElapsedTime){
const std::vector<Buff>attackBuffs{player->GetStatBuffs({"Attack","Attack %"})};
const std::vector<Buff>movespeedBuffs{player->GetBuffs(BuffType::SPEEDBOOST)};
const std::vector<Buff>adrenalineRushBuffs{player->GetBuffs(BuffType::ADRENALINE_RUSH)};
const std::vector<Buff>damageReductionBuffs{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);

@ -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::set<ItemAttribute>attr){
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::set<std::string>attr){
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();
}
@ -2080,3 +2082,21 @@ 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();
}

@ -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<std::reference_wrapper<Ability>>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_map<TimerType,Timer>timers;
std::unordered_map<PlayerTimerType,Timer>timers;
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;

@ -0,0 +1,44 @@
#pragma region License
/*
License (OLC-3)
~~~~~~~~~~~~~~~
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
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,
};

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

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

Loading…
Cancel
Save