Finish implementing Pirate Captain and Parrot behaviors. Refactor monster phase system to be per-strategy instead of a global phase for all strategies. Release Build 11666.

master
sigonasr2 2 months ago
parent a09ea9a9a7
commit aae5726f3d
  1. 8
      Adventures in Lestoria/Bear.cpp
  2. 10
      Adventures in Lestoria/Boar.cpp
  3. 4
      Adventures in Lestoria/BreakingPillar.cpp
  4. 2
      Adventures in Lestoria/DamageNumber.cpp
  5. 12
      Adventures in Lestoria/Frog.cpp
  6. 4
      Adventures in Lestoria/Goblin_Bomb.cpp
  7. 10
      Adventures in Lestoria/Goblin_Bow.cpp
  8. 8
      Adventures in Lestoria/Goblin_Dagger.cpp
  9. 10
      Adventures in Lestoria/Hawk.cpp
  10. 12
      Adventures in Lestoria/Monster.cpp
  11. 6
      Adventures in Lestoria/Monster.h
  12. 3
      Adventures in Lestoria/MonsterAttribute.h
  13. 3
      Adventures in Lestoria/MonsterStrategyHelpers.h
  14. 4
      Adventures in Lestoria/NPC.cpp
  15. 12
      Adventures in Lestoria/Parrot.cpp
  16. 12
      Adventures in Lestoria/Pirate_Buccaneer.cpp
  17. 36
      Adventures in Lestoria/Pirate_Captain.cpp
  18. 20
      Adventures in Lestoria/Pirate_Marauder.cpp
  19. 26
      Adventures in Lestoria/SlimeKing.cpp
  20. 36
      Adventures in Lestoria/StoneGolem.cpp
  21. 16
      Adventures in Lestoria/Stone_Elemental.cpp
  22. 30
      Adventures in Lestoria/Ursule.cpp
  23. 2
      Adventures in Lestoria/Version.h
  24. 10
      Adventures in Lestoria/Wolf.cpp
  25. 36
      Adventures in Lestoria/Zephy.cpp
  26. 8
      Adventures in Lestoria/assets/Campaigns/3_1.tmx
  27. 8
      Adventures in Lestoria/assets/Campaigns/3_2.tmx
  28. 8
      Adventures in Lestoria/assets/Campaigns/3_3.tmx
  29. 8
      Adventures in Lestoria/assets/Campaigns/3_5.tmx
  30. 8
      Adventures in Lestoria/assets/Campaigns/3_6.tmx
  31. 8
      Adventures in Lestoria/assets/Campaigns/3_8.tmx
  32. 4
      Adventures in Lestoria/assets/config/MonsterStrategies.txt
  33. 32
      Adventures in Lestoria/assets/config/Monsters.txt
  34. BIN
      Adventures in Lestoria/assets/gamepack.pak
  35. BIN
      x64/Release/Adventures in Lestoria.exe

@ -52,11 +52,11 @@ INCLUDE_MONSTER_DATA
using A=Attribute; using A=Attribute;
void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){ switch(PHASE()){
case 0:{ case 0:{
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length(); float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){ if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){
m.I(A::PHASE)=1; SETPHASE(1);
m.PerformShootAnimation(); m.PerformShootAnimation();
m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Chargeup Time");
//The bear slam attack indicator will move with the bear, and the LOCKON_POS variable will hold a polar coordinate indicating distance and angle for where it should be attacking relative to its position. //The bear slam attack indicator will move with the bear, and the LOCKON_POS variable will hold a polar coordinate indicating distance and angle for where it should be attacking relative to its position.
@ -75,7 +75,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
case 1:{ case 1:{
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime); m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
if(m.F(A::CASTING_TIMER)==0.f){ if(m.F(A::CASTING_TIMER)==0.f){
m.I(A::PHASE)=2; SETPHASE(2);
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Animation Wait Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Attack Animation Wait Time");
m.PerformAnimation("SLAM"); m.PerformAnimation("SLAM");
} }
@ -85,7 +85,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::CASTING_TIMER)==0.f){ if(m.F(A::CASTING_TIMER)==0.f){
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length(); float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
SoundEffect::PlaySFX("Bear Slam Attack",m.GetPos()+m.V(A::LOCKON_POS).cart()); SoundEffect::PlaySFX("Bear Slam Attack",m.GetPos()+m.V(A::LOCKON_POS).cart());
m.I(A::PHASE)=0; SETPHASE(0);
m.I(A::BEAR_STOMP_COUNT)++; m.I(A::BEAR_STOMP_COUNT)++;
geom2d::circle<float>attackCircle={m.GetPos()+m.V(A::LOCKON_POS).cart(),float(operator""_Pixels(ConfigFloat("Smash Attack Diameter"))/2.f)}; geom2d::circle<float>attackCircle={m.GetPos()+m.V(A::LOCKON_POS).cart(),float(operator""_Pixels(ConfigFloat("Smash Attack Diameter"))/2.f)};
if(geom2d::overlaps(attackCircle,game->GetPlayer()->Hitbox())){ if(geom2d::overlaps(attackCircle,game->GetPlayer()->Hitbox())){

@ -57,7 +57,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
RECOVERY, RECOVERY,
}; };
switch(m.phase){ switch(PHASE()){
case PhaseName::MOVE:{ case PhaseName::MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos()); float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(m.canMove&&distToPlayer>=ConfigInt("Closein Range")/100.f*24){ if(m.canMove&&distToPlayer>=ConfigInt("Closein Range")/100.f*24){
@ -75,7 +75,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
ScratchPhaseTransition: ScratchPhaseTransition:
m.PerformAnimation("SCRATCH"); m.PerformAnimation("SCRATCH");
m.F(A::CASTING_TIMER)=ConfigInt("Ground Scratch Count")*m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::CASTING_TIMER)=ConfigInt("Ground Scratch Count")*m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=PhaseName::SCRATCH; SETPHASE(PhaseName::SCRATCH);
vf2d chargeTargetPoint=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Charge Distance")/100.f*24); vf2d chargeTargetPoint=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Charge Distance")/100.f*24);
float distanceToChargePoint=geom2d::line<float>(m.GetPos(),chargeTargetPoint).length(); float distanceToChargePoint=geom2d::line<float>(m.GetPos(),chargeTargetPoint).length();
@ -89,7 +89,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){ if(m.F(A::CASTING_TIMER)<=0.f){
m.PerformShootAnimation(); m.PerformShootAnimation();
m.phase=PhaseName::CHARGE; SETPHASE(PhaseName::CHARGE);
m.AddBuff(BuffType::SPEEDBOOST,INFINITE,ConfigFloat("Charge Movespeed")/100.f-1); m.AddBuff(BuffType::SPEEDBOOST,INFINITE,ConfigFloat("Charge Movespeed")/100.f-1);
@ -102,7 +102,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length(); float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
auto TransitionToRecoveryPhase=[&](){ auto TransitionToRecoveryPhase=[&](){
m.phase=PhaseName::RECOVERY; SETPHASE(PhaseName::RECOVERY);
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time"); m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation(); m.PerformIdleAnimation();
}; };
@ -120,7 +120,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CHARGE_COOLDOWN)-=fElapsedTime; m.F(A::CHARGE_COOLDOWN)-=fElapsedTime;
m.targetAcquireTimer=0.f; m.targetAcquireTimer=0.f;
m.RemoveBuff(BuffType::SPEEDBOOST); m.RemoveBuff(BuffType::SPEEDBOOST);
if(m.F(A::CHARGE_COOLDOWN)<=0)m.phase=PhaseName::MOVE; if(m.F(A::CHARGE_COOLDOWN)<=0)SETPHASE(PhaseName::MOVE);
}break; }break;
} }
} }

@ -56,11 +56,11 @@ void Monster::STRATEGY::BREAKING_PILLAR(Monster&m,float fElapsedTime,std::string
m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 1 Animation Name")); m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 1 Animation Name"));
}else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name")); }else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name"));
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time"); m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f; m.F(A::SHAKE_TIMER)=0.2f;
m.phase=RUN; SETPHASE(RUN);
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){ m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f; m.lifetime=0.01f;

@ -143,7 +143,7 @@ void DamageNumber::Draw(){
float DamageNumber::GetOriginalRiseSpd(){ float DamageNumber::GetOriginalRiseSpd(){
float riseSpd{20.f}; float riseSpd{20.f};
if(type==INTERRUPT||type==MANA_GAIN)riseSpd=40.f; if(type==INTERRUPT||type==MANA_GAIN||type==HEALTH_GAIN)riseSpd=40.f;
if(type==DOT)riseSpd=-10.f; if(type==DOT)riseSpd=-10.f;
return riseSpd; return riseSpd;
} }

@ -51,11 +51,11 @@ using A=Attribute;
void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::LOCKON_WAITTIME)=std::max(0.0f,m.F(A::LOCKON_WAITTIME)-fElapsedTime); m.F(A::LOCKON_WAITTIME)=std::max(0.0f,m.F(A::LOCKON_WAITTIME)-fElapsedTime);
phase: phase:
switch(m.I(A::PHASE)){ switch(PHASE()){
case 0:{ //Move towards phase. case 0:{ //Move towards phase.
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length(); float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<24*ConfigInt("Range")/100.f){ if(distToPlayer<24*ConfigInt("Range")/100.f){
m.I(A::PHASE)++; SETPHASE(PHASE()+1);
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Lockon Wait Time"); m.F(A::LOCKON_WAITTIME)=ConfigFloat("Lockon Wait Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos(); m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.RotateTowardsPos(m.V(A::LOCKON_POS)); m.RotateTowardsPos(m.V(A::LOCKON_POS));
@ -72,23 +72,23 @@ void Monster::STRATEGY::FROG(Monster&m,float fElapsedTime,std::string strategy){
SoundEffect::PlaySFX("Slime Shoot",m.pos); SoundEffect::PlaySFX("Slime Shoot",m.pos);
CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet; CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation(); m.PerformShootAnimation();
m.I(A::PHASE)=2; SETPHASE(2);
} }
m.PerformIdleAnimation(); m.PerformIdleAnimation();
}break; }break;
case 2:{ case 2:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){ if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time"); m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time");
m.I(A::PHASE)=3; SETPHASE(3);
} }
}break; }break;
case 3:{ case 3:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){ if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.I(A::PHASE)=0; SETPHASE(0);
} }
}break; }break;
default:{ default:{
ERR(std::format("Unhandled phase {} for {} strategy!",m.I(A::PHASE),strategy)); ERR(std::format("Unhandled phase {} for {} strategy!",PHASE(),strategy));
} }
} }
} }

@ -60,10 +60,10 @@ void Monster::STRATEGY::GOBLIN_BOMB(Monster&m,float fElapsedTime,std::string str
RUN, RUN,
}; };
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::SHOOT_TIMER)=m.randomFrameOffset; m.F(A::SHOOT_TIMER)=m.randomFrameOffset;
m.phase=RUN; SETPHASE(RUN);
}break; }break;
case RUN:{ case RUN:{
m.F(A::SHOOT_TIMER)+=fElapsedTime; m.F(A::SHOOT_TIMER)+=fElapsedTime;

@ -66,10 +66,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
}; };
#pragma endregion #pragma endregion
switch(m.phase){ switch(PHASE()){
case INITIALIZE_PERCEPTION:{ case INITIALIZE_PERCEPTION:{
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level"); m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.phase=MOVE; SETPHASE(MOVE);
}break; }break;
case MOVE:{ case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime; m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -79,7 +79,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1); const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
auto PrepareToShoot=[&](){ auto PrepareToShoot=[&](){
m.phase=LOCKON; SETPHASE(LOCKON);
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time"); m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
@ -118,7 +118,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
m.V(A::EXTENDED_LINE)=extendedLine; m.V(A::EXTENDED_LINE)=extendedLine;
Arrow tempArrow{m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel()}; Arrow tempArrow{m.GetPos(),extendedLine,pointTowardsPlayer.vector().norm()*ConfigFloat("Arrow Spd"),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel()};
m.V(A::FIRE_VELOCITY)=tempArrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL)); m.V(A::FIRE_VELOCITY)=tempArrow.PointToBestTargetPath(m.F(A::PERCEPTION_LEVEL));
m.phase=WINDUP; SETPHASE(WINDUP);
} }
}break; }break;
case WINDUP:{ case WINDUP:{
@ -128,7 +128,7 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
CreateBullet(Arrow)(m.GetPos(),m.V(A::EXTENDED_LINE),m.V(A::FIRE_VELOCITY),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet; CreateBullet(Arrow)(m.GetPos(),m.V(A::EXTENDED_LINE),m.V(A::FIRE_VELOCITY),"goblin_arrow.png",ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet;
m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase")); m.F(A::PERCEPTION_LEVEL)=std::min(ConfigFloat("Maximum Perception Level"),m.F(A::PERCEPTION_LEVEL)+ConfigFloat("Perception Level Increase"));
m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.phase=MOVE; SETPHASE(MOVE);
} }
m.B(A::RANDOM_DIRECTION)=util::random()%2; m.B(A::RANDOM_DIRECTION)=util::random()%2;
m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1)); m.F(A::RANDOM_RANGE)=util::random_range(ConfigPixelsArr("Random Direction Range",0),ConfigPixelsArr("Random Direction Range",1));

