Refactored monster list to use shared pointers instead of unique pointers. Converted all raw monster pointers that needed to store monster data to use weak pointers instead in case a monster gets despawned while owning object is still alive. Implemented Witch's auto attack, added turn_towards_direction function for homing ability. Fixed player reflections being drawn without additive blending. Added 30s cooldown to Trapper's Explosive trap ability. Release Build 10345.

mac-build
sigonasr2 4 months ago
parent 4a7ad23196
commit 98208fb3e0
  1. 41
      Adventures in Lestoria/AdventuresInLestoria.cpp
  2. 4
      Adventures in Lestoria/AdventuresInLestoria.h
  3. 2
      Adventures in Lestoria/Bear.cpp
  4. 5
      Adventures in Lestoria/BulletTypes.h
  5. 2
      Adventures in Lestoria/DEFINES.h
  6. 2
      Adventures in Lestoria/FrogTongue.cpp
  7. 2
      Adventures in Lestoria/IBullet.cpp
  8. 2
      Adventures in Lestoria/LightningBolt.cpp
  9. 2
      Adventures in Lestoria/MajorHawk.cpp
  10. 24
      Adventures in Lestoria/Monster.cpp
  11. 3
      Adventures in Lestoria/Monster.h
  12. 4
      Adventures in Lestoria/Player.cpp
  13. 48
      Adventures in Lestoria/PurpleEnergyBall.cpp
  14. 4
      Adventures in Lestoria/StoneGolem.cpp
  15. 2
      Adventures in Lestoria/Thief.cpp
  16. 6
      Adventures in Lestoria/Trapper.cpp
  17. 2
      Adventures in Lestoria/Version.h
  18. 4
      Adventures in Lestoria/Warrior.cpp
  19. 9
      Adventures in Lestoria/Witch.cpp
  20. 2
      Adventures in Lestoria/assets/config/classes/Trapper.txt
  21. 3
      Adventures in Lestoria/assets/config/classes/Witch.txt
  22. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  23. BIN
      Adventures in Lestoria/assets/gamepack.pak
  24. BIN
      Adventures in Lestoria/assets/mark_trail.png
  25. BIN
      Adventures in Lestoria/assets/purpleenergyball_hit.png
  26. 15
      Adventures in Lestoria/util.cpp
  27. 2
      Adventures in Lestoria/util.h
  28. BIN
      x64/Release/Adventures in Lestoria.exe

