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