@ -66,13 +66,13 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
SLASH SLASH
}; };
switch(m.phase){ switch(PHASE()){
case MOVE:{ case MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos()); float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){ if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{ }else{
m.phase=WINDUP; SETPHASE(WINDUP);
m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash. m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash.
switch(m.I(A::ATTACK_TYPE)){ switch(m.I(A::ATTACK_TYPE)){
case STAB:{ case STAB:{
@ -90,7 +90,7 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
case WINDUP:{ case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){ if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY; SETPHASE(RECOVERY);
switch(m.I(A::ATTACK_TYPE)){ switch(m.I(A::ATTACK_TYPE)){
case STAB:{ case STAB:{
vf2d stabTarget=game->GetPlayer()->GetPos(); vf2d stabTarget=game->GetPlayer()->GetPos();
@ -113,7 +113,7 @@ void Monster::STRATEGY::GOBLIN_DAGGER(Monster&m,float fElapsedTime,std::string s
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::RECOVERY_TIME)-=fElapsedTime; m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));} if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE; if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
}break; }break;
} }
} }

@ -63,11 +63,11 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.SetZ(std::max(float(m.F(A::FLYING_HEIGHT)+ConfigFloat("Flight Oscillation Amount")*sin((PI*m.TimeSpentAlive())/1.5f)),0.f)); m.SetZ(std::max(float(m.F(A::FLYING_HEIGHT)+ConfigFloat("Flight Oscillation Amount")*sin((PI*m.TimeSpentAlive())/1.5f)),0.f));
#pragma endregion #pragma endregion
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::TARGET_FLYING_HEIGHT)=m.F(A::FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2); m.F(A::TARGET_FLYING_HEIGHT)=m.F(A::FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
m.B(A::RANDOM_DIRECTION)=int(util::random_range(0,2)); m.B(A::RANDOM_DIRECTION)=int(util::random_range(0,2));
m.phase=FLYING; SETPHASE(FLYING);
m.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,INFINITE,util::random_range(0,ConfigFloat("Flight Speed Variance")/100)); m.AddBuff(BuffType::SELF_INFLICTED_SLOWDOWN,INFINITE,util::random_range(0,ConfigFloat("Flight Speed Variance")/100));
m.F(A::ATTACK_COOLDOWN)=util::random_range(1.f,ConfigFloat("Flight Charge Cooldown")); m.F(A::ATTACK_COOLDOWN)=util::random_range(1.f,ConfigFloat("Flight Charge Cooldown"));
}break; }break;
@ -76,7 +76,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigFloat("Attack Z Speed")*fElapsedTime); if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigFloat("Attack Z Speed")*fElapsedTime);
if(m.F(A::ATTACK_COOLDOWN)<=0){ if(m.F(A::ATTACK_COOLDOWN)<=0){
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Wait Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Attack Wait Time");
m.phase=PREPARE_CHARGE; SETPHASE(PREPARE_CHARGE);
}else{ }else{
float dirToPlayer{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y}; float dirToPlayer{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
dirToPlayer+=m.B(A::RANDOM_DIRECTION)?0.25*PI:-0.25*PI; dirToPlayer+=m.B(A::RANDOM_DIRECTION)?0.25*PI:-0.25*PI;
@ -89,7 +89,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
m.UpdateFacingDirection(game->GetPlayer()->GetPos()); m.UpdateFacingDirection(game->GetPlayer()->GetPos());
m.PerformAnimation("ATTACK"); m.PerformAnimation("ATTACK");
if(m.F(A::CASTING_TIMER)<=0){ if(m.F(A::CASTING_TIMER)<=0){
m.phase=CHARGE; SETPHASE(CHARGE);
m.PerformAnimation("ATTACKING"); m.PerformAnimation("ATTACKING");
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Flight Distance")*2.f); m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Flight Distance")*2.f);
m.UpdateFacingDirection(m.target); m.UpdateFacingDirection(m.target);
@ -103,7 +103,7 @@ void Monster::STRATEGY::HAWK(Monster&m,float fElapsedTime,std::string strategy){
float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length(); float distToTarget=geom2d::line<float>(m.GetPos(),m.target).length();
if(distToTarget<12.f){ if(distToTarget<12.f){
m.F(A::TARGET_FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2); m.F(A::TARGET_FLYING_HEIGHT)=ConfigFloat("Flight Height")-ConfigFloat("Flight Height Variance")+util::random_range(0,ConfigFloat("Flight Height Variance")*2);
m.phase=FLYING; SETPHASE(FLYING);
m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown"); m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown");
m.PerformJumpAnimation(); m.PerformJumpAnimation();
} }

@ -81,6 +81,7 @@ Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
randomFrameOffset=(util::random()%1000)/1000.f; randomFrameOffset=(util::random()%1000)/1000.f;
monsterWalkSoundTimer=util::random(1.f); monsterWalkSoundTimer=util::random(1.f);
UpdateFacingDirection(game->GetPlayer()->GetPos()); UpdateFacingDirection(game->GetPlayer()->GetPos());
animation.UpdateState(internal_animState,randomFrameOffset);
} }
const vf2d&Monster::GetPos()const{ const vf2d&Monster::GetPos()const{
return pos; return pos;
@ -284,8 +285,8 @@ void Monster::Update(const float fElapsedTime){
unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime); unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime);
if(unconsciousTimer==0.f){ if(unconsciousTimer==0.f){
Heal(GetMaxHealth()); Heal(GetMaxHealth());
PerformIdleAnimation();
} }
} }
if(IsSolid()&&FadeoutWhenStandingBehind()){ if(IsSolid()&&FadeoutWhenStandingBehind()){
@ -402,7 +403,7 @@ void Monster::Update(const float fElapsedTime){
} }
if(!game->TestingModeEnabled()&&CanMove())Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime); if(!game->TestingModeEnabled()&&CanMove())Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime);
} }
animation.UpdateState(internal_animState,randomFrameOffset+fElapsedTime); animation.UpdateState(internal_animState,fElapsedTime);
if(HasMountedMonster())mounted_animation.value().UpdateState(internal_mounted_animState,fElapsedTime); if(HasMountedMonster())mounted_animation.value().UpdateState(internal_mounted_animState,fElapsedTime);
attackedByPlayer=false; attackedByPlayer=false;
} }
@ -1654,3 +1655,10 @@ const bool Monster::IsUnconscious()const{
const float Monster::UnconsciousTime()const{ const float Monster::UnconsciousTime()const{
return MONSTER_DATA.at(name).UnconsciousTime(); return MONSTER_DATA.at(name).UnconsciousTime();
} }
void Monster::SetPhase(const std::string&strategyName,int phase){
this->phase[strategyName]=phase;
}
const int Monster::GetPhase(const std::string&strategyName){
if(!phase.contains(strategyName))phase[strategyName]=0;
return this->phase[strategyName];
}

@ -60,6 +60,8 @@ enum class Attribute;
class GameEvent; class GameEvent;
using StrategyName=std::string;
namespace MonsterTests{ namespace MonsterTests{
class MonsterTest; class MonsterTest;
}; };
@ -229,6 +231,8 @@ public:
const bool FaceTarget()const; const bool FaceTarget()const;
void ResetCurseOfDeathDamage(); void ResetCurseOfDeathDamage();
void MoveForward(const vf2d&moveForwardVec,const float fElapsedTime); //Moves the monster forward in given vector direction (will be auto-normalized) applying speeed boosts and other proper movement requirements as if you wanted to move on a frame-by-frame basis. void MoveForward(const vf2d&moveForwardVec,const float fElapsedTime); //Moves the monster forward in given vector direction (will be auto-normalized) applying speeed boosts and other proper movement requirements as if you wanted to move on a frame-by-frame basis.
void SetPhase(const std::string&strategyName,int phase);
const int GetPhase(const std::string&strategyName);
private: private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!! //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. // 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.
@ -273,7 +277,7 @@ private:
float spriteRot=0; float spriteRot=0;
std::shared_ptr<DamageNumber>damageNumberPtr; std::shared_ptr<DamageNumber>damageNumberPtr;
std::shared_ptr<DamageNumber>dotNumberPtr; std::shared_ptr<DamageNumber>dotNumberPtr;
int phase=0; std::unordered_map<StrategyName,int>phase{}; //NOTE: THIS SHOULD NOT BE MODIFIED DIRECTLY!!! Use SetPhase(), GetPhase() / PHASE() SETPHASE() macros!
bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die. bool diesNormally=true; //If set to false, the monster death is handled in a special way. Set it to true when it's time to die.
float targetSize=0; float targetSize=0;
bool isBoss=false; bool isBoss=false;

@ -79,7 +79,6 @@ enum class Attribute{
ITEM_QUANTITY, ITEM_QUANTITY,
LAST_INVENTORY_TYPE_OPENED, LAST_INVENTORY_TYPE_OPENED,
NEXT_MENU, //Set to 0 for New Game, Set to 1 for Load Game Menu. This is used for the username checks NEXT_MENU, //Set to 0 for New Game, Set to 1 for Load Game Menu. This is used for the username checks
PHASE,
LOCKON_WAITTIME, LOCKON_WAITTIME,
LOCKON_POS, LOCKON_POS,
TARGET_TIMER, TARGET_TIMER,
@ -148,4 +147,6 @@ enum class Attribute{
EXTENDED_LINE, EXTENDED_LINE,
ENCHANT, ENCHANT,
PARROT_FLY_TIMER, PARROT_FLY_TIMER,
MID_PHASE,
RUM_DRINK_COUNT,
}; };

@ -50,3 +50,6 @@ All rights reserved.
#define ConfigFloatArr(param,ind) _GetFloat(m,param,strategy,ind) #define ConfigFloatArr(param,ind) _GetFloat(m,param,strategy,ind)
#define ConfigStringArr(param,ind) _GetString(m,param,strategy,ind) #define ConfigStringArr(param,ind) _GetString(m,param,strategy,ind)
#define ConfigVecArr(param,ind) _GetVec(m,param,strategy,ind) #define ConfigVecArr(param,ind) _GetVec(m,param,strategy,ind)
#define PHASE() m.GetPhase(strategy)
#define SETPHASE(phase) m.SetPhase(strategy,phase)

@ -53,7 +53,7 @@ INCLUDE_game
INCLUDE_DATA INCLUDE_DATA
void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){
if(m.phase==0){ //Initialization. if(PHASE()==0){ //Initialization.
if(m.npcData.function.length()>0){ if(m.npcData.function.length()>0){
m.SetStrategyDrawOverlayFunction([](AiL*game,Monster&m,const std::string&strategy){ m.SetStrategyDrawOverlayFunction([](AiL*game,Monster&m,const std::string&strategy){
vf2d nameTextSize=game->GetTextSizeProp(m.GetName()); vf2d nameTextSize=game->GetTextSizeProp(m.GetName());
@ -62,7 +62,7 @@ void Monster::STRATEGY::NPC(Monster&m,float fElapsedTime,std::string strategy){
game->KEY_CONFIRM.DrawPrimaryInput(&game->view,m.GetPos()+vf2d{ConfigFloatArr("Interaction Display Offset",0),ConfigFloatArr("Interaction Display Offset",1)},"Interact",alpha); game->KEY_CONFIRM.DrawPrimaryInput(&game->view,m.GetPos()+vf2d{ConfigFloatArr("Interaction Display Offset",0),ConfigFloatArr("Interaction Display Offset",1)},"Interact",alpha);
}); });
} }
m.phase=1; SETPHASE(1);
} }
float distFromPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length(); float distFromPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distFromPlayer<ConfigFloat("Interaction Distance")/100.f*24.f){ if(distFromPlayer<ConfigFloat("Interaction Distance")/100.f*24.f){

@ -51,18 +51,22 @@ void Monster::STRATEGY::PARROT(Monster&m,float fElapsedTime,std::string strategy
RUN, RUN,
FLY_AWAY, FLY_AWAY,
}; };
switch(m.phase){ switch(PHASE()){
case RUN:{ case RUN:{
if(!m.attachedTarget.expired())HAWK(m,fElapsedTime,"Hawk"); if(!m.attachedTarget.expired()&&m.attachedTarget.lock()->IsAlive()){
else{ m.PerformAnimation("FLYING");
HAWK(m,fElapsedTime,"Hawk");
}else{
m.lifetime=10.f; m.lifetime=10.f;
m.phase=FLY_AWAY; SETPHASE(FLY_AWAY);
m.F(A::PATH_DIR)=1.f; m.F(A::PATH_DIR)=1.f;
if(util::random(1.f)<0.5f)m.F(A::PATH_DIR)=-1.f; if(util::random(1.f)<0.5f)m.F(A::PATH_DIR)=-1.f;
m.manualIgnoreTerrain=true; m.manualIgnoreTerrain=true;
} }
}break; }break;
case FLY_AWAY:{ case FLY_AWAY:{
if(m.F(A::PATH_DIR)>0.f)m.PerformAnimation("FLYING",Direction::EAST);
else m.PerformAnimation("FLYING",Direction::WEST);
m.z+=fElapsedTime*ConfigFloat("Fly Away Z Speed"); m.z+=fElapsedTime*ConfigFloat("Fly Away Z Speed");
m.SetVelocity({m.F(A::PATH_DIR)*ConfigFloat("Fly Away Speed"),0.f}); m.SetVelocity({m.F(A::PATH_DIR)*ConfigFloat("Fly Away Speed"),0.f});
}break; }break;

@ -49,7 +49,6 @@ INCLUDE_game
void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::string strategy){
#pragma region Phase, Animation, and Helper function setup #pragma region Phase, Animation, and Helper function setup
enum PhaseName{ enum PhaseName{
INIT,
MOVE, MOVE,
LOCKON, LOCKON,
WINDUP, WINDUP,
@ -57,10 +56,7 @@ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::strin
}; };
#pragma endregion #pragma endregion
switch(m.phase){ switch(PHASE()){
case INIT:{
m.phase=MOVE;
}break;
case MOVE:{ case MOVE:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime; m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -69,7 +65,7 @@ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::strin
const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1); const bool outsideMaxShootingRange=distToPlayer>=ConfigPixelsArr("Stand Still and Shoot Range",1);
auto PrepareToShoot=[&](){ auto PrepareToShoot=[&](){
m.phase=WINDUP; SETPHASE(WINDUP);
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time"); m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SHOOT",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
}; };
@ -96,7 +92,7 @@ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::strin
CreateBullet(ChargedArrow)("musket_bullet.png","musket_trail.png",m.GetPos(),util::pointTo(m.GetPos(),m.V(A::LOCKON_POS))*ConfigFloat("Arrow Spd"),ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet; CreateBullet(ChargedArrow)("musket_bullet.png","musket_trail.png",m.GetPos(),util::pointTo(m.GetPos(),m.V(A::LOCKON_POS))*ConfigFloat("Arrow Spd"),ConfigFloat("Arrow Hitbox Radius"),m.GetAttack(),m.OnUpperLevel())EndBullet;
m.PerformAnimation("SHOOTING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SHOOTING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){}); m.SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){});
m.phase=FIRE_ANIMATION; SETPHASE(FIRE_ANIMATION);
m.F(A::SHOOT_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::SHOOT_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
}else }else
if(m.F(A::SHOOT_TIMER)>=ConfigFloat("Attack Lockon Time")){ if(m.F(A::SHOOT_TIMER)>=ConfigFloat("Attack Lockon Time")){
@ -115,7 +111,7 @@ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::strin
m.F(A::SHOOT_TIMER)-=fElapsedTime; m.F(A::SHOOT_TIMER)-=fElapsedTime;
if(m.F(A::SHOOT_TIMER)<=0.f){ if(m.F(A::SHOOT_TIMER)<=0.f){
m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.phase=MOVE; SETPHASE(MOVE);
} }
}break; }break;
} }

@ -63,12 +63,7 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
SLASH SLASH
}; };
if(!m.B(A::INITIALIZED)){ switch(PHASE()){
m.B(A::INITIALIZED)=true;
m.phase=INIT;
}
switch(m.phase){
case INIT:{ case INIT:{
m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency"); m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency");
m.F(A::PARROT_FLY_TIMER)=ConfigFloat("Parrot Fly Wait Time"); m.F(A::PARROT_FLY_TIMER)=ConfigFloat("Parrot Fly Wait Time");
@ -81,7 +76,7 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
} }
m.mountedSprOffset=ConfigVec("Imposed Monster Offset"); m.mountedSprOffset=ConfigVec("Imposed Monster Offset");
m.deathData.emplace_back(ConfigString("Spawned Monster"),1U); m.deathData.emplace_back(ConfigString("Spawned Monster"),1U);
m.phase=MOVE; SETPHASE(MOVE);
}break; }break;
case MOVE:{ case MOVE:{
if(m.F(A::PARROT_FLY_TIMER)>0.f){ if(m.F(A::PARROT_FLY_TIMER)>0.f){
@ -99,24 +94,25 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),m.GetPos())}; const float distToPlayer{util::distance(game->GetPlayer()->GetPos(),m.GetPos())};
if(distToPlayer<=ConfigFloat("Shoot Max Range")/100.f*24){ if(distToPlayer<=ConfigFloat("Shoot Max Range")/100.f*24){
m.F(A::SHOOT_TIMER)=ConfigFloat("Shooting Delay"); m.F(A::SHOOT_TIMER)=ConfigFloat("Shooting Delay");
m.phase=PREPARE_SHOOT; SETPHASE(PREPARE_SHOOT);
m.PerformAnimation("SHOOT",game->GetPlayer()->GetPos()); m.PerformAnimation("SHOOT",game->GetPlayer()->GetPos());
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos(); m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
} }
} }
m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency"); m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency");
}else }else
if(m.GetHealth()<ConfigInt("Rum Drink Threshold")){ if(m.GetHealth()<ConfigInt("Rum Drink Threshold")&&m.I(A::RUM_DRINK_COUNT)<ConfigInt("Rum Drink Count")){
m.PerformAnimation("DRINK"); m.PerformAnimation("DRINK");
m.F(A::BREAK_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::BREAK_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=DRINK_RUM; SETPHASE(DRINK_RUM);
m.I(A::RUM_DRINK_COUNT)++;
} }
else{ else{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos()); float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){ if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{ }else{
m.phase=WINDUP; SETPHASE(WINDUP);
m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash. m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash.
switch(m.I(A::ATTACK_TYPE)){ switch(m.I(A::ATTACK_TYPE)){
case STAB:{ case STAB:{
@ -138,38 +134,38 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
CreateBullet(Bullet)(m.GetPos(),util::pointTo(m.GetPos(),m.V(A::LOCKON_POS))*ConfigFloat("Bullet Speed"),ConfigFloat("Bullet Radius"),m.GetAttack(),m.OnUpperLevel(),false,ConfigPixel("Bullet Color"),vf2d{1.f,1.f}*ConfigFloat("Bullet Radius")/3.f)EndBullet; CreateBullet(Bullet)(m.GetPos(),util::pointTo(m.GetPos(),m.V(A::LOCKON_POS))*ConfigFloat("Bullet Speed"),ConfigFloat("Bullet Radius"),m.GetAttack(),m.OnUpperLevel(),false,ConfigPixel("Bullet Color"),vf2d{1.f,1.f}*ConfigFloat("Bullet Radius")/3.f)EndBullet;
m.PerformAnimation("SHOOTING"); m.PerformAnimation("SHOOTING");
m.F(A::SHOOT_ANIMATION_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::SHOOT_ANIMATION_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=SHOOT_RELOAD; SETPHASE(SHOOT_RELOAD);
} }
}break; }break;
case SHOOT_RELOAD:{ case SHOOT_RELOAD:{
m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime; m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime;
if(m.F(A::SHOOT_ANIMATION_TIME)<=0.f){ if(m.F(A::SHOOT_ANIMATION_TIME)<=0.f){
m.PerformAnimation("IDLE"); m.PerformAnimation("IDLE");
m.phase=MOVE; SETPHASE(MOVE);
} }
}break; }break;
case DRINK_RUM:{ case DRINK_RUM:{
m.F(A::BREAK_TIME)-=fElapsedTime; m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=0.f){ if(m.F(A::BREAK_TIME)<=0.f){
m.Heal(ConfigInt("Rum Health Recovery"),true); m.Heal(ConfigInt("Rum Health Recovery"),true);
m.phase=MOVE; SETPHASE(MOVE);
} }
}break; }break;
case WINDUP:{ case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){ if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY; SETPHASE(RECOVERY);
switch(m.I(A::ATTACK_TYPE)){ switch(m.I(A::ATTACK_TYPE)){
case STAB:{ case STAB:{
vf2d stabTarget=game->GetPlayer()->GetPos(); vf2d stabTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("STABBING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("STABBING");
CreateBullet(DaggerStab)(m,ConfigString("Dagger Stab Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(stabTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"), CreateBullet(DaggerStab)(m,ConfigString("Dagger Stab Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Stab Knockback"),m.OnUpperLevel(),m.GetFacingDirection(),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Stab Distance"),
DaggerStab::DirectionOffsets{ConfigVec("Dagger Up Offset"),ConfigVec("Dagger Down Offset"),ConfigVec("Dagger Right Offset"),ConfigVec("Dagger Left Offset")})EndBullet; DaggerStab::DirectionOffsets{ConfigVec("Dagger Up Offset"),ConfigVec("Dagger Down Offset"),ConfigVec("Dagger Right Offset"),ConfigVec("Dagger Left Offset")})EndBullet;
}break; }break;
case SLASH:{ case SLASH:{
vf2d slashTarget=game->GetPlayer()->GetPos(); vf2d slashTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("SLASHING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SLASHING");
CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet; CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirection(),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
}break; }break;
default:ERR(std::format("WARNING! Invalid Attack type {} provided. THIS SHOULD NOT BE HAPPENING!",m.I(A::ATTACK_TYPE))); default:ERR(std::format("WARNING! Invalid Attack type {} provided. THIS SHOULD NOT BE HAPPENING!",m.I(A::ATTACK_TYPE)));
} }
@ -181,7 +177,7 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::RECOVERY_TIME)-=fElapsedTime; m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));} if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE; if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
}break; }break;
} }
} }