@ -94,7 +94,7 @@ bool _DEBUG_MAP_LOAD_INFO = false;
//360x240 //360x240
vi2d WINDOW_SIZE={24*15,24*10}; vi2d WINDOW_SIZE={24*15,24*10};
safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA; safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
std::vector<std::unique_ptr<Monster>>MONSTER_LIST; std::vector<std::shared_ptr<Monster>>MONSTER_LIST;
std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST; std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST;
std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST; std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;
std::vector<std::unique_ptr<IBullet>>BULLET_LIST; std::vector<std::unique_ptr<IBullet>>BULLET_LIST;
@ -735,7 +735,7 @@ const HurtList AiL::Hurt(vf2d pos,float radius,int damage,bool upperLevel,float
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
if(CheckForMonsterCollisions){ if(CheckForMonsterCollisions){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult())); const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()));
if(InRange){ if(InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
@ -757,7 +757,7 @@ const HurtList AiL::HurtMonsterType(vf2d pos,float radius,int damage,bool upperL
if(!MONSTER_DATA.count(std::string(monsterName)))ERR(std::format("WARNING! Cannot check for monster type name {}! Does not exist!",monsterName)); if(!MONSTER_DATA.count(std::string(monsterName)))ERR(std::format("WARNING! Cannot check for monster type name {}! Does not exist!",monsterName));
HurtList hitList; HurtList hitList;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius())); const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius()));
if(m->GetName()==monsterName&&InRange){ if(m->GetName()==monsterName&&InRange){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
@ -774,7 +774,7 @@ const HurtList AiL::HurtConeNotHit(vf2d pos,float radius,float angle,float sweep
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
if(CheckForMonsterCollisions){ if(CheckForMonsterCollisions){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
float angleToMonster=geom2d::line<float>{pos,m->GetPos()}.vector().polar().y; float angleToMonster=geom2d::line<float>{pos,m->GetPos()}.vector().polar().y;
float angleDiff=util::angle_difference(angleToMonster,angle); float angleDiff=util::angle_difference(angleToMonster,angle);
@ -806,7 +806,7 @@ const HurtList AiL::HurtNotHit(vf2d pos,float radius,int damage,HitList&hitList,
const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER; const bool CheckForPlayerCollisions=hurtTargets&HurtType::PLAYER;
if(CheckForMonsterCollisions){ if(CheckForMonsterCollisions){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags); HurtReturnValue returnVal=m->Hurt(damage,upperLevel,z,hurtFlags);
affectedList.push_back({&*m,returnVal}); affectedList.push_back({&*m,returnVal});
@ -828,7 +828,7 @@ void AiL::ProximityKnockback(const vf2d pos,const float radius,const float knock
const bool CheckForMonsterCollisions=knockbackTargets&HurtType::MONSTER; const bool CheckForMonsterCollisions=knockbackTargets&HurtType::MONSTER;
const bool CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER; const bool CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER;
if(CheckForMonsterCollisions){ if(CheckForMonsterCollisions){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){
m->ProximityKnockback(pos,knockbackAmt); m->ProximityKnockback(pos,knockbackAmt);
} }
@ -881,7 +881,7 @@ void AiL::PopulateRenderLists(){
Player*pl=GetPlayer(); Player*pl=GetPlayer();
pl->rendered=false; pl->rendered=false;
std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::unique_ptr<Monster>&m1,std::unique_ptr<Monster>&m2){return m1->GetPos().y<m2->GetPos().y;}); std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::shared_ptr<Monster>&m1,std::shared_ptr<Monster>&m2){return m1->GetPos().y<m2->GetPos().y;});
std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y<id2.GetPos().y;}); std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y<id2.GetPos().y;});
std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&b1,std::unique_ptr<IBullet>&b2){return b1->pos.y<b2->pos.y;}); std::sort(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&b1,std::unique_ptr<IBullet>&b2){return b1->pos.y<b2->pos.y;});
std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;}); std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr<Effect>&e1,std::unique_ptr<Effect>&e2){return e1->pos.y<e2->pos.y;});
@ -1035,8 +1035,6 @@ void AiL::RenderWorld(float fElapsedTime){
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)))}; 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(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)))}; 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(player->IsUsingAdditiveBlending())SetDecalMode(DecalMode::ADDITIVE);
else SetDecalMode(DecalMode::NORMAL);
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); 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);
SetDecalMode(DecalMode::NORMAL); SetDecalMode(DecalMode::NORMAL);
if(player->GetState()==State::BLOCK){ if(player->GetState()==State::BLOCK){
@ -1104,7 +1102,7 @@ void AiL::RenderWorld(float fElapsedTime){
multiplierX*=(1-abs(cos(1.5f*reflectionStepTime))*"water_reflection_scale_factor"_F); multiplierX*=(1-abs(cos(1.5f*reflectionStepTime))*"water_reflection_scale_factor"_F);
float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F; float reflectionRatioX=abs(sin(reflectionStepTime))*"water_reflection_scale_factor"_F;
RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1}); RenderPlayer(player->GetPos()+vf2d{reflectionRatioX*player->GetFrame().GetSourceRect().size.x,float(player->GetFrame().GetSourceRect().size.y)-8}*player->GetSizeMult(),{multiplierX,-1});
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
m->DrawReflection(reflectionRatioX,multiplierX); m->DrawReflection(reflectionRatioX,multiplierX);
} }
SetDecalMode(DecalMode::NORMAL); SetDecalMode(DecalMode::NORMAL);
@ -1373,6 +1371,8 @@ void AiL::RenderWorld(float fElapsedTime){
vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24); vf2d shadowScale=vf2d{8*player->GetSizeMult()/3.f,1}/std::max(1.f,player->GetZ()/24);
view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK); view.DrawDecal(player->GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*player->GetSizeMult()},GFX["circle.png"].Decal(),shadowScale,BLACK);
} }
if(player->IsUsingAdditiveBlending())SetDecalMode(DecalMode::ADDITIVE);
else SetDecalMode(DecalMode::NORMAL);
RenderPlayer(player->GetPos(),{1,1}); RenderPlayer(player->GetPos(),{1,1});
} }
while(monstersAfterLowerIt!=monstersAfterLower.end()){ while(monstersAfterLowerIt!=monstersAfterLower.end()){
@ -1810,7 +1810,7 @@ void AiL::RenderWorld(float fElapsedTime){
} }
#pragma endregion #pragma endregion
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy()); m->strategyDrawOverlay(this,*m,MONSTER_DATA[m->GetName()].GetAIStrategy());
} }
@ -4225,7 +4225,7 @@ rcode AiL::LoadResource(Renderable&renderable,std::string_view imgPath,bool filt
} }
void AiL::UpdateMonsters(){ void AiL::UpdateMonsters(){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->markedForDeletion){ if(m->markedForDeletion){
AMonsterIsMarkedForDeletion(); AMonsterIsMarkedForDeletion();
continue; continue;
@ -4234,13 +4234,10 @@ void AiL::UpdateMonsters(){
} }
for(Monster&m:game->monstersToBeSpawned){ for(Monster&m:game->monstersToBeSpawned){
size_t prevCapacity=MONSTER_LIST.capacity(); size_t prevCapacity=MONSTER_LIST.capacity();
MONSTER_LIST.push_back(std::make_unique<Monster>(m)); MONSTER_LIST.emplace_back(std::make_shared<Monster>(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(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::unique_ptr<Monster>&m){ if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::shared_ptr<Monster>&m){return m->markedForDeletion;});
if(m->markedForDeletion)std::erase_if(lockOnTargets,[&](std::tuple<Monster*,StackCount,MarkTime>markData){return std::get<0>(markData)==&*m;}); //Marked targets may have dangling pointers, remove them before removing the monsters for good.
return m->markedForDeletion;
});
aMonsterIsMarkedForDeletion=false; aMonsterIsMarkedForDeletion=false;
game->monstersToBeSpawned.clear(); game->monstersToBeSpawned.clear();
} }
@ -4300,11 +4297,13 @@ void AiL::GlobalGameUpdates(){
lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime()); lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime());
if(lastLockOnTargetTime<=0.f){ if(lastLockOnTargetTime<=0.f){
const auto&[monster,stackCount,time]=lockOnTargets.front(); const auto&[monster,stackCount,time]=lockOnTargets.front();
monster->AddBuff(BuffType::TRAPPER_MARK,time,stackCount); if(!monster.expired()){
SoundEffect::PlaySFX("Lock On",monster->GetPos()); monster.lock()->AddBuff(BuffType::TRAPPER_MARK,time,stackCount);
lockOnTargets.erase(lockOnTargets.begin()); SoundEffect::PlaySFX("Lock On",monster.lock()->GetPos());
lastLockOnTargetTime=0.2f; lastLockOnTargetTime=0.2f;
} }
lockOnTargets.erase(lockOnTargets.begin());
}
} }
#pragma endregion #pragma endregion
@ -4435,6 +4434,6 @@ const std::map<std::string,TilesetData>&AiL::GetTilesets()const{
return MAP_TILESETS; return MAP_TILESETS;
} }
void AiL::AddToMarkedTargetList(std::tuple<Monster*,StackCount,MarkTime>markData){ void AiL::AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData){
lockOnTargets.emplace_back(markData); lockOnTargets.emplace_back(markData);
} }

