Add rotation parameter and hit iframe time to bullets. Iframe timers now compares current iframes to the new duration and keeps only the larger amount. Fix bug allowing monsters with jump attacks to still cause knockback and iframes even when the hit does not land. Fly Across attack implemented for second bonus boss. Release Build 9447.

mac-build
sigonasr2 6 months ago
parent bea3d5b6ee
commit e53f7cb4cd
  1. 4
      Adventures in Lestoria/AdventuresInLestoria.cpp
  2. 3
      Adventures in Lestoria/AdventuresInLestoria.h
  3. 33
      Adventures in Lestoria/Bullet.cpp
  4. 11
      Adventures in Lestoria/Bullet.h
  5. 44
      Adventures in Lestoria/BulletTypes.h
  6. 4
      Adventures in Lestoria/FrogTongue.cpp
  7. 20
      Adventures in Lestoria/Monster.cpp
  8. 9
      Adventures in Lestoria/Monster.h
  9. 1
      Adventures in Lestoria/MonsterAttribute.h
  10. 8
      Adventures in Lestoria/Player.cpp
  11. 4
      Adventures in Lestoria/Player.h
  12. 7
      Adventures in Lestoria/RunTowards.cpp
  13. 9
      Adventures in Lestoria/SlimeKing.cpp
  14. 4
      Adventures in Lestoria/State_MainMenu.cpp
  15. 2
      Adventures in Lestoria/Stone_Elemental.cpp
  16. 2
      Adventures in Lestoria/Version.h
  17. 67
      Adventures in Lestoria/Zephy.cpp
  18. BIN
      Adventures in Lestoria/assets/birdpoop.png
  19. 16
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  20. 1
      Adventures in Lestoria/assets/config/gfx/gfx.txt
  21. BIN
      Adventures in Lestoria/assets/gamepack.pak
  22. 2
      Adventures in Lestoria/assets/maps/Monsters.tsx
  23. BIN
      x64/Release/Adventures in Lestoria.exe

@ -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<Monster>(data.spawnPos,MONSTER_DATA[data.name]));
MONSTER_LIST.back()->SetIframes(INFINITE);
MONSTER_LIST.back()->ApplyIframes(INFINITE);
MONSTER_LIST.back()->npcData=data;
}
}

@ -62,7 +62,8 @@ All rights reserved.
class SteamKeyboardCallbackHandler;
class SteamStatsReceivedHandler;
#define CreateBullet(type) BULLET_LIST.push_back(std::make_unique<type>(type
#define CreateBullet(type) INCLUDE_BULLET_LIST \
BULLET_LIST.push_back(std::make_unique<type>(type
#define EndBullet ));
using HurtReturnValue=bool;

@ -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;}
@ -177,3 +187,8 @@ const bool Bullet::IsDead()const{
const float Bullet::GetZ()const{
return z;
}
Bullet&Bullet::SetIframeTimeOnHit(float iframeTimer){
iframeTimerOnHit=iframeTimer;
return *this;
}

@ -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::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
std::set<Monster*>hitList;
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);
};

@ -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;

@ -62,12 +62,12 @@ void FrogTongue::Update(float fElapsedTime){
geom2d::line<float>tongueLine(pos+drawVec,tongueEndPos);
if(!friendly&&geom2d::overlaps(game->GetPlayer()->Hitbox(),tongueLine)){
PlayerHit(game->GetPlayer());
_PlayerHit(game->GetPlayer());
}
if(friendly){
for(std::unique_ptr<Monster>&m:MONSTER_LIST){
if(hitList.find(&*m)==hitList.end()&&geom2d::overlaps(m->BulletCollisionHitbox(),tongueLine)){
MonsterHit(*m);
_MonsterHit(*m);
hitList.insert(&*m);
}
}

@ -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;i<spawnAmt;i++){
game->SpawnMonster(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;
}
@ -1073,3 +1079,7 @@ 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;
}

@ -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<void(Monster&,float,std::string)>&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;

@ -117,4 +117,5 @@ enum class Attribute{
FLYING_HEIGHT,
TARGET_FLYING_HEIGHT,
SPAWNER_TIMER,
ATTACK_CHOICE,
};

@ -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;
}

@ -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.

@ -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());
if(game->GetPlayer()->Hurt(jumpDamage,m.OnUpperLevel(),m.GetZ())){
game->GetPlayer()->ProximityKnockback(m.GetPos(),float(ConfigInt("JumpKnockbackFactor")));
game->GetPlayer()->SetIframes(game->GetPlayer()->GetIframeTime()+0.3f);
game->GetPlayer()->ApplyIframes(0.3f);
}
}
m.SetZ(0);
Landed();

@ -205,16 +205,17 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
game->AddEffect(std::make_unique<FallingDebris>(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(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);
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()->SetIframes(0.2f);
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:{

@ -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();

@ -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:{

@ -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

@ -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<extraPoopBitsCount;i++){
const bool RightDirection=util::random()%2;
float xOffset{0.f};
if(RightDirection)xOffset=1.f;
else xOffset=-1.f;
xOffset*=9.f;
CreateBullet(Bullet)(m.GetPos()+vf2d{xOffset+util::random_range(-3.f,3.f),-util::random(10.f)-4.f},vf2d{0.f,ConfigFloat("Fly Across Attack.Attack Y Speed")},1,ConfigInt("Fly Across Attack.Poop Damage"),"birdpoop.png",m.OnUpperLevel(),false,INFINITY,false,false,WHITE,vf2d{util::random_range(0.2f,0.3f),util::random_range(0.2f,0.3f)},util::random(2*PI))
.SetIframeTimeOnHit(0.25f)EndBullet;
}
m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Attack Frequency");
}
if(m.ReachedTargetPos()){
m.phase=IDLE;
m.RemoveBuff(BuffType::SPEEDBOOST);
m.targetAcquireTimer=0.f;
}
}break;
case TORNADO_ATTACK:{

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

@ -779,5 +779,21 @@ MonsterStrategy
{
Basic Hawk Spawn Time = 10s
Basic Hawk Spawn Radius = 1000
Fly Across Attack
{
Poop Damage = 25
# X,Y
Left Edge Start Pos = 1512, 1608
# X,Y
Right Edge Start Pos = 2520, 1608
Move Speed Multiplier = 1.0x
Attack Frequency = 0.33s
# Defined in units/sec
Attack Y Speed = 150
}
}
}

@ -97,6 +97,7 @@ Images
GFX_Rock = rock.png
GFX_RockOutline = rock_outline.png
GFX_BossIndicator = bossIndicator.png
GFX_BirdPoop = birdpoop.png
# Ability Icons
GFX_Warrior_BattleCry_Icon = Ability Icons/battlecry.png

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.10.2" name="Monsters" tilewidth="48" tileheight="48" tilecount="400" columns="10">
<tileset version="1.10" tiledversion="1.10.2" name="Monsters" tilewidth="48" tileheight="48" tilecount="100" columns="10">
<image source="monsters-tileset.png" width="480" height="480"/>
</tileset>

Loading…
Cancel
Save