diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 2af748e7..0d47230f 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -732,70 +732,9 @@ void AiL::UpdateEffects(float fElapsedTime){ void AiL::UpdateBullets(float fElapsedTime){ for(auto it=BULLET_LIST.begin();it!=BULLET_LIST.end();++it){ Bullet*b=(*it).get(); - b->UpdateFadeTime(fElapsedTime); - b->Update(fElapsedTime); - b->animation.UpdateState(b->internal_animState,fElapsedTime); - if(!b->deactivated){ - float totalDistance=(b->vel*fElapsedTime).mag(); - int iterations=int(std::max(1.f,(b->vel*fElapsedTime).mag())); - int totalIterations=iterations; - vf2d finalBulletPos=b->pos+b->vel*fElapsedTime; - b->distanceTraveled+=totalDistance/24.f*100.f; - const auto CollisionCheck=[&](){ - if(b->friendly){ - for(std::unique_ptr&m:MONSTER_LIST){ - if(geom2d::overlaps(m->Hitbox(),geom2d::circle(b->pos,b->radius))){ - if(b->hitList.find(&*m)==b->hitList.end()&&m->Hurt(b->damage,b->OnUpperLevel(),0)){ - if(!b->hitsMultiple){ - if(b->MonsterHit(*m)){ - b->dead=true; - } - return false; - } - b->hitList.insert(&*m); - } - } - } - } else { - if(geom2d::overlaps(player->Hitbox(),geom2d::circle(b->pos,b->radius))){ - if(player->Hurt(b->damage,b->OnUpperLevel(),0)){ - if(b->PlayerHit(&*player)){ - b->dead=true; - } - return false; - } - } - } - return true; - }; - - while(iterations>0){ - iterations--; - b->pos+=(b->vel*fElapsedTime)/float(totalIterations); - if(!CollisionCheck()){ - goto nextBullet; - } - } - b->pos=finalBulletPos; - if(!CollisionCheck()){ - goto nextBullet; - } - }else{ - b->pos+=b->vel*fElapsedTime; - } - if(/*World size in PIXELS!*/vi2d worldSize=GetCurrentMap().MapData.MapSize*GetCurrentMap().MapData.TileSize;b->pos.x+b->radius<-WINDOW_SIZE.x||b->pos.x-b->radius>worldSize.x+WINDOW_SIZE.x||b->pos.y+b->radius<-WINDOW_SIZE.y||b->pos.y-b->radius>worldSize.y+WINDOW_SIZE.y){ - b->dead=true; - continue; - } - b->lifetime-=fElapsedTime; - if(b->lifetime<=0){ - b->dead=true; - continue; - } - nextBullet: - while(false); + b->_Update(fElapsedTime); } - std::erase_if(BULLET_LIST,[](std::unique_ptr&b){return b->dead;}); + std::erase_if(BULLET_LIST,[](std::unique_ptr&b){return b->IsDead();}); } const MonsterHurtList AiL::HurtEnemies(vf2d pos,float radius,int damage,bool upperLevel,float z)const{ MonsterHurtList hitList; diff --git a/Adventures in Lestoria/Arrow.cpp b/Adventures in Lestoria/Arrow.cpp index 02fe9d65..a60891ca 100644 --- a/Adventures in Lestoria/Arrow.cpp +++ b/Adventures in Lestoria/Arrow.cpp @@ -44,13 +44,13 @@ All rights reserved. INCLUDE_game -Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,float acc,float radius,int damage,bool upperLevel,bool friendly,Pixel col) - :finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(acc),targetPos(targetPos), +Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col) + :finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos), Bullet(pos,vel,radius,damage, "arrow.png",upperLevel,false,INFINITE,true,friendly,col){} -Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float acc,float radius,int damage,bool upperLevel,bool friendly,Pixel col) - :finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(acc), +Arrow::Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly,Pixel col) + :finalDistance(geom2d::line(pos,targetPos).length()*1.2f),acc(PI/2*250),targetPos(targetPos), Bullet(pos,vel,radius,damage,std::string(gfx),upperLevel,false,INFINITE,true,friendly,col){} void Arrow::Update(float fElapsedTime){ @@ -63,8 +63,32 @@ void Arrow::Update(float fElapsedTime){ } } -void Arrow::PointToBestTargetPath(const int perceptionLevel){ - //TODO: Figure out arrow target and spawn an arrow. +void Arrow::PointToBestTargetPath(const uint8_t perceptionLevel){ + if(perceptionLevel>90)ERR(std::format("WARNING! Perception level {} provided. Acceptable range is 0-90.",perceptionLevel)); + Arrow copiedArrow{*this}; + float closestDist=std::numeric_limits::max(); + vf2d closestVel{}; + for(float angle=util::degToRad(-perceptionLevel);angle<=util::degToRad(perceptionLevel);angle+=util::degToRad(1.f)){ + Arrow simulatedArrow{copiedArrow}; + vf2d simulatedAimingDir=simulatedArrow.vel.polar(); + simulatedAimingDir.y+=angle; + + simulatedArrow.vel=simulatedAimingDir.cart(); + vf2d originalSimulatedShootingAngle=simulatedArrow.vel; + while(!simulatedArrow.deactivated){ + simulatedArrow.SimulateUpdate(1/30.f); + float distToPlayer=geom2d::line(simulatedArrow.pos,game->GetPlayer()->GetPos()).length(); + if(distToPlayer&m:MONSTER_LIST){ + if(geom2d::overlaps(m->Hitbox(),geom2d::circle(pos,radius))){ + if(hitList.find(&*m)==hitList.end()&&m->Hurt(damage,OnUpperLevel(),0)){ + if(!hitsMultiple){ + if(MonsterHit(*m)){ + dead=true; + } + return false; + } + hitList.insert(&*m); + } + } + } + } else { + if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){ + if(game->GetPlayer()->Hurt(damage,OnUpperLevel(),0)){ + if(PlayerHit(&*game->GetPlayer())){ + dead=true; + } + return false; + } + } + } + return true; + }; + + while(iterations>0){ + iterations--; + pos+=(vel*fElapsedTime)/float(totalIterations); + if(!CollisionCheck()){ + return; + } + } + pos=finalBulletPos; + if(!CollisionCheck()){ + return; + } + }else{ + pos+=vel*fElapsedTime; + } + if(/*World size in PIXELS!*/vi2d worldSize=game->GetCurrentMapData().MapSize*game->GetCurrentMapData().TileSize;pos.x+radius<-WINDOW_SIZE.x||pos.x-radius>worldSize.x+WINDOW_SIZE.x||pos.y+radius<-WINDOW_SIZE.y||pos.y-radius>worldSize.y+WINDOW_SIZE.y){ + dead=true; + return; + } + lifetime-=fElapsedTime; + if(lifetime<=0){ + dead=true; + return; + } +} + void Bullet::Draw()const{ auto lerp=[](uint8_t f1,uint8_t f2,float t){return uint8_t((float(f2)*t)+f1*(1-t));}; @@ -81,4 +153,8 @@ void Bullet::Draw()const{ bool Bullet::PlayerHit(Player*player){return true;} bool Bullet::MonsterHit(Monster&monster){return true;} -bool Bullet::OnUpperLevel(){return upperLevel;} \ No newline at end of file +bool Bullet::OnUpperLevel(){return upperLevel;} + +const bool Bullet::IsDead()const{ + return dead; +} \ No newline at end of file diff --git a/Adventures in Lestoria/Bullet.h b/Adventures in Lestoria/Bullet.h index af34eff9..d81a32ad 100644 --- a/Adventures in Lestoria/Bullet.h +++ b/Adventures in Lestoria/Bullet.h @@ -42,7 +42,6 @@ All rights reserved. #include "DEFINES.h" struct Bullet{ - friend class AiL; vf2d vel; vf2d pos; float radius; @@ -62,8 +61,10 @@ protected: float distanceTraveled=0.f; private: void UpdateFadeTime(float fElapsedTime); + virtual void Update(float fElapsedTime); vf2d scale={1,1}; bool dead=false; //When marked as dead it wil be removed by the next frame. + bool simulated=false; //A simulated bullet cannot interact / damage things in the world. It's simply used for simulating the trajectory and potential path of the bullet public: Animate2D::Animationanimation; Animate2D::AnimationState internal_animState; @@ -73,7 +74,9 @@ public: //Initializes a bullet with an animation. Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple=false,float lifetime=INFINITE,bool rotatesWithAngle=false,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1}); public: - virtual void Update(float fElapsedTime); + + void SimulateUpdate(const float fElapsedTime); + void _Update(const float fElapsedTime); //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. virtual bool PlayerHit(Player*player); //Used by special bullets to control custom despawning behavior! Return true when the bullet should be destroyed. Return false to handle it otherwise (like deactivating it instead). You become responsible for getting rid of the bullet. @@ -81,4 +84,5 @@ public: Animate2D::Frame GetFrame()const; virtual void Draw()const; bool OnUpperLevel(); + const bool IsDead()const; }; \ No newline at end of file diff --git a/Adventures in Lestoria/BulletTypes.h b/Adventures in Lestoria/BulletTypes.h index f3020b60..c9818378 100644 --- a/Adventures in Lestoria/BulletTypes.h +++ b/Adventures in Lestoria/BulletTypes.h @@ -68,10 +68,12 @@ struct Arrow:public Bullet{ float finalDistance=0; float acc=PI/2*250; vf2d targetPos; - Arrow(vf2d pos,vf2d targetPos,vf2d vel,float acc,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); - Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float acc,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); + Arrow(vf2d pos,vf2d targetPos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); + Arrow(vf2d pos,vf2d targetPos,vf2d vel,const std::string_view gfx,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - void PointToBestTargetPath(const int perceptionLevel); + // Change the arrow's heading by predicting a path somewhere in the future and aiming at the closest possible spot to its targetPos. + // The perception level can be a value from 0-90 indicating the sweep angle to check beyond the initial aiming angle. + void PointToBestTargetPath(const uint8_t perceptionLevel); bool PlayerHit(Player*player)override; bool MonsterHit(Monster&monster)override; }; diff --git a/Adventures in Lestoria/Goblin_Bow.cpp b/Adventures in Lestoria/Goblin_Bow.cpp index 60d201b9..810f77ff 100644 --- a/Adventures in Lestoria/Goblin_Bow.cpp +++ b/Adventures in Lestoria/Goblin_Bow.cpp @@ -59,6 +59,7 @@ using A=Attribute; void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string strategy){ #pragma region Phase, Animation, and Helper function setup enum PhaseName{ + INITIALIZE_PERCEPTION, MOVE, WINDUP, }; @@ -67,6 +68,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra m.F(A::ATTACK_COOLDOWN)+=fElapsedTime; switch(m.phase){ + case INITIALIZE_PERCEPTION:{ + m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level"); + m.phase=MOVE; + }break; case MOVE:{ float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos()); @@ -107,9 +112,11 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra if(m.F(A::SHOOT_TIMER)<=0){ geom2d::line pointTowardsPlayer(m.GetPos(),game->GetPlayer()->GetPos()); vf2d extendedLine=pointTowardsPlayer.upoint(1.1f); - CreateBullet(Arrow)(m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",PI/2*ConfigFloat("Arrow Spd"),ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet; + CreateBullet(Arrow)(m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet; Arrow&arrow=static_cast(*BULLET_LIST.back()); - arrow.PointToBestTargetPath(0); + arrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL)); + + m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase")); m.phase=MOVE; } m.B(A::RANDOM_DIRECTION)=util::random()%2; diff --git a/Adventures in Lestoria/MonsterAttribute.h b/Adventures in Lestoria/MonsterAttribute.h index 6c0c4949..863999f7 100644 --- a/Adventures in Lestoria/MonsterAttribute.h +++ b/Adventures in Lestoria/MonsterAttribute.h @@ -110,5 +110,6 @@ enum class Attribute{ ATTACK_TYPE, ATTACK_COOLDOWN, RANDOM_DIRECTION, - RANDOM_RANGE + RANDOM_RANGE, + PERCEPTION_LEVEL, }; \ No newline at end of file diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 26f7d250..96095e9c 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -680,7 +680,7 @@ void Player::Update(float fElapsedTime){ vf2d extendedLine=pointTowardsCursor.upoint(1.1f); float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); attack_cooldown_timer=ARROW_ATTACK_COOLDOWN; - BULLET_LIST.push_back(std::make_unique(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F-PI/8*"Ranger.Ability 1.ArrowSpd"_F)}+movementVelocity/1.5f,PI/2*"Ranger.Auto Attack.ArrowSpd"_F,12*"Ranger.Ability 1.ArrowRadius"_F/100,int(GetAttack()*"Ranger.Ability 1.DamageMult"_F),OnUpperLevel(),true))); + BULLET_LIST.push_back(std::make_unique(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F-PI/8*"Ranger.Ability 1.ArrowSpd"_F)}+movementVelocity/1.5f,12*"Ranger.Ability 1.ArrowRadius"_F/100,int(GetAttack()*"Ranger.Ability 1.DamageMult"_F),OnUpperLevel(),true))); SetAnimationBasedOnTargetingDirection(angleToCursor); rapidFireTimer=RAPID_FIRE_SHOOT_DELAY; }else{ diff --git a/Adventures in Lestoria/Ranger.cpp b/Adventures in Lestoria/Ranger.cpp index eaabb1e0..208b5988 100644 --- a/Adventures in Lestoria/Ranger.cpp +++ b/Adventures in Lestoria/Ranger.cpp @@ -71,7 +71,7 @@ bool Ranger::AutoAttack(){ vf2d extendedLine=pointTowardsCursor.upoint(1.1f); float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); attack_cooldown_timer=ARROW_ATTACK_COOLDOWN-GetAttackRecoveryRateReduction(); - BULLET_LIST.push_back(std::make_unique(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F-PI/8*"Ranger.Auto Attack.ArrowSpd"_F)}+movementVelocity/1.5f,PI/2*"Ranger.Auto Attack.ArrowSpd"_F,"Ranger.Auto Attack.Radius"_F,int(GetAttack()*"Ranger.Auto Attack.DamageMult"_F),OnUpperLevel(),true))); + BULLET_LIST.push_back(std::make_unique(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Auto Attack.ArrowSpd"_F-PI/8*"Ranger.Auto Attack.ArrowSpd"_F)}+movementVelocity/1.5f,"Ranger.Auto Attack.Radius"_F,int(GetAttack()*"Ranger.Auto Attack.DamageMult"_F),OnUpperLevel(),true))); SetState(State::SHOOT_ARROW); SetAnimationBasedOnTargetingDirection(angleToCursor); SoundEffect::PlaySFX("Ranger.Auto Attack.Sound"_S,SoundEffect::CENTERED); @@ -139,7 +139,7 @@ void Ranger::InitializeClassAbilities(){ const float newAngle=shootingAngle+leftAngle/2+i*increment; geom2d::line pointTowardsCursor=geom2d::line(p->GetPos(),p->GetPos()+vf2d{cos(newAngle),sin(newAngle)}*shootingDist); vf2d extendedLine=pointTowardsCursor.upoint(1.1f); - BULLET_LIST.push_back(std::make_unique(Arrow(p->GetPos(),extendedLine,vf2d{cos(newAngle)*"Ranger.Ability 3.ArrowSpd"_F,float(sin(newAngle)*"Ranger.Ability 3.ArrowSpd"_F-PI/8*"Ranger.Ability 3.ArrowSpd"_F)}+p->movementVelocity,PI/2*"Ranger.Auto Attack.ArrowSpd"_F,12*"Ranger.Ability 3.ArrowRadius"_F/100,int(p->GetAttack()*"Ranger.Ability 3.DamageMult"_F),p->OnUpperLevel(),true))); + BULLET_LIST.push_back(std::make_unique(Arrow(p->GetPos(),extendedLine,vf2d{cos(newAngle)*"Ranger.Ability 3.ArrowSpd"_F,float(sin(newAngle)*"Ranger.Ability 3.ArrowSpd"_F-PI/8*"Ranger.Ability 3.ArrowSpd"_F)}+p->movementVelocity,12*"Ranger.Ability 3.ArrowRadius"_F/100,int(p->GetAttack()*"Ranger.Ability 3.DamageMult"_F),p->OnUpperLevel(),true))); } p->rangerShootAnimationTimer=0.3f; p->SetState(State::SHOOT_ARROW); diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 3e85c186..fecceb19 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 0 -#define VERSION_BUILD 9153 +#define VERSION_BUILD 9169 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/assets/config/MonsterStrategies.txt b/Adventures in Lestoria/assets/config/MonsterStrategies.txt index 2d5038e0..1ab31ec5 100644 --- a/Adventures in Lestoria/assets/config/MonsterStrategies.txt +++ b/Adventures in Lestoria/assets/config/MonsterStrategies.txt @@ -595,7 +595,7 @@ MonsterStrategy # How long it takes to prepare the attack once an attack is queued. Attack Windup Time = 1.0s - Arrow Spd = 250 + Arrow Spd = 350 Arrow Hitbox Radius = 8 @@ -606,5 +606,11 @@ MonsterStrategy # Does not move and shoots from anywhere in these ranges. Stand Still and Shoot Range = 700,1000 # Anything outside the max "Stand Still and Shoot Range" will cause the monster to move towards the target instead. + + # The perception level indicates how accurate the bow user's shots become over time. Perception can be between 0-90. A perception level of 90 should never miss. This doesn't necessarily mean lower numbers will miss, just that it doesn't auto-correct for error as much. + Starting Perception Level = 0 + # Every shot taken, the bow user's perception level will increase by this amount. + Perception Level Increase = 2.5 + Maximum Perception Level = 45 } } \ No newline at end of file diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 0134f2f9..6064445f 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