@ -229,7 +229,7 @@ private:
Overlay hudOverlay{"pixel.png",BLANK}; Overlay hudOverlay{"pixel.png",BLANK};
float targetZoom{1.f}; float targetZoom{1.f};
float zoomAdjustSpeed{0.1f}; float zoomAdjustSpeed{0.1f};
std::vector<std::tuple<Monster*,StackCount,MarkTime>>lockOnTargets; std::vector<std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>>lockOnTargets;
float lastLockOnTargetTime{}; float lastLockOnTargetTime{};
public: public:
AiL(); AiL();
@ -385,7 +385,7 @@ public:
//Plays the correct footstep sound based on player's current tile. //Plays the correct footstep sound based on player's current tile.
void PlayFootstepSound(); void PlayFootstepSound();
const std::map<std::string,TilesetData>&GetTilesets()const; const std::map<std::string,TilesetData>&GetTilesets()const;
void AddToMarkedTargetList(std::tuple<Monster*,StackCount,MarkTime>markData); void AddToMarkedTargetList(std::tuple<std::weak_ptr<Monster>,StackCount,MarkTime>markData);
struct TileGroupData{ struct TileGroupData{
vi2d tilePos; vi2d tilePos;

@ -95,7 +95,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount")); game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount"));
} }
} }
for(std::unique_ptr<Monster>&otherM:MONSTER_LIST){ for(std::shared_ptr<Monster>&otherM:MONSTER_LIST){
if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->BulletCollisionHitbox())){ if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->BulletCollisionHitbox())){
otherM->Knockup(ConfigFloat("Attack Knockup Duration")); otherM->Knockup(ConfigFloat("Attack Knockup Duration"));
vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM->GetPos()).vector().norm(); vf2d monsterDirVecNorm=geom2d::line<float>(m.GetPos(),otherM->GetPos()).vector().norm();

@ -320,10 +320,13 @@ private:
}; };
struct PurpleEnergyBall:public Bullet{ struct PurpleEnergyBall:public Bullet{
PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1}); PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,bool hitsMultiple=false,float lifetime=INFINITE,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1});
void Update(float fElapsedTime)override; void Update(float fElapsedTime)override;
void Draw(const Pixel blendCol)const override; void Draw(const Pixel blendCol)const override;
BulletDestroyState PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!
BulletDestroyState MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!!
private: private:
const vf2d initialScale; const vf2d initialScale;
const float homingRadius; const float homingRadius;
std::optional<std::weak_ptr<Monster>>homingTarget;
}; };

