Move fadeouttimer for bullets to be private scope. Account for hits multiple flag for bullets that strike a player and adds them to the hit list. Coincidentally, this need also address Issue #17. Fix a bug with multi-hit bullets not applying additional hit effects to monsters. Tornado Attack Implemented. Release Build 9473.

pull/57/head
sigonasr2 6 months ago
parent bbdef1cdfb
commit 3a9bd4afff
  1. 2
      Adventures in Lestoria/Bomb.cpp
  2. 41
      Adventures in Lestoria/Bullet.cpp
  3. 11
      Adventures in Lestoria/Bullet.h
  4. 2
      Adventures in Lestoria/BulletTypes.h
  5. 17
      Adventures in Lestoria/Tornado.cpp
  6. 2
      Adventures in Lestoria/Version.h
  7. 21
      Adventures in Lestoria/Zephy.cpp
  8. 10
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  9. BIN
      x64/Release/Adventures in Lestoria.exe

@ -104,5 +104,5 @@ bool Bomb::MonsterHit(Monster&monster){
void Bomb::Draw()const{
Bullet::Draw();
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-fadeOutTimer)/fadeOutTime)))});
game->view.DrawPartialRotatedDecal(pos-vf2d{0,GetZ()},bomb_animation.GetFrame(animation).GetSourceImage()->Decal(),rotates?atan2(vel.y,vel.x)-PI/2:0,bomb_animation.GetFrame(animation).GetSourceRect().size/2,bomb_animation.GetFrame(animation).GetSourceRect().pos,bomb_animation.GetFrame(animation).GetSourceRect().size,scale,fadeOutTime==0?col:Pixel{col.r,col.g,col.b,uint8_t(util::lerp(col.a,0,1-((fadeOutTime-GetFadeoutTimer())/fadeOutTime)))});
}

@ -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,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,bool upperLevel,bool friendly,Pixel col,vf2d scale,float image_angle)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){};
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){
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 image_angle)
:pos(pos),vel(vel),radius(radius),damage(damage),col(col),animated(true),rotates(rotatesWithAngle),lifetime(lifetime),hitsMultiple(hitsMultiple),friendly(friendly),upperLevel(upperLevel),scale(scale),image_angle(image_angle){
this->animation.AddState(animation,ANIMATION_DATA[animation]);
this->animation.ChangeState(internal_animState,animation);
};
@ -104,18 +104,21 @@ void Bullet::_Update(const float fElapsedTime){
dead=true;
}
return false;
}
}else _MonsterHit(*m);
hitList.insert(&*m);
}
}
}
} else {
if(geom2d::overlaps(game->GetPlayer()->Hitbox(),geom2d::circle(pos,radius))){
if(game->GetPlayer()->Hurt(damage,OnUpperLevel(),z)){
if(_PlayerHit(&*game->GetPlayer())){
dead=true;
}
return false;
if(hitList.find(game->GetPlayer())==hitList.end()&&game->GetPlayer()->Hurt(damage,OnUpperLevel(),z)){
if(!hitsMultiple){
if(_PlayerHit(&*game->GetPlayer())){
dead=true;
}
return false;
}else _PlayerHit(&*game->GetPlayer());
hitList.insert(game->GetPlayer());
}
}
}
@ -169,22 +172,22 @@ 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+rot:rot,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+image_angle:image_angle,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,scale,blendCol);
}else{
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});
game->view.DrawRotatedDecal(pos-vf2d{0,GetZ()}+drawOffsetY-GFX["circle.png"].Sprite()->Size()*scale/2,GFX["circle.png"].Decal(),image_angle,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(),image_angle,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);
const bool destroyBullet=PlayerHit(player);
if(iframeTimerOnHit>0.f)player->ApplyIframes(iframeTimerOnHit);
return hitOccurred;
return destroyBullet;
}
bool Bullet::_MonsterHit(Monster&monster){
const bool hitOccurred=MonsterHit(monster);
const bool destroyBullet=MonsterHit(monster);
if(iframeTimerOnHit>0.f)monster.ApplyIframes(iframeTimerOnHit);
return hitOccurred;
return destroyBullet;
}
bool Bullet::PlayerHit(Player*player){return true;}
bool Bullet::MonsterHit(Monster&monster){return true;}
@ -208,4 +211,8 @@ Bullet&Bullet::SetFadeinTime(float fadeInTime){
lifetime+=durationDiff;
deactivated=true;
return *this;
}
const float&Bullet::GetFadeoutTimer()const{
return fadeOutTimer;
}

