|
|
|
#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 "DEFINES.h"
|
|
|
|
#include "AdventuresInLestoria.h"
|
|
|
|
#include "MonsterStrategyHelpers.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "FallingDebris.h"
|
|
|
|
#include "SoundEffect.h"
|
|
|
|
|
|
|
|
INCLUDE_game
|
|
|
|
INCLUDE_MONSTER_DATA
|
|
|
|
INCLUDE_GFX
|
|
|
|
|
|
|
|
using A=Attribute;
|
|
|
|
|
|
|
|
void Monster::STRATEGY::RUN_TOWARDS(Monster&m,float fElapsedTime,std::string strategy){
|
|
|
|
const bool jumpingEnabled=ConfigFloat("JumpTimer")>0.f;
|
|
|
|
|
|
|
|
if(!m.B(A::INITIALIZED)){
|
|
|
|
if(jumpingEnabled){
|
|
|
|
m.F(A::LAST_JUMP_TIMER)=ConfigFloat("JumpTimer");
|
|
|
|
}
|
|
|
|
m.B(A::INITIALIZED)=true;
|
|
|
|
}
|
|
|
|
|
|
|
|
m.targetAcquireTimer=std::max(0.f,m.targetAcquireTimer-fElapsedTime);
|
|
|
|
m.F(A::JUMP_LANDING_TIMER)=std::max(0.f,m.F(A::JUMP_LANDING_TIMER)-fElapsedTime);
|
|
|
|
|
|
|
|
if(m.targetAcquireTimer==0&&m.GetState()!=State::JUMP){
|
|
|
|
m.targetAcquireTimer=ConfigFloat("WaitTime");
|
|
|
|
|
|
|
|
auto desiredTargetLine = geom2d::line(m.pos,game->GetPlayer()->GetPos());
|
|
|
|
if(desiredTargetLine.length()>=ConfigInt("MaxDistance")/100.f*24){
|
|
|
|
//Trim to max distance desired.
|
|
|
|
m.target=desiredTargetLine.rpoint(ConfigInt("MaxDistance")/100.f*24);
|
|
|
|
} else {
|
|
|
|
m.target=desiredTargetLine.upoint(1.2f);
|
|
|
|
}
|
|
|
|
m.SetState(State::MOVE_TOWARDS);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(geom2d::line(m.pos,m.target).length()>4.f){
|
|
|
|
m.UpdateFacingDirection(m.target);
|
|
|
|
}
|
|
|
|
|
|
|
|
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 Landed=[&](){
|
|
|
|
SoundEffect::PlaySFX("Slime Land",m.GetPos());
|
|
|
|
};
|
|
|
|
|
|
|
|
if(m.GetState()==State::NORMAL&&m.target!=vf2d{})m.SetState(State::MOVE_TOWARDS);
|
|
|
|
|
|
|
|
switch(m.GetState()){
|
|
|
|
case State::MOVE_TOWARDS:{
|
|
|
|
if(geom2d::line(m.pos,m.target).length()>100*fElapsedTime*m.GetMoveSpdMult()){
|
|
|
|
vf2d newPos=m.pos+geom2d::line(m.pos,m.target).vector().norm()*100*fElapsedTime*m.GetMoveSpdMult();
|
|
|
|
m.canMove=m.SetPos(newPos);
|
|
|
|
if(m.GetPos()!=newPos){
|
|
|
|
bool pathFound=m.StartPathfinding(4);
|
|
|
|
if(!pathFound){
|
|
|
|
m.SetState(State::MOVE_TOWARDS);
|
|
|
|
//Choose a random position around us and move towards it.
|
|
|
|
float randomAngle=util::random(2*PI);
|
|
|
|
float randomDist=util::random(24*6);
|
|
|
|
m.target=m.GetPos()+vf2d{sin(randomAngle),cos(randomAngle)}*randomDist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!m.B(A::IGNORE_DEFAULT_ANIMATIONS))m.PerformJumpAnimation();
|
|
|
|
} else {
|
|
|
|
m.SetState(State::NORMAL);//Revert state once we've finished moving towards target.
|
|
|
|
if(!m.B(A::IGNORE_DEFAULT_ANIMATIONS))m.PerformIdleAnimation();
|
|
|
|
}
|
|
|
|
if(jumpingEnabled){
|
|
|
|
m.F(A::LAST_JUMP_TIMER)=std::max(0.f,m.F(A::LAST_JUMP_TIMER)-fElapsedTime);
|
|
|
|
if(m.F(A::LAST_JUMP_TIMER)==0.f){
|
|
|
|
if(geom2d::line(m.pos,m.target).length()<=ConfigInt("MaxPlayerJumpEngageDistance")/100.f*24){
|
|
|
|
StartJumpTowardsPlayer(ConfigFloat("JumpDelayTime"),ConfigFloat("JumpRecoveryTime"),ConfigFloat("JumpMoveSpd"),ConfigFloat("JumpLockinTargetTime"));
|
|
|
|
}
|
|
|
|
m.F(A::LAST_JUMP_TIMER)=ConfigFloat("JumpTimer");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
case State::PATH_AROUND:{
|
|
|
|
m.PathAroundBehavior(fElapsedTime);
|
|
|
|
}break;
|
|
|
|
case 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.f,float(ConfigInt("JumpHeight")),1-jumpLandingTimerRatio));
|
|
|
|
}else{
|
|
|
|
m.SetZ(util::lerp(0.f,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<m.GetSizeMult()*25;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()){
|
|
|
|
int jumpDamage=0;
|
|
|
|
if(ConfigInt("JumpAttackDamage")>0)jumpDamage=ConfigInt("JumpAttackDamage");
|
|
|
|
if(game->GetPlayer()->Hurt(jumpDamage,m.OnUpperLevel(),m.GetZ())){
|
|
|
|
game->GetPlayer()->ProximityKnockback(m.GetPos(),float(ConfigInt("JumpKnockbackFactor")));
|
|
|
|
game->GetPlayer()->ApplyIframes(0.3f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
m.SetZ(0);
|
|
|
|
Landed();
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
case State::RECOVERY:{
|
|
|
|
m.F(A::RECOVERY_TIME)=std::max(0.f,m.F(A::RECOVERY_TIME)-fElapsedTime);
|
|
|
|
if(m.F(A::RECOVERY_TIME)==0.f){
|
|
|
|
m.SetState(State::NORMAL);
|
|
|
|
m.F(A::LAST_JUMP_TIMER)=ConfigFloat("JumpTimer");
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
default:{}
|
|
|
|
}
|
|
|
|
}
|