|
|
|
#include "Monster.h"
|
|
|
|
#include "DamageNumber.h"
|
|
|
|
#include "Crawler.h"
|
|
|
|
#include "Bullet.h"
|
|
|
|
#include "BulletTypes.h"
|
|
|
|
#include "DEFINES.h"
|
|
|
|
|
|
|
|
INCLUDE_ANIMATION_DATA
|
|
|
|
INCLUDE_MONSTER_DATA
|
|
|
|
INCLUDE_MONSTER_LIST
|
|
|
|
INCLUDE_DAMAGENUMBER_LIST
|
|
|
|
INCLUDE_game
|
|
|
|
INCLUDE_BULLET_LIST
|
|
|
|
|
|
|
|
Monster::Monster(vf2d pos,MonsterData data):
|
|
|
|
pos(pos),hp(data.GetHealth()),maxhp(data.GetHealth()),atk(data.GetAttack()),moveSpd(data.GetMoveSpdMult()),size(data.GetSizeMult()),strategy(data.GetAIStrategy()),type(data.GetType()){
|
|
|
|
bool firstAnimation=true;
|
|
|
|
for(AnimationState&anim:data.GetAnimations()){
|
|
|
|
animation.AddState(anim,ANIMATION_DATA[anim]);
|
|
|
|
if(firstAnimation){
|
|
|
|
animation.ChangeState(internal_animState,anim);
|
|
|
|
firstAnimation=false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
randomFrameOffset=(rand()%1000)/1000.f;
|
|
|
|
}
|
|
|
|
vf2d&Monster::GetPos(){
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
int Monster::GetHealth(){
|
|
|
|
return hp;
|
|
|
|
}
|
|
|
|
int Monster::GetAttack(){
|
|
|
|
float mod_atk=atk;
|
|
|
|
for(Buff&b:GetBuffs(ATTACK_UP)){
|
|
|
|
mod_atk+=atk*b.intensity;
|
|
|
|
}
|
|
|
|
return int(mod_atk);
|
|
|
|
}
|
|
|
|
float Monster::GetMoveSpdMult(){
|
|
|
|
float mod_moveSpd=moveSpd;
|
|
|
|
for(Buff&b:GetBuffs(SLOWDOWN)){
|
|
|
|
mod_moveSpd-=moveSpd*b.intensity;
|
|
|
|
}
|
|
|
|
return mod_moveSpd;
|
|
|
|
}
|
|
|
|
float Monster::GetSizeMult(){
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
Animate2D::Frame Monster::GetFrame(){
|
|
|
|
return animation.GetFrame(internal_animState);
|
|
|
|
}
|
|
|
|
void Monster::UpdateAnimation(AnimationState state){
|
|
|
|
animation.ChangeState(internal_animState,state);
|
|
|
|
}
|
|
|
|
void Monster::PerformJumpAnimation(){
|
|
|
|
animation.ChangeState(internal_animState,MONSTER_DATA[type].GetJumpAnimation());
|
|
|
|
}
|
|
|
|
void Monster::PerformShootAnimation(){
|
|
|
|
animation.ChangeState(internal_animState,MONSTER_DATA[type].GetShootAnimation());
|
|
|
|
}
|
|
|
|
bool Monster::SetX(float x){
|
|
|
|
vf2d newPos={x,pos.y};
|
|
|
|
vi2d tilePos=vi2d(newPos/24)*24;
|
|
|
|
geom2d::rect<int>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
|
|
|
|
if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){
|
|
|
|
pos.x=std::clamp(x,12.f*GetSizeMult(),float(game->WORLD_SIZE.x*24-12*GetSizeMult()));
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
geom2d::rect<float>collision={collisionRect.pos,collisionRect.size};
|
|
|
|
collision.pos+=tilePos;
|
|
|
|
if(!geom2d::overlaps(newPos,collision)){
|
|
|
|
pos.x=std::clamp(x,12.f*GetSizeMult(),float(game->WORLD_SIZE.x*24-12*GetSizeMult()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bool Monster::SetY(float y){
|
|
|
|
vf2d newPos={pos.x,y};
|
|
|
|
vi2d tilePos=vi2d(newPos/24)*24;
|
|
|
|
geom2d::rect<int>collisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel);
|
|
|
|
if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){
|
|
|
|
pos.y=std::clamp(y,12.f*GetSizeMult(),float(game->WORLD_SIZE.y*24-12*GetSizeMult()));
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
geom2d::rect<float>collision={collisionRect.pos,collisionRect.size};
|
|
|
|
collision.pos+=tilePos;
|
|
|
|
if(!geom2d::overlaps(newPos,collision)){
|
|
|
|
pos.y=std::clamp(y,12.f*GetSizeMult(),float(game->WORLD_SIZE.y*24-12*GetSizeMult()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
bool Monster::Update(float fElapsedTime){
|
|
|
|
if(IsAlive()){
|
|
|
|
for(std::vector<Buff>::iterator it=buffList.begin();it!=buffList.end();++it){
|
|
|
|
Buff&b=*it;
|
|
|
|
b.duration-=fElapsedTime;
|
|
|
|
if(b.duration<=0){
|
|
|
|
it=buffList.erase(it);
|
|
|
|
if(it==buffList.end())break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(Monster&m:MONSTER_LIST){
|
|
|
|
if(&m==this)continue;
|
|
|
|
if(geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){
|
|
|
|
m.Collision(*this);
|
|
|
|
geom2d::line line(pos,m.GetPos());
|
|
|
|
float dist = line.length();
|
|
|
|
m.SetPosition(line.rpoint(dist*1.1));
|
|
|
|
if(m.IsAlive()){
|
|
|
|
vel=line.vector().norm()*-128;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!game->GetPlayer().HasIframes()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(game->GetPlayer().GetPos(),12*game->GetPlayer().GetSizeMult()/2))){
|
|
|
|
geom2d::line line(pos,game->GetPlayer().GetPos());
|
|
|
|
float dist = line.length();
|
|
|
|
SetPosition(line.rpoint(-0.1));
|
|
|
|
vel=line.vector().norm()*-128;
|
|
|
|
}
|
|
|
|
if(state==NORMAL){
|
|
|
|
if(game->GetPlayer().GetX()>pos.x){
|
|
|
|
facingDirection=RIGHT;
|
|
|
|
} else {
|
|
|
|
facingDirection=LEFT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch(strategy){
|
|
|
|
case RUN_TOWARDS:{
|
|
|
|
targetAcquireTimer=std::max(0.f,targetAcquireTimer-fElapsedTime);
|
|
|
|
if(targetAcquireTimer==0){
|
|
|
|
targetAcquireTimer=3;
|
|
|
|
target=geom2d::line(pos,game->GetPlayer().GetPos()).upoint(1.2);
|
|
|
|
state=MOVE_TOWARDS;
|
|
|
|
hasHitPlayer=false;
|
|
|
|
}
|
|
|
|
if(state==MOVE_TOWARDS&&geom2d::line(pos,target).length()>100*fElapsedTime*GetMoveSpdMult()){
|
|
|
|
SetPosition(pos+geom2d::line(pos,target).vector().norm()*100*fElapsedTime*GetMoveSpdMult());
|
|
|
|
PerformJumpAnimation();
|
|
|
|
} else {
|
|
|
|
if(state==MOVE_TOWARDS){
|
|
|
|
state=NORMAL;//Revert state once we've finished moving towards target.
|
|
|
|
UpdateAnimation(MONSTER_DATA[type].GetAnimations()[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
case SHOOT_AFAR:{
|
|
|
|
targetAcquireTimer=std::max(0.f,targetAcquireTimer-fElapsedTime);
|
|
|
|
attackCooldownTimer=std::max(0.f,attackCooldownTimer-fElapsedTime);
|
|
|
|
if(queueShotTimer>0){
|
|
|
|
queueShotTimer-=fElapsedTime;
|
|
|
|
if(queueShotTimer<0){
|
|
|
|
queueShotTimer=0;
|
|
|
|
{
|
|
|
|
BULLET_LIST.push_back(std::make_unique<Bullet>(Bullet(pos + vf2d{ 0,-4 }, geom2d::line(pos + vf2d{ 0,-4 }, game->GetPlayer().GetPos()).vector().norm() * 24 * 3.f, 2, GetAttack(),false, { 75 / 2,162 / 2,225 / 2 })));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
geom2d::line line(pos,game->GetPlayer().GetPos());
|
|
|
|
if(targetAcquireTimer==0&&queueShotTimer==0){
|
|
|
|
targetAcquireTimer=1;
|
|
|
|
if(line.length()<24*6){
|
|
|
|
target=line.upoint(-1.2);
|
|
|
|
if(canMove){
|
|
|
|
state=MOVE_AWAY;
|
|
|
|
} else {
|
|
|
|
state=NORMAL;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
if(line.length()>24*7){
|
|
|
|
target=line.upoint(1.2);
|
|
|
|
state=MOVE_TOWARDS;
|
|
|
|
} else {
|
|
|
|
state=NORMAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
canMove=true;
|
|
|
|
geom2d::line moveTowardsLine=geom2d::line(pos,target);
|
|
|
|
switch(state){
|
|
|
|
case MOVE_TOWARDS:{
|
|
|
|
if(moveTowardsLine.length()>1){
|
|
|
|
vf2d newPos=pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult();
|
|
|
|
canMove=SetX(newPos.x)&&SetY(newPos.y);
|
|
|
|
}
|
|
|
|
if(line.length()<=24*7){
|
|
|
|
state=NORMAL;
|
|
|
|
}
|
|
|
|
if(moveTowardsLine.vector().x>0){
|
|
|
|
facingDirection=RIGHT;
|
|
|
|
} else {
|
|
|
|
facingDirection=LEFT;
|
|
|
|
}
|
|
|
|
PerformJumpAnimation();
|
|
|
|
}break;
|
|
|
|
case MOVE_AWAY:{
|
|
|
|
if(moveTowardsLine.length()>1){
|
|
|
|
vf2d newPos=pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult();
|
|
|
|
canMove=SetX(newPos.x)&&SetY(newPos.y);
|
|
|
|
}
|
|
|
|
if(line.length()>=24*6){
|
|
|
|
state=NORMAL;
|
|
|
|
}
|
|
|
|
if(moveTowardsLine.vector().x>0){
|
|
|
|
facingDirection=RIGHT;
|
|
|
|
} else {
|
|
|
|
facingDirection=LEFT;
|
|
|
|
}
|
|
|
|
PerformJumpAnimation();
|
|
|
|
}break;
|
|
|
|
default:{
|
|
|
|
if(attackCooldownTimer==0){
|
|
|
|
attackCooldownTimer=1;
|
|
|
|
queueShotTimer=0.7;
|
|
|
|
PerformShootAnimation();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}break;
|
|
|
|
}
|
|
|
|
if(vel.x>0){
|
|
|
|
vel.x=std::max(0.f,vel.x-friction*fElapsedTime);
|
|
|
|
} else {
|
|
|
|
vel.x=std::min(0.f,vel.x+friction*fElapsedTime);
|
|
|
|
}
|
|
|
|
if(vel.y>0){
|
|
|
|
vel.y=std::max(0.f,vel.y-friction*fElapsedTime);
|
|
|
|
} else {
|
|
|
|
vel.y=std::min(0.f,vel.y+friction*fElapsedTime);
|
|
|
|
}
|
|
|
|
if(vel!=vf2d{0,0}){
|
|
|
|
SetX(pos.x+vel.x*fElapsedTime);
|
|
|
|
SetY(pos.y+vel.y*fElapsedTime);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(hp<=0){
|
|
|
|
deathTimer+=fElapsedTime;
|
|
|
|
if(deathTimer>3){
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
animation.UpdateState(internal_animState,randomFrameOffset+fElapsedTime);
|
|
|
|
randomFrameOffset=0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Key Monster::GetFacingDirection(){
|
|
|
|
return facingDirection;
|
|
|
|
}
|
|
|
|
void Monster::Draw(){
|
|
|
|
if(GetFacingDirection()==RIGHT){
|
|
|
|
game->view.DrawPartialDecal((GetPos()+vf2d{float(GetFrame().GetSourceRect().size.x),0}*GetSizeMult())-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*-1,GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE);
|
|
|
|
} else {
|
|
|
|
game->view.DrawPartialDecal(GetPos()-vf2d{12,12}*GetSizeMult(),GetFrame().GetSourceImage()->Decal(),GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult(),GetSizeMult()),GetBuffs(BuffType::SLOWDOWN).size()>0?Pixel{uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(255*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration))),uint8_t(128+127*abs(sin(1.4*GetBuffs(BuffType::SLOWDOWN)[0].duration)))}:WHITE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void Monster::Collision(Player&p){
|
|
|
|
if(MONSTER_DATA[type].GetCollisionDmg()>0&&!hasHitPlayer){
|
|
|
|
hasHitPlayer=true;
|
|
|
|
p.Hurt(MONSTER_DATA[type].GetCollisionDmg());
|
|
|
|
}
|
|
|
|
Collision();
|
|
|
|
}
|
|
|
|
void Monster::Collision(Monster&m){
|
|
|
|
Collision();
|
|
|
|
}
|
|
|
|
void Monster::Collision(){
|
|
|
|
if(strategy==RUN_TOWARDS&&state==MOVE_TOWARDS){
|
|
|
|
state=NORMAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void Monster::SetVelocity(vf2d vel){
|
|
|
|
this->vel=vel;
|
|
|
|
}
|
|
|
|
bool Monster::SetPosition(vf2d pos){
|
|
|
|
bool resultX=SetX(pos.x);
|
|
|
|
bool resultY=SetY(pos.y);
|
|
|
|
if(resultY&&!resultX){
|
|
|
|
resultX=SetX(pos.x);
|
|
|
|
}
|
|
|
|
return resultX|resultY;
|
|
|
|
}
|
|
|
|
AnimationState Monster::GetDeathAnimationName(){
|
|
|
|
return MONSTER_DATA[type].GetDeathAnimation();
|
|
|
|
}
|
|
|
|
bool Monster::Hurt(int damage){
|
|
|
|
if(hp<=0) return false;
|
|
|
|
float mod_dmg=damage;
|
|
|
|
for(Buff&b:GetBuffs(BuffType::DAMAGE_REDUCTION)){
|
|
|
|
mod_dmg-=damage*b.intensity;
|
|
|
|
}
|
|
|
|
hp=std::max(0,hp-int(mod_dmg));
|
|
|
|
DAMAGENUMBER_LIST.push_back(DamageNumber(pos,int(mod_dmg)));
|
|
|
|
if(hp<=0){
|
|
|
|
animation.ChangeState(internal_animState,GetDeathAnimationName());
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Monster::IsAlive(){
|
|
|
|
return hp>0;
|
|
|
|
}
|
|
|
|
vf2d&Monster::GetTargetPos(){
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
MonsterSpawner::MonsterSpawner(){}
|
|
|
|
MonsterSpawner::MonsterSpawner(vf2d pos,vf2d range,std::vector<std::pair<MonsterName,vf2d>>monsters):
|
|
|
|
pos(pos),range(range),monsters(monsters){
|
|
|
|
}
|
|
|
|
bool MonsterSpawner::SpawnTriggered(){
|
|
|
|
return triggered;
|
|
|
|
}
|
|
|
|
vf2d MonsterSpawner::GetRange(){
|
|
|
|
return range;
|
|
|
|
}
|
|
|
|
vf2d MonsterSpawner::GetPos(){
|
|
|
|
return pos;
|
|
|
|
}
|
|
|
|
void MonsterSpawner::SetTriggered(bool trigger,bool spawnMonsters){
|
|
|
|
triggered=trigger;
|
|
|
|
if(spawnMonsters){
|
|
|
|
for(std::pair<MonsterName,vf2d>&monsterInfo:monsters){
|
|
|
|
MONSTER_LIST.push_back(Monster(pos+monsterInfo.second,MONSTER_DATA[monsterInfo.first]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Monster::AddBuff(BuffType type,float duration,float intensity){
|
|
|
|
buffList.push_back(Buff{type,duration,intensity});
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Buff>Monster::GetBuffs(BuffType buff){
|
|
|
|
std::vector<Buff>filteredBuffs;
|
|
|
|
std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;});
|
|
|
|
return filteredBuffs;
|
|
|
|
}
|