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.
AdventuresInLestoria/Crawler/Monster.cpp

384 lines
11 KiB

#include "Monster.h"
#include "DamageNumber.h"
#include "Crawler.h"
#include "Bullet.h"
#include "BulletTypes.h"
#include "DEFINES.h"
#include "safemap.h"
INCLUDE_ANIMATION_DATA
INCLUDE_MONSTER_DATA
INCLUDE_MONSTER_LIST
INCLUDE_DAMAGENUMBER_LIST
INCLUDE_game
INCLUDE_BULLET_LIST
INCLUDE_DATA
INCLUDE_STRATEGY_DATA
safemap<std::string,int>STRATEGY_DATA;
safemap<int,std::string>STRATEGY_ID_DATA;
std::map<int,Renderable*>MonsterData::imgs;
Monster::Monster(vf2d pos,MonsterData data,bool upperLevel):
pos(pos),hp(data.GetHealth()),maxhp(data.GetHealth()),atk(data.GetAttack()),moveSpd(data.GetMoveSpdMult()),size(data.GetSizeMult()),targetSize(data.GetSizeMult()),strategy(data.GetAIStrategy()),id(data.GetID()),upperLevel(upperLevel){
bool firstAnimation=true;
for(std::string&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(std::string state){
animation.ChangeState(internal_animState,state);
}
void Monster::PerformJumpAnimation(){
animation.ChangeState(internal_animState,MONSTER_DATA[id].GetJumpAnimation());
}
void Monster::PerformShootAnimation(){
animation.ChangeState(internal_animState,MONSTER_DATA[id].GetShootAnimation());
}
void Monster::PerformIdleAnimation(){
animation.ChangeState(internal_animState,MONSTER_DATA[id].GetIdleAnimation());
}
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()));
Moved();
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()));
Moved();
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()));
Moved();
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()));
Moved();
return true;
}
}
return false;
}
bool Monster::Update(float fElapsedTime){
lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime);
iframe_timer=std::max(0.f,iframe_timer-fElapsedTime);
if(size!=targetSize){
if(size>targetSize){
size=std::max(targetSize,size-Crawler::SIZE_CHANGE_SPEED*fElapsedTime);
}else{
size=std::min(targetSize,size+Crawler::SIZE_CHANGE_SPEED*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;
}
}
if(!HasIframes()){
for(Monster&m:MONSTER_LIST){
if(&m==this)continue;
if(!m.HasIframes()&&OnUpperLevel()==m.OnUpperLevel()&&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()&&game->GetPlayer()->OnUpperLevel()==OnUpperLevel()&&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(GetState()==State::NORMAL){
if(game->GetPlayer()->GetX()>pos.x){
facingDirection=RIGHT;
} else {
facingDirection=LEFT;
}
}
Monster::STRATEGY::RUN_STRATEGY(*this,fElapsedTime);
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(!IsAlive()){
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(GetZ()>0){
vf2d shadowScale=vf2d{8*GetSizeMult()/3.f,1}/std::max(1.f,GetZ()/24);
game->view.DrawDecal(GetPos()-vf2d{3,3}*shadowScale/2+vf2d{0,6*GetSizeMult()},game->GFX_Circle.Decal(),shadowScale,BLACK);
}
if(GetFacingDirection()==RIGHT){
game->view.DrawPartialDecal((GetPos()+vf2d{float(GetFrame().GetSourceRect().size.x),0}*GetSizeMult())-vf2d{12,12}*GetSizeMult()-vf2d{0,GetZ()},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()-vf2d{0,GetZ()},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[id].GetCollisionDmg()>0&&!hasHitPlayer){
hasHitPlayer=true;
p->Hurt(MONSTER_DATA[id].GetCollisionDmg(),OnUpperLevel());
}
Collision();
}
void Monster::Collision(Monster&m){
Collision();
}
void Monster::Collision(){
if(strategy==0&&GetState()==MOVE_TOWARDS){//The run towards strategy causes state to return to normal upon a collision.
SetState(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;
}
void Monster::Moved(){
ZoneData&zoneData=game->GetZoneData(game->GetCurrentLevel());
for(geom2d::rect<int>&upperLevelZone:zoneData["UpperZone"]){
if(geom2d::overlaps(upperLevelZone,pos)){
upperLevel=true;
}
}
for(geom2d::rect<int>&lowerLevelZone:zoneData["LowerZone"]){
if(geom2d::overlaps(lowerLevelZone,pos)){
upperLevel=false;
}
}
}
std::string Monster::GetDeathAnimationName(){
return MONSTER_DATA[id].GetDeathAnimation();
}
bool Monster::Hurt(int damage,bool onUpperLevel){
if(!IsAlive()||onUpperLevel!=OnUpperLevel()||HasIframes()) 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));
if(lastHitTimer>0){
damageNumberPtr.get()->damage+=int(mod_dmg);
damageNumberPtr.get()->pauseTime=0.4;
} else {
DAMAGENUMBER_LIST.push_back(std::make_shared<DamageNumber>(pos,int(mod_dmg)));
std::shared_ptr<DamageNumber>numb=*(DAMAGENUMBER_LIST.end()-1);
damageNumberPtr=numb;
}
lastHitTimer=0.05;
if(!IsAlive()){
animation.ChangeState(internal_animState,GetDeathAnimationName());
}
if(GetStrategy()=="Slime King"&&phase==5){
iframe_timer=iframeTimeUponHit;
}
return true;
}
bool Monster::IsAlive(){
return hp>0||!diesNormally;
}
vf2d&Monster::GetTargetPos(){
return target;
}
MonsterSpawner::MonsterSpawner(){}
MonsterSpawner::MonsterSpawner(vf2d pos,vf2d range,std::vector<std::pair<int,vf2d>>monsters,bool upperLevel):
pos(pos),range(range),monsters(monsters),upperLevel(upperLevel){
}
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<int,vf2d>&monsterInfo:monsters){
MONSTER_LIST.push_back(Monster(pos+monsterInfo.second,MONSTER_DATA[monsterInfo.first],DoesUpperLevelSpawning()));
}
}
}
bool MonsterSpawner::DoesUpperLevelSpawning(){
return upperLevel;
}
bool Monster::OnUpperLevel(){
return upperLevel;
}
void Monster::AddBuff(BuffType type,float duration,float intensity){
buffList.push_back(Buff{type,duration,intensity});
}
void Monster::StartPathfinding(float pathingTime){
SetState(PATH_AROUND);
path=game->pathfinder.Solve_AStar(pos,target,12,OnUpperLevel());
if(path.size()>0){
pathIndex=0;
//We gives this mob 5 seconds to figure out a path to the target.
targetAcquireTimer=pathingTime;
}
}
void Monster::PathAroundBehavior(float fElapsedTime){
if(path.size()>0){
//Move towards the new path.
geom2d::line moveTowardsLine=geom2d::line(pos,path[pathIndex]*24);
if(moveTowardsLine.length()>2){
SetPosition(pos+moveTowardsLine.vector().norm()*100*fElapsedTime*GetMoveSpdMult());
if(moveTowardsLine.vector().x>0){
facingDirection=RIGHT;
} else {
facingDirection=LEFT;
}
}else{
if(pathIndex+1>=path.size()){
//We have reached the end of the path!
targetAcquireTimer=0;
}else{
pathIndex++;
}
}
} else {
//We actually can't do anything so just quit.
targetAcquireTimer=0;
}
}
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;
}
State Monster::GetState(){
return state;
}
void Monster::SetState(State newState){
state=newState;
}
void Monster::InitializeStrategies(){
int readCounter=0;
while(DATA["MonsterStrategy"].HasProperty(std::to_string(readCounter))){
std::string strategyName=DATA["MonsterStrategy"][std::to_string(readCounter)]["Name"].GetString();
STRATEGY_DATA[strategyName]=readCounter;
STRATEGY_ID_DATA[readCounter]=strategyName;
readCounter++;
}
STRATEGY_DATA.SetInitialized();
STRATEGY_ID_DATA.SetInitialized();
}
bool Monster::HasIframes(){
return iframe_timer>0;
}
float Monster::GetZ(){
return z;
}
std::string Monster::GetStrategy(){
return STRATEGY_ID_DATA[strategy];
}
void Monster::SetSize(float newSize,bool immediate){
if(immediate){
size=targetSize=newSize;
}else{
targetSize=newSize;
}
}