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;
void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){
switch(PHASE()){
case 0:{
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<operator""_Pixels(ConfigFloat("Attack Range"))){
m.I(A::PHASE)=1;
SETPHASE(1);
m.PerformShootAnimation();
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.
@ -75,7 +75,7 @@ void Monster::STRATEGY::BEAR(Monster&m,float fElapsedTime,std::string strategy){
case 1:{
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
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.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){
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
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)++;
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())){

@ -57,7 +57,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
RECOVERY,
};
switch(m.phase){
switch(PHASE()){
case PhaseName::MOVE:{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
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:
m.PerformAnimation("SCRATCH");
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);
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;
if(m.F(A::CASTING_TIMER)<=0.f){
m.PerformShootAnimation();
m.phase=PhaseName::CHARGE;
SETPHASE(PhaseName::CHARGE);
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();
auto TransitionToRecoveryPhase=[&](){
m.phase=PhaseName::RECOVERY;
SETPHASE(PhaseName::RECOVERY);
m.F(A::CHARGE_COOLDOWN)=ConfigFloat("Charge Recovery Time");
m.PerformIdleAnimation();
};
@ -120,7 +120,7 @@ void Monster::STRATEGY::BOAR(Monster&m,float fElapsedTime,std::string strategy){
m.F(A::CHARGE_COOLDOWN)-=fElapsedTime;
m.targetAcquireTimer=0.f;
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;
}
}

@ -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"));
}else m.animation.ModifyDisplaySprite(m.internal_animState,ConfigString("Break Phase 2 Animation Name"));
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::BREAK_TIME)=ConfigFloat("Break Time");
m.F(A::SHAKE_TIMER)=0.2f;
m.phase=RUN;
SETPHASE(RUN);
m.SetStrategyDeathFunction([&](GameEvent&deathEvent,Monster&m,const std::string&strategy){
m.lifetime=0.01f;

@ -143,7 +143,7 @@ void DamageNumber::Draw(){
float DamageNumber::GetOriginalRiseSpd(){
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;
return riseSpd;
}

@ -51,11 +51,11 @@ using A=Attribute;
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);
phase:
switch(m.I(A::PHASE)){
switch(PHASE()){
case 0:{ //Move towards phase.
float distToPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
if(distToPlayer<24*ConfigInt("Range")/100.f){
m.I(A::PHASE)++;
SETPHASE(PHASE()+1);
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Lockon Wait Time");
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
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);
CreateBullet(FrogTongue)(m,tongueMaxRangePos,ConfigFloat("Attack Duration"),m.GetAttack(),m.OnUpperLevel(),ConfigFloat("Tongue Knockback Strength"),false,ConfigPixel("Tongue Color"))EndBullet;
m.PerformShootAnimation();
m.I(A::PHASE)=2;
SETPHASE(2);
}
m.PerformIdleAnimation();
}break;
case 2:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.F(A::LOCKON_WAITTIME)=ConfigFloat("Attack Recovery Time");
m.I(A::PHASE)=3;
SETPHASE(3);
}
}break;
case 3:{
if(m.F(A::LOCKON_WAITTIME)==0.0f){
m.I(A::PHASE)=0;
SETPHASE(0);
}
}break;
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,
};
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::SHOOT_TIMER)=m.randomFrameOffset;
m.phase=RUN;
SETPHASE(RUN);
}break;
case RUN:{
m.F(A::SHOOT_TIMER)+=fElapsedTime;

@ -66,10 +66,10 @@ void Monster::STRATEGY::GOBLIN_BOW(Monster&m,float fElapsedTime,std::string stra
};
#pragma endregion
switch(m.phase){
switch(PHASE()){
case INITIALIZE_PERCEPTION:{
m.F(A::PERCEPTION_LEVEL)=ConfigFloat("Starting Perception Level");
m.phase=MOVE;
SETPHASE(MOVE);
}break;
case MOVE:{
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);
auto PrepareToShoot=[&](){
m.phase=LOCKON;
SETPHASE(LOCKON);
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
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;
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.phase=WINDUP;
SETPHASE(WINDUP);
}
}break;
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;
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.phase=MOVE;
SETPHASE(MOVE);
}
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));

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

