Move the player outline repeating timer into a new OnLevelStart() callback for the player. Fixes a bug where the player outline will no longer update after the initial stage. Added more player timer helper functions for better control. Release Build 11031.

mac-build
sigonasr2 7 months ago
parent f717abfa54
commit 45e6027c9e
  1. 20
      Adventures in Lestoria Tests/EnchantTests.cpp
  2. 34
      Adventures in Lestoria/AdventuresInLestoria.cpp
  3. 3
      Adventures in Lestoria/AdventuresInLestoria.h
  4. 6
      Adventures in Lestoria/Animation.cpp
  5. 8
      Adventures in Lestoria/Buff.cpp
  6. 2
      Adventures in Lestoria/Buff.h
  7. 86
      Adventures in Lestoria/Monster.cpp
  8. 5
      Adventures in Lestoria/Monster.h
  9. 21
      Adventures in Lestoria/Player.cpp
  10. 2
      Adventures in Lestoria/Player.h
  11. 1
      Adventures in Lestoria/PlayerTimerType.h
  12. 2
      Adventures in Lestoria/Version.h
  13. 6
      Adventures in Lestoria/assets/config/audio/events.txt
  14. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  15. 2
      Adventures in Lestoria/assets/config/items/ItemEnchants.txt
  16. BIN
      Adventures in Lestoria/assets/gamepack.pak
  17. BIN
      Adventures in Lestoria/assets/sounds/lockon_special.ogg
  18. BIN
      Adventures in Lestoria/assets/special_target.png
  19. BIN
      x64/Release/Adventures in Lestoria.exe

@ -949,5 +949,25 @@ namespace EnchantTests
trap.MonsterHit(newMonster,1); //Simulate 1 mark stack on the monster to trigger the bleed. trap.MonsterHit(newMonster,1); //Simulate 1 mark stack on the monster to trigger the bleed.
Assert::AreEqual("Trapper.Ability 2.Marked Target Bleed"_f[1]+"Lingering Scent"_ENC["BLEED EXTRA DURATION"],newMonster.GetBuffs(BuffType::OVER_TIME)[0].duration,L"Bleed duration should be 10 seconds by default."); Assert::AreEqual("Trapper.Ability 2.Marked Target Bleed"_f[1]+"Lingering Scent"_ENC["BLEED EXTRA DURATION"],newMonster.GetBuffs(BuffType::OVER_TIME)[0].duration,L"Bleed duration should be 10 seconds by default.");
} }
TEST_METHOD(SpecialMarkNoEnchantCheck){
game->ChangePlayerClass(TRAPPER);
player=game->GetPlayer();
Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
for(int i:std::ranges::iota_view(0,10)){
game->SetElapsedTime(1.f);
game->OnUserUpdate(1.f);
}
Assert::AreEqual(size_t(0),newMonster.GetBuffs(BuffType::SPECIAL_MARK).size(),L"A special mark should not be spawned without the enchant.");
}
TEST_METHOD(SpecialMarkEnchantCheck){
game->ChangePlayerClass(TRAPPER);
player=game->GetPlayer();
Monster&newMonster{game->SpawnMonster({},MONSTER_DATA["TestName"])};
for(int i:std::ranges::iota_view(0,10)){
game->SetElapsedTime(1.f);
game->OnUserUpdate(1.f);
}
Assert::AreEqual(size_t(1),newMonster.GetBuffs(BuffType::SPECIAL_MARK).size(),L"A special mark should spawned with the enchant.");
}
}; };
} }