@ -44,7 +44,7 @@ using BackdropName=std::string;
using MonsterSpawnerID=int; using MonsterSpawnerID=int;
#define INCLUDE_ANIMATION_DATA extern safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA; #define INCLUDE_ANIMATION_DATA extern safemap<std::string,Animate2D::FrameSequence>ANIMATION_DATA;
#define INCLUDE_MONSTER_LIST extern std::vector<std::unique_ptr<Monster>>MONSTER_LIST; #define INCLUDE_MONSTER_LIST extern std::vector<std::shared_ptr<Monster>>MONSTER_LIST;
#define INCLUDE_SPAWNER_LIST extern std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST; #define INCLUDE_SPAWNER_LIST extern std::unordered_map<MonsterSpawnerID,MonsterSpawner>SPAWNER_LIST;
#define INCLUDE_SPAWNER_CONTROLLER extern std::optional<std::queue<MonsterSpawnerID>>SPAWNER_CONTROLLER; #define INCLUDE_SPAWNER_CONTROLLER extern std::optional<std::queue<MonsterSpawnerID>>SPAWNER_CONTROLLER;
#define INCLUDE_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST; #define INCLUDE_DAMAGENUMBER_LIST extern std::vector<std::shared_ptr<DamageNumber>>DAMAGENUMBER_LIST;

