diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 0eaf5219..26012880 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -94,7 +94,7 @@ bool _DEBUG_MAP_LOAD_INFO = false; //360x240 vi2d WINDOW_SIZE={24*15,24*10}; safemapANIMATION_DATA; -std::vector>MONSTER_LIST; +std::vector>MONSTER_LIST; std::unordered_mapSPAWNER_LIST; std::vector>DAMAGENUMBER_LIST; std::vector>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; if(CheckForMonsterCollisions){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult())); if(InRange){ 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)); HurtList hitList; - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ const bool InRange=geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),m->GetCollisionRadius())); if(m->GetName()==monsterName&&InRange){ 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; if(CheckForMonsterCollisions){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(!hitList.count(&*m)&&geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ float angleToMonster=geom2d::line{pos,m->GetPos()}.vector().polar().y; 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; if(CheckForMonsterCollisions){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ 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); 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 CheckForPlayerCollisions=knockbackTargets&HurtType::PLAYER; if(CheckForMonsterCollisions){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(geom2d::overlaps(geom2d::circle(pos,radius),geom2d::circle(m->GetPos(),12*m->GetSizeMult()))){ m->ProximityKnockback(pos,knockbackAmt); } @@ -881,7 +881,7 @@ void AiL::PopulateRenderLists(){ Player*pl=GetPlayer(); pl->rendered=false; - std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::unique_ptr&m1,std::unique_ptr&m2){return m1->GetPos().yGetPos().y;}); + std::sort(MONSTER_LIST.begin(),MONSTER_LIST.end(),[](std::shared_ptr&m1,std::shared_ptr&m2){return m1->GetPos().yGetPos().y;}); std::sort(ItemDrop::drops.begin(),ItemDrop::drops.end(),[](ItemDrop&id1,ItemDrop&id2){return id1.GetPos().y&b1,std::unique_ptr&b2){return b1->pos.ypos.y;}); std::sort(foregroundEffects.begin(),foregroundEffects.end(),[](std::unique_ptr&e1,std::unique_ptr&e2){return e1->pos.ypos.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)))}; 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(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); SetDecalMode(DecalMode::NORMAL); 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); 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}); - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ m->DrawReflection(reflectionRatioX,multiplierX); } 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); 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}); } while(monstersAfterLowerIt!=monstersAfterLower.end()){ @@ -1810,7 +1810,7 @@ void AiL::RenderWorld(float fElapsedTime){ } #pragma endregion - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ 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(){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(m->markedForDeletion){ AMonsterIsMarkedForDeletion(); continue; @@ -4234,13 +4234,10 @@ void AiL::UpdateMonsters(){ } for(Monster&m:game->monstersToBeSpawned){ size_t prevCapacity=MONSTER_LIST.capacity(); - MONSTER_LIST.push_back(std::make_unique(m)); + MONSTER_LIST.emplace_back(std::make_shared(m)); if(MONSTER_LIST.capacity()>prevCapacity)LOG(std::format("WARNING! The monster list has automatically reserved more space and resized to {}! This caused one potential frame where bullet/effect hitlists that stored information on what monsters were hit to potentially be hit a second time or cause monsters that should've been hit to never be hit. Consider starting with a larger default reserved size for MONSTER_LIST if your intention was to have this many monsters!",MONSTER_LIST.capacity())); } - if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::unique_ptr&m){ - if(m->markedForDeletion)std::erase_if(lockOnTargets,[&](std::tuplemarkData){return std::get<0>(markData)==&*m;}); //Marked targets may have dangling pointers, remove them before removing the monsters for good. - return m->markedForDeletion; - }); + if(aMonsterIsMarkedForDeletion)std::erase_if(MONSTER_LIST,[&](const std::shared_ptr&m){return m->markedForDeletion;}); aMonsterIsMarkedForDeletion=false; game->monstersToBeSpawned.clear(); } @@ -4300,10 +4297,12 @@ void AiL::GlobalGameUpdates(){ lastLockOnTargetTime=std::max(0.f,lastLockOnTargetTime-GetElapsedTime()); if(lastLockOnTargetTime<=0.f){ const auto&[monster,stackCount,time]=lockOnTargets.front(); - monster->AddBuff(BuffType::TRAPPER_MARK,time,stackCount); - SoundEffect::PlaySFX("Lock On",monster->GetPos()); + if(!monster.expired()){ + monster.lock()->AddBuff(BuffType::TRAPPER_MARK,time,stackCount); + SoundEffect::PlaySFX("Lock On",monster.lock()->GetPos()); + lastLockOnTargetTime=0.2f; + } lockOnTargets.erase(lockOnTargets.begin()); - lastLockOnTargetTime=0.2f; } } #pragma endregion @@ -4435,6 +4434,6 @@ const std::map&AiL::GetTilesets()const{ return MAP_TILESETS; } -void AiL::AddToMarkedTargetList(std::tuplemarkData){ +void AiL::AddToMarkedTargetList(std::tuple,StackCount,MarkTime>markData){ lockOnTargets.emplace_back(markData); } \ No newline at end of file diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index dc7345ee..16202f19 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -229,7 +229,7 @@ private: Overlay hudOverlay{"pixel.png",BLANK}; float targetZoom{1.f}; float zoomAdjustSpeed{0.1f}; - std::vector>lockOnTargets; + std::vector,StackCount,MarkTime>>lockOnTargets; float lastLockOnTargetTime{}; public: AiL(); @@ -385,7 +385,7 @@ public: //Plays the correct footstep sound based on player's current tile. void PlayFootstepSound(); const std::map&GetTilesets()const; - void AddToMarkedTargetList(std::tuplemarkData); + void AddToMarkedTargetList(std::tuple,StackCount,MarkTime>markData); struct TileGroupData{ vi2d tilePos; diff --git a/Adventures in Lestoria/Bear.cpp b/Adventures in Lestoria/Bear.cpp index 3e7eb690..a2b5ddf6 100644 --- a/Adventures in Lestoria/Bear.cpp +++ b/Adventures in Lestoria/Bear.cpp @@ -95,7 +95,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){ game->GetPlayer()->Knockback(playerDirVecNorm*ConfigFloat("Attack Knockback Amount")); } } - for(std::unique_ptr&otherM:MONSTER_LIST){ + for(std::shared_ptr&otherM:MONSTER_LIST){ if(!otherM->AttackAvoided(m.GetZ())&&&m!=otherM.get()&&geom2d::overlaps(attackCircle,otherM->BulletCollisionHitbox())){ otherM->Knockup(ConfigFloat("Attack Knockup Duration")); vf2d monsterDirVecNorm=geom2d::line(m.GetPos(),otherM->GetPos()).vector().norm(); diff --git a/Adventures in Lestoria/BulletTypes.h b/Adventures in Lestoria/BulletTypes.h index 2d684c71..48f5cda2 100644 --- a/Adventures in Lestoria/BulletTypes.h +++ b/Adventures in Lestoria/BulletTypes.h @@ -320,10 +320,13 @@ private: }; 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 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: const vf2d initialScale; const float homingRadius; + std::optional>homingTarget; }; \ No newline at end of file diff --git a/Adventures in Lestoria/DEFINES.h b/Adventures in Lestoria/DEFINES.h index 7bca4aca..2ecf8ac3 100644 --- a/Adventures in Lestoria/DEFINES.h +++ b/Adventures in Lestoria/DEFINES.h @@ -44,7 +44,7 @@ using BackdropName=std::string; using MonsterSpawnerID=int; #define INCLUDE_ANIMATION_DATA extern safemapANIMATION_DATA; -#define INCLUDE_MONSTER_LIST extern std::vector>MONSTER_LIST; +#define INCLUDE_MONSTER_LIST extern std::vector>MONSTER_LIST; #define INCLUDE_SPAWNER_LIST extern std::unordered_mapSPAWNER_LIST; #define INCLUDE_SPAWNER_CONTROLLER extern std::optional>SPAWNER_CONTROLLER; #define INCLUDE_DAMAGENUMBER_LIST extern std::vector>DAMAGENUMBER_LIST; diff --git a/Adventures in Lestoria/FrogTongue.cpp b/Adventures in Lestoria/FrogTongue.cpp index e08b15a1..47d8af69 100644 --- a/Adventures in Lestoria/FrogTongue.cpp +++ b/Adventures in Lestoria/FrogTongue.cpp @@ -65,7 +65,7 @@ void FrogTongue::Update(float fElapsedTime){ _PlayerHit(game->GetPlayer()); } if(friendly){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){ _MonsterHit(*m,m->GetMarkStacks()); hitList.insert(&*m); diff --git a/Adventures in Lestoria/IBullet.cpp b/Adventures in Lestoria/IBullet.cpp index 2c4ffe74..7d6a51c5 100644 --- a/Adventures in Lestoria/IBullet.cpp +++ b/Adventures in Lestoria/IBullet.cpp @@ -99,7 +99,7 @@ void IBullet::_Update(const float fElapsedTime){ const auto CollisionCheck=[&](){ if(simulated)return true; if(friendly){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){ if(hitList.find(&*m)==hitList.end()){ HurtDamageInfo damageData{&*m,damage,OnUpperLevel(),z,HurtFlag::NONE}; diff --git a/Adventures in Lestoria/LightningBolt.cpp b/Adventures in Lestoria/LightningBolt.cpp index 2c876282..5d612909 100644 --- a/Adventures in Lestoria/LightningBolt.cpp +++ b/Adventures in Lestoria/LightningBolt.cpp @@ -91,7 +91,7 @@ BulletDestroyState LightningBolt::MonsterHit(Monster&monster,const uint8_t markS fadeOutTime="Wizard.Ability 2.BulletFadeoutTime"_F; game->AddEffect(std::make_unique(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; - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(&*m==&monster||monster.OnUpperLevel()!=m->OnUpperLevel())continue; geom2d::linelineToTarget=geom2d::line(monster.GetPos(),m->GetPos()); float dist=lineToTarget.length(); diff --git a/Adventures in Lestoria/MajorHawk.cpp b/Adventures in Lestoria/MajorHawk.cpp index 421937bf..70bfdd6f 100644 --- a/Adventures in Lestoria/MajorHawk.cpp +++ b/Adventures in Lestoria/MajorHawk.cpp @@ -49,7 +49,7 @@ using A=Attribute; 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. - const int majorHawkCount=std::accumulate(MONSTER_LIST.begin(),MONSTER_LIST.end(),0,[&](const int&acc,const std::unique_ptr&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){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"); else HAWK(m,fElapsedTime,"Hawk"); //Use normal hawk behaviors when there are too many Major Hawks. diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 989eec73..f373ecb7 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -331,7 +331,7 @@ bool Monster::Update(float fElapsedTime){ std::for_each(buffList.begin(),buffList.end(),[&](Buff&b){b.Update(game,fElapsedTime);}); std::erase_if(buffList,[](Buff&b){return b.duration<=0;}); if(!HasIframes()){ - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ const float monsterRadius{GetCollisionRadius()}; const float otherMonsterRadius{m->GetCollisionRadius()}; if(&*m==this)continue; @@ -1257,29 +1257,29 @@ void Monster::ApplyMark(float time,uint8_t stackCount){ } } }else{ - game->AddToMarkedTargetList({this,stackCount,time}); + game->AddToMarkedTargetList({GetWeakPointer(),stackCount,time}); } markApplicationTimer=0.5f; } -std::optionalMonster::GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z){ - std::optionalclosestMonster; - std::optionalclosestGenericMonster; - for(std::unique_ptr&m:MONSTER_LIST){ +std::optional>Monster::GetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z){ + std::optional>closestMonster; + std::optional>closestGenericMonster; + for(std::shared_ptr&m:MONSTER_LIST){ geom2d::lineaimingLine=geom2d::line(point,m->GetPos()); float distToMonster=aimingLine.length(); float distToClosestPoint,distToClosestGenericPoint; - if(closestMonster.has_value())distToClosestPoint=geom2d::line(point,closestMonster.value()->GetPos()).length(); + if(closestMonster.has_value())distToClosestPoint=geom2d::line(point,closestMonster.value().lock()->GetPos()).length(); else distToClosestPoint=std::numeric_limits::max(); - if(closestGenericMonster.has_value())distToClosestGenericPoint=geom2d::line(point,closestGenericMonster.value()->GetPos()).length(); + if(closestGenericMonster.has_value())distToClosestGenericPoint=geom2d::line(point,closestGenericMonster.value().lock()->GetPos()).length(); else distToClosestGenericPoint=std::numeric_limits::max(); if(!m->InUndamageableState(onUpperLevel,z)){ if(distToClosestPoint>distToMonster&&distToMonster<=maxDistance){ - closestMonster=&*m; + closestMonster=m; } } if(m->IsAlive()&&distToClosestGenericPoint>distToMonster&&distToMonster<=maxDistance){ - closestGenericMonster=&*m; + closestGenericMonster=m; } } if(closestMonster.has_value()){ @@ -1302,4 +1302,8 @@ void Monster::AddBuff(BuffRestorationType type,BuffOverTimeType::BuffOverTimeTyp const bool Monster::CanMove()const{ return knockUpTimer==0.f&&IsAlive(); +} + +const std::weak_ptrMonster::GetWeakPointer()const{ + return *std::find_if(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr&ptr){return &*ptr==this;}); } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index 229c9c20..3a111299 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -204,9 +204,10 @@ public: 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. //Gets the nearest target that can be immediately targeted - static std::optionalGetNearestMonster(const vf2d point,const float maxDistance,const bool onUpperLevel,const float z); + static std::optional>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 CanMove()const; + const std::weak_ptrGetWeakPointer()const; private: //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. diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index b0777801..8faba25c 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -576,7 +576,7 @@ void Player::Update(float fElapsedTime){ if(item3.cooldown<0){ item3.cooldown=0; } - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ const float playerRadius{12*GetSizeMult()/2}; 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))){ @@ -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). std::optionalclosestPoint; std::optionalclosestGenericPoint; //Even if the monster is invulnerable, it might be worth targeting if a normal target is not found. - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ geom2d::lineaimingLine=geom2d::line(GetPos(),m->GetPos()); float distToMonster=aimingLine.length(); float distToClosestPoint=geom2d::line(GetPos(),closestPoint.value_or(MAX)).length(); diff --git a/Adventures in Lestoria/PurpleEnergyBall.cpp b/Adventures in Lestoria/PurpleEnergyBall.cpp index bc77f198..2e5adb18 100644 --- a/Adventures in Lestoria/PurpleEnergyBall.cpp +++ b/Adventures in Lestoria/PurpleEnergyBall.cpp @@ -38,19 +38,59 @@ All rights reserved. #include "BulletTypes.h" #include "AdventuresInLestoria.h" +#include "util.h" INCLUDE_game -PurpleEnergyBall::PurpleEnergyBall(vf2d pos,float radius,float homingRadius,int damage,bool upperLevel,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){} +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,speed,radius,damage,"mark_trail.png",upperLevel,hitsMultiple,INFINITE,false,friendly,col,scale/2.f,0.f){} + 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{ const vf2d lightOrbScale{initialScale.lerp(initialScale/2.f,sin(12*PI*game->GetRunTime())/2.f+0.5f)}; 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); Bullet::Draw(blendCol); +} + +BulletDestroyState PurpleEnergyBall::PlayerHit(Player*player) +{ + fadeOutTime="Witch.Auto Attack.BulletHitFadeoutTime"_F; + std::unique_ptrhitEffect{std::make_unique(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_ptrhitEffect{std::make_unique(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; } \ No newline at end of file diff --git a/Adventures in Lestoria/StoneGolem.cpp b/Adventures in Lestoria/StoneGolem.cpp index fbae9bc6..717e4790 100644 --- a/Adventures in Lestoria/StoneGolem.cpp +++ b/Adventures in Lestoria/StoneGolem.cpp @@ -71,7 +71,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str const auto PrepareSafeAreas=[&](){ m.VEC(A::STAGE_POLYGONS).clear(); - std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::unique_ptr&monsterPtr){ + std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr&monsterPtr){ if(monsterPtr->GetName()!="Stone Golem Pillar"&&monsterPtr->GetName()!="Breaking Stone Golem Pillar"){ 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")); } m.VEC(A::STAGE_POLYGONS).clear(); - std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::unique_ptr&monsterPtr){ + std::for_each(MONSTER_LIST.begin(),MONSTER_LIST.end(),[&](const std::shared_ptr&monsterPtr){ if(monsterPtr->GetName()!="Stone Golem Pillar")return; monsterPtr->_DealTrueDamage(ConfigInt("Shockwave.Pillar Damage")); }); diff --git a/Adventures in Lestoria/Thief.cpp b/Adventures in Lestoria/Thief.cpp index 48aef5b1..627b2900 100644 --- a/Adventures in Lestoria/Thief.cpp +++ b/Adventures in Lestoria/Thief.cpp @@ -72,7 +72,7 @@ bool Thief::AutoAttack(){ bool attack=false; Monster*closest=nullptr; float closest_dist=999999; - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(m->IsAlive()&& geom2d::overlaps(geom2d::circle(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle(m->GetPos(),m->GetSizeMult()*12))&& geom2d::line(GetWorldAimingLocation(),m->GetPos()).length()nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())}; + std::optional>nearestMonster{Monster::GetNearestMonster(pos,Trapper::ability1.precastInfo.range,p->OnUpperLevel(),p->GetZ())}; vf2d targetPos{pos}; if(nearestMonster.has_value()){ - targetPos=nearestMonster.value()->GetPos(); - nearestMonster.value()->ApplyMark("Trapper.Ability 1.Duration"_F,"Trapper.Ability 1.Stack Count"_I); + targetPos=nearestMonster.value().lock()->GetPos(); + 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))){ float drawDist{i*16.f}; diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 9d6aeb6d..0bc40504 100644 --- a/Adventures in Lestoria/Version.h +++ b/Adventures in Lestoria/Version.h @@ -39,7 +39,7 @@ All rights reserved. #define VERSION_MAJOR 1 #define VERSION_MINOR 2 #define VERSION_PATCH 3 -#define VERSION_BUILD 10324 +#define VERSION_BUILD 10345 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Warrior.cpp b/Adventures in Lestoria/Warrior.cpp index 9ed6a3c1..81cdde58 100644 --- a/Adventures in Lestoria/Warrior.cpp +++ b/Adventures in Lestoria/Warrior.cpp @@ -70,7 +70,7 @@ bool Warrior::AutoAttack(){ bool attack=false; Monster*closest=nullptr; float closest_dist=999999; - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(m->IsAlive()&& geom2d::overlaps(geom2d::circle(GetPos(),attack_range*GetSizeMult()*12),geom2d::circle(m->GetPos(),m->GetSizeMult()*12))&& geom2d::line(GetWorldAimingLocation(),m->GetPos()).length()AddEffect(std::make_unique(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::DAMAGE_REDUCTION,"Warrior.Ability 1.DamageReductionDuration"_F,"Warrior.Ability 1.DamageReduction"_F); - for(std::unique_ptr&m:MONSTER_LIST){ + for(std::shared_ptr&m:MONSTER_LIST){ if(m->GetSizeMult()>="Warrior.Ability 1.AffectedSizeRange"_f[0]&&m->GetSizeMult()<="Warrior.Ability 1.AffectedSizeRange"_f[1]&&geom2d::overlaps(geom2d::circle(p->GetPos(),12*"Warrior.Ability 1.Range"_I/100.f),geom2d::circle(m->GetPos(),m->GetSizeMult()*12))){ m->AddBuff(BuffType::SLOWDOWN,"Warrior.Ability 1.SlowdownDuration"_F,"Warrior.Ability 1.SlowdownAmt"_F); } diff --git a/Adventures in Lestoria/Witch.cpp b/Adventures in Lestoria/Witch.cpp index aaf5d4ce..ffe99f63 100644 --- a/Adventures in Lestoria/Witch.cpp +++ b/Adventures in Lestoria/Witch.cpp @@ -41,6 +41,8 @@ All rights reserved. #include "Effect.h" #include "AdventuresInLestoria.h" #include "config.h" +#include "SoundEffect.h" +#include "BulletTypes.h" INCLUDE_MONSTER_LIST INCLUDE_BULLET_LIST @@ -65,7 +67,12 @@ void Witch::OnUpdate(float fElapsedTime){ } 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(){ #pragma region Witch Right-click Ability (???) diff --git a/Adventures in Lestoria/assets/config/classes/Trapper.txt b/Adventures in Lestoria/assets/config/classes/Trapper.txt index f8ed027f..7d92b767 100644 --- a/Adventures in Lestoria/assets/config/classes/Trapper.txt +++ b/Adventures in Lestoria/assets/config/classes/Trapper.txt @@ -106,7 +106,7 @@ Trapper Short Name = EXTRAP Description = Explodes on touch or in 5 seconds, marks hit targets. Already marked targets triggers mark twice. Icon = explosive_trap.png - Cooldown = 0 + Cooldown = 30 Mana Cost = 0 # Whether or not this ability cancels casts. CancelCast = 0 diff --git a/Adventures in Lestoria/assets/config/classes/Witch.txt b/Adventures in Lestoria/assets/config/classes/Witch.txt index 4b3d03e3..ba9c526a 100644 --- a/Adventures in Lestoria/assets/config/classes/Witch.txt +++ b/Adventures in Lestoria/assets/config/classes/Witch.txt @@ -22,6 +22,9 @@ Witch # Whether or not this ability cancels casts. 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. Max Range = 1200 diff --git a/Adventures in Lestoria/assets/config/gfx/gfx.txt b/Adventures in Lestoria/assets/config/gfx/gfx.txt index 74693f0c..b4787d70 100644 --- a/Adventures in Lestoria/assets/config/gfx/gfx.txt +++ b/Adventures in Lestoria/assets/config/gfx/gfx.txt @@ -114,6 +114,7 @@ Images GFX_BearTrap = bear_trap.png GFX_ExplosiveTrap = explosive_trap.png GFX_Explosion = explosionframes.png + GFX_PurpleEnergyBallHit = purpleenergyball_hit.png GFX_Thief_Sheet = nico-thief.png GFX_Trapper_Sheet = nico-trapper.png diff --git a/Adventures in Lestoria/assets/gamepack.pak b/Adventures in Lestoria/assets/gamepack.pak index f289c7f1..18da1ae9 100644 Binary files a/Adventures in Lestoria/assets/gamepack.pak and b/Adventures in Lestoria/assets/gamepack.pak differ diff --git a/Adventures in Lestoria/assets/mark_trail.png b/Adventures in Lestoria/assets/mark_trail.png index 483c926c..601af13b 100644 Binary files a/Adventures in Lestoria/assets/mark_trail.png and b/Adventures in Lestoria/assets/mark_trail.png differ diff --git a/Adventures in Lestoria/assets/purpleenergyball_hit.png b/Adventures in Lestoria/assets/purpleenergyball_hit.png new file mode 100644 index 00000000..baa95aa2 Binary files /dev/null and b/Adventures in Lestoria/assets/purpleenergyball_hit.png differ diff --git a/Adventures in Lestoria/util.cpp b/Adventures in Lestoria/util.cpp index 2d994599..afe70583 100644 --- a/Adventures in Lestoria/util.cpp +++ b/Adventures in Lestoria/util.cpp @@ -205,4 +205,19 @@ float util::angle_difference(float angle_1, float angle_2) const float util::distance(const vf2d&point1,const vf2d&point2){ 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. } \ No newline at end of file diff --git a/Adventures in Lestoria/util.h b/Adventures in Lestoria/util.h index b55ca0a9..ff91d81f 100644 --- a/Adventures in Lestoria/util.h +++ b/Adventures in Lestoria/util.h @@ -62,6 +62,8 @@ namespace olc::util{ float angle_difference(float angle_1, float angle_2); std::string GetHash(std::string file); 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 diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 39c1f51d..ff0e17ec 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