@ -2403,6 +2403,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
backgroundEffects.clear(); backgroundEffects.clear();
foregroundEffects.clear(); foregroundEffects.clear();
lockOnTargets.clear(); lockOnTargets.clear();
lockOnSpecialTargets.clear();
ItemDrop::drops.clear(); ItemDrop::drops.clear();
GameEvent::events.clear(); GameEvent::events.clear();
Audio::SetBGMPitch(1.f); Audio::SetBGMPitch(1.f);
@ -2795,6 +2796,8 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){
SteamUserStats()->StoreStats(); SteamUserStats()->StoreStats();
) )
ClearGarbage(); ClearGarbage();
GetPlayer()->OnLevelStart();
return true; return true;
}); });
} }
@ -4414,6 +4417,13 @@ void AiL::GlobalGameUpdates(){
} }
#pragma endregion #pragma endregion
auto EnfeebledTargetEffect = [](const MarkTime&time,const std::weak_ptr<Monster>&monster){
if(game->GetPlayer()->HasEnchant("Enfeebled Target")){
monster.lock()->AddBuff(BuffType::SLOWDOWN,time,"Enfeebled Target"_ENC["SLOW PCT"]/100.f);
monster.lock()->AddBuff(BuffType::STAT_UP,time,-"Enfeebled Target"_ENC["DAMAGE REDUCTION PCT"]/100.f,{"Attack %"});
}
};
#pragma region Marked Targets Update #pragma region Marked Targets Update
if(lockOnTargets.size()>0){ if(lockOnTargets.size()>0){
lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime()); lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime());
@ -4421,10 +4431,7 @@ void AiL::GlobalGameUpdates(){
const auto&[monster,stackCount,time]=lockOnTargets.front(); const auto&[monster,stackCount,time]=lockOnTargets.front();
if(!monster.expired()){ if(!monster.expired()){
monster.lock()->AddBuff(BuffType::TRAPPER_MARK,time,stackCount); monster.lock()->AddBuff(BuffType::TRAPPER_MARK,time,stackCount);
if(game->GetPlayer()->HasEnchant("Enfeebled Target")){ EnfeebledTargetEffect(time,monster);
monster.lock()->AddBuff(BuffType::SLOWDOWN,time,"Enfeebled Target"_ENC["SLOW PCT"]/100.f);
monster.lock()->AddBuff(BuffType::STAT_UP,time,-"Enfeebled Target"_ENC["DAMAGE REDUCTION PCT"]/100.f,{"Attack %"});
}
SoundEffect::PlaySFX("Lock On",monster.lock()->GetPos()); SoundEffect::PlaySFX("Lock On",monster.lock()->GetPos());
lastLockOnTargetTime=0.2f; lastLockOnTargetTime=0.2f;
} }
@ -4432,6 +4439,21 @@ void AiL::GlobalGameUpdates(){
} }
} }
#pragma endregion #pragma endregion
#pragma region Special Marked Targets Update
if(lockOnSpecialTargets.size()>0){
lastLockOnSpecialTargetTime=std::max(0.f,lastLockOnSpecialTargetTime-GetElapsedTime());
if(lastLockOnSpecialTargetTime<=0.f){
const auto&[monster,stackCount,time]=lockOnSpecialTargets.front();
if(!monster.expired()){
monster.lock()->AddBuff(BuffType::SPECIAL_MARK,time,stackCount);
EnfeebledTargetEffect(time,monster);
SoundEffect::PlaySFX("Lock On Special",monster.lock()->GetPos());
lastLockOnSpecialTargetTime=0.2f;
}
lockOnSpecialTargets.erase(lockOnSpecialTargets.begin());
}
}
#pragma endregion
if(GetMousePos()!=lastMousePos){ if(GetMousePos()!=lastMousePos){
lastMouseMovement=0.f; lastMouseMovement=0.f;
@ -4565,6 +4587,10 @@ void AiL::AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,Mar
lockOnTargets.emplace_back(markData); lockOnTargets.emplace_back(markData);
} }
void AiL::AddToSpecialMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData){
lockOnSpecialTargets.emplace_back(markData);
}
void AiL::_SetCurrentLevel(const MapName map){ void AiL::_SetCurrentLevel(const MapName map){
currentLevel=map; currentLevel=map;
} }

@ -228,7 +228,9 @@ private:
float targetZoom{1.f}; float targetZoom{1.f};
float zoomAdjustSpeed{0.1f}; float zoomAdjustSpeed{0.1f};
std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnTargets; std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnTargets;
std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnSpecialTargets;
float lastLockOnTargetTime{}; float lastLockOnTargetTime{};
float lastLockOnSpecialTargetTime{};
void AdminConsole(); void AdminConsole();
virtual bool OnConsoleCommand(const std::string& sCommand)override final; virtual bool OnConsoleCommand(const std::string& sCommand)override final;
Pixel vignetteOverlayCol{"Interface.Vignette Color"_Pixel}; Pixel vignetteOverlayCol{"Interface.Vignette Color"_Pixel};
@ -387,6 +389,7 @@ public:
void PlayFootstepSound(); void PlayFootstepSound();
const std::map<std::string,TilesetData>&GetTilesets()const; const std::map<std::string,TilesetData>&GetTilesets()const;
void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData); void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
void AddToSpecialMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
void InitializeClasses(); void InitializeClasses();
void _SetCurrentLevel(const MapName map); //NOTE: This will modify the currentLevel variable without triggering anything else in-game, this will normally mess up the state in the game. Ideally this is only used when initializing a test level. void _SetCurrentLevel(const MapName map); //NOTE: This will modify the currentLevel variable without triggering anything else in-game, this will normally mess up the state in the game. Ideally this is only used when initializing a test level.