@ -65,7 +65,7 @@ void FrogTongue::Update(float fElapsedTime){
_PlayerHit(game->GetPlayer()); _PlayerHit(game->GetPlayer());
} }
if(friendly){ if(friendly){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){ if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){
_MonsterHit(*m,m->GetMarkStacks()); _MonsterHit(*m,m->GetMarkStacks());
hitList.insert(&*m); hitList.insert(&*m);

@ -99,7 +99,7 @@ void IBullet::_Update(const float fElapsedTime){
const auto CollisionCheck=[&](){ const auto CollisionCheck=[&](){
if(simulated)return true; if(simulated)return true;
if(friendly){ if(friendly){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){ if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){
if(hitList.find(&*m)==hitList.end()){ if(hitList.find(&*m)==hitList.end()){
HurtDamageInfo damageData{&*m,damage,OnUpperLevel(),z,HurtFlag::NONE}; HurtDamageInfo damageData{&*m,damage,OnUpperLevel(),z,HurtFlag::NONE};

@ -91,7 +91,7 @@ BulletDestroyState LightningBolt::MonsterHit(Monster&monster,const uint8_t markS
fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F; fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F;
game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange)); game->AddEffect(std::make_unique<Effect>(monster.GetPos(),"Wizard.Ability 2.SplashLifetime"_F,"lightning_splash_effect.png",upperLevel,monster.GetSizeMult(),"Wizard.Ability 2.SplashFadeoutTime"_F,vf2d{},WHITE,"Wizard.Ability 2.SplashRotationRange"_FRange));
int targetsHit=0; int targetsHit=0;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue; if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue;
geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m->GetPos()); geom2d::line<float>lineToTarget=geom2d::line<float>(monster.GetPos(),m->GetPos());
float dist=lineToTarget.length(); float dist=lineToTarget.length();

@ -49,7 +49,7 @@ using A=Attribute;
void Monster::STRATEGY::MAJOR_HAWK(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::MAJOR_HAWK(Monster&m,float fElapsedTime,std::string strategy){
//Runs the Hawk strategy and has an aggressive mode when the amount of this monster falls below an amount. //Runs the Hawk strategy and has an aggressive mode when the amount of this monster falls below an amount.
const int majorHawkCount=std::accumulate(MONSTER_LIST.begin(),MONSTER_LIST.end(),0,[&](const int&acc,const std::unique_ptr<Monster>&monster){return std::move(acc)+((monster->IsAlive()&&monster->GetName()==ConfigString("Aggressive Name Check"))?1:0);}); const int majorHawkCount=std::accumulate(MONSTER_LIST.begin(),MONSTER_LIST.end(),0,[&](const int&acc,const std::shared_ptr<Monster>&monster){return std::move(acc)+((monster->IsAlive()&&monster->GetName()==ConfigString("Aggressive Name Check"))?1:0);});
if(majorHawkCount<=ConfigInt("Aggressive Hawk Count"))HAWK(m,fElapsedTime,"Major Hawk"); if(majorHawkCount<=ConfigInt("Aggressive Hawk Count"))HAWK(m,fElapsedTime,"Major Hawk");
else HAWK(m,fElapsedTime,"Hawk"); //Use normal hawk behaviors when there are too many Major Hawks. else HAWK(m,fElapsedTime,"Hawk"); //Use normal hawk behaviors when there are too many Major Hawks.

@ -331,7 +331,7 @@ bool Monster::Update(float fElapsedTime){
std::for_each(buffList.begin(),buffList.end(),[&](Buff&b){b.Update(game,fElapsedTime);}); std::for_each(buffList.begin(),buffList.end(),[&](Buff&b){b.Update(game,fElapsedTime);});
std::erase_if(buffList,[](Buff&b){return b.duration<=0;}); std::erase_if(buffList,[](Buff&b){return b.duration<=0;});
if(!HasIframes()){ if(!HasIframes()){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const float monsterRadius{GetCollisionRadius()}; const float monsterRadius{GetCollisionRadius()};
const float otherMonsterRadius{m->GetCollisionRadius()}; const float otherMonsterRadius{m->GetCollisionRadius()};
if(&*m==this)continue; if(&*m==this)continue;
@ -1257,29 +1257,29 @@ void Monster::ApplyMark(float time,uint8_t stackCount){
} }
} }
}else{ }else{
game->AddToMarkedTargetList({this,stackCount,time}); game->AddToMarkedTargetList({GetWeakPointer(),stackCount,time});
} }
markApplicationTimer=0.5f; markApplicationTimer=0.5f;
} }
std::optional<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<Monster*>closestMonster; std::optional<std::weak_ptr<Monster>>closestMonster;
std::optional<Monster*>closestGenericMonster; std::optional<std::weak_ptr<Monster>>closestGenericMonster;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
geom2d::line<float>aimingLine=geom2d::line<float>(point,m->GetPos()); geom2d::line<float>aimingLine=geom2d::line<float>(point,m->GetPos());
float distToMonster=aimingLine.length(); float distToMonster=aimingLine.length();
float distToClosestPoint,distToClosestGenericPoint; float distToClosestPoint,distToClosestGenericPoint;
if(closestMonster.has_value())distToClosestPoint=geom2d::line<float>(point,closestMonster.value()->GetPos()).length(); if(closestMonster.has_value())distToClosestPoint=geom2d::line<float>(point,closestMonster.value().lock()->GetPos()).length();
else distToClosestPoint=std::numeric_limits<float>::max(); else distToClosestPoint=std::numeric_limits<float>::max();
if(closestGenericMonster.has_value())distToClosestGenericPoint=geom2d::line<float>(point,closestGenericMonster.value()->GetPos()).length(); if(closestGenericMonster.has_value())distToClosestGenericPoint=geom2d::line<float>(point,closestGenericMonster.value().lock()->GetPos()).length();
else distToClosestGenericPoint=std::numeric_limits<float>::max(); else distToClosestGenericPoint=std::numeric_limits<float>::max();
if(!m->InUndamageableState(onUpperLevel,z)){ if(!m->InUndamageableState(onUpperLevel,z)){
if(distToClosestPoint>distToMonster&&distToMonster<=maxDistance){ if(distToClosestPoint>distToMonster&&distToMonster<=maxDistance){
closestMonster=&*m; closestMonster=m;
} }
} }
if(m->IsAlive()&&distToClosestGenericPoint>distToMonster&&distToMonster<=maxDistance){ if(m->IsAlive()&&distToClosestGenericPoint>distToMonster&&distToMonster<=maxDistance){
closestGenericMonster=&*m; closestGenericMonster=m;
} }
} }
if(closestMonster.has_value()){ if(closestMonster.has_value()){
@ -1303,3 +1303,7 @@ void Monster::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeTyp
const bool Monster::CanMove()const{ const bool Monster::CanMove()const{
return knockUpTimer==0.f&&IsAlive(); return knockUpTimer==0.f&&IsAlive();
} }
const std::weak_ptr<Monster>Monster::GetWeakPointer()const{
return *std::find_if(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr<Monster>&ptr){return &*ptr==this;});
}

@ -204,9 +204,10 @@ public:
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.
//Gets the nearest target that can be immediately targeted //Gets the nearest target that can be immediately targeted
static std::optional<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;
const bool CanMove()const; const bool CanMove()const;
const std::weak_ptr<Monster>GetWeakPointer()const;
private: private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! //NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
// The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop. // The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.

@ -576,7 +576,7 @@ void Player::Update(float fElapsedTime){
if(item3.cooldown<0){ if(item3.cooldown<0){
item3.cooldown=0; item3.cooldown=0;
} }
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
const float playerRadius{12*GetSizeMult()/2}; const float playerRadius{12*GetSizeMult()/2};
const float monsterRadius{m->GetCollisionRadius()}; const float monsterRadius{m->GetCollisionRadius()};
if(!HasIframes()&&abs(m->GetZ()-GetZ())<=1&&OnUpperLevel()==m->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,playerRadius),geom2d::circle(m->GetPos(),monsterRadius))){ if(!HasIframes()&&abs(m->GetZ()-GetZ())<=1&&OnUpperLevel()==m->OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,playerRadius),geom2d::circle(m->GetPos(),monsterRadius))){
@ -1583,7 +1583,7 @@ const vf2d Player::GetAimingLocation(bool useWalkDir,bool invert){
//Find the closest monster target. Provide a "Generic" target in case a target that is invulnerable is the only target that is available (and alive). //Find the closest monster target. Provide a "Generic" target in case a target that is invulnerable is the only target that is available (and alive).
std::optional<vf2d>closestPoint; std::optional<vf2d>closestPoint;
std::optional<vf2d>closestGenericPoint; //Even if the monster is invulnerable, it might be worth targeting if a normal target is not found. std::optional<vf2d>closestGenericPoint; //Even if the monster is invulnerable, it might be worth targeting if a normal target is not found.
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos()); geom2d::line<float>aimingLine=geom2d::line<float>(GetPos(),m->GetPos());
float distToMonster=aimingLine.length(); float distToMonster=aimingLine.length();
float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint.value_or(MAX)).length(); float distToClosestPoint=geom2d::line<float>(GetPos(),closestPoint.value_or(MAX)).length();

@ -38,19 +38,59 @@ All rights reserved.
#include "BulletTypes.h" #include "BulletTypes.h"
#include "AdventuresInLestoria.h" #include "AdventuresInLestoria.h"
#include "util.h"
INCLUDE_game INCLUDE_game
PurpleEnergyBall::PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale) PurpleEnergyBall::PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,vf2d speed,bool hitsMultiple,float lifetime,bool friendly,Pixel col,vf2d scale)
:initialScale(scale),homingRadius(homingRadius),Bullet(pos,{},radius,damage,"mark_trail.png",upperLevel,hitsMultiple,INFINITE,false,friendly,col,scale/2.f,0.f){} :initialScale(scale),homingRadius(homingRadius),Bullet(pos,speed,radius,damage,"mark_trail.png",upperLevel,hitsMultiple,INFINITE,false,friendly,col,scale/2.f,0.f){}
void PurpleEnergyBall::Update(float fElapsedTime){
void PurpleEnergyBall::Update(float fElapsedTime){
if(homingTarget.has_value()){
if(!homingTarget.value().expired()){
const bool TargetInRange{util::distance(pos,homingTarget.value().lock()->GetPos())<="Witch.Auto Attack.Homing Range"_F};
const bool TargetIsAlive{homingTarget.value().lock()->IsAlive()};
if(TargetIsAlive&&TargetInRange){
const float targetAngle{util::angleTo(pos,homingTarget.value().lock()->GetPos())};
float currentAngle{vel.polar().y};
util::turn_towards_direction(currentAngle,targetAngle,util::degToRad("Witch.Auto Attack.Homing Turning Radius"_F)*fElapsedTime);
vel=vf2d{vel.mag(),currentAngle}.cart();
}else{
homingTarget.reset();
}
}else{
homingTarget.reset();
}
}else homingTarget=Monster::GetNearestMonster(pos,"Witch.Auto Attack.Homing Range"_F,OnUpperLevel(),GetZ());
if(distanceTraveled>"Witch.Auto Attack.Max Range"_F&&IsActivated()){
fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F;
}
const vf2d energyBallScale{initialScale.lerp(initialScale*0.8f,cos(12*PI*game->GetRunTime())/2.f+0.5f)};
scale=energyBallScale;
} }
void PurpleEnergyBall::Draw(const Pixel blendCol)const{ void PurpleEnergyBall::Draw(const Pixel blendCol)const{
const vf2d lightOrbScale{initialScale.lerp(initialScale/2.f,sin(12*PI*game->GetRunTime())/2.f+0.5f)}; const vf2d lightOrbScale{initialScale.lerp(initialScale/2.f,sin(12*PI*game->GetRunTime())/2.f+0.5f)};
game->SetDecalMode(DecalMode::ADDITIVE); game->SetDecalMode(DecalMode::ADDITIVE);
game->DrawPartialDecal(pos,initialScale+0.2f,animation.GetFrame(internal_animState).GetSourceImage()->Decal(),animation.GetFrame(internal_animState).GetSourceRect().pos,animation.GetFrame(internal_animState).GetSourceRect().size); game->view.DrawPartialRotatedDecal(pos,animation.GetFrame(internal_animState).GetSourceImage()->Decal(),0.f,animation.GetFrame(internal_animState).GetSourceRect().size/2.f,animation.GetFrame(internal_animState).GetSourceRect().pos,animation.GetFrame(internal_animState).GetSourceRect().size,lightOrbScale+0.2f,blendCol);
game->SetDecalMode(DecalMode::NORMAL); game->SetDecalMode(DecalMode::NORMAL);
Bullet::Draw(blendCol); Bullet::Draw(blendCol);
} }
BulletDestroyState PurpleEnergyBall::PlayerHit(Player*player)
{
fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F;
std::unique_ptr<Effect>hitEffect{std::make_unique<Effect>(player->GetPos(),0,"purpleenergyball_hit.png",upperLevel,player->GetSizeMult(),"Witch.Auto Attack.SplashEffectFadeoutTime"_F,vf2d{},WHITE,util::random(2*PI),5*PI)};
hitEffect->scaleSpd={-0.3f,-0.3f};
game->AddEffect(std::move(hitEffect));
return BulletDestroyState::KEEP_ALIVE;
}
BulletDestroyState PurpleEnergyBall::MonsterHit(Monster&monster,const uint8_t markStacksBeforeHit)
{
fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F;
std::unique_ptr<Effect>hitEffect{std::make_unique<Effect>(monster.GetPos(),0,"purpleenergyball_hit.png",upperLevel,monster.GetSizeMult(),"Witch.Auto Attack.SplashEffectFadeoutTime"_F,vf2d{},WHITE,util::random(2*PI),5*PI)};
hitEffect->scaleSpd={-0.3f,-0.3f};
game->AddEffect(std::move(hitEffect));
return BulletDestroyState::KEEP_ALIVE;
}

