#include "Monster.h" #include "DamageNumber.h" #include "Crawler.h" #include "Bullet.h" #include "BulletTypes.h" #include "DEFINES.h" #include "safemap.h" #include "MonsterStrategyHelpers.h" #include "utils.h" #include "MonsterAttribute.h" INCLUDE_ANIMATION_DATA INCLUDE_MONSTER_DATA INCLUDE_MONSTER_LIST INCLUDE_DAMAGENUMBER_LIST INCLUDE_game INCLUDE_BULLET_LIST INCLUDE_DATA INCLUDE_STRATEGY_DATA INCLUDE_GFX safemapSTRATEGY_DATA; safemapSTRATEGY_ID_DATA; std::mapMonsterData::imgs; Monster::Monster(vf2d pos,MonsterData data,bool upperLevel,bool bossMob): 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),isBoss(bossMob){ 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/game->GetCurrentMap().tilewidth)*game->GetCurrentMap().tilewidth; geom2d::rectcollisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel); if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){ pos.x=std::clamp(x,game->GetCurrentMap().tilewidth/2*GetSizeMult(),float(game->GetCurrentMap().width*game->GetCurrentMap().tilewidth-game->GetCurrentMap().tilewidth/2*GetSizeMult())); Moved(); return true; } else { geom2d::rectcollision={collisionRect.pos,collisionRect.size}; collision.pos+=tilePos; if(!geom2d::overlaps(geom2d::circle(newPos,12*GetSizeMult()),collision)){ pos.x=std::clamp(x,game->GetCurrentMap().tilewidth/2*GetSizeMult(),float(game->GetCurrentMap().width*game->GetCurrentMap().tilewidth-game->GetCurrentMap().tilewidth/2*GetSizeMult())); Moved(); return true; } } return false; } bool Monster::SetY(float y){ vf2d newPos={pos.x,y}; vi2d tilePos=vi2d(newPos/game->GetCurrentMap().tilewidth)*game->GetCurrentMap().tilewidth; geom2d::rectcollisionRect=game->GetTileCollision(game->GetCurrentLevel(),newPos,upperLevel); if(collisionRect.pos==vi2d{0,0}&&collisionRect.size==vi2d{1,1}){ pos.y=std::clamp(y,game->GetCurrentMap().tilewidth/2*GetSizeMult(),float(game->GetCurrentMap().height*game->GetCurrentMap().tilewidth-game->GetCurrentMap().tilewidth/2*GetSizeMult())); Moved(); return true; } else { geom2d::rectcollision={collisionRect.pos,collisionRect.size}; collision.pos+=tilePos; if(!geom2d::overlaps(geom2d::circle(newPos,game->GetCurrentMap().tilewidth/2*GetSizeMult()),collision)){ pos.y=std::clamp(y,game->GetCurrentMap().tilewidth/2*GetSizeMult(),float(game->GetCurrentMap().height*game->GetCurrentMap().tilewidth-game->GetCurrentMap().tilewidth/2*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::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()&&abs(m.GetZ()-GetZ())<=1&&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.SetPos(line.rpoint(dist*1.1)); if(m.IsAlive()){ vel=line.vector().norm()*-128; } } } if(!game->GetPlayer()->HasIframes()&&abs(game->GetPlayer()->GetZ()-GetZ())<=1&&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(); SetPos(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()},GFX["circle.png"].Decal(),shadowScale,BLACK); } game->view.DrawPartialRotatedDecal(GetPos()-vf2d{0,GetZ()},GetFrame().GetSourceImage()->Decal(),0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*(GetFacingDirection()==RIGHT?-1: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); } void Monster::DrawReflection(float drawRatioX,float multiplierX){ game->SetDecalMode(DecalMode::ADDITIVE); game->view.DrawPartialRotatedDecal(GetPos()+vf2d{drawRatioX*GetFrame().GetSourceRect().size.x,GetZ()+(GetFrame().GetSourceRect().size.y-16)*GetSizeMult()},GetFrame().GetSourceImage()->Decal(),0,GetFrame().GetSourceRect().size/2,GetFrame().GetSourceRect().pos,GetFrame().GetSourceRect().size,vf2d(GetSizeMult()*(GetFacingDirection()==RIGHT?-1:1)*multiplierX,GetSizeMult()*-1),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); game->SetDecalMode(DecalMode::NORMAL); } void Monster::Collision(Player*p){ if(MONSTER_DATA[id].GetCollisionDmg()>0&&!hasHitPlayer){ if(p->Hurt(MONSTER_DATA[id].GetCollisionDmg(),OnUpperLevel(),GetZ())){ hasHitPlayer=true; } } Collision(); } void Monster::Collision(Monster&m){ Collision(); } void Monster::Collision(){ if(strategy==0&&GetState()==State::MOVE_TOWARDS&&util::random(Monster::STRATEGY::_GetInt(*this,"BumpStopChance",strategy))<1){//The run towards strategy causes state to return to normal upon a collision. SetState(State::NORMAL); } } void Monster::SetVelocity(vf2d vel){ this->vel=vel; } bool Monster::SetPos(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&upperLevelZone:zoneData["UpperZone"]){ if(geom2d::overlaps(upperLevelZone,pos)){ upperLevel=true; } } for(geom2d::rect&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,float z){ if(!IsAlive()||onUpperLevel!=OnUpperLevel()||HasIframes()||abs(GetZ()-z)>1) return false; if(game->InBossEncounter()){ game->StartBossEncounter(); } 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 { damageNumberPtr=std::make_shared(pos,int(mod_dmg)); DAMAGENUMBER_LIST.push_back(damageNumberPtr); } lastHitTimer=0.05; if(!IsAlive()){ animation.ChangeState(internal_animState,GetDeathAnimationName()); if(isBoss){ game->ReduceBossEncounterMobCount(); } }else{ hp=std::max(1,hp); //Make sure it stays alive if it's supposed to be alive... } if(game->InBossEncounter()){ game->BossDamageDealt(int(mod_dmg)); } GetInt(Attribute::HITS_UNTIL_DEATH)=std::max(0,GetInt(Attribute::HITS_UNTIL_DEATH)-1); iframe_timer=GetFloat(Attribute::IFRAME_TIME_UPON_HIT); return true; } bool Monster::IsAlive(){ return hp>0||!diesNormally; } vf2d&Monster::GetTargetPos(){ return target; } MonsterSpawner::MonsterSpawner(){} MonsterSpawner::MonsterSpawner(vf2d pos,vf2d range,std::vector>monsters,bool upperLevel,std::string bossNameDisplay) :pos(pos),range(range),monsters(monsters),upperLevel(upperLevel),bossNameDisplay(bossNameDisplay){ } 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&monsterInfo:monsters){ game->SpawnMonster(pos+monsterInfo.second,&MONSTER_DATA[monsterInfo.first],DoesUpperLevelSpawning(),bossNameDisplay!=""); } if(bossNameDisplay!=""){ game->SetBossNameDisplay(bossNameDisplay); } } } 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(State::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]*game->GetCurrentMap().tilewidth); if(moveTowardsLine.length()>2){ SetPos(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::vectorMonster::GetBuffs(BuffType buff){ std::vectorfilteredBuffs; std::copy_if(buffList.begin(),buffList.end(),std::back_inserter(filteredBuffs),[buff](Buff&b){return b.type==buff;}); return filteredBuffs; } State::State Monster::GetState(){ return state; } void Monster::SetState(State::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; } } void Monster::SetZ(float z){ this->z=z; } void Monster::SetStrategyDrawFunction(std::functionfunc){ strategyDraw=func; }