diff --git a/Crawler/Crawler.vcxproj b/Crawler/Crawler.vcxproj
index 64f55478..d86fada3 100644
--- a/Crawler/Crawler.vcxproj
+++ b/Crawler/Crawler.vcxproj
@@ -308,6 +308,7 @@
+
diff --git a/Crawler/Crawler.vcxproj.filters b/Crawler/Crawler.vcxproj.filters
index 38fdedbc..ec38e974 100644
--- a/Crawler/Crawler.vcxproj.filters
+++ b/Crawler/Crawler.vcxproj.filters
@@ -242,6 +242,9 @@
Source Files\Effects
+
+ Source Files\Monster Strategies
+
diff --git a/Crawler/Monster.cpp b/Crawler/Monster.cpp
index 59840e13..f4963b5a 100644
--- a/Crawler/Monster.cpp
+++ b/Crawler/Monster.cpp
@@ -83,7 +83,7 @@ bool Monster::SetX(float x){
} else {
geom2d::rectcollision={collisionRect.pos,collisionRect.size};
collision.pos+=tilePos;
- if(!geom2d::overlaps(newPos,collision)){
+ if(!geom2d::overlaps(geom2d::circle(newPos,12*GetSizeMult()),collision)){
pos.x=std::clamp(x,12.f*GetSizeMult(),float(game->WORLD_SIZE.x*24-12*GetSizeMult()));
Moved();
return true;
@@ -102,7 +102,7 @@ bool Monster::SetY(float y){
} else {
geom2d::rectcollision={collisionRect.pos,collisionRect.size};
collision.pos+=tilePos;
- if(!geom2d::overlaps(newPos,collision)){
+ if(!geom2d::overlaps(geom2d::circle(newPos,12*GetSizeMult()),collision)){
pos.y=std::clamp(y,12.f*GetSizeMult(),float(game->WORLD_SIZE.y*24-12*GetSizeMult()));
Moved();
return true;
diff --git a/Crawler/Monster.h b/Crawler/Monster.h
index a49ba87b..81dd6607 100644
--- a/Crawler/Monster.h
+++ b/Crawler/Monster.h
@@ -156,6 +156,7 @@ private:
static void SHOOT_AFAR(Monster&m,float fElapsedTime,int strategyNumber);
static void TURRET(Monster&m,float fElapsedTime,int strategyNumber);
static void SLIMEKING(Monster&m,float fElapsedTime,int strategyNumber);
+ static void RUN_AWAY(Monster&m,float fElapsedTime,int strategyNumber);
};
};
diff --git a/Crawler/MonsterAttribute.h b/Crawler/MonsterAttribute.h
index f9699834..eefef509 100644
--- a/Crawler/MonsterAttribute.h
+++ b/Crawler/MonsterAttribute.h
@@ -26,4 +26,6 @@ enum class Attribute{
JUMP_COUNT,
CASTING_TIMER,
TELEPORT_TO_PLAYER,
+ RUN_AWAY_TIMER,
+ PHASE_REPEAT_COUNT,
};
\ No newline at end of file
diff --git a/Crawler/RUN_STRATEGY.cpp b/Crawler/RUN_STRATEGY.cpp
index f47b65e7..24ee7925 100644
--- a/Crawler/RUN_STRATEGY.cpp
+++ b/Crawler/RUN_STRATEGY.cpp
@@ -40,5 +40,8 @@ void Monster::STRATEGY::RUN_STRATEGY(Monster&m,float fElapsedTime){
case 3:{//Slime King
Monster::STRATEGY::SLIMEKING(m,fElapsedTime,m.strategy);
}break;
+ case 4:{//Run Away Strategy
+ Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,m.strategy);
+ }break;
}
}
\ No newline at end of file
diff --git a/Crawler/RunAway.cpp b/Crawler/RunAway.cpp
new file mode 100644
index 00000000..bf4d9d73
--- /dev/null
+++ b/Crawler/RunAway.cpp
@@ -0,0 +1,80 @@
+#include "Monster.h"
+#include "DEFINES.h"
+#include "Crawler.h"
+#include "MonsterStrategyHelpers.h"
+
+INCLUDE_BULLET_LIST
+INCLUDE_game
+
+void Monster::STRATEGY::RUN_AWAY(Monster&m,float fElapsedTime,int strategyNumber){
+ m.targetAcquireTimer=std::max(0.f,m.targetAcquireTimer-fElapsedTime);
+ m.attackCooldownTimer=std::max(0.f,m.attackCooldownTimer-fElapsedTime);
+ geom2d::line line(m.pos,game->GetPlayer()->GetPos());
+ if(m.targetAcquireTimer==0&&m.queueShotTimer==0){
+ m.targetAcquireTimer=1;
+ if(line.length()<24.f*ConfigInt("Range")/100.f){
+ m.target=line.upoint(-1.2);
+ if(m.canMove){
+ m.SetState(State::MOVE_AWAY);
+ } else {
+ m.SetState(State::NORMAL);
+ }
+ } else
+ if(line.length()>24.f*ConfigInt("CloseInRange")/100.0f){
+ m.target=line.upoint(1.2);
+ m.SetState(State::MOVE_TOWARDS);
+ } else {
+ m.SetState(State::NORMAL);
+ }
+ }
+ m.canMove=true;
+ geom2d::line moveTowardsLine=geom2d::line(m.pos,m.target);
+ bool pathfindingDecision=false;
+ switch(m.state){
+ case State::MOVE_TOWARDS:{
+ if(moveTowardsLine.length()>1){
+ vf2d newPos=m.pos+moveTowardsLine.vector().norm()*100*fElapsedTime*m.GetMoveSpdMult();
+ bool movedX=m.SetX(newPos.x);
+ bool movedY=m.SetY(newPos.y);
+ pathfindingDecision=movedX|movedY;
+ m.canMove=movedX&&movedY;
+ }
+ if(!pathfindingDecision){
+ m.StartPathfinding(2.5);
+ }else
+ if(line.length()<=24.f*ConfigInt("CloseInRange")/100.0f){
+ m.SetState(State::NORMAL);
+ }
+ if(moveTowardsLine.vector().x>0){
+ m.facingDirection=RIGHT;
+ } else {
+ m.facingDirection=LEFT;
+ }
+ m.PerformJumpAnimation();
+ }break;
+ case State::MOVE_AWAY:{
+ if(moveTowardsLine.length()>1){
+ vf2d newPos=m.pos+moveTowardsLine.vector().norm()*100*fElapsedTime*m.GetMoveSpdMult();
+ bool movedX=m.SetX(newPos.x);
+ bool movedY=m.SetY(newPos.y);
+ pathfindingDecision=movedX|movedY;
+ m.canMove=movedX&&movedY;
+ }
+ if(!pathfindingDecision){
+ m.StartPathfinding(2.5);
+ }else
+ if(line.length()>=24.f*ConfigInt("Range")/100.f){
+ m.SetState(State::NORMAL);
+ }
+ if(moveTowardsLine.vector().x>0){
+ m.facingDirection=RIGHT;
+ } else {
+ m.facingDirection=LEFT;
+ }
+ m.PerformJumpAnimation();
+ }break;
+ case State::PATH_AROUND:{
+ m.PathAroundBehavior(fElapsedTime);
+ }break;
+ }
+}
\ No newline at end of file
diff --git a/Crawler/SlimeKing.cpp b/Crawler/SlimeKing.cpp
index 9cb407a9..0c92f83f 100644
--- a/Crawler/SlimeKing.cpp
+++ b/Crawler/SlimeKing.cpp
@@ -24,6 +24,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
m.F(A::SHOOT_RING_DELAY)=std::max(0.f,m.F(A::SHOOT_RING_DELAY)-fElapsedTime);
m.F(A::JUMP_LANDING_TIMER)=std::max(0.f,m.F(A::JUMP_LANDING_TIMER)-fElapsedTime);
m.F(A::CASTING_TIMER)=std::max(0.f,m.F(A::CASTING_TIMER)-fElapsedTime);
+ m.F(A::RUN_AWAY_TIMER)=std::max(0.f,m.F(A::RUN_AWAY_TIMER)-fElapsedTime);
const auto ShootBulletRing=[&](float angleOffset){
int bulletCount=ConfigInt("Phase1.RingBulletCount");
@@ -42,6 +43,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
const auto TransitionPhase=[&](int newPhase){
const int MAX_ATTEMPTS=100; //Maximum number of tries to find a valid location.
Player*player=game->GetPlayer();
+ m.I(A::PATTERN_REPEAT_COUNT)=0;
const auto PositionInRangeOfPlayer=[&player](vf2d&pos,float radius){
return geom2d::line(player->GetPos(),pos).length()<=player->GetSizeMult()*12*2+radius;
};
@@ -105,6 +107,12 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
}
};
+ if(m.F(A::RUN_AWAY_TIMER)>0){
+ Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,4);
+ /*HACK ALERT!! This is kind of a hack. If the Run Away script changes the 4 would be inaccurate, but the run away script doesn't use this value so it's probably fine.*/
+ return;
+ }
+
if(m.GetState()==State::RECOVERY){
m.F(A::RECOVERY_TIME)=std::max(0.f,m.F(A::RECOVERY_TIME)-fElapsedTime);
if(m.F(A::RECOVERY_TIME)==0){
@@ -116,17 +124,17 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
if(m.GetState()==State::JUMP){
float jumpLandingTimerRatio=m.F(A::JUMP_LANDING_TIMER)/m.F(A::JUMP_ORIGINAL_LANDING_TIMER);
- if(m.GetPos().x>game->GetPlayer()->GetPos().x){
- m.SetX(std::max(game->GetPlayer()->GetPos().x,m.GetPos().x-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
+ if(m.GetPos().x>m.V(A::JUMP_TARGET_POS).x){
+ m.SetX(std::max(m.V(A::JUMP_TARGET_POS).x,m.GetPos().x-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
} else
- if(m.GetPos().xGetPlayer()->GetPos().x){
- m.SetX(std::min(game->GetPlayer()->GetPos().x,m.GetPos().x+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
+ if(m.GetPos().xGetElapsedTime()));
}
- if(m.GetPos().y>game->GetPlayer()->GetPos().y){
- m.SetY(std::max(game->GetPlayer()->GetPos().y,m.GetPos().y-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
+ if(m.GetPos().y>m.V(A::JUMP_TARGET_POS).y){
+ m.SetY(std::max(m.V(A::JUMP_TARGET_POS).y,m.GetPos().y-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
} else
- if(m.GetPos().yGetPlayer()->GetPos().y){
- m.SetY(std::min(game->GetPlayer()->GetPos().y,m.GetPos().y+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
+ if(m.GetPos().yGetElapsedTime()));
}
if(m.F(A::JUMP_LANDING_TIMER)>=m.F(A::JUMP_ORIGINAL_LANDING_TIMER)/2){
m.SetZ(util::lerp(0,ConfigInt("JumpHeight"),1-jumpLandingTimerRatio));
@@ -250,6 +258,7 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
if(m.hp<=m.maxhp*ConfigFloat("Phase4.Change")/100){
m.phase=4;
m.SetSize(ConfigFloat("Phase4.Size")/100,false);
+ m.AddBuff(BuffType::SLOWDOWN,9999999,ConfigFloat("Phase4.MoveSpdModifier")/100);
TransitionPhase(m.phase);
return;
}
@@ -280,6 +289,31 @@ void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,int strategyNumbe
TransitionPhase(m.phase);
return;
}
+ if(m.I(A::PHASE_REPEAT_COUNT)>=5){
+ m.I(A::PHASE_REPEAT_COUNT)=0;
+ float jumpAngle=util::angleTo(m.GetPos(),game->GetWorldSize()*24/2); //We jump towards the center to keep the player from constantly dealing with a stuck boss.
+ float jumpDistance=ConfigFloat("Phase4.JumpDistance")/100*24;
+ float jumpSpd=jumpDistance/ConfigFloat("Phase4.JumpDuration");
+ StartJump(ConfigFloat("Phase4.JumpDuration"),m.GetPos()+vf2d{cos(jumpAngle)*jumpDistance,sin(jumpAngle)*jumpDistance},0,jumpSpd);
+ }else
+ if(m.I(A::PATTERN_REPEAT_COUNT)<5&&m.F(A::SHOOT_TIMER)==0){
+ m.I(A::PATTERN_REPEAT_COUNT)++;
+ m.F(A::SHOOT_TIMER)=ConfigFloat("Phase4.ShootRate");
+ float bulletAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
+ float spreadAngle=util::degToRad(ConfigFloat("Phase4.RandomOffsetAngle"));
+ bulletAngle+=util::random(spreadAngle*2)-spreadAngle;
+ BULLET_LIST.emplace_back(std::make_unique(m.GetPos(),vf2d{cos(bulletAngle),sin(bulletAngle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
+ }else
+ if(m.I(A::PATTERN_REPEAT_COUNT)==5){
+ m.I(A::PATTERN_REPEAT_COUNT)++;
+ m.F(A::RUN_AWAY_TIMER)=ConfigFloat("Phase4.RunAwayTime");
+ }else
+ if(m.I(A::PATTERN_REPEAT_COUNT)==6&&m.F(A::RUN_AWAY_TIMER)==0){
+ m.F(A::RECOVERY_TIME)=ConfigFloat("Phase4.WaitTime");
+ m.SetState(State::RECOVERY);
+ m.I(A::PATTERN_REPEAT_COUNT)=0;
+ m.I(A::PHASE_REPEAT_COUNT)++;
+ }
}break;
}
}
\ No newline at end of file
diff --git a/Crawler/Version.h b/Crawler/Version.h
index 0df419c9..547f7a21 100644
--- a/Crawler/Version.h
+++ b/Crawler/Version.h
@@ -2,7 +2,7 @@
#define VERSION_MAJOR 0
#define VERSION_MINOR 2
#define VERSION_PATCH 0
-#define VERSION_BUILD 1390
+#define VERSION_BUILD 1400
#define stringify(a) stringify_(a)
#define stringify_(a) #a
diff --git a/Crawler/assets/config/MonsterStrategies.txt b/Crawler/assets/config/MonsterStrategies.txt
index 2de3c8d9..0fbb5407 100644
--- a/Crawler/assets/config/MonsterStrategies.txt
+++ b/Crawler/assets/config/MonsterStrategies.txt
@@ -145,9 +145,15 @@ MonsterStrategy
# Percentage of health to transition to Phase 4
Change = 25%
MonsterSpawnOnChange = Blue Slime, 2
+ # Percentage of normal move spd the Slime King will move.
MoveSpdModifier = 50%
ShootRate = 0.1
- RandomOffsetAngle = 75
+ RandomOffsetAngle = 35
+
+ RunAwayTime = 2.5
+ WaitTime = 1.0
+
+ JumpDuration = 3.0
JumpDistance = 1000
}
Phase5
@@ -159,4 +165,12 @@ MonsterStrategy
Change = 0%
}
}
+ 4
+ {
+ Name = Run Away
+ # How far away the monster attempts to distance itself from the player
+ Range = 700
+ # If the player is farther than this distance, close in on them.
+ CloseInRange = 850
+ }
}
\ No newline at end of file
diff --git a/Crawler/assets/config/Monsters.txt b/Crawler/assets/config/Monsters.txt
index f6f93c5d..fe5de6b1 100644
--- a/Crawler/assets/config/Monsters.txt
+++ b/Crawler/assets/config/Monsters.txt
@@ -135,11 +135,11 @@ Monsters
CollisionDmg = 0
- MoveSpd = 0
+ MoveSpd = 100
Size = 800
Strategy = Slime King
- StartPhase = 3
+ StartPhase = 1
#Size of each animation frame
SheetFrameSize = 24,24