@ -71,7 +71,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
const auto PrepareSafeAreas=[&](){ const auto PrepareSafeAreas=[&](){
m.VEC(A::STAGE_POLYGONS).clear(); m.VEC(A::STAGE_POLYGONS).clear();
std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::unique_ptr<Monster>&monsterPtr){ std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr<Monster>&monsterPtr){
if(monsterPtr->GetName()!="Stone Golem Pillar"&&monsterPtr->GetName()!="Breaking Stone Golem Pillar"){ if(monsterPtr->GetName()!="Stone Golem Pillar"&&monsterPtr->GetName()!="Breaking Stone Golem Pillar"){
return; return;
} }
@ -282,7 +282,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
game->GetPlayer()->Knockback(util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Shockwave.Knockback Amount")); game->GetPlayer()->Knockback(util::pointTo(m.GetPos(),game->GetPlayer()->GetPos())*ConfigFloat("Shockwave.Knockback Amount"));
} }
m.VEC(A::STAGE_POLYGONS).clear(); m.VEC(A::STAGE_POLYGONS).clear();
std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::unique_ptr<Monster>&monsterPtr){ std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr<Monster>&monsterPtr){
if(monsterPtr->GetName()!="Stone Golem Pillar")return; if(monsterPtr->GetName()!="Stone Golem Pillar")return;
monsterPtr->_DealTrueDamage(ConfigInt("Shockwave.Pillar Damage")); monsterPtr->_DealTrueDamage(ConfigInt("Shockwave.Pillar Damage"));
}); });