@ -419,6 +419,12 @@ void sig::Animation::InitializeAnimations(){
targetAnim.AddFrame({&GFX["target.png"],{{int(0*24),0},{24,24}}}); targetAnim.AddFrame({&GFX["target.png"],{{int(0*24),0},{24,24}}});
targetAnim.AddFrame({&GFX["target.png"],{{int(1*24),0},{24,24}}}); targetAnim.AddFrame({&GFX["target.png"],{{int(1*24),0},{24,24}}});
ANIMATION_DATA["target.png"]=targetAnim; ANIMATION_DATA["target.png"]=targetAnim;
AnimationData specialTargetAnimData{.frameDuration{0.1f},.style{Animate2D::Style::Repeat}};
Animate2D::FrameSequence specialTargetAnim(specialTargetAnimData.frameDuration,specialTargetAnimData.style);
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(0*24),0},{24,24}}});
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(0*24),0},{24,24}}});
specialTargetAnim.AddFrame({&GFX["special_target.png"],{{int(1*24),0},{24,24}}});
ANIMATION_DATA["special_target.png"]=specialTargetAnim;
#pragma endregion #pragma endregion
CreateHorizontalAnimationSequence("bear_trap.png",3,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::PingPong}}); CreateHorizontalAnimationSequence("bear_trap.png",3,{24,24},AnimationData{.frameDuration{0.1f},.style{Animate2D::Style::PingPong}});

@ -41,11 +41,11 @@ All rights reserved.
#include "Monster.h" #include "Monster.h"
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity) Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){} :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr) Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr){} :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),attr(attr),originalDuration(this->duration){}
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr) Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<std::string> attr)
:attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity){ :attachedTarget(attachedTarget),type(type),duration(duration),intensity(intensity),originalDuration(this->duration){
for(const std::string&s:attr){ for(const std::string&s:attr){
this->attr.insert(ItemAttribute::attributes.at(s)); this->attr.insert(ItemAttribute::attributes.at(s));
} }
@ -109,7 +109,7 @@ Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType t
} }
Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,BuffRestorationType restorationType,BuffOverTimeType::BuffOverTimeType overTimeType,float duration,float intensity,float timeBetweenTicks) Buff::Buff(std::variant<Player*,std::weak_ptr<Monster>>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){} :attachedTarget(attachedTarget),type(type),restorationType(restorationType),duration(duration),intensity(intensity),nextTick(timeBetweenTicks),timeBetweenTicks(timeBetweenTicks),overTimeType(overTimeType),originalDuration(this->duration){}
void Buff::Update(AiL*game,float fElapsedTime){ void Buff::Update(AiL*game,float fElapsedTime){
duration-=fElapsedTime; duration-=fElapsedTime;

@ -52,6 +52,7 @@ enum BuffType{
SELF_INFLICTED_SLOWDOWN, //Used for monsters and can't be applied by any player abilities. SELF_INFLICTED_SLOWDOWN, //Used for monsters and can't be applied by any player abilities.
ADRENALINE_RUSH, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack. ADRENALINE_RUSH, //Intensity indicates the stack count (used by the Bloodlust enchant) this buff gives which in turn increases attack.
TRAPPER_MARK, TRAPPER_MARK,
SPECIAL_MARK, //The mark applied by the Opportunity Shot.
OVER_TIME, OVER_TIME,
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. 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, OVER_TIME_DURING_CAST,
@ -94,6 +95,7 @@ struct Buff{
std::variant<Player*,std::weak_ptr<Monster>>attachedTarget; //Who has this buff. std::variant<Player*,std::weak_ptr<Monster>>attachedTarget; //Who has this buff.
PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){}; PlayerBuffExpireCallbackFunction playerBuffCallbackFunc=[](Player*p,Buff&b){};
MonsterBuffExpireCallbackFunction monsterBuffCallbackFunc=[](std::weak_ptr<Monster>m,Buff&b){}; MonsterBuffExpireCallbackFunction monsterBuffCallbackFunc=[](std::weak_ptr<Monster>m,Buff&b){};
float originalDuration{};
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity); Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity);
Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr); Buff(std::variant<Player*,std::weak_ptr<Monster>>attachedTarget,BuffType type,float duration,float intensity,std::set<ItemAttribute> attr);