@ -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));
#pragma endregion
switch(m.phase){
switch(PHASE()){
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.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.F(A::ATTACK_COOLDOWN)=util::random_range(1.f,ConfigFloat("Flight Charge Cooldown"));
}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::ATTACK_COOLDOWN)<=0){
m.F(A::CASTING_TIMER)=ConfigFloat("Attack Wait Time");
m.phase=PREPARE_CHARGE;
SETPHASE(PREPARE_CHARGE);
}else{
float dirToPlayer{util::pointTo(game->GetPlayer()->GetPos(),m.GetPos()).polar().y};
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.PerformAnimation("ATTACK");
if(m.F(A::CASTING_TIMER)<=0){
m.phase=CHARGE;
SETPHASE(CHARGE);
m.PerformAnimation("ATTACKING");
m.target=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).rpoint(ConfigFloat("Flight Distance")*2.f);
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();
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.phase=FLYING;
SETPHASE(FLYING);
m.F(A::ATTACK_COOLDOWN)=ConfigFloat("Flight Charge Cooldown");
m.PerformJumpAnimation();
}

@ -81,6 +81,7 @@ Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob):
randomFrameOffset=(util::random()%1000)/1000.f;
monsterWalkSoundTimer=util::random(1.f);
UpdateFacingDirection(game->GetPlayer()->GetPos());
animation.UpdateState(internal_animState,randomFrameOffset);
}
const vf2d&Monster::GetPos()const{
return pos;
@ -284,8 +285,8 @@ void Monster::Update(const float fElapsedTime){
unconsciousTimer=std::max(0.f,unconsciousTimer-fElapsedTime);
if(unconsciousTimer==0.f){
Heal(GetMaxHealth());
PerformIdleAnimation();
}
}
if(IsSolid()&&FadeoutWhenStandingBehind()){
@ -402,7 +403,7 @@ void Monster::Update(const float 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);
attackedByPlayer=false;
}
@ -1654,3 +1655,10 @@ const bool Monster::IsUnconscious()const{
const float Monster::UnconsciousTime()const{
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;
using StrategyName=std::string;
namespace MonsterTests{
class MonsterTest;
};
@ -229,6 +231,8 @@ public:
const bool FaceTarget()const;
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 SetPhase(const std::string&strategyName,int phase);
const int GetPhase(const std::string&strategyName);
private:
//NOTE: Marking a monster for deletion does not trigger any death events. It just simply removes the monster from the field!!
// The way this works is that monsters marked for deletion will cause the monster update loop to detect there's at least one or more monsters that must be deleted and will call erase_if on the list at the end of the iteration loop.
@ -273,7 +277,7 @@ private:
float spriteRot=0;
std::shared_ptr<DamageNumber>damageNumberPtr;
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.
float targetSize=0;
bool isBoss=false;

@ -79,7 +79,6 @@ enum class Attribute{
ITEM_QUANTITY,
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
PHASE,
LOCKON_WAITTIME,
LOCKON_POS,
TARGET_TIMER,
@ -148,4 +147,6 @@ enum class Attribute{
EXTENDED_LINE,
ENCHANT,
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 ConfigStringArr(param,ind) _GetString(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
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){
m.SetStrategyDrawOverlayFunction([](AiL*game,Monster&m,const std::string&strategy){
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);
});
}
m.phase=1;
SETPHASE(1);
}
float distFromPlayer=geom2d::line<float>(m.GetPos(),game->GetPlayer()->GetPos()).length();
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,
FLY_AWAY,
};
switch(m.phase){
switch(PHASE()){
case RUN:{
if(!m.attachedTarget.expired())HAWK(m,fElapsedTime,"Hawk");
else{
if(!m.attachedTarget.expired()&&m.attachedTarget.lock()->IsAlive()){
m.PerformAnimation("FLYING");
HAWK(m,fElapsedTime,"Hawk");
}else{
m.lifetime=10.f;
m.phase=FLY_AWAY;
SETPHASE(FLY_AWAY);
m.F(A::PATH_DIR)=1.f;
if(util::random(1.f)<0.5f)m.F(A::PATH_DIR)=-1.f;
m.manualIgnoreTerrain=true;
}
}break;
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.SetVelocity({m.F(A::PATH_DIR)*ConfigFloat("Fly Away Speed"),0.f});
}break;

@ -49,7 +49,6 @@ INCLUDE_game
void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::string strategy){
#pragma region Phase, Animation, and Helper function setup
enum PhaseName{
INIT,
MOVE,
LOCKON,
WINDUP,
@ -57,10 +56,7 @@ void Monster::STRATEGY::PIRATE_BUCCANEER(Monster&m,float fElapsedTime,std::strin
};
#pragma endregion
switch(m.phase){
case INIT:{
m.phase=MOVE;
}break;
switch(PHASE()){
case MOVE:{
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);
auto PrepareToShoot=[&](){
m.phase=WINDUP;
SETPHASE(WINDUP);
m.F(A::SHOOT_TIMER)=ConfigFloat("Attack Windup Time");
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;
m.PerformAnimation("SHOOTING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
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();
}else
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;
if(m.F(A::SHOOT_TIMER)<=0.f){
m.PerformAnimation("IDLE",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.phase=MOVE;
SETPHASE(MOVE);
}
}break;
}

@ -63,12 +63,7 @@ void Monster::STRATEGY::PIRATE_CAPTAIN(Monster&m,float fElapsedTime,std::string
SLASH
};
if(!m.B(A::INITIALIZED)){
m.B(A::INITIALIZED)=true;
m.phase=INIT;
}
switch(m.phase){
switch(PHASE()){
case INIT:{
m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency");
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.deathData.emplace_back(ConfigString("Spawned Monster"),1U);
m.phase=MOVE;
SETPHASE(MOVE);
}break;
case MOVE:{
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())};
if(distToPlayer<=ConfigFloat("Shoot Max Range")/100.f*24){
m.F(A::SHOOT_TIMER)=ConfigFloat("Shooting Delay");
m.phase=PREPARE_SHOOT;
SETPHASE(PREPARE_SHOOT);
m.PerformAnimation("SHOOT",game->GetPlayer()->GetPos());
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
}
}
m.F(A::TARGET_TIMER)=ConfigFloat("Shooting Frequency");
}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.F(A::BREAK_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=DRINK_RUM;
SETPHASE(DRINK_RUM);
m.I(A::RUM_DRINK_COUNT)++;
}
else{
float distToPlayer=m.GetDistanceFrom(game->GetPlayer()->GetPos());
if(distToPlayer>ConfigFloat("Attack Spacing")/100.f*24){
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{
m.phase=WINDUP;
SETPHASE(WINDUP);
m.I(A::ATTACK_TYPE)=util::random()%2; //Choose randomly between stab or slash.
switch(m.I(A::ATTACK_TYPE)){
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;
m.PerformAnimation("SHOOTING");
m.F(A::SHOOT_ANIMATION_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=SHOOT_RELOAD;
SETPHASE(SHOOT_RELOAD);
}
}break;
case SHOOT_RELOAD:{
m.F(A::SHOOT_ANIMATION_TIME)-=fElapsedTime;
if(m.F(A::SHOOT_ANIMATION_TIME)<=0.f){
m.PerformAnimation("IDLE");
m.phase=MOVE;
SETPHASE(MOVE);
}
}break;
case DRINK_RUM:{
m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=0.f){
m.Heal(ConfigInt("Rum Health Recovery"),true);
m.phase=MOVE;
SETPHASE(MOVE);
}
}break;
case WINDUP:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY;
SETPHASE(RECOVERY);
switch(m.I(A::ATTACK_TYPE)){
case STAB:{
vf2d stabTarget=game->GetPlayer()->GetPos();
m.PerformAnimation("STABBING",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
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"),
m.PerformAnimation("STABBING");
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;
}break;
case SLASH:{
vf2d slashTarget=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;
m.PerformAnimation("SLASHING");
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;
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::RECOVERY_TIME)-=fElapsedTime;
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;
}
}

@ -57,10 +57,10 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
WHIRLWIND,
};
switch(m.phase){
switch(PHASE()){
case INIT:{
m.F(A::CHASE_TIMER)=ConfigFloat("Ability Choose Timer");
m.phase=MOVE;
SETPHASE(MOVE);
}break;
case MOVE:{
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")};
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.I(A::ABILITY_COUNT)--;
const float impactArea{ConfigFloat("Jump Attack Impact Area")};
@ -92,7 +92,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
}else
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){
m.phase=PREPARE_WHIRLWIND;
SETPHASE(PREPARE_WHIRLWIND);
m.I(A::ABILITY_COUNT)--;
vf2d aimingTarget{game->GetPlayer()->GetPos()};
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){
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
}else{
m.phase=WINDUP;
SETPHASE(WINDUP);
m.F(A::CASTING_TIMER)=ConfigFloat("Slash Windup Time");
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:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0){
m.phase=RECOVERY;
SETPHASE(RECOVERY);
vf2d slashTarget=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;
@ -129,7 +129,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
m.F(A::CASTING_TIMER)-=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::RECOVERY_TIME)<=0)m.phase=MOVE;
if(m.F(A::RECOVERY_TIME)<=0)SETPHASE(MOVE);
}break;
case LEAP:{
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);
m.SetZ(0.f);
m.SetPos(m.V(A::JUMP_TARGET_POS));
m.phase=MOVE;
SETPHASE(MOVE);
m.PerformIdleAnimation();
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:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=WHIRLWIND;
SETPHASE(WHIRLWIND);
m.F(A::CHASE_TIMER)=0.f;
m.PerformAnimation("SPINNING");
}
@ -159,7 +159,7 @@ void Monster::STRATEGY::PIRATE_MARAUDER(Monster&m,float fElapsedTime,std::string
case WHIRLWIND:{
m.F(A::CHASE_TIMER)+=fElapsedTime;
if(m.F(A::CHASE_TIMER)>=ConfigFloat("Whirlwind Spin Time")){
m.phase=MOVE;
SETPHASE(MOVE);
m.PerformIdleAnimation();
}
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=[&](){
switch(m.phase){
switch(PHASE()){
case 2:{
switch(m.I(A::JUMP_COUNT)){
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};
}
game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor")));
if(m.phase!=2){
if(PHASE()!=2){
game->GetPlayer()->ApplyIframes(1.f);
}else{ //In phase 2 you can get hit by multiple knockbacks, so the iframe time is a lot shorter.
game->GetPlayer()->ApplyIframes(0.2f);
@ -219,7 +219,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}
}
m.SetZ(0);
Landed(m.phase);
Landed(PHASE());
m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){});
} else
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;
}
switch(m.phase){
switch(PHASE()){
case 0:{
m.size=ConfigInt("Phase1.Size")/100.f;
m.diesNormally=false;
m.F(A::IFRAME_TIME_UPON_HIT)=0;
m.ApplyIframes(ConfigFloat("Phase5.IframeTimePerHit"));
m.phase=ConfigInt("StartPhase");
SETPHASE(ConfigInt("StartPhase"));
}break;
case 1:{
if(m.GetHealthRatio()<=ConfigFloat("Phase2.Change")/100.f){
m.phase=2;
SETPHASE(2);
m.SetSize(ConfigFloat("Phase2.Size")/100,false);
TransitionPhase(m.phase);
TransitionPhase(PHASE());
return;
}
if(m.F(A::SHOOT_RING_TIMER)==0){
@ -287,12 +287,12 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break;
case 2:{
if(m.GetHealthRatio()<=ConfigFloat("Phase3.Change")/100.f){
m.phase=3;
SETPHASE(3);
m.SetSize(ConfigFloat("Phase3.Size")/100,false);
if(m.I(A::PATTERN_REPEAT_COUNT)==0){
m.I(A::PATTERN_REPEAT_COUNT)=1;
}
TransitionPhase(m.phase);
TransitionPhase(PHASE());
return;
}
if(m.F(A::SHOOT_TIMER)==0){
@ -315,10 +315,10 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break;
case 3:{
if(m.GetHealthRatio()<=ConfigFloat("Phase4.Change")/100.f){
m.phase=4;
SETPHASE(4);
m.SetSize(ConfigFloat("Phase4.Size")/100,false);
m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100);
TransitionPhase(m.phase);
TransitionPhase(PHASE());
return;
}
if(m.I(A::PATTERN_REPEAT_COUNT)==0){
@ -344,10 +344,10 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strat
}break;
case 4:{
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.I(A::HITS_UNTIL_DEATH)=int(m.GetSizeMult()*100/ConfigFloat("Phase5.SizeLossPerHit"))-1;
TransitionPhase(m.phase);
TransitionPhase(PHASE());
return;
}
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
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time");
m.I(A::PATTERN_REPEAT_COUNT)=ConfigInt("Beginning Phase.Repeat Count");
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.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)));
}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)));
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.phase=SPAWN_PILLAR_CAST;
SETPHASE(SPAWN_PILLAR_CAST);
}
}break;
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->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){
m.phase=STANDARD;
SETPHASE(STANDARD);
}else{
m.PerformIdleAnimation(m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
m.F(A::RECOVERY_TIME)=ConfigFloat("Beginning Phase.Pillar Cast Delay Time");
m.phase=SPAWN_PILLAR_PREPARE;
SETPHASE(SPAWN_PILLAR_PREPARE);
}
}
}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.PerformAnimation("CAST",m.GetFacingDirectionToTarget(game->GetPlayer()->GetPos()));
PrepareSafeAreas();
m.phase=SHOCKWAVE;
SETPHASE(SHOCKWAVE);
break;
}
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.
m.phase=STONE_THROW_CAST;
SETPHASE(STONE_THROW_CAST);
m.V(A::LOCKON_POS)=game->GetPlayer()->GetPos();
m.PerformAnimation("TOSS ROCK CAST");
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;
}else{
m.phase=BEAR_ATTACK;
SETPHASE(BEAR_ATTACK);
m.F(A::CHASE_TIMER)=0.f;
}
}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));
m.PerformAnimation("TOSS ROCK");
m.F(A::RECOVERY_TIME)=m.GetCurrentAnimation().GetTotalAnimationDuration();
m.phase=STONE_THROW_FINISH_ANIMATION;
SETPHASE(STONE_THROW_FINISH_ANIMATION);
}
}break;
case STONE_THROW_FINISH_ANIMATION:{
m.F(A::RECOVERY_TIME)-=fElapsedTime;
if(m.F(A::RECOVERY_TIME)<=0.f){
m.phase=STANDARD;
SETPHASE(STANDARD);
}
}break;
case SHOCKWAVE:{
@ -259,7 +259,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
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.
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.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());
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);
}
}break;
case FIX_SAFE_AREAS:{
if(m.F(A::SAFE_AREA_WAIT_TIMER)<=0.f){
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;
}break;
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...
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){
m.phase=STANDARD;
SETPHASE(STANDARD);
m.I(A::ATTACK_COUNT)=m.I(A::BEAR_STOMP_COUNT);
}else
if(m.I(A::PHASE)==0&&m.F(A::CHASE_TIMER)>=ConfigFloat("Max Chase Time")){
m.phase=DOUBLE_ROCK_TOSS;
if(m.GetPhase("Bear")==0&&m.F(A::CHASE_TIMER)>=ConfigFloat("Max Chase Time")){
SETPHASE(DOUBLE_ROCK_TOSS);
m.PerformAnimation("RAISE ROCK");
m.I(A::STONE_TOSS_COUNT)=ConfigInt("Stone Rain.Initial Stone Toss Count");
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));
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.PerformAnimation("CAST");
@ -338,7 +338,7 @@ void Monster::STRATEGY::STONE_GOLEM(Monster&m,float fElapsedTime,std::string str
case STONE_RAIN:{
m.F(A::BREAK_TIME)-=fElapsedTime;
if(m.F(A::BREAK_TIME)<=0.f){
m.phase=STANDARD;
SETPHASE(STANDARD);
}
}break;
}