@ -72,7 +72,7 @@ bool Thief::AutoAttack(){
bool attack=false; bool attack=false;
Monster*closest=nullptr; Monster*closest=nullptr;
float closest_dist=999999; float closest_dist=999999;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive()&& if(m->IsAlive()&&
geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))&& geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))&&
geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length()<closest_dist){ geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length()<closest_dist){

@ -96,11 +96,11 @@ void Trapper::InitializeClassAbilities(){
#pragma region Trapper Ability 1 (Mark Target) #pragma region Trapper Ability 1 (Mark Target)
Trapper::ability1.action= Trapper::ability1.action=
[](Player*p,vf2d pos={}){ [](Player*p,vf2d pos={}){
std::optional<Monster*>nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())}; std::optional<std::weak_ptr<Monster>>nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())};
vf2d targetPos{pos}; vf2d targetPos{pos};
if(nearestMonster.has_value()){ if(nearestMonster.has_value()){
targetPos=nearestMonster.value()->GetPos(); targetPos=nearestMonster.value().lock()->GetPos();
nearestMonster.value()->ApplyMark("Trapper.Ability 1.Duration"_F,"Trapper.Ability 1.Stack Count"_I); nearestMonster.value().lock()->ApplyMark("Trapper.Ability 1.Duration"_F,"Trapper.Ability 1.Stack Count"_I);
} }
for(int i:std::ranges::iota_view(0,int(util::distance(p->GetPos(),targetPos)/16))){ for(int i:std::ranges::iota_view(0,int(util::distance(p->GetPos(),targetPos)/16))){
float drawDist{i*16.f}; float drawDist{i*16.f};

@ -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 10324 #define VERSION_BUILD 10345
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -70,7 +70,7 @@ bool Warrior::AutoAttack(){
bool attack=false; bool attack=false;
Monster*closest=nullptr; Monster*closest=nullptr;
float closest_dist=999999; float closest_dist=999999;
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->IsAlive()&& if(m->IsAlive()&&
geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))&& geom2d::overlaps(geom2d::circle<float>(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))&&
geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length()<closest_dist){ geom2d::line<float>(GetWorldAimingLocation(),m->GetPos()).length()<closest_dist){
@ -121,7 +121,7 @@ void Warrior::InitializeClassAbilities(){
game->AddEffect(std::make_unique<Effect>(p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F)); game->AddEffect(std::make_unique<Effect>(p->GetPos(),"Warrior.Ability 1.EffectLifetime"_F,"battlecry_effect.png",p->upperLevel,"Warrior.Ability 1.Range"_F/350,"Warrior.Ability 1.EffectFadetime"_F));
p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"}); p->AddBuff(BuffType::STAT_UP,"Warrior.Ability 1.AttackUpDuration"_F,"Warrior.Ability 1.AttackIncrease"_F,{"Attack %"});
p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F); p->AddBuff(BuffType::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F);
for(std::unique_ptr<Monster>&m:MONSTER_LIST){ for(std::shared_ptr<Monster>&m:MONSTER_LIST){
if(m->GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m->GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))){ if(m->GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m->GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle<float>(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle<float>(m->GetPos(),m->GetSizeMult()*12))){
m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F); m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F);
} }