@ -57,12 +57,12 @@ struct Bullet{
bool upperLevel=false;
bool alwaysOnTop=false;
float z=0.f;
float rot=0.f;
float image_angle=0.f;
protected:
float fadeOutTimer=0;
float distanceTraveled=0.f;
vf2d scale={1,1};
private:
float fadeOutTimer=0;
float fadeInTime=0; //Setting the fade in time causes the bullet to be disabled and the bullet's alpha will fade in from zero to the actual alpha of the bullet. When the fade in timer reaches the fade in time automatically, the bullet will be enabled.
float fadeInTimer=0;
void UpdateFadeTime(float fElapsedTime);
@ -74,14 +74,15 @@ protected:
float drawOffsetY{};
bool _PlayerHit(Player*player);
bool _MonsterHit(Monster&monster);
const float&GetFadeoutTimer()const;
public:
Animate2D::Animation<std::string>animation;
Animate2D::AnimationState internal_animState;
std::set<Monster*>hitList;
std::set<std::variant<Player*,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},float rot=0.f);
Bullet(vf2d pos,vf2d vel,float radius,int damage,bool upperLevel,bool friendly=false,Pixel col=WHITE,vf2d scale={1,1},float image_angle=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},float rot=0.f);
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 image_angle=0.f);
public:
void SimulateUpdate(const float fElapsedTime);

