diff --git a/Adventures in Lestoria/AdventuresInLestoria.cpp b/Adventures in Lestoria/AdventuresInLestoria.cpp index 4383c915..2c7616e5 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.cpp +++ b/Adventures in Lestoria/AdventuresInLestoria.cpp @@ -2462,7 +2462,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){ GetPlayer()->SetState(State::NORMAL); GetPlayer()->GetCastInfo()={}; GetPlayer()->ResetAccumulatedXP(); - GetPlayer()->SetIframes(0.f); + GetPlayer()->_SetIframes(0.f); GetPlayer()->SetInvisible(false); GetPlayer()->ResetVelocity(); @@ -2745,7 +2745,7 @@ void AiL::_PrepareLevel(MapName map,MusicChange changeMusic){ for(NPCData data:game->MAP_DATA[game->GetCurrentLevel()].npcs){ if(Unlock::IsUnlocked(data.unlockCondition)){ MONSTER_LIST.push_back(std::make_unique(data.spawnPos,MONSTER_DATA[data.name])); - MONSTER_LIST.back()->SetIframes(INFINITE); + MONSTER_LIST.back()->ApplyIframes(INFINITE); MONSTER_LIST.back()->npcData=data; } } diff --git a/Adventures in Lestoria/AdventuresInLestoria.h b/Adventures in Lestoria/AdventuresInLestoria.h index c8302068..f1b2284a 100644 --- a/Adventures in Lestoria/AdventuresInLestoria.h +++ b/Adventures in Lestoria/AdventuresInLestoria.h @@ -62,7 +62,8 @@ All rights reserved. class SteamKeyboardCallbackHandler; class SteamStatsReceivedHandler; -#define CreateBullet(type) BULLET_LIST.push_back(std::make_unique(type +#define CreateBullet(type) INCLUDE_BULLET_LIST \ +BULLET_LIST.push_back(std::make_unique(type #define EndBullet )); using HurtReturnValue=bool; diff --git a/Adventures in Lestoria/Bullet.cpp b/Adventures in Lestoria/Bullet.cpp index 0e46dfec..b60f5147 100644 --- a/Adventures in Lestoria/Bullet.cpp +++ b/Adventures in Lestoria/Bullet.cpp @@ -47,11 +47,11 @@ INCLUDE_GFX INCLUDE_MONSTER_LIST INCLUDE_WINDOW_SIZE -Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale) - :pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale){}; +Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly,Pixel col,vf2d scale,float rot) + :pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),rot(rot){}; -Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale) - :pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale){ +Bullet::Bullet(vf2d pos,vf2d vel,float radius,int damage,std::string animation,bool upperLevel,bool hitsMultiple,float lifetime,bool rotatesWithAngle,bool friendly,Pixel col,vf2d scale,float rot) + :pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),rot(rot){ this->animation.AddState(animation,ANIMATION_DATA[animation]); this->animation.ChangeState(internal_animState,animation); }; @@ -94,7 +94,7 @@ void Bullet::_Update(const float fElapsedTime){ if(geom2d::overlaps(m->BulletCollisionHitbox(),geom2d::circle(pos,radius))){ if(hitList.find(&*m)==hitList.end()&&m->Hurt(damage,OnUpperLevel(),z)){ if(!hitsMultiple){ - if(MonsterHit(*m)){ + if(_MonsterHit(*m)){ dead=true; } return false; @@ -106,7 +106,7 @@ void Bullet::_Update(const float fElapsedTime){ } else { if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){ if(game->GetPlayer()->Hurt(damage,OnUpperLevel(),z)){ - if(PlayerHit(&*game->GetPlayer())){ + if(_PlayerHit(&*game->GetPlayer())){ dead=true; } return false; @@ -159,13 +159,23 @@ void Bullet::Draw()const{ } if(animated){ - game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,blendCol); + game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY,GetFrame().GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2+rot:rot,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,blendCol); }else{ - game->view.DrawDecal(pos-vf2d{0,GetZ()}+drawOffsetY-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle.png"].Decal(),scale,blendCol); - game->view.DrawDecal(pos-vf2d{0,GetZ()}+drawOffsetY-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle_outline.png"].Decal(),scale,Pixel{WHITE.r,WHITE.g,WHITE.b,blendCol.a}); + game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle.png"].Decal(),rot,GFX["circle.png"].Sprite()->Size()/2,scale,blendCol); + game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle_outline.png"].Decal(),rot,GFX["circle.png"].Sprite()->Size()/2,scale,Pixel{WHITE.r,WHITE.g,WHITE.b,blendCol.a}); } } +bool Bullet::_PlayerHit(Player*player){ + const bool hitOccurred=PlayerHit(player); + if(iframeTimerOnHit>0.f)player->ApplyIframes(iframeTimerOnHit); + return hitOccurred; +} +bool Bullet::_MonsterHit(Monster&monster){ + const bool hitOccurred=MonsterHit(monster); + if(iframeTimerOnHit>0.f)monster.ApplyIframes(iframeTimerOnHit); + return hitOccurred; +} bool Bullet::PlayerHit(Player*player){return true;} bool Bullet::MonsterHit(Monster&monster){return true;} bool Bullet::OnUpperLevel(){return upperLevel;} @@ -176,4 +186,9 @@ const bool Bullet::IsDead()const{ const float Bullet::GetZ()const{ return z; +} + +Bullet&Bullet::SetIframeTimeOnHit(float iframeTimer){ + iframeTimerOnHit=iframeTimer; + return *this; } \ No newline at end of file diff --git a/Adventures in Lestoria/Bullet.h b/Adventures in Lestoria/Bullet.h index b4d533fc..0b789671 100644 --- a/Adventures in Lestoria/Bullet.h +++ b/Adventures in Lestoria/Bullet.h @@ -57,6 +57,7 @@ struct Bullet{ bool upperLevel=false; bool alwaysOnTop=false; float z=0.f; + float rot=0.f; protected: float fadeOutTimer=0; float distanceTraveled=0.f; @@ -66,27 +67,33 @@ private: virtual void Update(float fElapsedTime); 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 + float iframeTimerOnHit{0.f}; protected: float drawOffsetY{}; + bool _PlayerHit(Player*player); + bool _MonsterHit(Monster&monster); public: Animate2D::Animationanimation; Animate2D::AnimationState internal_animState; std::sethitList; virtual ~Bullet()=default; - Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1}); + Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float rot=0.f); //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}); + 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},float rot=0.f); public: 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. + //DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! 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. + //DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! virtual bool MonsterHit(Monster&monster); Animate2D::Frame GetFrame()const; virtual void Draw()const; bool OnUpperLevel(); const bool IsDead()const; const float GetZ()const; + Bullet&SetIframeTimeOnHit(float iframeTimer); }; \ No newline at end of file diff --git a/Adventures in Lestoria/BulletTypes.h b/Adventures in Lestoria/BulletTypes.h index 3033fcc9..ac6d67f4 100644 --- a/Adventures in Lestoria/BulletTypes.h +++ b/Adventures in Lestoria/BulletTypes.h @@ -43,24 +43,24 @@ struct EnergyBolt:public Bullet{ float lastParticleSpawn=0; EnergyBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct FireBolt:public Bullet{ float lastParticleSpawn=0; FireBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct LightningBolt:public Bullet{ float lastParticleSpawn=0; LightningBolt(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct Arrow:public Bullet{ @@ -74,16 +74,16 @@ struct Arrow:public Bullet{ // 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; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct ChargedArrow:public Bullet{ vf2d lastLaserPos; ChargedArrow(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct FrogTongue:public Bullet{ @@ -94,16 +94,16 @@ struct FrogTongue:public Bullet{ Monster&sourceMonster; FrogTongue(Monster&sourceMonster,vf2d targetPos,float lifetime,int damage,bool upperLevel,float knockbackStrength=1.0f,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! void Draw()const override; }; struct Wisp:public Bullet{ Wisp(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; enum class HorizontalFlip{ @@ -130,8 +130,8 @@ struct DaggerStab:public Bullet{ DirectionOffsets daggerPositionOffsets; DaggerStab(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerStabDistance,const DirectionOffsets offsets,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct DaggerSlash:public Bullet{ @@ -143,8 +143,8 @@ struct DaggerSlash:public Bullet{ float knockbackAmt; DaggerSlash(Monster&sourceMonster,float radius,int damage,const float knockbackAmt,bool upperLevel,const Direction facingDir,const float daggerFrameDuration,const float daggerSlashDistance,bool friendly=false,Pixel col=WHITE); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! }; struct Bomb:public Bullet{ @@ -158,8 +158,8 @@ struct Bomb:public Bullet{ float bombKnockbackFactor{0.f}; Bomb(const vf2d pos,const float z,const float gravity,const float detonationTime,const float bombFadeoutTime,float bombKnockbackFactor,const vf2d targetPos,const float radius,const int damage,const bool upperLevel,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1}); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! void Draw()const override; }; @@ -179,8 +179,8 @@ struct LevitatingRock:public Bullet{ //The lock on time is how long the rocks will follow the player. The wait time is how long to wait until firing. LevitatingRock(const Monster&attachedTarget,const vf2d&attackingTarget,const float fadeInTime,const float facingRotOffset,const float distance,const float lockOnTime,const float waitTime,const float targetSpd,const float radius,const int damage,const bool upperLevel,const bool friendly=false,const Pixel col=WHITE,const vf2d scale={1,1}); void Update(float fElapsedTime)override; - bool PlayerHit(Player*player)override; - bool MonsterHit(Monster&monster)override; + bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!! + bool MonsterHit(Monster&monster)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _MonsterHit()!! void Draw()const override; void AssignMaster(LevitatingRock*masterRock); const bool IsMaster()const; diff --git a/Adventures in Lestoria/FrogTongue.cpp b/Adventures in Lestoria/FrogTongue.cpp index 99ba5eb7..b19efc8d 100644 --- a/Adventures in Lestoria/FrogTongue.cpp +++ b/Adventures in Lestoria/FrogTongue.cpp @@ -62,12 +62,12 @@ void FrogTongue::Update(float fElapsedTime){ geom2d::linetongueLine(pos+drawVec,tongueEndPos); if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){ - PlayerHit(game->GetPlayer()); + _PlayerHit(game->GetPlayer()); } if(friendly){ for(std::unique_ptr&m:MONSTER_LIST){ if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){ - MonsterHit(*m); + _MonsterHit(*m); hitList.insert(&*m); } } diff --git a/Adventures in Lestoria/Monster.cpp b/Adventures in Lestoria/Monster.cpp index 22e65a06..975836d0 100644 --- a/Adventures in Lestoria/Monster.cpp +++ b/Adventures in Lestoria/Monster.cpp @@ -628,8 +628,10 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){ if(game->InBossEncounter()){ game->BossDamageDealt(int(mod_dmg)); } - GetInt(Attribute::HITS_UNTIL_DEATH)=std::max(0,GetInt(Attribute::HITS_UNTIL_DEATH)-1); - SetIframes(GetFloat(Attribute::IFRAME_TIME_UPON_HIT)); + + using A=Attribute; + GetInt(A::HITS_UNTIL_DEATH)=std::max(0,GetInt(A::HITS_UNTIL_DEATH)-1); + ApplyIframes(GetFloat(A::IFRAME_TIME_UPON_HIT)); return true; } @@ -637,7 +639,7 @@ bool Monster::Hurt(int damage,bool onUpperLevel,float z){ const bool Monster::IsAlive()const{ return hp>0||!diesNormally; } -vf2d&Monster::GetTargetPos(){ +const vf2d&Monster::GetTargetPos()const{ return target; } @@ -1015,7 +1017,7 @@ DeathSpawnInfo::DeathSpawnInfo(const std::string_view monsterName,const uint8_t void DeathSpawnInfo::Spawn(const vf2d monsterDeathPos,const bool onUpperLevel){ for(uint8_t i=0;iSpawnMonster(monsterDeathPos+spawnLocOffset,MONSTER_DATA.at(monsterSpawnName),onUpperLevel).SetIframes(0.25f); + game->SpawnMonster(monsterDeathPos+spawnLocOffset,MONSTER_DATA.at(monsterSpawnName),onUpperLevel).ApplyIframes(0.25f); } } @@ -1062,7 +1064,11 @@ const bool Monster::IsDead()const{ return !IsAlive(); } -void Monster::SetIframes(const float iframeTime){ +void Monster::ApplyIframes(const float iframeTime){ + iframe_timer=std::max(iframe_timer,iframeTime); +} + +void Monster::_SetIframes(const float iframeTime){ iframe_timer=iframeTime; } @@ -1072,4 +1078,8 @@ const std::string_view Monster::GetDisplayName()const{ const bool Monster::HasArrowIndicator()const{ return MONSTER_DATA.at(GetName()).HasArrowIndicator(); +} + +const bool Monster::ReachedTargetPos(const float maxDistanceFromTarget)const{ + return util::distance(GetPos(),GetTargetPos())<=maxDistanceFromTarget; } \ No newline at end of file diff --git a/Adventures in Lestoria/Monster.h b/Adventures in Lestoria/Monster.h index bacb403e..1456c80a 100644 --- a/Adventures in Lestoria/Monster.h +++ b/Adventures in Lestoria/Monster.h @@ -91,7 +91,7 @@ public: //If you need to hurt multiple enemies try AiL::HurtEnemies() bool Hurt(int damage,bool onUpperLevel,float z); const bool IsAlive()const; - vf2d&GetTargetPos(); + const vf2d&GetTargetPos()const; Direction GetFacingDirection()const; //Will make the monster face in the correct direction relative to a given target point to look at. void UpdateFacingDirection(vf2d facingTargetPoint); @@ -134,7 +134,9 @@ public: void SetState(State::State newState); static void InitializeStrategies(); const bool HasIframes()const; - void SetIframes(const float iframeTime); + void ApplyIframes(const float iframeTime); + //NOTE: Will directly override the iframe timer for the player, as opposed to _ApplyIframes which actually keeps the longest current iframe time. + void _SetIframes(const float iframeTime); const float GetZ()const; void SetZ(float z); const std::function&GetStrategy()const; @@ -176,6 +178,7 @@ public: const bool IsDead()const; const std::string_view GetDisplayName()const; const bool HasArrowIndicator()const; + const bool ReachedTargetPos(const float maxDistanceFromTarget=4.f)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. @@ -184,7 +187,7 @@ private: vf2d pos; vf2d vel={0,0}; float friction=400; - vf2d target={0,0}; + vf2d target{}; float targetAcquireTimer=0; vf2d spawnPos; int hp; diff --git a/Adventures in Lestoria/MonsterAttribute.h b/Adventures in Lestoria/MonsterAttribute.h index e0eb5e67..7246fe13 100644 --- a/Adventures in Lestoria/MonsterAttribute.h +++ b/Adventures in Lestoria/MonsterAttribute.h @@ -117,4 +117,5 @@ enum class Attribute{ FLYING_HEIGHT, TARGET_FLYING_HEIGHT, SPAWNER_TIMER, + ATTACK_CHOICE, }; \ No newline at end of file diff --git a/Adventures in Lestoria/Player.cpp b/Adventures in Lestoria/Player.cpp index 12de3f9f..e831817d 100644 --- a/Adventures in Lestoria/Player.cpp +++ b/Adventures in Lestoria/Player.cpp @@ -1068,7 +1068,11 @@ void Player::SetAnimationBasedOnTargetingDirection(float targetDirection){ } } -void Player::SetIframes(float duration){ +void Player::ApplyIframes(float duration){ + iframe_time=std::max(iframe_time,duration); +} + +void Player::_SetIframes(float duration){ iframe_time=duration; } @@ -1537,7 +1541,6 @@ void Player::SetInvisible(const bool invisibleState){ invisibility=invisibleState; } - const bool Player::IsInvisible()const{ return invisibility; } @@ -1546,7 +1549,6 @@ void Player::ResetVelocity(){ vel={}; } - const float Player::GetHealthGrowthRate()const{ return hpGrowthRate; } diff --git a/Adventures in Lestoria/Player.h b/Adventures in Lestoria/Player.h index 57ae99ce..6cc119a6 100644 --- a/Adventures in Lestoria/Player.h +++ b/Adventures in Lestoria/Player.h @@ -142,7 +142,9 @@ public: bool CanAct(Ability&ability); void Knockback(vf2d vel); void ProximityKnockback(const vf2d centerPoint,const float knockbackFactor); - void SetIframes(float duration); + void ApplyIframes(float duration); + //NOTE: Will directly override the iframe timer for the player, as opposed to _ApplyIframes which actually keeps the longest current iframe time. + void _SetIframes(float duration); void RestoreMana(int amt,bool suppressDamageNumber=false); void ConsumeMana(int amt); //Returns true if the move was valid and successful. diff --git a/Adventures in Lestoria/RunTowards.cpp b/Adventures in Lestoria/RunTowards.cpp index 3a00fb0f..a7d2822b 100644 --- a/Adventures in Lestoria/RunTowards.cpp +++ b/Adventures in Lestoria/RunTowards.cpp @@ -91,6 +91,8 @@ void Monster::STRATEGY::RUN_TOWARDS(Monster&m,float fElapsedTime,std::string str SoundEffect::PlaySFX("Slime Land",m.GetPos()); }; + if(m.GetState()==State::NORMAL&&m.target!=vf2d{})m.SetState(State::MOVE_TOWARDS); + switch(m.GetState()){ case State::MOVE_TOWARDS:{ if(geom2d::line(m.pos,m.target).length()>100*fElapsedTime*m.GetMoveSpdMult()){ @@ -161,9 +163,10 @@ void Monster::STRATEGY::RUN_TOWARDS(Monster&m,float fElapsedTime,std::string str if(dist<12*m.GetSizeMult()){ int jumpDamage=0; if(ConfigInt("JumpAttackDamage")>0)jumpDamage=ConfigInt("JumpAttackDamage"); - game->GetPlayer()->Hurt(jumpDamage,m.OnUpperLevel(),m.GetZ()); - game->GetPlayer()->ProximityKnockback(m.GetPos(),float(ConfigInt("JumpKnockbackFactor"))); - game->GetPlayer()->SetIframes(game->GetPlayer()->GetIframeTime()+0.3f); + if(game->GetPlayer()->Hurt(jumpDamage,m.OnUpperLevel(),m.GetZ())){ + game->GetPlayer()->ProximityKnockback(m.GetPos(),float(ConfigInt("JumpKnockbackFactor"))); + game->GetPlayer()->ApplyIframes(0.3f); + } } m.SetZ(0); Landed(); diff --git a/Adventures in Lestoria/SlimeKing.cpp b/Adventures in Lestoria/SlimeKing.cpp index 34fea963..958cfb44 100644 --- a/Adventures in Lestoria/SlimeKing.cpp +++ b/Adventures in Lestoria/SlimeKing.cpp @@ -205,16 +205,17 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat game->AddEffect(std::make_unique(m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*m.GetSizeMult()*8,util::random(1),"circle.png",m.OnUpperLevel(),vf2d{1,1},0.5,vf2d{cos(randomDir)*util::random(5),sin(randomDir)*-util::random(15)-5}*30,BLACK),true); } if(dist<12*m.GetSizeMult()){ - game->GetPlayer()->Hurt(ConfigInt("JumpAttackDamage"),m.OnUpperLevel(),m.GetZ()); - if(dist<0.001){ - float randomDir=util::random(2*PI); - lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1}; - } - game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor"))); - if(m.phase!=2){ - game->GetPlayer()->SetIframes(1.f); - }else{ //In phase 2 you can get hit by multiple knockbacks, so the iframe time is a lot shorter. - game->GetPlayer()->SetIframes(0.2f); + if(game->GetPlayer()->Hurt(ConfigInt("JumpAttackDamage"),m.OnUpperLevel(),m.GetZ())){ + if(dist<0.001){ + float randomDir=util::random(2*PI); + lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1}; + } + game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor"))); + if(m.phase!=2){ + game->GetPlayer()->ApplyIframes(1.f); + }else{ //In phase 2 you can get hit by multiple knockbacks, so the iframe time is a lot shorter. + game->GetPlayer()->ApplyIframes(0.2f); + } } } m.SetZ(0); @@ -248,7 +249,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat m.size=ConfigInt("Phase1.Size")/100.f; m.diesNormally=false; m.F(A::IFRAME_TIME_UPON_HIT)=0; - m.SetIframes(ConfigFloat("Phase5.IframeTimePerHit")); + m.ApplyIframes(ConfigFloat("Phase5.IframeTimePerHit")); m.phase=ConfigInt("StartPhase"); }break; case 1:{ diff --git a/Adventures in Lestoria/State_MainMenu.cpp b/Adventures in Lestoria/State_MainMenu.cpp index 1005bff9..790071bc 100644 --- a/Adventures in Lestoria/State_MainMenu.cpp +++ b/Adventures in Lestoria/State_MainMenu.cpp @@ -55,13 +55,13 @@ void State_MainMenu::OnStateChange(GameState*prevState){ game->LoadLevel("starting_map"_S,AiL::NO_MUSIC_CHANGE); }; void State_MainMenu::OnLevelLoad(){ - game->GetPlayer()->SetIframes(999999.f); + game->GetPlayer()->_SetIframes(999999.f); game->GetPlayer()->SetInvisible(true); SelectAndMoveToNewFocusArea(); game->ResetGame(false); } void State_MainMenu::OnUserUpdate(AiL*game){ - game->GetPlayer()->SetIframes(999999.f); + game->GetPlayer()->_SetIframes(999999.f); game->GetPlayer()->ForceSetPos(game->GetPlayer()->GetPos()+cameraMoveDir*8*game->GetElapsedTime()); lastMoveTime+=game->GetElapsedTime(); if(lastMoveTime>8.f)SelectAndMoveToNewFocusArea(); diff --git a/Adventures in Lestoria/Stone_Elemental.cpp b/Adventures in Lestoria/Stone_Elemental.cpp index 009a2c77..baf519ad 100644 --- a/Adventures in Lestoria/Stone_Elemental.cpp +++ b/Adventures in Lestoria/Stone_Elemental.cpp @@ -172,7 +172,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string m.AddBuff(BuffType::SPEEDBOOST,ConfigFloat("Burrow Wait Time"),targetMoveSpdRatio); m.F(A::CASTING_TIMER)=ConfigFloat("Burrow Wait Time"); m.B(A::IGNORE_DEFAULT_ANIMATIONS)=true; - m.SetIframes(ConfigFloat("Burrow Wait Time")+m.GetAnimation("RISE FROM UNDERGROUND").GetTotalAnimationDuration()); + m.ApplyIframes(ConfigFloat("Burrow Wait Time")+m.GetAnimation("RISE FROM UNDERGROUND").GetTotalAnimationDuration()); } }break; case DIVE_UNDERGROUND_MOVE:{ diff --git a/Adventures in Lestoria/Version.h b/Adventures in Lestoria/Version.h index 1701bc39..4659fc79 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 9434 +#define VERSION_BUILD 9447 #define stringify(a) stringify_(a) #define stringify_(a) #a diff --git a/Adventures in Lestoria/Zephy.cpp b/Adventures in Lestoria/Zephy.cpp index 16013895..96eeb0f8 100644 --- a/Adventures in Lestoria/Zephy.cpp +++ b/Adventures in Lestoria/Zephy.cpp @@ -51,27 +51,92 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy) enum Phase{ INITIALIZE, IDLE, + FLY_ACROSS_PREPARE, FLY_ACROSS, TORNADO_ATTACK, WIND_ATTACK, HALFHEALTH_PHASE, }; + enum class AttackChoice{ + RIGHT, + LEFT + }; + if(m.phase!=HALFHEALTH_PHASE)m.F(A::SPAWNER_TIMER)-=fElapsedTime; if(m.F(A::SPAWNER_TIMER)<=0.f){ const float randomDir=util::random(2*PI); game->SpawnMonster(m.GetPos()+vf2d{ConfigFloat("Basic Hawk Spawn Radius"),randomDir}.cart(),MONSTER_DATA.at("Hawk_NOXP"),m.OnUpperLevel()); + m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time"); } switch(m.phase){ case INITIALIZE:{ m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time"); + m.phase=IDLE; }break; case IDLE:{ - + const int randomAttackChoice=util::random()%1; + + switch(randomAttackChoice){ + case 0:{ + m.I(A::ATTACK_CHOICE)=util::random()%2; + + const bool RightDirectionChosen=m.I(A::ATTACK_CHOICE)==int(AttackChoice::RIGHT); + + if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos"); + else m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos"); + m.phase=FLY_ACROSS_PREPARE; + }break; + case 1:{ + m.phase=TORNADO_ATTACK; + }break; + case 2:{ + m.phase=WIND_ATTACK; + }break; + case 3:{ + m.phase=HALFHEALTH_PHASE; + }break; + } + }break; + case FLY_ACROSS_PREPARE:{ + m.targetAcquireTimer=20.f; + RUN_TOWARDS(m,fElapsedTime,"Run Towards"); + if(m.ReachedTargetPos()){ + const bool RightDirectionChosen=m.I(A::ATTACK_CHOICE)==int(AttackChoice::RIGHT); + + //We're choosing the opposite side of the field to direct the boss towards for this attack. + if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos"); + else m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos"); + m.phase=FLY_ACROSS; + + m.AddBuff(BuffType::SPEEDBOOST,INFINITY,ConfigFloat("Fly Across Attack.Move Speed Multiplier")-1.f); + } }break; case FLY_ACROSS:{ - + m.targetAcquireTimer=20.f; + RUN_TOWARDS(m,fElapsedTime,"Run Towards"); + m.F(A::SHOOT_TIMER)-=fElapsedTime; + if(m.F(A::SHOOT_TIMER)<=0.f){ + CreateBullet(Bullet)(m.GetPos(),vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},4,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{1.f,1.25f}) + .SetIframeTimeOnHit(0.25f)EndBullet; + const int extraPoopBitsCount=util::random()%6; + for(int i=0;i - + diff --git a/x64/Release/Adventures in Lestoria.exe b/x64/Release/Adventures in Lestoria.exe index 23cacdf3..0fc204f5 100644 Binary files a/x64/Release/Adventures in Lestoria.exe and b/x64/Release/Adventures in Lestoria.exe differ