@ -57,10 +57,10 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
WHIRLWIND, WHIRLWIND,
}; };
switch(m.phase){ switch(PHASE()){
case INIT:{ case INIT:{
m.F(A::CHASE_TIMER)=ConfigFloat("Ability Choose Timer"); m.F(A::CHASE_TIMER)=ConfigFloat("Ability Choose Timer");
m.phase=MOVE; SETPHASE(MOVE);
}break; }break;
case MOVE:{ case MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos()); float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
@ -78,7 +78,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
std::pair<float,float>whirlwindAttackRollRange{jumpAttackRollRange.second,jumpAttackRollRange.second+ConfigFloat("Whirlwind Attack Chance")}; std::pair<float,float>whirlwindAttackRollRange{jumpAttackRollRange.second,jumpAttackRollRange.second+ConfigFloat("Whirlwind Attack Chance")};
if(roll<jumpAttackRollRange.second&&distanceToPlayer>=ConfigFloatArr("Jump Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Jump Attack Ranges",1)/100.f*24.f){ if(roll<jumpAttackRollRange.second&&distanceToPlayer>=ConfigFloatArr("Jump Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Jump Attack Ranges",1)/100.f*24.f){
m.phase=LEAP; SETPHASE(LEAP);
m.V(A::JUMP_TARGET_POS)=game->GetPlayer()->GetPos(); m.V(A::JUMP_TARGET_POS)=game->GetPlayer()->GetPos();
m.I(A::ABILITY_COUNT)--; m.I(A::ABILITY_COUNT)--;
const float impactArea{ConfigFloat("Jump Attack Impact Area")}; const float impactArea{ConfigFloat("Jump Attack Impact Area")};
@ -92,7 +92,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
}else }else
if(roll>=whirlwindAttackRollRange.first&&roll<whirlwindAttackRollRange.second if(roll>=whirlwindAttackRollRange.first&&roll<whirlwindAttackRollRange.second
&&distanceToPlayer>=ConfigFloatArr("Whirlwind Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Whirlwind Attack Ranges",1)/100.f*24.f){ &&distanceToPlayer>=ConfigFloatArr("Whirlwind Attack Ranges",0)/100.f*24.f&&distanceToPlayer<=ConfigFloatArr("Whirlwind Attack Ranges",1)/100.f*24.f){
m.phase=PREPARE_WHIRLWIND; SETPHASE(PREPARE_WHIRLWIND);
m.I(A::ABILITY_COUNT)--; m.I(A::ABILITY_COUNT)--;
vf2d aimingTarget{game->GetPlayer()->GetPos()}; vf2d aimingTarget{game->GetPlayer()->GetPos()};
if(aimingTarget==m.GetPos()){ //Handle edge case. if(aimingTarget==m.GetPos()){ //Handle edge case.
@ -109,7 +109,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){ if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{ }else{
m.phase=WINDUP; SETPHASE(WINDUP);
m.F(A::CASTING_TIMER)=ConfigFloat("Slash Windup Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Slash Windup Time");
m.PerformAnimation("SLASH",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SLASH",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
} }
@ -117,7 +117,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
case WINDUP:{ case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){ if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY; SETPHASE(RECOVERY);
vf2d slashTarget=game->GetPlayer()->GetPos(); vf2d slashTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("SLASHING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("SLASHING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet; CreateBullet(DaggerSlash)(m,ConfigString("Dagger Slash Image"),ConfigFloat("Dagger Hit Radius"),m.GetAttack(),ConfigFloat("Dagger Slash Knockback"),m.OnUpperLevel(),m.GetFacingDirectionToTarget(slashTarget),ConfigFloat("Dagger Frame Duration"),ConfigFloat("Dagger Slash Distance"))EndBullet;
@ -129,7 +129,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
m.F(A::RECOVERY_TIME)-=fElapsedTime; m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));} if(m.F(A::CASTING_TIMER)<=0){m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));}
if(m.F(A::RECOVERY_TIME)<=0)m.phase=MOVE; if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
}break; }break;
case LEAP:{ case LEAP:{
m.F(A::JUMP_MOVE_TO_TARGET_TIMER)+=fElapsedTime; m.F(A::JUMP_MOVE_TO_TARGET_TIMER)+=fElapsedTime;
@ -143,7 +143,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
game->ProximityKnockback(m.GetPos(),ConfigFloat("Jump Attack Impact Area")/100.f*24.f,ConfigFloat("Jump Attack Knockback Amount"),HurtType::MONSTER|HurtType::PLAYER); game->ProximityKnockback(m.GetPos(),ConfigFloat("Jump Attack Impact Area")/100.f*24.f,ConfigFloat("Jump Attack Knockback Amount"),HurtType::MONSTER|HurtType::PLAYER);
m.SetZ(0.f); m.SetZ(0.f);
m.SetPos(m.V(A::JUMP_TARGET_POS)); m.SetPos(m.V(A::JUMP_TARGET_POS));
m.phase=MOVE; SETPHASE(MOVE);
m.PerformIdleAnimation(); m.PerformIdleAnimation();
m.SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){}); m.SetStrategyDrawFunction([](AiL*game,Monster&monster,const std::string&strategy){});
} }
@ -151,7 +151,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
case PREPARE_WHIRLWIND:{ case PREPARE_WHIRLWIND:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){ if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=WHIRLWIND; SETPHASE(WHIRLWIND);
m.F(A::CHASE_TIMER)=0.f; m.F(A::CHASE_TIMER)=0.f;
m.PerformAnimation("SPINNING"); m.PerformAnimation("SPINNING");
} }
@ -159,7 +159,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
case WHIRLWIND:{ case WHIRLWIND:{
m.F(A::CHASE_TIMER)+=fElapsedTime; m.F(A::CHASE_TIMER)+=fElapsedTime;
if(m.F(A::CHASE_TIMER)>=ConfigFloat("Whirlwind Spin Time")){ if(m.F(A::CHASE_TIMER)>=ConfigFloat("Whirlwind Spin Time")){
m.phase=MOVE; SETPHASE(MOVE);
m.PerformIdleAnimation(); m.PerformIdleAnimation();
} }
m.MoveForward(m.V(A::PATH_DIR),fElapsedTime); m.MoveForward(m.V(A::PATH_DIR),fElapsedTime);

@ -136,7 +136,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}; };
const auto Recovered=[&](){ const auto Recovered=[&](){
switch(m.phase){ switch(PHASE()){
case 2:{ case 2:{
switch(m.I(A::JUMP_COUNT)){ switch(m.I(A::JUMP_COUNT)){
case 1: case 1:
@ -211,7 +211,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1}; lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1};
} }
game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor"))); game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor")));
if(m.phase!=2){ if(PHASE()!=2){
game->GetPlayer()->ApplyIframes(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. }else{ //In phase 2 you can get hit by multiple knockbacks, so the iframe time is a lot shorter.
game->GetPlayer()->ApplyIframes(0.2f); game->GetPlayer()->ApplyIframes(0.2f);
@ -219,7 +219,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
} }
} }
m.SetZ(0); m.SetZ(0);
Landed(m.phase); Landed(PHASE());
m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){}); m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){});
} else } else
if(m.F(A::JUMP_LANDING_TIMER)<=ConfigFloat("JumpWarningIndicatorTime")){ if(m.F(A::JUMP_LANDING_TIMER)<=ConfigFloat("JumpWarningIndicatorTime")){
@ -244,19 +244,19 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
return; return;
} }
switch(m.phase){ switch(PHASE()){
case 0:{ case 0:{
m.size=ConfigInt("Phase1.Size")/100.f; m.size=ConfigInt("Phase1.Size")/100.f;
m.diesNormally=false; m.diesNormally=false;
m.F(A::IFRAME_TIME_UPON_HIT)=0; m.F(A::IFRAME_TIME_UPON_HIT)=0;
m.ApplyIframes(ConfigFloat("Phase5.IframeTimePerHit")); m.ApplyIframes(ConfigFloat("Phase5.IframeTimePerHit"));
m.phase=ConfigInt("StartPhase"); SETPHASE(ConfigInt("StartPhase"));
}break; }break;
case 1:{ case 1:{
if(m.GetHealthRatio()<=ConfigFloat("Phase2.Change")/100.f){ if(m.GetHealthRatio()<=ConfigFloat("Phase2.Change")/100.f){
m.phase=2; SETPHASE(2);
m.SetSize(ConfigFloat("Phase2.Size")/100,false); m.SetSize(ConfigFloat("Phase2.Size")/100,false);
TransitionPhase(m.phase); TransitionPhase(PHASE());
return; return;
} }
if(m.F(A::SHOOT_RING_TIMER)==0){ if(m.F(A::SHOOT_RING_TIMER)==0){
@ -287,12 +287,12 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break; }break;
case 2:{ case 2:{
if(m.GetHealthRatio()<=ConfigFloat("Phase3.Change")/100.f){ if(m.GetHealthRatio()<=ConfigFloat("Phase3.Change")/100.f){
m.phase=3; SETPHASE(3);
m.SetSize(ConfigFloat("Phase3.Size")/100,false); m.SetSize(ConfigFloat("Phase3.Size")/100,false);
if(m.I(A::PATTERN_REPEAT_COUNT)==0){ if(m.I(A::PATTERN_REPEAT_COUNT)==0){
m.I(A::PATTERN_REPEAT_COUNT)=1; m.I(A::PATTERN_REPEAT_COUNT)=1;
} }
TransitionPhase(m.phase); TransitionPhase(PHASE());
return; return;
} }
if(m.F(A::SHOOT_TIMER)==0){ if(m.F(A::SHOOT_TIMER)==0){
@ -315,10 +315,10 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break; }break;
case 3:{ case 3:{
if(m.GetHealthRatio()<=ConfigFloat("Phase4.Change")/100.f){ if(m.GetHealthRatio()<=ConfigFloat("Phase4.Change")/100.f){
m.phase=4; SETPHASE(4);
m.SetSize(ConfigFloat("Phase4.Size")/100,false); m.SetSize(ConfigFloat("Phase4.Size")/100,false);
m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100); m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100);
TransitionPhase(m.phase); TransitionPhase(PHASE());
return; return;
} }
if(m.I(A::PATTERN_REPEAT_COUNT)==0){ if(m.I(A::PATTERN_REPEAT_COUNT)==0){
@ -344,10 +344,10 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break; }break;
case 4:{ case 4:{
if(m.hp<=1){ //HP can't reach 0 when the dies normally flag is on. if(m.hp<=1){ //HP can't reach 0 when the dies normally flag is on.
m.phase=5; SETPHASE(5);
m.F(A::IFRAME_TIME_UPON_HIT)=1; m.F(A::IFRAME_TIME_UPON_HIT)=1;
m.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-1; m.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-1;
TransitionPhase(m.phase); TransitionPhase(PHASE());
return; return;
} }
if(m.I(A::PHASE_REPEAT_COUNT)>=5){ if(m.I(A::PHASE_REPEAT_COUNT)>=5){

@ -157,14 +157,14 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
} }
#pragma endregion #pragma endregion
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time"); m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time");
m.I(A::PATTERN_REPEAT_COUNT)=ConfigInt("Beginning Phase.Repeat Count"); m.I(A::PATTERN_REPEAT_COUNT)=ConfigInt("Beginning Phase.Repeat Count");
m.F(A::HEALTH_PCT_PHASE)=1.f; m.F(A::HEALTH_PCT_PHASE)=1.f;
m.F(A::NEXT_HEALTH_PCT_PILLAR_PHASE)=ConfigFloat("Pillar Respawns.Start HP Threshold")/100.f; m.F(A::NEXT_HEALTH_PCT_PILLAR_PHASE)=ConfigFloat("Pillar Respawns.Start HP Threshold")/100.f;
m.I(A::SHOCKWAVE_COLOR)=ConfigPixel("Shockwave.Danger Area Color").n; m.I(A::SHOCKWAVE_COLOR)=ConfigPixel("Shockwave.Danger Area Color").n;
m.phase=SPAWN_PILLAR_PREPARE; SETPHASE(SPAWN_PILLAR_PREPARE);
if(ConfigIntArr("Pillar Respawns.Respawn Count",0)<ConfigIntArr("Pillar Respawns.Respawn Count",1))ERR(std::format("WARNING! {} Stone golem pillars were declared damaged when only {} will spawn. Please make sure the number is the same or equal to the total spawned pillars! (\"Pillar Respawns.Respawn Count\" strategy property)",ConfigIntArr("Pillar Respawns.Respawn Count",1),ConfigIntArr("Pillar Respawns.Respawn Count",0))); if(ConfigIntArr("Pillar Respawns.Respawn Count",0)<ConfigIntArr("Pillar Respawns.Respawn Count",1))ERR(std::format("WARNING! {} Stone golem pillars were declared damaged when only {} will spawn. Please make sure the number is the same or equal to the total spawned pillars! (\"Pillar Respawns.Respawn Count\" strategy property)",ConfigIntArr("Pillar Respawns.Respawn Count",1),ConfigIntArr("Pillar Respawns.Respawn Count",0)));
}break; }break;
@ -176,7 +176,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.PerformAnimation("CAST",m.GetFacingDirectionToTarget(m.V(A::LOCKON_POS))); m.PerformAnimation("CAST",m.GetFacingDirectionToTarget(m.V(A::LOCKON_POS)));
game->AddEffect(std::make_unique<SpellCircle>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"range_indicator.png","spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Circle Rotation Spd")),false,vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Insignia Rotation Spd"))),true); game->AddEffect(std::make_unique<SpellCircle>(m.V(A::LOCKON_POS),ConfigFloat("Beginning Phase.Pillar Cast Time"),"range_indicator.png","spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Circle Rotation Spd")),false,vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Beginning Phase.Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Beginning Phase.Pillar Spell Insignia Rotation Spd"))),true);
m.F(A::CASTING_TIMER)=ConfigFloat("Beginning Phase.Pillar Cast Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Beginning Phase.Pillar Cast Time");
m.phase=SPAWN_PILLAR_CAST; SETPHASE(SPAWN_PILLAR_CAST);
} }
}break; }break;
case SPAWN_PILLAR_CAST:{ case SPAWN_PILLAR_CAST:{
@ -188,11 +188,11 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
game->SpawnMonster(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar"),m.OnUpperLevel()); game->SpawnMonster(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar"),m.OnUpperLevel());
game->Hurt(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult(),MONSTER_DATA.at("Stone Golem Pillar").GetAttack(),m.OnUpperLevel(),0.f,HurtType::PLAYER); game->Hurt(m.V(A::LOCKON_POS),MONSTER_DATA.at("Stone Golem Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Golem Pillar").GetSizeMult(),MONSTER_DATA.at("Stone Golem Pillar").GetAttack(),m.OnUpperLevel(),0.f,HurtType::PLAYER);
if(m.I(A::PATTERN_REPEAT_COUNT)<=0){ if(m.I(A::PATTERN_REPEAT_COUNT)<=0){
m.phase=STANDARD; SETPHASE(STANDARD);
}else{ }else{
m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time"); m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time");
m.phase=SPAWN_PILLAR_PREPARE; SETPHASE(SPAWN_PILLAR_PREPARE);
} }
} }
}break; }break;
@ -205,7 +205,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
m.SIZET(A::PREVIOUS_MONSTER_COUNT)=MONSTER_LIST.size(); m.SIZET(A::PREVIOUS_MONSTER_COUNT)=MONSTER_LIST.size();
m.PerformAnimation("CAST",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos())); m.PerformAnimation("CAST",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
PrepareSafeAreas(); PrepareSafeAreas();
m.phase=SHOCKWAVE; SETPHASE(SHOCKWAVE);
break; break;
} }
if(m.F(A::NEXT_HEALTH_PCT_PILLAR_PHASE)>=m.GetHealthRatio()){ if(m.F(A::NEXT_HEALTH_PCT_PILLAR_PHASE)>=m.GetHealthRatio()){
@ -218,7 +218,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
} }
if(StoneThrowRollSucceeds){ //The intent is one or the other attack is supposed to happen. We can't do the slam and a throw, rerolling repeatedly each tick is unncessary. if(StoneThrowRollSucceeds){ //The intent is one or the other attack is supposed to happen. We can't do the slam and a throw, rerolling repeatedly each tick is unncessary.
m.phase=STONE_THROW_CAST; SETPHASE(STONE_THROW_CAST);
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos(); m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.PerformAnimation("TOSS ROCK CAST"); m.PerformAnimation("TOSS ROCK CAST");
m.F(A::CASTING_TIMER)=ConfigFloat("Standard Attack.Stone Throw Cast Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Standard Attack.Stone Throw Cast Time");
@ -233,7 +233,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
CreateBullet(LargeStone)(m.GetPos()+vf2d{0,ConfigFloat("Standard Attack.Stone Throw Height Offset")/2.f},ConfigFloat("Standard Attack.Stone Throw Time"),m.V(A::LOCKON_POS),m.F(A::CASTING_TIMER),ConfigPixels("Standard Attack.Stone Radius"),ConfigFloat("Standard Attack.Stone Throw Height Offset"),acc,ConfigInt("Standard Attack.Stone Damage"),ConfigFloat("Standard Attack.Stone Throw Knockback Factor"),m.OnUpperLevel(),false,INFINITY,false,WHITE,vf2d{1,1}*m.GetSizeMult(),util::random(2*PI))EndBullet; CreateBullet(LargeStone)(m.GetPos()+vf2d{0,ConfigFloat("Standard Attack.Stone Throw Height Offset")/2.f},ConfigFloat("Standard Attack.Stone Throw Time"),m.V(A::LOCKON_POS),m.F(A::CASTING_TIMER),ConfigPixels("Standard Attack.Stone Radius"),ConfigFloat("Standard Attack.Stone Throw Height Offset"),acc,ConfigInt("Standard Attack.Stone Damage"),ConfigFloat("Standard Attack.Stone Throw Knockback Factor"),m.OnUpperLevel(),false,INFINITY,false,WHITE,vf2d{1,1}*m.GetSizeMult(),util::random(2*PI))EndBullet;
}else{ }else{
m.phase=BEAR_ATTACK; SETPHASE(BEAR_ATTACK);
m.F(A::CHASE_TIMER)=0.f; m.F(A::CHASE_TIMER)=0.f;
} }
}break; }break;
@ -243,13 +243,13 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
SoundEffect::StopLoopingSFX(m.SIZET(A::LOOPING_SOUND_ID)); SoundEffect::StopLoopingSFX(m.SIZET(A::LOOPING_SOUND_ID));
m.PerformAnimation("TOSS ROCK"); m.PerformAnimation("TOSS ROCK");
m.F(A::RECOVERY_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::RECOVERY_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=STONE_THROW_FINISH_ANIMATION; SETPHASE(STONE_THROW_FINISH_ANIMATION);
} }
}break; }break;
case STONE_THROW_FINISH_ANIMATION:{ case STONE_THROW_FINISH_ANIMATION:{
m.F(A::RECOVERY_TIME)-=fElapsedTime; m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::RECOVERY_TIME)<=0.f){ if(m.F(A::RECOVERY_TIME)<=0.f){
m.phase=STANDARD; SETPHASE(STANDARD);
} }
}break; }break;
case SHOCKWAVE:{ case SHOCKWAVE:{
@ -259,7 +259,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
game->SetWorldColor(newCol); game->SetWorldColor(newCol);
if(m.SIZET(A::PREVIOUS_MONSTER_COUNT)!=MONSTER_LIST.size()){ //The monster list has changed...Whether it's a pillar getting added or removed, it's important we recalculate safe areas proper. if(m.SIZET(A::PREVIOUS_MONSTER_COUNT)!=MONSTER_LIST.size()){ //The monster list has changed...Whether it's a pillar getting added or removed, it's important we recalculate safe areas proper.
m.phase=FIX_SAFE_AREAS; //HACK ALERT! Since spawning/removing monsters doesn't immediately occur in the MONSTER_LIST structure, we must defer the safe areas until the next tick and then recalculate them. To do this, we put the monster into another state and pause the shockwave attack for a frame to fix the new spawn areas on the next tick. SETPHASE(FIX_SAFE_AREAS); //HACK ALERT! Since spawning/removing monsters doesn't immediately occur in the MONSTER_LIST structure, we must defer the safe areas until the next tick and then recalculate them. To do this, we put the monster into another state and pause the shockwave attack for a frame to fix the new spawn areas on the next tick.
m.F(A::SAFE_AREA_WAIT_TIMER)=0.01f; m.F(A::SAFE_AREA_WAIT_TIMER)=0.01f;
m.SIZET(A::PREVIOUS_MONSTER_COUNT)=MONSTER_LIST.size(); m.SIZET(A::PREVIOUS_MONSTER_COUNT)=MONSTER_LIST.size();
} }
@ -288,14 +288,14 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
}); });
SoundEffect::PlaySFX("Shockwave",m.GetPos()); SoundEffect::PlaySFX("Shockwave",m.GetPos());
game->AddEffect(std::make_unique<ExpandingRing>(m.GetPos(),ConfigFloat("Shockwave.Shockwave Ring Lifetime"),"finishring.png",m.OnUpperLevel(),vf2d{ConfigFloat("Shockwave.Ring Expand Speed"),ConfigFloat("Shockwave.Ring Expand Speed")},vf2d{1.f,1.f},ConfigFloat("Shockwave.Shockwave Fadeout Time"),vf2d{},ConfigPixel("Shockwave.Shockwave Color")),true); game->AddEffect(std::make_unique<ExpandingRing>(m.GetPos(),ConfigFloat("Shockwave.Shockwave Ring Lifetime"),"finishring.png",m.OnUpperLevel(),vf2d{ConfigFloat("Shockwave.Ring Expand Speed"),ConfigFloat("Shockwave.Ring Expand Speed")},vf2d{1.f,1.f},ConfigFloat("Shockwave.Shockwave Fadeout Time"),vf2d{},ConfigPixel("Shockwave.Shockwave Color")),true);
m.phase=STANDARD; SETPHASE(STANDARD);
game->SetWorldColor(WHITE); game->SetWorldColor(WHITE);
} }
}break; }break;
case FIX_SAFE_AREAS:{ case FIX_SAFE_AREAS:{
if(m.F(A::SAFE_AREA_WAIT_TIMER)<=0.f){ if(m.F(A::SAFE_AREA_WAIT_TIMER)<=0.f){
PrepareSafeAreas(); //Recalculate safe areas if the shockwave attack is going off. PrepareSafeAreas(); //Recalculate safe areas if the shockwave attack is going off.
m.phase=SHOCKWAVE; SETPHASE(SHOCKWAVE);
}else m.F(A::SAFE_AREA_WAIT_TIMER)-=fElapsedTime; }else m.F(A::SAFE_AREA_WAIT_TIMER)-=fElapsedTime;
}break; }break;
case BEAR_ATTACK:{ case BEAR_ATTACK:{
@ -304,11 +304,11 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
//Extending the bear script's variables to read the state of it... //Extending the bear script's variables to read the state of it...
const bool SlamHasFinished=m.I(A::ATTACK_COUNT)!=m.I(A::BEAR_STOMP_COUNT); //The bear script uses the internal phase variable to determine the state of things. const bool SlamHasFinished=m.I(A::ATTACK_COUNT)!=m.I(A::BEAR_STOMP_COUNT); //The bear script uses the internal phase variable to determine the state of things.
if(SlamHasFinished){ if(SlamHasFinished){
m.phase=STANDARD; SETPHASE(STANDARD);
m.I(A::ATTACK_COUNT)=m.I(A::BEAR_STOMP_COUNT); m.I(A::ATTACK_COUNT)=m.I(A::BEAR_STOMP_COUNT);
}else }else
if(m.I(A::PHASE)==0&&m.F(A::CHASE_TIMER)>=ConfigFloat("Max Chase Time")){ if(m.GetPhase("Bear")==0&&m.F(A::CHASE_TIMER)>=ConfigFloat("Max Chase Time")){
m.phase=DOUBLE_ROCK_TOSS; SETPHASE(DOUBLE_ROCK_TOSS);
m.PerformAnimation("RAISE ROCK"); m.PerformAnimation("RAISE ROCK");
m.I(A::STONE_TOSS_COUNT)=ConfigInt("Stone Rain.Initial Stone Toss Count"); m.I(A::STONE_TOSS_COUNT)=ConfigInt("Stone Rain.Initial Stone Toss Count");
m.F(A::STONE_TOSS_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::STONE_TOSS_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
@ -325,7 +325,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
game->AddEffect(std::make_unique<RockLaunch>(m.GetPos()+vf2d{util::random_range(throwPos.GetReal(0),throwPos.GetReal(2)),util::random_range(throwPos.GetReal(1),throwPos.GetReal(3))},10.f,"rock.png",ConfigFloat("Stone Rain.Stone Toss Delay"),ConfigFloat("Stone Rain.Stone Toss Rock Size Mult"),0.1f,vf2d{0.f,-ConfigFloat("Stone Rain.Stone Toss Throw Speed")},WHITE,util::random(2*PI),0.f)); game->AddEffect(std::make_unique<RockLaunch>(m.GetPos()+vf2d{util::random_range(throwPos.GetReal(0),throwPos.GetReal(2)),util::random_range(throwPos.GetReal(1),throwPos.GetReal(3))},10.f,"rock.png",ConfigFloat("Stone Rain.Stone Toss Delay"),ConfigFloat("Stone Rain.Stone Toss Rock Size Mult"),0.1f,vf2d{0.f,-ConfigFloat("Stone Rain.Stone Toss Throw Speed")},WHITE,util::random(2*PI),0.f));
if(m.I(A::STONE_TOSS_COUNT)<=0){ if(m.I(A::STONE_TOSS_COUNT)<=0){
m.phase=STONE_RAIN; SETPHASE(STONE_RAIN);
m.F(A::BREAK_TIME)=ConfigFloat("Stone Rain.Stone Golem Wait Time"); m.F(A::BREAK_TIME)=ConfigFloat("Stone Rain.Stone Golem Wait Time");
m.PerformAnimation("CAST"); m.PerformAnimation("CAST");
@ -338,7 +338,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
case STONE_RAIN:{ case STONE_RAIN:{
m.F(A::BREAK_TIME)-=fElapsedTime; m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=0.f){ if(m.F(A::BREAK_TIME)<=0.f){
m.phase=STANDARD; SETPHASE(STANDARD);
} }
}break; }break;
} }

@ -72,15 +72,15 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
}; };
auto ReturnToWaitingPhase=[&](){ auto ReturnToWaitingPhase=[&](){
m.phase=WAITING; SETPHASE(WAITING);
m.PerformIdleAnimation(); m.PerformIdleAnimation();
m.F(A::ATTACK_COOLDOWN)=0.f; m.F(A::ATTACK_COOLDOWN)=0.f;
}; };
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::ATTACK_COOLDOWN)=util::random(ConfigFloat("Attack Wait Time")/1.5f); m.F(A::ATTACK_COOLDOWN)=util::random(ConfigFloat("Attack Wait Time")/1.5f);
m.phase=WAITING; SETPHASE(WAITING);
}break; }break;
case WAITING:{ case WAITING:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime; m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -95,7 +95,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 0:{ case 0:{
m.PerformAnimation("STONE PILLAR CAST"); m.PerformAnimation("STONE PILLAR CAST");
m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos()); m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos());
m.phase=STONE_PILLAR_CAST; SETPHASE(STONE_PILLAR_CAST);
m.F(A::CASTING_TIMER)=ConfigFloat("Stone Pillar Cast Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Stone Pillar Cast Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos(); m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
game->AddEffect(std::make_unique<SpellCircle>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"range_indicator.png","spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Circle Rotation Spd")),false,vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Insignia Rotation Spd"))),false); game->AddEffect(std::make_unique<SpellCircle>(m.V(A::LOCKON_POS),ConfigFloat("Stone Pillar Cast Time"),"range_indicator.png","spell_insignia.png",m.OnUpperLevel(),vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*1.25f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Circle Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Circle Rotation Spd")),false,vf2d{1.f,1.f}*(MONSTER_DATA.at("Stone Pillar").GetCollisionRadius()*MONSTER_DATA.at("Stone Pillar").GetSizeMult()/12.f)*0.9f,0.3f,vf2d{},ConfigPixel("Stone Pillar Spell Insignia Color"),util::random(2*PI),util::degToRad(ConfigFloat("Stone Pillar Spell Insignia Rotation Spd"))),false);
@ -103,7 +103,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 1:{ case 1:{
m.PerformAnimation("ROCK TOSS CAST"); m.PerformAnimation("ROCK TOSS CAST");
m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos()); m.SIZET(A::LOOPING_SOUND_ID)=SoundEffect::PlayLoopingSFX("Rock Toss Cast",m.GetPos());
m.phase=SHOOT_STONE_CAST; SETPHASE(SHOOT_STONE_CAST);
m.B(A::PLAYED_FLAG)=false; m.B(A::PLAYED_FLAG)=false;
m.F(A::CASTING_TIMER)=ConfigFloat("Rock Toss Track Time")+ConfigFloat("Rock Toss Wait Time"); m.F(A::CASTING_TIMER)=ConfigFloat("Rock Toss Track Time")+ConfigFloat("Rock Toss Wait Time");
CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,0.f,ConfigPixels("Rock Toss Max Spawn Distance"),ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius"),std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{1,1})EndBullet; CreateBullet(LevitatingRock)(m,game->GetPlayer()->GetPos(),1.f,0.f,ConfigPixels("Rock Toss Max Spawn Distance"),ConfigFloat("Rock Toss Track Time"),ConfigFloat("Rock Toss Wait Time"),ConfigFloat("Rock Toss Bullet Speed"),ConfigFloat("Rock Radius"),std::max(1,ConfigInt("Rock Toss Damage")/5),m.OnUpperLevel(),false,WHITE,vf2d{1,1})EndBullet;
@ -121,7 +121,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 2:{ case 2:{
SoundEffect::PlaySFX("Dig",m.GetPos()); SoundEffect::PlaySFX("Dig",m.GetPos());
m.PerformAnimation("BURROW UNDERGROUND"); m.PerformAnimation("BURROW UNDERGROUND");
m.phase=DIVE_UNDERGROUND_DIG; SETPHASE(DIVE_UNDERGROUND_DIG);
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
}break; }break;
} }
@ -167,7 +167,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case DIVE_UNDERGROUND_DIG:{ case DIVE_UNDERGROUND_DIG:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){ if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=DIVE_UNDERGROUND_MOVE; SETPHASE(DIVE_UNDERGROUND_MOVE);
float randomAngle=util::random(2*PI); float randomAngle=util::random(2*PI);
const float minDist=ConfigPixelsArr("Burrow Teleport Distance",0); const float minDist=ConfigPixelsArr("Burrow Teleport Distance",0);
@ -198,7 +198,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
m.B(A::IGNORE_DEFAULT_ANIMATIONS)=false; m.B(A::IGNORE_DEFAULT_ANIMATIONS)=false;
SoundEffect::PlaySFX("Rise",m.GetPos()); SoundEffect::PlaySFX("Rise",m.GetPos());
m.PerformAnimation("RISE FROM UNDERGROUND"); m.PerformAnimation("RISE FROM UNDERGROUND");
m.phase=DIVE_UNDERGROUND_SURFACE; SETPHASE(DIVE_UNDERGROUND_SURFACE);
m.targetAcquireTimer=0; m.targetAcquireTimer=0;
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration(); m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();

@ -53,9 +53,9 @@ INCLUDE_DATA
using A=Attribute; using A=Attribute;
void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy){
switch(m.phase){ switch(PHASE()){
case 0:{ case 0:{
m.phase=ConfigInt("StartPhase"); SETPHASE(ConfigInt("StartPhase"));
m.overlaySprite=ConfigString("Overlay Sprite"); m.overlaySprite=ConfigString("Overlay Sprite");
m.overlaySpriteTransparency=0U; m.overlaySpriteTransparency=0U;
@ -94,7 +94,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
}break; }break;
case 1:{ //Run bear strategy in phase 1. case 1:{ //Run bear strategy in phase 1.
auto TransitionToPhase2=[&](){ auto TransitionToPhase2=[&](){
m.phase=2; SETPHASE(2);
m.PerformAnimation("SIT"); m.PerformAnimation("SIT");
m.AddBuff(BARRIER_DAMAGE_REDUCTION,INFINITE,ConfigFloat("Phase 2.Barrier Damage Reduction")/100.f); m.AddBuff(BARRIER_DAMAGE_REDUCTION,INFINITE,ConfigFloat("Phase 2.Barrier Damage Reduction")/100.f);
m.I(A::PHASE_REPEAT_COUNT)=ConfigInt("Phase 2.Wisp Pattern Spawn Count"); m.I(A::PHASE_REPEAT_COUNT)=ConfigInt("Phase 2.Wisp Pattern Spawn Count");
@ -106,7 +106,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
if(m.GetHealthRatio()<=ConfigFloat("Phase 2.Change")/100.f){ if(m.GetHealthRatio()<=ConfigFloat("Phase 2.Change")/100.f){
//before moving to Phase 2, we need to make sure we're in Phase 0 of the bear AI. //before moving to Phase 2, we need to make sure we're in Phase 0 of the bear AI.
if(m.overlaySpriteTransparency<210U){ if(m.overlaySpriteTransparency<210U){
if(m.I(A::PHASE)!=0)goto bear; if(m.GetPhase("Bear")!=0)goto bear;
else{ else{
if(m.F(A::RUN_AWAY_TIMER)==0.f)m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 1.Fur Change Color Time"); if(m.F(A::RUN_AWAY_TIMER)==0.f)m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 1.Fur Change Color Time");
else{ else{
@ -142,9 +142,9 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
if(m.I(A::BEAR_STOMP_COUNT)%(ConfigInt("Phase 1.Stomp Count")+1)==ConfigInt("Phase 1.Stomp Count")){ if(m.I(A::BEAR_STOMP_COUNT)%(ConfigInt("Phase 1.Stomp Count")+1)==ConfigInt("Phase 1.Stomp Count")){
m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 1.Run Time"); m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 1.Run Time");
m.I(A::PREVIOUS_PHASE)=m.phase; m.I(A::PREVIOUS_PHASE)=PHASE();
m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 1.Run Speed Boost")/100.f); m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 1.Run Speed Boost")/100.f);
m.phase=6; SETPHASE(6);
break; break;
} }
@ -219,7 +219,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
}else{ }else{
auto TransitionToPhase3=[&](){ auto TransitionToPhase3=[&](){
m.phase=3; SETPHASE(3);
game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-in Color")); game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-in Color"));
m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 3.Environment Fade-in Time"); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 3.Environment Fade-in Time");
m.I(A::ENVIRONMENT_PHASE)=0; m.I(A::ENVIRONMENT_PHASE)=0;
@ -266,11 +266,11 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length(); float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length();
m.F(A::CHARGE_COOLDOWN)=std::max(0.f,m.F(A::CHARGE_COOLDOWN)-fElapsedTime); m.F(A::CHARGE_COOLDOWN)=std::max(0.f,m.F(A::CHARGE_COOLDOWN)-fElapsedTime);
if(m.I(A::PHASE)!=0)goto bear2; //Prevent doing anything else if a part of bear AI is still running. if(m.GetPhase("Bear")!=0)goto bear2; //Prevent doing anything else if a part of bear AI is still running.
if(m.GetHealthRatio()<=ConfigFloat("Phase 4.Change")/100.f){ if(m.GetHealthRatio()<=ConfigFloat("Phase 4.Change")/100.f){
auto TransitionToPhase5=[&](){ auto TransitionToPhase5=[&](){
m.phase=5; SETPHASE(5);
m.PerformAnimation("SIT"); m.PerformAnimation("SIT");
SoundEffect::PlaySFX("Ursule Phase Transition",SoundEffect::CENTERED); SoundEffect::PlaySFX("Ursule Phase Transition",SoundEffect::CENTERED);
m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 4.Environment Fade-out Time"); m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 4.Environment Fade-out Time");
@ -311,7 +311,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
if(m.F(A::CASTING_TIMER)>0.f){ if(m.F(A::CASTING_TIMER)>0.f){
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime); m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
if(m.F(A::CASTING_TIMER)==0.f){ if(m.F(A::CASTING_TIMER)==0.f){
m.phase=4; SETPHASE(4);
m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 3.Charge Speed Boost")/100.f); m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 3.Charge Speed Boost")/100.f);
m.AddBuff(FIXED_COLLISION_DMG,10.f,ConfigFloat("Phase 3.Charge Attack Damage")); m.AddBuff(FIXED_COLLISION_DMG,10.f,ConfigFloat("Phase 3.Charge Attack Damage"));
m.AddBuff(COLLISION_KNOCKBACK_STRENGTH,10.f,ConfigFloat("Phase 3.Charge Attack Knockback Strength")); m.AddBuff(COLLISION_KNOCKBACK_STRENGTH,10.f,ConfigFloat("Phase 3.Charge Attack Knockback Strength"));
@ -332,8 +332,8 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
if(m.I(A::BEAR_STOMP_COUNT)%(ConfigInt("Phase 3.Stomp Count")+1)==ConfigInt("Phase 3.Stomp Count")){ if(m.I(A::BEAR_STOMP_COUNT)%(ConfigInt("Phase 3.Stomp Count")+1)==ConfigInt("Phase 3.Stomp Count")){
m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 3.Run Time"); m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 3.Run Time");
m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 3.Run Speed Boost")/100.f); m.AddBuff(SPEEDBOOST,10.f,ConfigFloat("Phase 3.Run Speed Boost")/100.f);
m.I(A::PREVIOUS_PHASE)=m.phase; m.I(A::PREVIOUS_PHASE)=PHASE();
m.phase=6; SETPHASE(6);
break; break;
} }
} }
@ -352,7 +352,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
float distToTarget=geom2d::line<float>(m.target,m.GetPos()).length(); float distToTarget=geom2d::line<float>(m.target,m.GetPos()).length();
if(distToTarget<=4.f||m.F(A::TARGET_TIMER)==0.f||m.B(A::COLLIDED_WITH_PLAYER)){ if(distToTarget<=4.f||m.F(A::TARGET_TIMER)==0.f||m.B(A::COLLIDED_WITH_PLAYER)){
m.phase=3; SETPHASE(3);
m.RemoveBuff(SPEEDBOOST); m.RemoveBuff(SPEEDBOOST);
m.RemoveBuff(FIXED_COLLISION_DMG); m.RemoveBuff(FIXED_COLLISION_DMG);
m.RemoveBuff(COLLISION_KNOCKBACK_STRENGTH); m.RemoveBuff(COLLISION_KNOCKBACK_STRENGTH);
@ -429,13 +429,13 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
m.target=game->GetPlayer()->GetPos(); m.target=game->GetPlayer()->GetPos();
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.F(A::RUN_AWAY_TIMER)==0.f){ if(m.F(A::RUN_AWAY_TIMER)==0.f){
m.phase=m.I(A::PREVIOUS_PHASE); SETPHASE(m.I(A::PREVIOUS_PHASE));
m.RemoveBuff(SPEEDBOOST); m.RemoveBuff(SPEEDBOOST);
m.I(A::BEAR_STOMP_COUNT)=0; m.I(A::BEAR_STOMP_COUNT)=0;
} }
}break; }break;
default:{ default:{
ERR(std::format("WARNING! Unknown phase {} for {} reached!",m.phase,m.GetName())); ERR(std::format("WARNING! Unknown phase {} for {} reached!",PHASE(),m.GetName()));
} }
} }
} }

@ -39,7 +39,7 @@ All rights reserved.
#define VERSION_MAJOR 1 #define VERSION_MAJOR 1
#define VERSION_MINOR 3 #define VERSION_MINOR 3
#define VERSION_PATCH 0 #define VERSION_PATCH 0
#define VERSION_BUILD 11657 #define VERSION_BUILD 11666
#define stringify(a) stringify_(a) #define stringify(a) stringify_(a)
#define stringify_(a) #a #define stringify_(a) #a

@ -49,11 +49,11 @@ INCLUDE_BULLET_LIST
using A=Attribute; using A=Attribute;
void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){ void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){ switch(PHASE()){
case 0:{ //Run towards the player. case 0:{ //Run towards the player.
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length(); float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length();
if(distToPlayer<=ConfigFloat("Lockon Range")/100*24.f){ if(distToPlayer<=ConfigFloat("Lockon Range")/100*24.f){
m.I(A::PHASE)=1; SETPHASE(1);
m.V(A::LOCKON_POS)=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(1.2f); m.V(A::LOCKON_POS)=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).upoint(1.2f);
m.AddBuff(BuffType::LOCKON_SPEEDBOOST,INFINITE,ConfigFloat("Lockon Speed Boost")/100); m.AddBuff(BuffType::LOCKON_SPEEDBOOST,INFINITE,ConfigFloat("Lockon Speed Boost")/100);
m.F(A::TARGET_TIMER)=5.0f; m.F(A::TARGET_TIMER)=5.0f;
@ -65,7 +65,7 @@ void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::TARGET_TIMER)=std::max(0.f,m.F(A::TARGET_TIMER)-fElapsedTime); m.F(A::TARGET_TIMER)=std::max(0.f,m.F(A::TARGET_TIMER)-fElapsedTime);
float distToTarget=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).length(); float distToTarget=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).length();
if(distToTarget<=12.f||m.F(A::TARGET_TIMER)==0.f){ if(distToTarget<=12.f||m.F(A::TARGET_TIMER)==0.f){
m.I(A::PHASE)=2; SETPHASE(2);
m.F(A::RECOVERY_TIME)=ConfigFloat("Charge Recovery Time"); m.F(A::RECOVERY_TIME)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation(); m.PerformIdleAnimation();
}else{ }else{
@ -97,7 +97,7 @@ void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
m.I(A::PATH_DIR)=util::random()%2; m.I(A::PATH_DIR)=util::random()%2;
if(m.I(A::PATH_DIR)==0)m.I(A::PATH_DIR)=-1; if(m.I(A::PATH_DIR)==0)m.I(A::PATH_DIR)=-1;
m.pathIndex=util::random()%disengagePoints.size(); m.pathIndex=util::random()%disengagePoints.size();
m.I(A::PHASE)=3; SETPHASE(3);
m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Disengage Duration"); m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Disengage Duration");
m.PerformJumpAnimation(); m.PerformJumpAnimation();
} }
@ -110,7 +110,7 @@ void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
m.pathIndex=m.pathIndex+m.path.points.size(); m.pathIndex=m.pathIndex+m.path.points.size();
} }
if(m.F(A::RUN_AWAY_TIMER)==0.f){ if(m.F(A::RUN_AWAY_TIMER)==0.f){
m.I(A::PHASE)=0; SETPHASE(0);
} }
m.target=m.path.GetSplinePoint(m.pathIndex).pos; m.target=m.path.GetSplinePoint(m.pathIndex).pos;
geom2d::line<float>moveTowardsLine=geom2d::line(m.pos,m.path.GetSplinePoint(m.pathIndex).pos); geom2d::line<float>moveTowardsLine=geom2d::line(m.pos,m.path.GetSplinePoint(m.pathIndex).pos);

@ -71,7 +71,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
LEFT LEFT
}; };
if(m.phase!=HALFHEALTH_PHASE)m.F(A::SPAWNER_TIMER)-=fElapsedTime; if(PHASE()!=HALFHEALTH_PHASE)m.F(A::SPAWNER_TIMER)-=fElapsedTime;
if(m.F(A::SPAWNER_TIMER)<=0.f){ if(m.F(A::SPAWNER_TIMER)<=0.f){
const float randomDir=util::random(2*PI); 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(),true); game->SpawnMonster(m.GetPos()+vf2d{ConfigFloat("Basic Hawk Spawn Radius"),randomDir}.cart(),MONSTER_DATA.at("Hawk_NOXP"),m.OnUpperLevel(),true);
@ -85,10 +85,10 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigPixels("Fly Rise/Fall Speed")*fElapsedTime); if(m.F(A::FLYING_HEIGHT)<m.F(A::TARGET_FLYING_HEIGHT))m.F(A::FLYING_HEIGHT)=std::min(m.F(A::TARGET_FLYING_HEIGHT),m.F(A::FLYING_HEIGHT)+ConfigPixels("Fly Rise/Fall Speed")*fElapsedTime);
else if(m.F(A::FLYING_HEIGHT)>-ConfigFloat("Flight Oscillation Amount"))m.F(A::FLYING_HEIGHT)=std::max(-ConfigFloat("Flight Oscillation Amount"),m.F(A::FLYING_HEIGHT)-ConfigPixels("Fly Rise/Fall Speed")*fElapsedTime); else if(m.F(A::FLYING_HEIGHT)>-ConfigFloat("Flight Oscillation Amount"))m.F(A::FLYING_HEIGHT)=std::max(-ConfigFloat("Flight Oscillation Amount"),m.F(A::FLYING_HEIGHT)-ConfigPixels("Fly Rise/Fall Speed")*fElapsedTime);
switch(m.phase){ switch(PHASE()){
case INITIALIZE:{ case INITIALIZE:{
m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time"); m.F(A::SPAWNER_TIMER)=ConfigFloat("Basic Hawk Spawn Time");
m.phase=IDLE; SETPHASE(IDLE);
game->SetOverlay(ConfigString("Wind Attack.Wind Overlay Sprite"),ConfigPixel("Wind Attack.Wind Overlay Color")); game->SetOverlay(ConfigString("Wind Attack.Wind Overlay Sprite"),ConfigPixel("Wind Attack.Wind Overlay Color"));
game->GetOverlay().Disable(); game->GetOverlay().Disable();
@ -110,9 +110,9 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
}break; }break;
case IDLE:{ case IDLE:{
#pragma region Mid Phase Check #pragma region Mid Phase Check
if(m.GetHealthRatio()<=ConfigFloat("Mid Phase Health Transition %")/100.f&&!m.B(A::PHASE)){ if(m.GetHealthRatio()<=ConfigFloat("Mid Phase Health Transition %")/100.f&&!m.B(A::MID_PHASE)){
m.B(A::PHASE)=true; m.B(A::MID_PHASE)=true;
m.phase=HALFHEALTH_PREPARE_PHASE; SETPHASE(HALFHEALTH_PREPARE_PHASE);
m.F(A::TARGET_FLYING_HEIGHT)=50.f; m.F(A::TARGET_FLYING_HEIGHT)=50.f;
m.target=ConfigVec("Mid Phase.Pillar Position"); m.target=ConfigVec("Mid Phase.Pillar Position");
for(int i=0;i<ConfigInt("Mid Phase.Basic Hawk Spawn Count");i++){ for(int i=0;i<ConfigInt("Mid Phase.Basic Hawk Spawn Count");i++){
@ -150,14 +150,14 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos"); if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos");
else m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos"); else m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
m.phase=FLY_ACROSS_PREPARE; SETPHASE(FLY_ACROSS_PREPARE);
}break; }break;
case 1:{ case 1:{
m.phase=TORNADO_ATTACK_PREPARE; SETPHASE(TORNADO_ATTACK_PREPARE);
m.target=ConfigVec("Tornado Attack.Landing Area"); m.target=ConfigVec("Tornado Attack.Landing Area");
}break; }break;
case 2:{ case 2:{
m.phase=WIND_ATTACK_FLY; SETPHASE(WIND_ATTACK_FLY);
m.F(A::TARGET_FLYING_HEIGHT)=ConfigPixels("Wind Attack.Fly Up Height"); m.F(A::TARGET_FLYING_HEIGHT)=ConfigPixels("Wind Attack.Fly Up Height");
const bool LeftLandingSite=m.I(A::ATTACK_CHOICE)=util::random()%2; const bool LeftLandingSite=m.I(A::ATTACK_CHOICE)=util::random()%2;
if(LeftLandingSite)m.target=ConfigVec("Wind Attack.Left Landing Site"); if(LeftLandingSite)m.target=ConfigVec("Wind Attack.Left Landing Site");
@ -174,7 +174,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
//We're choosing the opposite side of the field to direct the boss towards for this attack. //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"); if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
else m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos"); else m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos");
m.phase=FLY_ACROSS; SETPHASE(FLY_ACROSS);
m.AddBuff(BuffType::SPEEDBOOST,INFINITY,ConfigFloat("Fly Across Attack.Move Speed Multiplier")-1.f); m.AddBuff(BuffType::SPEEDBOOST,INFINITY,ConfigFloat("Fly Across Attack.Move Speed Multiplier")-1.f);
} }
@ -199,7 +199,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Attack Frequency"); m.F(A::SHOOT_TIMER)=ConfigFloat("Fly Across Attack.Attack Frequency");
} }
if(m.ReachedTargetPos()){ if(m.ReachedTargetPos()){
m.phase=IDLE; SETPHASE(IDLE);
m.RemoveBuff(BuffType::SPEEDBOOST); m.RemoveBuff(BuffType::SPEEDBOOST);
m.targetAcquireTimer=0.f; m.targetAcquireTimer=0.f;
} }
@ -208,7 +208,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
m.targetAcquireTimer=20.f; m.targetAcquireTimer=20.f;
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.ReachedTargetPos()){ if(m.ReachedTargetPos()){
m.phase=TORNADO_ATTACK; SETPHASE(TORNADO_ATTACK);
m.PerformAnimation("ATTACK",Direction::SOUTH); m.PerformAnimation("ATTACK",Direction::SOUTH);
m.targetAcquireTimer=0.f; m.targetAcquireTimer=0.f;
m.F(A::CASTING_TIMER)=ConfigFloat("Tornado Attack.Attack Duration"); m.F(A::CASTING_TIMER)=ConfigFloat("Tornado Attack.Attack Duration");
@ -235,19 +235,19 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
}break; }break;
case TORNADO_ATTACK:{ case TORNADO_ATTACK:{
m.F(A::CASTING_TIMER)-=fElapsedTime; m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f)m.phase=IDLE; if(m.F(A::CASTING_TIMER)<=0.f)SETPHASE(IDLE);
}break; }break;
case WIND_ATTACK_FLY:{ case WIND_ATTACK_FLY:{
m.targetAcquireTimer=20.f; m.targetAcquireTimer=20.f;
RUN_TOWARDS(m,fElapsedTime,"Run Towards"); RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.ReachedTargetPos()){ if(m.ReachedTargetPos()){
m.phase=WIND_ATTACK_LAND; SETPHASE(WIND_ATTACK_LAND);
m.F(A::TARGET_FLYING_HEIGHT)=0.f; m.F(A::TARGET_FLYING_HEIGHT)=0.f;
} }
}break; }break;
case WIND_ATTACK_LAND:{ case WIND_ATTACK_LAND:{
if(m.GetZ()==0.f){ if(m.GetZ()==0.f){
m.phase=WIND_ATTACK; SETPHASE(WIND_ATTACK);
game->GetOverlay().Enable(); game->GetOverlay().Enable();
m.F(A::CASTING_TIMER)=ConfigFloat("Wind Attack.Wind Duration"); m.F(A::CASTING_TIMER)=ConfigFloat("Wind Attack.Wind Duration");
m.F(A::WIND_STRENGTH)=ConfigFloat("Wind Attack.Wind Starting Strength")/100.f; m.F(A::WIND_STRENGTH)=ConfigFloat("Wind Attack.Wind Starting Strength")/100.f;
@ -310,7 +310,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
#pragma endregion #pragma endregion
if(m.F(A::CASTING_TIMER)<=0.f){ if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=IDLE; SETPHASE(IDLE);
game->GetOverlay().Disable(); game->GetOverlay().Disable();
game->SetWindSpeed({}); game->SetWindSpeed({});
} }
@ -337,7 +337,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
const bool HasLandedOnGround=m.GetZ()==0.f; const bool HasLandedOnGround=m.GetZ()==0.f;
if(HasLandedOnGround){ if(HasLandedOnGround){
m.phase=HALFHEALTH_PHASE; SETPHASE(HALFHEALTH_PHASE);
CreateBullet(LargeTornado)(ConfigVec("Mid Phase.Large Tornado Position"),ConfigPixels("Mid Phase.Large Tornado Suction"),ConfigFloat("Mid Phase.Large Tornado Knockup Duration"),ConfigFloat("Mid Phase.Large Tornado Knockback Amount"),ConfigInt("Mid Phase.Large Tornado Damage"),ConfigFloat("Mid Phase.Large Tornado Radius"),INFINITY,m.OnUpperLevel())EndBullet; CreateBullet(LargeTornado)(ConfigVec("Mid Phase.Large Tornado Position"),ConfigPixels("Mid Phase.Large Tornado Suction"),ConfigFloat("Mid Phase.Large Tornado Knockup Duration"),ConfigFloat("Mid Phase.Large Tornado Knockback Amount"),ConfigInt("Mid Phase.Large Tornado Damage"),ConfigFloat("Mid Phase.Large Tornado Radius"),INFINITY,m.OnUpperLevel())EndBullet;
BULLET_LIST.back()->SetFadeinTime(1.0f); BULLET_LIST.back()->SetFadeinTime(1.0f);
m.F(A::SHOOT_TIMER)=2.f; m.F(A::SHOOT_TIMER)=2.f;
@ -350,7 +350,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
m.UpdateFacingDirection(Direction::SOUTH); m.UpdateFacingDirection(Direction::SOUTH);
m.PerformAnimation("ATTACK"); m.PerformAnimation("ATTACK");
if(game->BossEncounterMobCount()==1){ if(game->BossEncounterMobCount()==1){
m.phase=IDLE; SETPHASE(IDLE);
std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&bullet){ std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&bullet){
if(bullet->GetBulletType()==BulletType::LARGE_TORNADO){ if(bullet->GetBulletType()==BulletType::LARGE_TORNADO){
bullet->fadeOutTime=1.f; bullet->fadeOutTime=1.f;

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="335" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="16"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="335" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="17">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -870,5 +870,11 @@
<property name="spawner" type="object" value="8"/> <property name="spawner" type="object" value="8"/>
</properties> </properties>
</object> </object>
<object id="16" name="Waterfall" type="AudioEnvironmentalSound" x="5352" y="192">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="288" height="287" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="3"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="288" height="287" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="4">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1471,5 +1471,11 @@
</properties> </properties>
</object> </object>
<object id="2" name="Player Spawn" type="PlayerSpawnLocation" x="531.333" y="246" width="24" height="24"/> <object id="2" name="Player Spawn" type="PlayerSpawnLocation" x="531.333" y="246" width="24" height="24"/>
<object id="3" name="Waterfall" type="AudioEnvironmentalSound" x="4680" y="5112">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="339" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="3"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="339" height="165" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="4">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -861,5 +861,11 @@
</properties> </properties>
</object> </object>
<object id="2" name="Player Spawn" type="PlayerSpawnLocation" x="282" y="504" width="24" height="24"/> <object id="2" name="Player Spawn" type="PlayerSpawnLocation" x="282" y="504" width="24" height="24"/>
<object id="3" name="Waterfall" type="AudioEnvironmentalSound" x="4152" y="3144">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="288" height="208" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="3"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="288" height="208" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="4">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1076,5 +1076,11 @@
<property name="Upper?" type="bool" value="false"/> <property name="Upper?" type="bool" value="false"/>
</properties> </properties>
</object> </object>
<object id="3" name="Waterfall" type="AudioEnvironmentalSound" x="3216" y="1488">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="356" height="357" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="3"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="356" height="357" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="4">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1821,5 +1821,11 @@
<property name="Upper?" type="bool" value="false"/> <property name="Upper?" type="bool" value="false"/>
</properties> </properties>
</object> </object>
<object id="3" name="Waterfall" type="AudioEnvironmentalSound" x="4896" y="5112">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="254" height="370" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="3"> <map version="1.10" tiledversion="1.10.2" class="Map" orientation="orthogonal" renderorder="right-down" width="254" height="370" tilewidth="24" tileheight="24" infinite="0" nextlayerid="8" nextobjectid="4">
<properties> <properties>
<property name="Background Music" propertytype="BGM" value="beach"/> <property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/> <property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1886,5 +1886,11 @@
<property name="Upper?" type="bool" value="false"/> <property name="Upper?" type="bool" value="false"/>
</properties> </properties>
</object> </object>
<object id="3" name="Waterfall" type="AudioEnvironmentalSound" x="2088" y="8064">
<properties>
<property name="Sound Name" propertytype="EnvironmentalSounds" value="Waterfall"/>
</properties>
<point/>
</object>
</objectgroup> </objectgroup>
</map> </map>

@ -1148,8 +1148,8 @@ MonsterStrategy
# Amount of time parrot remains stunned on death. # Amount of time parrot remains stunned on death.
Stun Time = 5s Stun Time = 5s
Fly Away Speed = 100 Fly Away Speed = 140
Fly Away Z Speed = 10 Fly Away Z Speed = 30
} }
Crab Crab
{ {

@ -1263,10 +1263,10 @@ Monsters
Dagger Slash Distance = 12 Dagger Slash Distance = 12
# Slash Attack windup time # Slash Attack windup time
Slash Windup Time = 0.2s Slash Windup Time = 0.4s
# Stab Attack windup time # Stab Attack windup time
Stab Windup Time = 0.2s Stab Windup Time = 0.4s
# Amount of time where nothing happens after an attack. # Amount of time where nothing happens after an attack.
Attack Recovery Time = 0.6s Attack Recovery Time = 0.6s
@ -1343,7 +1343,7 @@ Monsters
Attack Spacing = 100 Attack Spacing = 100
# Slash Attack windup time # Slash Attack windup time
Slash Windup Time = 0.2s Slash Windup Time = 0.4s
Dagger Slash Image = "pirate_slash.png" Dagger Slash Image = "pirate_slash.png"
@ -1415,10 +1415,10 @@ Monsters
Dagger Slash Distance = 12 Dagger Slash Distance = 12
# Slash Attack windup time # Slash Attack windup time
Slash Windup Time = 0.2s Slash Windup Time = 0.4s
# Stab Attack windup time # Stab Attack windup time
Stab Windup Time = 0.2s Stab Windup Time = 0.4s
# Amount of time where nothing happens after an attack. # Amount of time where nothing happens after an attack.
Attack Recovery Time = 0.6s Attack Recovery Time = 0.6s
@ -1434,9 +1434,9 @@ Monsters
# Offset for the dagger stab effect per direction from the monster's center. # Offset for the dagger stab effect per direction from the monster's center.
Dagger Up Offset = -6,-5.5 Dagger Up Offset = -6,-5.5
Dagger Down Offset = -5,-1 Dagger Down Offset = -7,-1
Dagger Right Offset = 9,0 Dagger Right Offset = 10,1
Dagger Left Offset = -8,-2 Dagger Left Offset = -8,0
######## ########
@ -1455,7 +1455,7 @@ Monsters
SHOOTING = 3, 0.2, OneShot SHOOTING = 3, 0.2, OneShot
SHOOT = 1, 0.1, OneShot SHOOT = 1, 0.1, OneShot
# Drink is approximately 2 seconds long. # Drink is approximately 2 seconds long.
DRINK = 3, 0.65, PingPong DRINK = 2, 0.65, PingPong
} }
# Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity # Drop Item Name, Drop Percentage(0-100%), Drop Min Quantity, Drop Max Quantity
@ -1602,6 +1602,8 @@ Monsters
Strategy = Seagull Strategy = Seagull
Ignore Collisions = True
#Size of each animation frame #Size of each animation frame
SheetFrameSize = 48,48 SheetFrameSize = 48,48
@ -1666,7 +1668,7 @@ Monsters
Hurt Sound = Monster Hurt Hurt Sound = Monster Hurt
Death Sound = Slime Dead Death Sound = Slime Dead
Walk Sound = Slime Walk Walk Sound = Wing Flap
} }
Parrot Parrot
{ {
@ -1676,12 +1678,14 @@ Monsters
CollisionDmg = 22 CollisionDmg = 22
MoveSpd = 180% MoveSpd = 180%
Size = 50% Size = 100%
XP = 0 XP = 0
Collision Radius = 7 Collision Radius = 7
Ignore Collisions = True
Strategy = Parrot Strategy = Parrot
# Instead of the monster dying, it gets knocked unconscious # Instead of the monster dying, it gets knocked unconscious
@ -1698,10 +1702,10 @@ Monsters
# Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot) # Frame Count, Frame Speed (s), Frame Cycling (Repeat,OneShot,PingPong,Reverse,ReverseOneShot)
# Animations must be defined in the same order as they are in their sprite sheets # Animations must be defined in the same order as they are in their sprite sheets
# The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator. # The First Four animations must represent a standing, walking, attack, and death animation. Their names are up to the creator.
IDLE = 4, 0.2, Repeat IDLE = 1, 0.2, Repeat
FLYING = 4, 0.15, Repeat FLYING = 4, 0.15, Repeat
ATTACKING = 2, 0.3, Repeat ATTACKING = 2, 0.3, Repeat
DEATH = 4, 0.15, OneShot DEATH = 1, 0.15, OneShot
ATTACK = 2, 0.3, Repeat ATTACK = 2, 0.3, Repeat
} }
@ -1710,6 +1714,6 @@ Monsters
Hurt Sound = Monster Hurt Hurt Sound = Monster Hurt
Death Sound = Slime Dead Death Sound = Slime Dead
Walk Sound = Slime Walk Walk Sound = Wing Flap
} }
} }
Loading…
Cancel
Save