@ -41,6 +41,8 @@ All rights reserved.
#include "Effect.h" #include "Effect.h"
#include "AdventuresInLestoria.h" #include "AdventuresInLestoria.h"
#include "config.h" #include "config.h"
#include "SoundEffect.h"
#include "BulletTypes.h"
INCLUDE_MONSTER_LIST INCLUDE_MONSTER_LIST
INCLUDE_BULLET_LIST INCLUDE_BULLET_LIST
@ -65,7 +67,12 @@ void Witch::OnUpdate(float fElapsedTime){
} }
bool Witch::AutoAttack(){ bool Witch::AutoAttack(){
return false; attack_cooldown_timer=MAGIC_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction();
float angleToCursor=atan2(GetWorldAimingLocation().y-GetPos().y,GetWorldAimingLocation().x-GetPos().x);
CreateBullet(PurpleEnergyBall)(GetPos(),"Witch.Auto Attack.Radius"_F/100*12,"Witch.Auto Attack.Homing Range"_F/100*24,int(GetAttack()*"Witch.Auto Attack.DamageMult"_F),upperLevel,{cos(angleToCursor)*"Witch.Auto Attack.Speed"_F,sin(angleToCursor)*"Witch.Auto Attack.Speed"_F},false,INFINITE,true)EndBullet;
BULLET_LIST.back()->SetIsPlayerAutoAttackProjectile();
SoundEffect::PlaySFX("Wizard Auto Attack",SoundEffect::CENTERED);
return true;
} }
void Witch::InitializeClassAbilities(){ void Witch::InitializeClassAbilities(){
#pragma region Witch Right-click Ability (???) #pragma region Witch Right-click Ability (???)

@ -106,7 +106,7 @@ Trapper
Short Name = EXTRAP Short Name = EXTRAP
Description = Explodes on touch or in 5 seconds, marks hit targets. Already marked targets triggers mark twice. Description = Explodes on touch or in 5 seconds, marks hit targets. Already marked targets triggers mark twice.
Icon = explosive_trap.png Icon = explosive_trap.png
Cooldown = 0 Cooldown = 30
Mana Cost = 0 Mana Cost = 0
# Whether or not this ability cancels casts. # Whether or not this ability cancels casts.
CancelCast = 0 CancelCast = 0

@ -22,6 +22,9 @@ Witch
# Whether or not this ability cancels casts. # Whether or not this ability cancels casts.
CancelCast = 0 CancelCast = 0
# How fast the homing tracking turns to face its target.
Homing Turning Radius = 225deg/sec
# Maximum distance this attack will travel before it becomes deactivated. # Maximum distance this attack will travel before it becomes deactivated.
Max Range = 1200 Max Range = 1200

@ -114,6 +114,7 @@ Images
GFX_BearTrap = bear_trap.png GFX_BearTrap = bear_trap.png
GFX_ExplosiveTrap = explosive_trap.png GFX_ExplosiveTrap = explosive_trap.png
GFX_Explosion = explosionframes.png GFX_Explosion = explosionframes.png
GFX_PurpleEnergyBallHit = purpleenergyball_hit.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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 B

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

@ -206,3 +206,18 @@ float util::angle_difference(float angle_1, float angle_2)
const float util::distance(const vf2d&point1,const vf2d&point2){ const float util::distance(const vf2d&point1,const vf2d&point2){
return vf2d{point1-point2}.mag(); return vf2d{point1-point2}.mag();
} }
void util::turn_towards_direction(float&angle,float target,float rate)
{
const auto median_3=[](float a,float b,float c){
return std::max(std::min(a, b), std::min(std::max(a, b), c));
};
float diff = angle_difference(angle,target);
angle += median_3(-rate, rate, diff);
float newAngleDiff = angle_difference(angle,target);
if(diff>0&&newAngleDiff<0||
diff<0&&newAngleDiff>0)angle=fmod(target,2*PI); //We have crossed the angle difference threshold and can safely say we reached it.
}

@ -62,6 +62,8 @@ namespace olc::util{
float angle_difference(float angle_1, float angle_2); float angle_difference(float angle_1, float angle_2);
std::string GetHash(std::string file); std::string GetHash(std::string file);
const float distance(const vf2d&point1,const vf2d&point2); const float distance(const vf2d&point1,const vf2d&point2);
//Modifies angle argument directly to turn towards said direction. rate is in radians, please multiply by fElapsedTime if you intend to use this per frame.
void turn_towards_direction(float&angle,float target,float rate);
} }
template<class TL, class TR> template<class TL, class TR>

Loading…
Cancel
Save