The open source repository for the action RPG game in development by Sig Productions titled 'Adventures in Lestoria'!
https://forums.lestoria.net
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
16 KiB
388 lines
16 KiB
#pragma region License
|
|
/*
|
|
License (OLC-3)
|
|
~~~~~~~~~~~~~~~
|
|
|
|
Copyright 2024 Joshua Sigona <sigonasr2@gmail.com>
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions or derivations of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions or derivative works in binary form must reproduce the above
|
|
copyright notice. This list of conditions and the following disclaimer must be
|
|
reproduced in the documentation and/or other materials provided with the distribution.
|
|
|
|
3. Neither the name of the copyright holder nor the names of its contributors may
|
|
be used to endorse or promote products derived from this software without specific
|
|
prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
|
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
Portions of this software are copyright © 2024 The FreeType
|
|
Project (www.freetype.org). Please see LICENSE_FT.txt for more information.
|
|
All rights reserved.
|
|
*/
|
|
#pragma endregion
|
|
#include "Monster.h"
|
|
#include "MonsterStrategyHelpers.h"
|
|
#include "DEFINES.h"
|
|
#include "AdventuresInLestoria.h"
|
|
#include "util.h"
|
|
#include "safemap.h"
|
|
#include "Effect.h"
|
|
#include "FallingDebris.h"
|
|
#include "MonsterAttribute.h"
|
|
#include "SoundEffect.h"
|
|
|
|
INCLUDE_game
|
|
INCLUDE_BULLET_LIST
|
|
INCLUDE_ANIMATION_DATA
|
|
INCLUDE_MONSTER_DATA
|
|
INCLUDE_GFX
|
|
|
|
using A=Attribute;
|
|
|
|
void Monster::STRATEGY::SLIMEKING(Monster&m,float fElapsedTime,std::string strategy){
|
|
float bulletSpd=ConfigFloat("BulletSpd")/100*24;
|
|
|
|
m.F(A::SHOOT_TIMER)=std::max(0.f,m.F(A::SHOOT_TIMER)-fElapsedTime);
|
|
m.F(A::SHOOT_RING_TIMER)=std::max(0.f,m.F(A::SHOOT_RING_TIMER)-fElapsedTime);
|
|
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");
|
|
for(int i=0;i<bulletCount;i++){
|
|
float angle=((2*PI)/bulletCount)*i+angleOffset;
|
|
BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
|
|
}
|
|
SoundEffect::PlaySFX("Slime King Shoot",m.GetPos());
|
|
};
|
|
|
|
const auto Landed=[&ShootBulletRing,&m](int currentPhase){
|
|
if(currentPhase==1){
|
|
ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
|
|
}
|
|
SoundEffect::PlaySFX("Slime King Land",m.GetPos());
|
|
};
|
|
|
|
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<float>(player->GetPos(),pos).length()<=player->GetSizeMult()*12*2+radius;
|
|
};
|
|
const auto SpawnMonsterFromConfig=[&](int phase){
|
|
std::string spawnMonster=ConfigStringArr("Phase"+std::to_string(phase)+".MonsterSpawnOnChange",0);
|
|
int spawnCount=ConfigIntArr("Phase"+std::to_string(phase)+".MonsterSpawnOnChange",1);
|
|
for(int i=0;i<spawnCount;i++){
|
|
float randomAngle=util::random(2*PI);
|
|
vf2d spawnPos=m.pos+vf2d{cos(randomAngle),sin(randomAngle)}*m.GetSizeMult()*12;
|
|
for(int attempts=0;attempts<MAX_ATTEMPTS&&PositionInRangeOfPlayer(spawnPos,MONSTER_DATA[spawnMonster].GetSizeMult()*12);attempts++){
|
|
randomAngle=util::random(2*PI);
|
|
spawnPos=m.pos+vf2d{cos(randomAngle),sin(randomAngle)}*m.GetSizeMult()*12;
|
|
}
|
|
game->SpawnMonster(spawnPos,MONSTER_DATA[spawnMonster]);
|
|
}
|
|
};
|
|
switch(newPhase){
|
|
case 2:{
|
|
SpawnMonsterFromConfig(2);
|
|
}break;
|
|
case 3:{
|
|
SpawnMonsterFromConfig(3);
|
|
}break;
|
|
case 4:{
|
|
SpawnMonsterFromConfig(4);
|
|
}break;
|
|
case 5:{
|
|
|
|
}break;
|
|
}
|
|
};
|
|
|
|
const auto StartJumpTowardsPlayer=[&](float jumpDuration,float recoveryTime,float jumpMoveSpd,float moveTowardsTargetLockinTime){
|
|
m.F(A::JUMP_ORIGINAL_LANDING_TIMER)=m.F(A::JUMP_LANDING_TIMER)=jumpDuration;
|
|
m.B(A::JUMP_TOWARDS_PLAYER)=true;
|
|
m.F(A::RECOVERY_TIME)=recoveryTime;
|
|
m.F(A::JUMP_MOVE_SPD)=jumpMoveSpd;
|
|
m.F(A::JUMP_MOVE_TO_TARGET_TIMER)=moveTowardsTargetLockinTime;
|
|
m.SetState(State::JUMP);
|
|
};
|
|
|
|
const auto StartJump=[&](float jumpDuration,vf2d targetPos,float recoveryTime,float jumpMoveSpd,float moveTowardsTargetLockinTime){
|
|
m.F(A::JUMP_ORIGINAL_LANDING_TIMER)=m.F(A::JUMP_LANDING_TIMER)=jumpDuration;
|
|
m.V(A::JUMP_TARGET_POS)=targetPos;
|
|
m.B(A::JUMP_TOWARDS_PLAYER)=false;
|
|
m.F(A::RECOVERY_TIME)=recoveryTime;
|
|
m.F(A::JUMP_MOVE_SPD)=jumpMoveSpd;
|
|
m.F(A::JUMP_MOVE_TO_TARGET_TIMER)=moveTowardsTargetLockinTime;
|
|
m.SetState(State::JUMP);
|
|
};
|
|
|
|
const auto Recovered=[&](){
|
|
switch(m.phase){
|
|
case 2:{
|
|
switch(m.I(A::JUMP_COUNT)){
|
|
case 1:
|
|
case 2:{ //After the 1st and 2nd jumps we still have another jump to accomplish.
|
|
m.I(A::JUMP_COUNT)++;
|
|
float jumpTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",0);
|
|
float jumpSpd=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",1);
|
|
float lockedInTargetTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",2);
|
|
StartJumpTowardsPlayer(jumpTime,0.2f,jumpSpd,lockedInTargetTime);
|
|
}break;
|
|
default:{
|
|
m.PerformIdleAnimation();
|
|
m.SetState(State::NORMAL);
|
|
}
|
|
}
|
|
}break;
|
|
}
|
|
};
|
|
|
|
if(m.F(A::RUN_AWAY_TIMER)>0){
|
|
Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,"Run Away");
|
|
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){
|
|
m.SetState(State::NORMAL);
|
|
Recovered();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(m.GetState()==State::JUMP){
|
|
float jumpLandingTimerRatio=m.F(A::JUMP_LANDING_TIMER)/m.F(A::JUMP_ORIGINAL_LANDING_TIMER);
|
|
vf2d jumpTargetPos=m.V(A::JUMP_TARGET_POS);
|
|
if(m.B(A::JUMP_TOWARDS_PLAYER)){
|
|
jumpTargetPos=game->GetPlayer()->GetPos();
|
|
}
|
|
if(m.F(A::JUMP_LANDING_TIMER)>m.F(A::JUMP_MOVE_TO_TARGET_TIMER)){
|
|
if(m.GetPos().x>jumpTargetPos.x){
|
|
m.SetX(std::max(jumpTargetPos.x,m.GetPos().x-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
|
|
} else
|
|
if(m.GetPos().x<jumpTargetPos.x){
|
|
m.SetX(std::min(jumpTargetPos.x,m.GetPos().x+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
|
|
}
|
|
if(m.GetPos().y>jumpTargetPos.y){
|
|
m.SetY(std::max(jumpTargetPos.y,m.GetPos().y-m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
|
|
} else
|
|
if(m.GetPos().y<jumpTargetPos.y){
|
|
m.SetY(std::min(jumpTargetPos.y,m.GetPos().y+m.F(A::JUMP_MOVE_SPD)*game->GetElapsedTime()));
|
|
}
|
|
}
|
|
if(m.F(A::JUMP_LANDING_TIMER)>=m.F(A::JUMP_ORIGINAL_LANDING_TIMER)/2){
|
|
m.SetZ(util::lerp(0,float(ConfigInt("JumpHeight")),1-jumpLandingTimerRatio));
|
|
}else{
|
|
m.SetZ(util::lerp(0,float(ConfigInt("JumpHeight")),jumpLandingTimerRatio*2));
|
|
}
|
|
if(m.F(A::JUMP_LANDING_TIMER)==0){
|
|
m.state=State::RECOVERY;
|
|
game->SetupWorldShake(0.6f);
|
|
geom2d::line<float>lineToPlayer(m.GetPos(),game->GetPlayer()->GetPos());
|
|
float dist=lineToPlayer.length();
|
|
for(int i=0;i<200;i++){
|
|
float randomDir=util::random(2*PI);
|
|
game->AddEffect(std::make_unique<FallingDebris>(m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*m.GetSizeMult()*8,util::random(1),"circle.png",m.OnUpperLevel(),vf2d{1,1},0.5,vf2d{cos(randomDir)*util::random(5),sin(randomDir)*-util::random(15)-5}*30,BLACK),true);
|
|
}
|
|
if(dist<12*m.GetSizeMult()){
|
|
game->GetPlayer()->Hurt(ConfigInt("JumpAttackDamage"),m.OnUpperLevel(),m.GetZ());
|
|
if(dist<0.001){
|
|
float randomDir=util::random(2*PI);
|
|
lineToPlayer={m.GetPos(),m.GetPos()+vf2d{cos(randomDir),sin(randomDir)}*1};
|
|
}
|
|
game->GetPlayer()->Knockback(lineToPlayer.vector().norm()*float(ConfigInt("JumpKnockbackFactor")));
|
|
if(m.phase!=2){ //In phase 2, the player can get slammed multiple times. No iframes for messing up.
|
|
game->GetPlayer()->SetIframes(1);
|
|
}
|
|
}
|
|
m.SetZ(0);
|
|
Landed(m.phase);
|
|
m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){});
|
|
} else
|
|
if(m.F(A::JUMP_LANDING_TIMER)<=ConfigFloat("JumpWarningIndicatorTime")){
|
|
m.SetStrategyDrawFunction([](AiL*game,Monster&m,const std::string&strategy){
|
|
Decal*dec=GFX["range_indicator.png"].Decal();
|
|
game->view.DrawRotatedDecal(m.GetPos(),dec,0,dec->sprite->Size()/2,vf2d{m.GetSizeMult(),m.GetSizeMult()},RED);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(m.GetState()==State::CASTING){
|
|
m.PerformAnimation("CHARGEUP");
|
|
if(m.F(A::CASTING_TIMER)==0){
|
|
m.SetState(State::NORMAL);
|
|
m.I(A::JUMP_COUNT)++;
|
|
float jumpTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",0);
|
|
float jumpSpd=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",1);
|
|
float lockedInTargetTime=ConfigFloatArr("Phase2.Jump["+std::to_string(m.I(A::JUMP_COUNT))+"]",2);
|
|
StartJumpTowardsPlayer(jumpTime,0.2f,jumpSpd,lockedInTargetTime);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch(m.phase){
|
|
case 0:{
|
|
m.size=ConfigInt("Phase1.Size")/100.f;
|
|
m.diesNormally=false;
|
|
m.F(A::IFRAME_TIME_UPON_HIT)=0;
|
|
m.iframe_timer=ConfigFloat("Phase5.IframeTimePerHit");
|
|
m.phase=ConfigInt("StartPhase");
|
|
}break;
|
|
case 1:{
|
|
if(m.GetRemainingHPPct()<=ConfigFloat("Phase2.Change")/100.f){
|
|
m.phase=2;
|
|
m.SetSize(ConfigFloat("Phase2.Size")/100,false);
|
|
TransitionPhase(m.phase);
|
|
return;
|
|
}
|
|
if(m.F(A::SHOOT_RING_TIMER)==0){
|
|
if(m.I(A::PATTERN_REPEAT_COUNT)>=ConfigInt("Phase1.JumpAfter")){
|
|
StartJumpTowardsPlayer(ConfigFloat("Phase1.AirborneTime"),ConfigFloat("Phase1.LandingRecoveryTime"),ConfigFloat("JumpMoveSpd"),ConfigFloat("Phase1.JumpLockinTargetTime"));
|
|
m.I(A::PATTERN_REPEAT_COUNT)=0;
|
|
return;
|
|
}
|
|
m.I(A::SHOOT_RING_COUNTER)=ConfigInt("Phase1.ShootRingCount")-1;
|
|
m.F(A::SHOOT_RING_DELAY)=ConfigFloat("Phase1.ShootRingDelay");
|
|
ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
|
|
m.F(A::SHOOT_RING_TIMER)=ConfigFloat("Phase1.ShootRepeatTime");
|
|
m.B(A::SHOOT_RING_RIGHT)=bool(util::random()%2);
|
|
m.I(A::PATTERN_REPEAT_COUNT)++;
|
|
}
|
|
if(m.I(A::SHOOT_RING_COUNTER)>0){
|
|
if(m.F(A::SHOOT_RING_DELAY)==0){
|
|
m.I(A::SHOOT_RING_COUNTER)--;
|
|
m.F(A::SHOOT_RING_DELAY)=ConfigFloat("Phase1.ShootRingDelay");
|
|
if(m.B(A::SHOOT_RING_RIGHT)){
|
|
m.F(A::SHOOT_RING_OFFSET)+=util::degToRad(ConfigFloat("Phase1.RingOffset"));
|
|
}else{
|
|
m.F(A::SHOOT_RING_OFFSET)-=util::degToRad(ConfigFloat("Phase1.RingOffset"));
|
|
}
|
|
ShootBulletRing(m.F(A::SHOOT_RING_OFFSET));
|
|
}
|
|
}
|
|
}break;
|
|
case 2:{
|
|
if(m.GetRemainingHPPct()<=ConfigFloat("Phase3.Change")/100.f){
|
|
m.phase=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);
|
|
return;
|
|
}
|
|
if(m.F(A::SHOOT_TIMER)==0){
|
|
m.F(A::SHOOT_TIMER)=float(ConfigInt("Phase2.ShootRate"));
|
|
m.I(A::PATTERN_REPEAT_COUNT)++;
|
|
int bulletCount=ConfigInt("Phase2.ShootProjectileCount");
|
|
for(int i=0;i<bulletCount;i++){
|
|
float initialAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
|
|
float angle=(i-(bulletCount/2))*util::degToRad(ConfigFloat("Phase2.ShootAngleSpread"))+initialAngle;
|
|
BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
|
|
}
|
|
SoundEffect::PlaySFX("Slime King Shoot",m.GetPos());
|
|
}
|
|
if(m.I(A::PATTERN_REPEAT_COUNT)>ConfigInt("Phase2.ShootCount")){
|
|
m.I(A::PATTERN_REPEAT_COUNT)=0;
|
|
m.I(A::JUMP_COUNT)=0;
|
|
m.F(A::CASTING_TIMER)=5;
|
|
m.SetState(State::CASTING);
|
|
}
|
|
}break;
|
|
case 3:{
|
|
if(m.GetRemainingHPPct()<=ConfigFloat("Phase4.Change")/100.f){
|
|
m.phase=4;
|
|
m.SetSize(ConfigFloat("Phase4.Size")/100,false);
|
|
m.AddBuff(BuffType::SLOWDOWN,99999,ConfigFloat("Phase4.MoveSpdModifier")/100);
|
|
TransitionPhase(m.phase);
|
|
return;
|
|
}
|
|
if(m.I(A::PATTERN_REPEAT_COUNT)==0){
|
|
StartJumpTowardsPlayer(ConfigFloat("Phase3.JumpDelayTime"),ConfigFloat("Phase3.JumpRecoveryTime"),ConfigFloat("Phase3.JumpMoveSpd"),ConfigFloat("Phase3.JumpLockinTargetTime"));
|
|
m.I(A::PATTERN_REPEAT_COUNT)++;
|
|
}else
|
|
if(m.I(A::PATTERN_REPEAT_COUNT)<4&&m.F(A::SHOOT_TIMER)==0){
|
|
m.F(A::SHOOT_TIMER)=ConfigFloat("Phase3.ShootRate");
|
|
m.I(A::PATTERN_REPEAT_COUNT)++;
|
|
int bulletCount=ConfigInt("Phase3.ShootProjectileCount");
|
|
for(int i=0;i<bulletCount;i++){
|
|
float initialAngle=util::angleTo(m.GetPos(),game->GetPlayer()->GetPos());
|
|
float angle=(i-(bulletCount/2))*util::degToRad(ConfigFloat("Phase3.ShootAngleSpread"))+initialAngle;
|
|
BULLET_LIST.emplace_back(std::make_unique<Bullet>(m.GetPos(),vf2d{cos(angle),sin(angle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
|
|
}
|
|
SoundEffect::PlaySFX("Slime King Shoot",m.GetPos());
|
|
}else
|
|
if(m.I(A::PATTERN_REPEAT_COUNT)>=4){
|
|
m.F(A::RECOVERY_TIME)=ConfigFloat("Phase3.PhaseRecoveryTime");
|
|
m.SetState(State::RECOVERY);
|
|
m.I(A::PATTERN_REPEAT_COUNT)=0;
|
|
}
|
|
}break;
|
|
case 4:{
|
|
if(m.hp<=1){ //HP can't reach 0 when the dies normally flag is on.
|
|
m.phase=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);
|
|
return;
|
|
}
|
|
if(m.I(A::PHASE_REPEAT_COUNT)>=5){
|
|
m.I(A::PHASE_REPEAT_COUNT)=0;
|
|
float jumpAngle=util::angleTo(m.GetPos(),game->GetCurrentMapData().MapSize/2*game->GetCurrentMapData().tilewidth); //We jump towards the center to keep the player from constantly dealing with a stuck boss.
|
|
float jumpDistance=ConfigFloat("Phase4.JumpDistance")/100*game->GetCurrentMapData().tilewidth;
|
|
float jumpSpd=jumpDistance/ConfigFloat("Phase4.JumpDuration");
|
|
float lockedInTargetTime=ConfigFloat("Phase4.JumpLockinTargetTime");
|
|
StartJump(ConfigFloat("Phase4.JumpDuration"),m.GetPos()+vf2d{cos(jumpAngle)*jumpDistance,sin(jumpAngle)*jumpDistance},0,jumpSpd,lockedInTargetTime);
|
|
}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<Bullet>(m.GetPos(),vf2d{cos(bulletAngle),sin(bulletAngle)}*bulletSpd,6,ConfigInt("ProjectileDamage"),m.OnUpperLevel(),false,YELLOW,vf2d{6,6}));
|
|
SoundEffect::PlaySFX("Slime King Shoot",m.GetPos());
|
|
}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;
|
|
case 5:{
|
|
float targetSize=ConfigFloat("Phase5.SizeLossPerHit")/100*m.I(A::HITS_UNTIL_DEATH);
|
|
Monster::STRATEGY::RUN_AWAY(m,fElapsedTime,"Run Away");
|
|
if(targetSize>0){
|
|
m.SetSize(targetSize,false);
|
|
}else{
|
|
m.diesNormally=true;
|
|
}
|
|
}break;
|
|
}
|
|
} |