@ -192,6 +192,8 @@ struct Tornado:public Bullet{
float knockupDuration{};
float knockbackAmt{};
float fadeInDuration{};
float rot{0.f};
bool fadeOut{false};
Tornado(vf2d centerPoint,float distance,float initialRot,float rotSpd,int damage,const float knockupAmt,const float knockbackAmt,const float lifetime,bool upperLevel,bool friendly=false,Pixel col=WHITE,const vf2d scale={1,1});
void Update(float fElapsedTime)override;
bool PlayerHit(Player*player)override;//DO NOT CALL THIS DIRECTLY! INSTEAD USE _PlayerHit()!!

@ -41,14 +41,21 @@ All rights reserved.
#include "util.h"
Tornado::Tornado(vf2d centerPoint,float distance,float initialRot,float rotSpd,int damage,const float knockupDuration,const float knockbackAmt,const float lifetime,bool upperLevel,bool friendly,Pixel col,const vf2d scale)
:polarAngle({distance,initialRot}),rotatingSpd(rotSpd),knockupDuration(knockupDuration),knockbackAmt(knockbackAmt),Bullet(centerPoint+polarAngle.cart(),vf2d{},6.f,damage,"tornado2.png",upperLevel,true,lifetime,false,friendly,col,scale){}
:polarAngle({distance,initialRot}),rot(initialRot),rotatingSpd(rotSpd),knockupDuration(knockupDuration),knockbackAmt(knockbackAmt),centerPoint(centerPoint),Bullet(centerPoint+polarAngle.cart(),vf2d{},6.f,damage,"tornado2.png",upperLevel,true,lifetime+0.5f,false,friendly,col,scale){}
void Tornado::Update(float fElapsedTime){
rot+=rotatingSpd;
rot+=rotatingSpd*fElapsedTime;
polarAngle={polarAngle.x,rot};
pos=centerPoint+polarAngle.cart();
if(lifetime<=0.5f&&!fadeOut){
fadeOut=true;
fadeOutTime=0.5f;
}
hitList.clear(); //This is a slight hack to make it so these tornados will always hit things multiple times.
}
bool Tornado::PlayerHit(Player*player){
@ -57,7 +64,7 @@ bool Tornado::PlayerHit(Player*player){
player->ApplyIframes(knockupDuration);
return true;
return false;
}
bool Tornado::MonsterHit(Monster&monster){
@ -66,5 +73,7 @@ bool Tornado::MonsterHit(Monster&monster){
monster.Knockback(util::pointTo(centerPoint,monster.GetPos())*knockbackAmt);
monster.Knockup(knockupDuration);
return true;
monster.ApplyIframes(knockupDuration);
return false;
}

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3
#define VERSION_BUILD 9455
#define VERSION_BUILD 9473
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -91,7 +91,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
}break;
case 1:{
m.phase=TORNADO_ATTACK_PREPARE;
m.target=ConfigVec("Tornado Attack.Arena Center");
m.target=ConfigVec("Tornado Attack.Landing Area");
}break;
case 2:{
m.phase=WIND_ATTACK;
@ -132,7 +132,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
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.Landing Area");
m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Attack Frequency");
}
if(m.ReachedTargetPos()){
m.phase=IDLE;
@ -154,19 +154,24 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
//From strategy documentation:
//# For each Ring: Distance from Boss in Units, # of tornados, Rotation Speed (degrees/sec).
const float tornadoDistance=ConfigPixelsArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),0);
const int tornadoCount=ConfigIntArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),1);
const float tornadoRotSpd=util::degToRad(ConfigFloatArr(std::format("Tornado Attack.Tornados.Ring{}",tornadoRingId),2)); //It's in degrees, let's convert it to radians now.
const float tornadoDistance=ConfigPixelsArr(std::format("Tornado Attack.Tornados.Ring {}",tornadoRingId),0);
const int tornadoCount=ConfigIntArr(std::format("Tornado Attack.Tornados.Ring {}",tornadoRingId),1);
const float tornadoRotSpd=util::degToRad(ConfigFloatArr(std::format("Tornado Attack.Tornados.Ring {}",tornadoRingId),2)); //It's in degrees, let's convert it to radians now.
const float randomRotDir=util::random(2*PI);
CreateBullet(Tornado)(m.GetPos(),tornadoDistance,randomRotDir,tornadoRotSpd,m.GetAttack(),ConfigFloat("Tornado Attack.Knockup Duration"),ConfigFloat("Tornado Attack.Knockback Amount"),ConfigFloat("Tornado Attack.Attack Duration"),m.OnUpperLevel(),false,WHITE)EndBullet;
BULLET_LIST.back()->SetFadeinTime(ConfigFloat("Tornado Fade-In Time"));
for(int tornadoCounter=0;tornadoCounter<tornadoCount;tornadoCounter++){
const float startAngle=randomRotDir+tornadoCounter*((2*PI)/tornadoCount);
CreateBullet(Tornado)(m.GetPos(),tornadoDistance,startAngle,tornadoRotSpd,m.GetAttack(),ConfigFloat("Tornado Attack.Knockup Duration"),ConfigFloat("Tornado Attack.Knockback Amount"),ConfigFloat("Tornado Attack.Attack Duration"),m.OnUpperLevel(),false,WHITE)EndBullet;
BULLET_LIST.back()->SetFadeinTime(ConfigFloat("Tornado Attack.Tornado Fade-In Time"));
}
}
}
}break;
case TORNADO_ATTACK:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f)m.phase=IDLE;
}break;
case WIND_ATTACK:{

@ -811,10 +811,12 @@ MonsterStrategy
{
# For each Ring: Distance from Boss in Units, # of tornados, Rotation Speed (degrees/sec).
# Freely add/subtract rings to create or remove more tornados.
Ring 1 = 100, 2, 120
Ring 2 = 200, 3, -75
Ring 3 = 300, 5, 60
Ring 4 = 400, 8, -40
Ring 1 = 100, 2, 90
Ring 2 = 235, 3, -60
Ring 3 = 370, 5, 40
Ring 4 = 505, 8, -20
Ring 5 = 640, 12, 10
Ring 6 = 775, 18, -7
}
}
}

Loading…
Cancel
Save