@ -525,6 +525,32 @@ void Monster::Draw()const{
game->view.DrawPartialRotatedDecal(drawPos,markImg.GetSourceImage()->Decal(),markRotation,markImg.GetSourceRect().size/2.f,markImg.GetSourceRect().pos,markImg.GetSourceRect().size,markScale,markCol); game->view.DrawPartialRotatedDecal(drawPos,markImg.GetSourceImage()->Decal(),markRotation,markImg.GetSourceRect().size/2.f,markImg.GetSourceRect().pos,markImg.GetSourceRect().size,markScale,markCol);
} }
#pragma endregion #pragma endregion
#pragma region Render Trapper Special Marked Targets
const uint8_t specialMarkStackCount{GetSpecialMarkStacks()};
float specialMarkOriginalDuration{};
if(specialMarkStackCount>0){
const std::vector<Buff>&buffList{GetBuffs(BuffType::SPECIAL_MARK)};
float remainingStackDuration{};
for(const Buff&b:buffList){
if(b.type==BuffType::SPECIAL_MARK){
remainingStackDuration=b.duration;
specialMarkOriginalDuration=b.originalDuration;
break;
}
}
const bool OpportunityShotActive{game->GetPlayer()->HasEnchant("Opportunity Shot")&&specialMarkOriginalDuration-remainingStackDuration<="Opportunity Shot"_ENC["MARK TRIGGER TIME"]};
float markRotation{-util::lerp(0.f,10.f,specialMarkApplicationTimer/0.5f)*sin(PI*specialMarkApplicationTimer)};
vf2d markScale{vf2d{}.lerp(vf2d{GetSizeMult(),GetSizeMult()},(0.5f-specialMarkApplicationTimer)/0.5f)};
Animate2D::Frame markImg={ANIMATION_DATA["target.png"].GetFrame(game->GetRunTime())};
if(OpportunityShotActive)markImg={ANIMATION_DATA["special_target.png"].GetFrame(game->GetRunTime())};
Pixel markCol{WHITE};
if(remainingStackDuration<1.f)markCol.a*=remainingStackDuration;
game->view.DrawPartialRotatedDecal(drawPos,markImg.GetSourceImage()->Decal(),markRotation,markImg.GetSourceRect().size/2.f,markImg.GetSourceRect().pos,markImg.GetSourceRect().size,markScale,markCol);
}
#pragma endregion
if(GameSettings::TerrainCollisionBoxesEnabled()&&IsSolid()&&solidFadeTimer>0.f){ if(GameSettings::TerrainCollisionBoxesEnabled()&&IsSolid()&&solidFadeTimer>0.f){
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),GetPos()).length(); float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),GetPos()).length();
@ -694,8 +720,18 @@ bool Monster::_Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag da
} }
if(HitByPlayerAbility){ if(HitByPlayerAbility){
if(GetMarkStacks()>0){ auto ApplyMarkEffects=[&](){
Hurt(game->GetPlayer()->GetAttack()*"Trapper.Ability 1.Damage Increase Bonus"_F/100.f,OnUpperLevel(),GetZ(),HurtFlag::DOT); Hurt(game->GetPlayer()->GetAttack()*"Trapper.Ability 1.Damage Increase Bonus"_F/100.f,OnUpperLevel(),GetZ(),HurtFlag::DOT);
};
if(GetSpecialMarkStacks()>0){
ApplyMarkEffects();
const Buff specialMarkBuff{GetBuffs(BuffType::SPECIAL_MARK)[0]};
if(game->GetPlayer()->HasEnchant("Opportunity Shot")&&specialMarkBuff.originalDuration-specialMarkBuff.duration<="Opportunity Shot"_ENC["MARK TRIGGER TIME"])Trapper::ability1.cooldown-="Opportunity Shot"_ENC["MARK COOLDOWN REDUCE"];
RemoveSpecialMarkStack();
}else
if(GetMarkStacks()>0){
ApplyMarkEffects();
RemoveMarkStack(); RemoveMarkStack();
} }
if(game->GetPlayer()->HasEnchant("Lethal Tempo")){ if(game->GetPlayer()->HasEnchant("Lethal Tempo")){
@ -1310,6 +1346,15 @@ const uint8_t Monster::GetMarkStacks()const{
return stackCount; return stackCount;
} }
const uint8_t Monster::GetSpecialMarkStacks()const{
const std::vector<Buff>&markBuffs{GetBuffs(BuffType::SPECIAL_MARK)};
int stackCount{};
for(const Buff&b:markBuffs){
stackCount+=b.intensity;
}
return stackCount;
}
void Monster::RemoveMarkStack(){ void Monster::RemoveMarkStack(){
if(!IsAlive())return; if(!IsAlive())return;
if(GetMarkStacks()<=0)ERR("WARNING! Tried to remove a mark stack, but no stacks exist. THIS SHOULD NOT BE HAPPENING!"); if(GetMarkStacks()<=0)ERR("WARNING! Tried to remove a mark stack, but no stacks exist. THIS SHOULD NOT BE HAPPENING!");
@ -1324,13 +1369,31 @@ void Monster::RemoveMarkStack(){
if(removeMarkDebuff)RemoveBuff(BuffType::TRAPPER_MARK); if(removeMarkDebuff)RemoveBuff(BuffType::TRAPPER_MARK);
} }
void Monster::RemoveSpecialMarkStack(){
if(!IsAlive())return;
if(GetSpecialMarkStacks()<=0)ERR("WARNING! Tried to remove a mark stack, but no stacks exist. THIS SHOULD NOT BE HAPPENING!");
bool removeMarkDebuff{false};
for(Buff&b:buffList){
if(b.type==BuffType::SPECIAL_MARK&&b.intensity>0){
b.intensity--;
if(b.intensity==0)removeMarkDebuff=true;
break;
}
}
if(removeMarkDebuff)RemoveBuff(BuffType::SPECIAL_MARK);
}
void Monster::TriggerMark(){ void Monster::TriggerMark(){
Hurt(0,OnUpperLevel(),GetZ(),HurtFlag::PLAYER_ABILITY); Hurt(0,OnUpperLevel(),GetZ(),HurtFlag::PLAYER_ABILITY);
} }
void Monster::LongLastingMarkEffect(float&out_markDuration){
if(game->GetPlayer()->HasEnchant("Long-Lasting Mark"))out_markDuration="Long-Lasting Mark"_ENC["DURATION"];
}
void Monster::ApplyMark(float time,uint8_t stackCount){ void Monster::ApplyMark(float time,uint8_t stackCount){
float markDuration{time}; float markDuration{time};
if(game->GetPlayer()->HasEnchant("Long-Lasting Mark"))markDuration="Long-Lasting Mark"_ENC["DURATION"]; LongLastingMarkEffect(markDuration);
if(GetMarkStacks()>0){ if(GetMarkStacks()>0){
for(Buff&b:buffList){ for(Buff&b:buffList){
@ -1347,6 +1410,25 @@ void Monster::ApplyMark(float time,uint8_t stackCount){
markApplicationTimer=0.5f; markApplicationTimer=0.5f;
} }
void Monster::ApplySpecialMark(float time,uint8_t stackCount){
float markDuration{time};
LongLastingMarkEffect(markDuration);
if(GetSpecialMarkStacks()>0){
for(Buff&b:buffList){
if(b.type==BuffType::SPECIAL_MARK){
b.intensity+=stackCount;
b.duration=std::max(b.duration,markDuration);
break;
}
}
}else{
game->AddToSpecialMarkedTargetList({GetWeakPointer(),stackCount,markDuration});
}
markApplicationTimer=0.5f;
}
std::optional<std::weak_ptr<Monster>>Monster::GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z){ std::optional<std::weak_ptr<Monster>>Monster::GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z){
std::optional<std::weak_ptr<Monster>>closestMonster; std::optional<std::weak_ptr<Monster>>closestMonster;
std::optional<std::weak_ptr<Monster>>closestGenericMonster; std::optional<std::weak_ptr<Monster>>closestGenericMonster;

@ -204,8 +204,10 @@ public:
//The collision rectangle is only used currently for determining the safe spots for the stone golem boss fight. //The collision rectangle is only used currently for determining the safe spots for the stone golem boss fight.
const std::optional<geom2d::rect<float>>&GetRectangleCollision()const; const std::optional<geom2d::rect<float>>&GetRectangleCollision()const;
const uint8_t GetMarkStacks()const; //Number of Trapper marks on this target. const uint8_t GetMarkStacks()const; //Number of Trapper marks on this target.
const uint8_t GetSpecialMarkStacks()const; //Number of Trapper marks on this target.
void TriggerMark(); //Deals no damage, but causes a mark proc to occur. void TriggerMark(); //Deals no damage, but causes a mark proc to occur.
void ApplyMark(float time,uint8_t stackCount); //Adds stackCount mark stacks to the target, refreshing the buff to time time. void ApplyMark(float time,uint8_t stackCount); //Adds stackCount mark stacks to the target, refreshing the buff to time time.
void ApplySpecialMark(float time,uint8_t stackCount); //Adds stackCount special mark stacks to the target, refreshing the buff to time time.
//Gets the nearest target that can be immediately targeted //Gets the nearest target that can be immediately targeted
static std::optional<std::weak_ptr<Monster>>GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z); static std::optional<std::weak_ptr<Monster>>GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z);
const bool InUndamageableState(const bool onUpperLevel,const float z)const; const bool InUndamageableState(const bool onUpperLevel,const float z)const;
@ -298,9 +300,12 @@ private:
float solidFadeTimer{0.f}; float solidFadeTimer{0.f};
bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags); bool _Hurt(int damage,bool onUpperLevel,float z,const TrueDamageFlag damageRule,HurtFlag::HurtFlag hurtFlags);
void RemoveMarkStack(); void RemoveMarkStack();
void RemoveSpecialMarkStack();
float markApplicationTimer{}; //Used for animations involving the mark being applied to the target. float markApplicationTimer{}; //Used for animations involving the mark being applied to the target.
float specialMarkApplicationTimer{}; //Used for animations involving the mark being applied to the target.
void SetWeakPointer(std::shared_ptr<Monster>&sharedMonsterPtr); void SetWeakPointer(std::shared_ptr<Monster>&sharedMonsterPtr);
std::weak_ptr<Monster>weakPtr; std::weak_ptr<Monster>weakPtr;
void LongLastingMarkEffect(float&out_markDuration); //Modifies a given duration.
private: private:
struct STRATEGY{ struct STRATEGY{
static std::string ERR; static std::string ERR;

@ -121,8 +121,24 @@ void Player::Initialize(){
} }
afterImage.Decal()->Update(); afterImage.Decal()->Update();
playerOutline.Create(24,24); playerOutline.Create(24,24);
}
void Player::OnLevelStart(){
AddTimer(PlayerTimerType::PLAYER_OUTLINE_TIMER,Timer{"Player Outline Update Timer",0.05f,[&](){UpdatePlayerOutline();},Timer::REPEAT}); AddTimer(PlayerTimerType::PLAYER_OUTLINE_TIMER,Timer{"Player Outline Update Timer",0.05f,[&](){UpdatePlayerOutline();},Timer::REPEAT});
AddTimer(PlayerTimerType::OPPORTUNITY_SHOT_RANDOM_SPECIAL_MARK,Timer{"Opportunity Shot","Opportunity Shot"_ENC["MARK TIME"],[&](){
if(HasEnchant("Opportunity Shot")){
std::vector<std::weak_ptr<Monster>>onScreenMonsters;
std::copy_if(MONSTER_LIST.begin(),MONSTER_LIST.end(),std::back_inserter(onScreenMonsters),[](const std::shared_ptr<Monster>&monster){
geom2d::rect<float>gameView{game->camera.GetViewPosition(),game->camera.GetViewSize()};
return geom2d::overlaps(gameView,geom2d::circle<float>(monster->GetPos(),monster->GetCollisionRadius()));
});
if(onScreenMonsters.size()>0){
uint8_t randomChoice{uint8_t(util::random()%onScreenMonsters.size())};
onScreenMonsters[randomChoice].lock()->ApplySpecialMark("Trapper.Ability 1.Duration"_F,1U);
}
}
},Timer::TimerType::REPEAT});
} }
void Player::InitializeMinimapImage(){ void Player::InitializeMinimapImage(){
@ -803,7 +819,6 @@ void Player::Update(float fElapsedTime){
} }
#pragma endregion #pragma endregion
#pragma region Handle knockup timers #pragma region Handle knockup timers
if(knockUpTimer>0.f){ if(knockUpTimer>0.f){
knockUpTimer=std::max(0.f,knockUpTimer-fElapsedTime); knockUpTimer=std::max(0.f,knockUpTimer-fElapsedTime);
@ -2077,6 +2092,10 @@ Timer&Player::AddTimer(const PlayerTimerType type,const Timer&timer){
return newTimer; return newTimer;
} }
const bool Player::HasTimer(const PlayerTimerType type)const{
return timers.count(type);
}
Timer&Player::GetTimer(const PlayerTimerType type){ Timer&Player::GetTimer(const PlayerTimerType type){
return timers.at(type); return timers.at(type);
} }

@ -315,6 +315,7 @@ public:
const bool HasEnchantWithAbilityAffected(const std::string_view abilityName)const; //Returns whether or not the player has an enchant that affects the provided name. 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 PlayerTimerType type,const Timer&timer); Timer&AddTimer(const PlayerTimerType type,const Timer&timer);
Timer&GetTimer(const PlayerTimerType type); Timer&GetTimer(const PlayerTimerType type);
const bool HasTimer(const PlayerTimerType type)const;
void CancelTimer(const PlayerTimerType type); void CancelTimer(const PlayerTimerType type);
void ResetTimers(); void ResetTimers();
void _ForceCastSpell(Ability&ability); void _ForceCastSpell(Ability&ability);
@ -406,6 +407,7 @@ private:
Renderable playerOutline; Renderable playerOutline;
void UpdatePlayerOutline(); void UpdatePlayerOutline();
void OnBuffAdd(Buff&newBuff); void OnBuffAdd(Buff&newBuff);
void OnLevelStart();
protected: protected:
const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F; const float ATTACK_COOLDOWN="Warrior.Auto Attack.Cooldown"_F;
const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F; const float MAGIC_ATTACK_COOLDOWN="Wizard.Auto Attack.Cooldown"_F;

@ -41,4 +41,5 @@ enum class PlayerTimerType{
DEADLY_MIRAGE_SECOND_CAST, DEADLY_MIRAGE_SECOND_CAST,
ADRENALINE_STIM, ADRENALINE_STIM,
PLAYER_OUTLINE_TIMER, PLAYER_OUTLINE_TIMER,
OPPORTUNITY_SHOT_RANDOM_SPECIAL_MARK,
}; };

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 2 #define VERSION_MINOR 2
#define VERSION_PATCH 3 #define VERSION_PATCH 3
#define VERSION_BUILD 11022 #define VERSION_BUILD 11031
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -221,6 +221,12 @@ Events
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%) # Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = lockon.ogg, 100%, 140%, 140% File[0] = lockon.ogg, 100%, 140%, 140%
} }
Lock On Special
{
CombatSound = True
# Specify file names, followed by volume %. Optional min and max pitch adjustment (Defaults are 90%,110%)
File[0] = lockon_special.ogg, 100%
}
Menu Navigate Menu Navigate
{ {
# Muted for now. # Muted for now.

@ -122,6 +122,7 @@ Images
GFX_MonsterSoul = monstersoul.png GFX_MonsterSoul = monstersoul.png
GFX_MonsterSoulGlow = monstersoulglow.png GFX_MonsterSoulGlow = monstersoulglow.png
GFX_Glow = glow.png GFX_Glow = glow.png
GFX_SpecialTargetMark = special_target.png
GFX_Thief_Sheet = nico-thief.png GFX_Thief_Sheet = nico-thief.png
GFX_Trapper_Sheet = nico-trapper.png GFX_Trapper_Sheet = nico-trapper.png

@ -286,7 +286,7 @@ Item Enchants
} }
Opportunity Shot Opportunity Shot
{ {
Description = "Every {MARK TIME} seconds a random enemy on screen gets {MARK AMOUNT} Mark. If a mark on that Target is triggered within {MARK TRIGGER TIME} seconds, your Mark Target's cooldown is reduced by {MARK COOLDOWN REDUCE} seconds." Description = "Every {MARK TIME} seconds an enemy gets marked. If triggered within {MARK TRIGGER TIME} seconds, Mark Target's cooldown is reduced by {MARK COOLDOWN REDUCE} seconds."
Affects = Auto Attack Affects = Auto Attack
MARK TIME = 8s MARK TIME = 8s

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Loading…
Cancel
Save