@ -72,15 +72,15 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
};
auto ReturnToWaitingPhase=[&](){
m.phase=WAITING;
SETPHASE(WAITING);
m.PerformIdleAnimation();
m.F(A::ATTACK_COOLDOWN)=0.f;
};
switch(m.phase){
switch(PHASE()){
case INITIALIZE:{
m.F(A::ATTACK_COOLDOWN)=util::random(ConfigFloat("Attack Wait Time")/1.5f);
m.phase=WAITING;
SETPHASE(WAITING);
}break;
case WAITING:{
m.F(A::ATTACK_COOLDOWN)+=fElapsedTime;
@ -95,7 +95,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 0:{
m.PerformAnimation("STONE PILLAR CAST");
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.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);
@ -103,7 +103,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 1:{
m.PerformAnimation("ROCK TOSS CAST");
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.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;
@ -121,7 +121,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case 2:{
SoundEffect::PlaySFX("Dig",m.GetPos());
m.PerformAnimation("BURROW UNDERGROUND");
m.phase=DIVE_UNDERGROUND_DIG;
SETPHASE(DIVE_UNDERGROUND_DIG);
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();
}break;
}
@ -167,7 +167,7 @@ void Monster::STRATEGY::STONE_ELEMENTAL(Monster&m,float fElapsedTime,std::string
case DIVE_UNDERGROUND_DIG:{
m.F(A::CASTING_TIMER)-=fElapsedTime;
if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=DIVE_UNDERGROUND_MOVE;
SETPHASE(DIVE_UNDERGROUND_MOVE);
float randomAngle=util::random(2*PI);
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;
SoundEffect::PlaySFX("Rise",m.GetPos());
m.PerformAnimation("RISE FROM UNDERGROUND");
m.phase=DIVE_UNDERGROUND_SURFACE;
SETPHASE(DIVE_UNDERGROUND_SURFACE);
m.targetAcquireTimer=0;
m.F(A::CASTING_TIMER)=m.GetCurrentAnimation().GetTotalAnimationDuration();

@ -53,9 +53,9 @@ INCLUDE_DATA
using A=Attribute;
void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy){
switch(m.phase){
switch(PHASE()){
case 0:{
m.phase=ConfigInt("StartPhase");
SETPHASE(ConfigInt("StartPhase"));
m.overlaySprite=ConfigString("Overlay Sprite");
m.overlaySpriteTransparency=0U;
@ -94,7 +94,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
}break;
case 1:{ //Run bear strategy in phase 1.
auto TransitionToPhase2=[&](){
m.phase=2;
SETPHASE(2);
m.PerformAnimation("SIT");
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");
@ -106,7 +106,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
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.
if(m.overlaySpriteTransparency<210U){
if(m.I(A::PHASE)!=0)goto bear;
if(m.GetPhase("Bear")!=0)goto bear;
else{
if(m.F(A::RUN_AWAY_TIMER)==0.f)m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase 1.Fur Change Color Time");
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")){
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.phase=6;
SETPHASE(6);
break;
}
@ -219,7 +219,7 @@ void Monster::STRATEGY::URSULE(Monster&m,float fElapsedTime,std::string strategy
}else{
auto TransitionToPhase3=[&](){
m.phase=3;
SETPHASE(3);
game->SetWorldColor(ConfigPixel("Phase 3.Environment Fade-in Color"));
m.F(A::ENVIRONMENT_TIMER)=ConfigFloat("Phase 3.Environment Fade-in Time");
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();
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){
auto TransitionToPhase5=[&](){
m.phase=5;
SETPHASE(5);
m.PerformAnimation("SIT");
SoundEffect::PlaySFX("Ursule Phase Transition",SoundEffect::CENTERED);
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){
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
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(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"));
@ -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")){
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.I(A::PREVIOUS_PHASE)=m.phase;
m.phase=6;
m.I(A::PREVIOUS_PHASE)=PHASE();
SETPHASE(6);
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();
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(FIXED_COLLISION_DMG);
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();
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
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.I(A::BEAR_STOMP_COUNT)=0;
}
}break;
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_MINOR 3
#define VERSION_PATCH 0
#define VERSION_BUILD 11657
#define VERSION_BUILD 11666
#define stringify(a) stringify_(a)
#define stringify_(a) #a

@ -49,11 +49,11 @@ INCLUDE_BULLET_LIST
using A=Attribute;
void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
switch(m.I(A::PHASE)){
switch(PHASE()){
case 0:{ //Run towards the player.
float distToPlayer=geom2d::line<float>(game->GetPlayer()->GetPos(),m.GetPos()).length();
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.AddBuff(BuffType::LOCKON_SPEEDBOOST,INFINITE,ConfigFloat("Lockon Speed Boost")/100);
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);
float distToTarget=geom2d::line<float>(m.GetPos(),m.V(A::LOCKON_POS)).length();
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.PerformIdleAnimation();
}else{
@ -97,7 +97,7 @@ void Monster::STRATEGY::WOLF(Monster&m,float fElapsedTime,std::string strategy){
m.I(A::PATH_DIR)=util::random()%2;
if(m.I(A::PATH_DIR)==0)m.I(A::PATH_DIR)=-1;
m.pathIndex=util::random()%disengagePoints.size();
m.I(A::PHASE)=3;
SETPHASE(3);
m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Disengage Duration");
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();
}
if(m.F(A::RUN_AWAY_TIMER)==0.f){
m.I(A::PHASE)=0;
SETPHASE(0);
}
m.target=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
};
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){
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);
@ -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);
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:{
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->GetOverlay().Disable();
@ -110,9 +110,9 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
}break;
case IDLE:{
#pragma region Mid Phase Check
if(m.GetHealthRatio()<=ConfigFloat("Mid Phase Health Transition %")/100.f&&!m.B(A::PHASE)){
m.B(A::PHASE)=true;
m.phase=HALFHEALTH_PREPARE_PHASE;
if(m.GetHealthRatio()<=ConfigFloat("Mid Phase Health Transition %")/100.f&&!m.B(A::MID_PHASE)){
m.B(A::MID_PHASE)=true;
SETPHASE(HALFHEALTH_PREPARE_PHASE);
m.F(A::TARGET_FLYING_HEIGHT)=50.f;
m.target=ConfigVec("Mid Phase.Pillar Position");
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");
else m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
m.phase=FLY_ACROSS_PREPARE;
SETPHASE(FLY_ACROSS_PREPARE);
}break;
case 1:{
m.phase=TORNADO_ATTACK_PREPARE;
SETPHASE(TORNADO_ATTACK_PREPARE);
m.target=ConfigVec("Tornado Attack.Landing Area");
}break;
case 2:{
m.phase=WIND_ATTACK_FLY;
SETPHASE(WIND_ATTACK_FLY);
m.F(A::TARGET_FLYING_HEIGHT)=ConfigPixels("Wind Attack.Fly Up Height");
const bool LeftLandingSite=m.I(A::ATTACK_CHOICE)=util::random()%2;
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.
if(RightDirectionChosen)m.target=ConfigVec("Fly Across Attack.Left Edge Start Pos");
else m.target=ConfigVec("Fly Across Attack.Right Edge Start Pos");
m.phase=FLY_ACROSS;
SETPHASE(FLY_ACROSS);
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");
}
if(m.ReachedTargetPos()){
m.phase=IDLE;
SETPHASE(IDLE);
m.RemoveBuff(BuffType::SPEEDBOOST);
m.targetAcquireTimer=0.f;
}
@ -208,7 +208,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
m.targetAcquireTimer=20.f;
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.ReachedTargetPos()){
m.phase=TORNADO_ATTACK;
SETPHASE(TORNADO_ATTACK);
m.PerformAnimation("ATTACK",Direction::SOUTH);
m.targetAcquireTimer=0.f;
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;
case TORNADO_ATTACK:{
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;
case WIND_ATTACK_FLY:{
m.targetAcquireTimer=20.f;
RUN_TOWARDS(m,fElapsedTime,"Run Towards");
if(m.ReachedTargetPos()){
m.phase=WIND_ATTACK_LAND;
SETPHASE(WIND_ATTACK_LAND);
m.F(A::TARGET_FLYING_HEIGHT)=0.f;
}
}break;
case WIND_ATTACK_LAND:{
if(m.GetZ()==0.f){
m.phase=WIND_ATTACK;
SETPHASE(WIND_ATTACK);
game->GetOverlay().Enable();
m.F(A::CASTING_TIMER)=ConfigFloat("Wind Attack.Wind Duration");
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
if(m.F(A::CASTING_TIMER)<=0.f){
m.phase=IDLE;
SETPHASE(IDLE);
game->GetOverlay().Disable();
game->SetWindSpeed({});
}
@ -337,7 +337,7 @@ void Monster::STRATEGY::ZEPHY(Monster&m,float fElapsedTime,std::string strategy)
const bool HasLandedOnGround=m.GetZ()==0.f;
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;
BULLET_LIST.back()->SetFadeinTime(1.0f);
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.PerformAnimation("ATTACK");
if(game->BossEncounterMobCount()==1){
m.phase=IDLE;
SETPHASE(IDLE);
std::for_each(BULLET_LIST.begin(),BULLET_LIST.end(),[](std::unique_ptr<IBullet>&bullet){
if(bullet->GetBulletType()==BulletType::LARGE_TORNADO){
bullet->fadeOutTime=1.f;

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -870,5 +870,11 @@
<property name="spawner" type="object" value="8"/>
</properties>
</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>
</map>

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1471,5 +1471,11 @@
</properties>
</object>
<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>
</map>

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -861,5 +861,11 @@
</properties>
</object>
<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>
</map>

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1076,5 +1076,11 @@
<property name="Upper?" type="bool" value="false"/>
</properties>
</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>
</map>

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1821,5 +1821,11 @@
<property name="Upper?" type="bool" value="false"/>
</properties>
</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>
</map>

@ -1,5 +1,5 @@
<?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>
<property name="Background Music" propertytype="BGM" value="beach"/>
<property name="Level Type" type="int" propertytype="LevelType" value="0"/>
@ -1886,5 +1886,11 @@
<property name="Upper?" type="bool" value="false"/>
</properties>
</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>
</map>

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

@ -1263,10 +1263,10 @@ Monsters
Dagger Slash Distance = 12
# Slash Attack windup time
Slash Windup Time = 0.2s
Slash Windup Time = 0.4s
# Stab Attack windup time
Stab Windup Time = 0.2s
Stab Windup Time = 0.4s
# Amount of time where nothing happens after an attack.
Attack Recovery Time = 0.6s
@ -1343,7 +1343,7 @@ Monsters
Attack Spacing = 100
# Slash Attack windup time
Slash Windup Time = 0.2s
Slash Windup Time = 0.4s
Dagger Slash Image = "pirate_slash.png"
@ -1415,10 +1415,10 @@ Monsters
Dagger Slash Distance = 12
# Slash Attack windup time
Slash Windup Time = 0.2s
Slash Windup Time = 0.4s
# Stab Attack windup time
Stab Windup Time = 0.2s
Stab Windup Time = 0.4s
# Amount of time where nothing happens after an attack.
Attack Recovery Time = 0.6s
@ -1434,9 +1434,9 @@ Monsters
# Offset for the dagger stab effect per direction from the monster's center.
Dagger Up Offset = -6,-5.5
Dagger Down Offset = -5,-1
Dagger Right Offset = 9,0
Dagger Left Offset = -8,-2
Dagger Down Offset = -7,-1
Dagger Right Offset = 10,1
Dagger Left Offset = -8,0
########
@ -1455,7 +1455,7 @@ Monsters
SHOOTING = 3, 0.2, OneShot
SHOOT = 1, 0.1, OneShot
# 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
@ -1602,6 +1602,8 @@ Monsters
Strategy = Seagull
Ignore Collisions = True
#Size of each animation frame
SheetFrameSize = 48,48
@ -1666,7 +1668,7 @@ Monsters
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Walk Sound = Slime Walk
Walk Sound = Wing Flap
}
Parrot
{
@ -1676,12 +1678,14 @@ Monsters
CollisionDmg = 22
MoveSpd = 180%
Size = 50%
Size = 100%
XP = 0
Collision Radius = 7
Ignore Collisions = True
Strategy = Parrot
# 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)
# 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.
IDLE = 4, 0.2, Repeat
IDLE = 1, 0.2, Repeat
FLYING = 4, 0.15, Repeat
ATTACKING = 2, 0.3, Repeat
DEATH = 4, 0.15, OneShot
DEATH = 1, 0.15, OneShot
ATTACK = 2, 0.3, Repeat
}
@ -1710,6 +1714,6 @@ Monsters
Hurt Sound = Monster Hurt
Death Sound = Slime Dead
Walk Sound = Slime Walk
Walk Sound = Wing Flap
}
}
Loading…
Cancel
Save