#include "Monster.h" #include "Player.h" #include "Crawler.h" #include "DamageNumber.h" #include "Bullet.h" #include "BulletTypes.h" #include "DEFINES.h" #include "safemap.h" #include "utils.h" #include "Key.h" #include "Menu.h" #include "GameState.h" INCLUDE_MONSTER_DATA INCLUDE_MONSTER_LIST INCLUDE_ANIMATION_DATA INCLUDE_SPAWNER_LIST INCLUDE_BULLET_LIST INCLUDE_DAMAGENUMBER_LIST INCLUDE_game INCLUDE_DATA float Player::GROUND_SLAM_SPIN_TIME=0.6f; InputGroup Player::KEY_ABILITY1; InputGroup Player::KEY_ABILITY2; InputGroup Player::KEY_ABILITY3; InputGroup Player::KEY_ABILITY4; InputGroup Player::KEY_DEFENSIVE; Player::Player() :lastReleasedMovementKey(DOWN),facingDirection(DOWN),state(State::NORMAL){ Initialize(); } Player::Player(Player*player) :pos(player->GetPos()),vel(player->GetVelocity()),iframe_time(player->iframe_time),lastReleasedMovementKey(DOWN), facingDirection(DOWN),state(State::NORMAL){ Initialize(); } void Player::Initialize(){ Player::GROUND_SLAM_SPIN_TIME="Warrior.Ability 2.SpinTime"_F; } bool Player::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); #pragma region lambdas auto NoTileCollisionExistsHere=[&](){return collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size;}; #pragma endregion if(NoTileCollisionExistsHere()){ 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}; #pragma region lambdas auto NoPlayerCollisionWithTile=[&](){return !geom2d::overlaps(newPos,collision);}; #pragma endregion collision.pos+=tilePos; if(NoPlayerCollisionWithTile()){ 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 Player::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); #pragma region lambdas auto NoTileCollisionExistsHere=[&](){return collisionRect.pos==game->NO_COLLISION.pos&&collisionRect.size==game->NO_COLLISION.size;}; #pragma endregion if(NoTileCollisionExistsHere()){ 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}; #pragma region lambdas auto NoPlayerCollisionWithTile=[&](){return !geom2d::overlaps(newPos,collision);}; #pragma endregion collision.pos+=tilePos; if(NoPlayerCollisionWithTile()){ 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; } void Player::SetZ(float z){ this->z=z; } bool Player::SetPos(vf2d pos){ bool resultX=SetX(pos.x); bool resultY=SetY(pos.y); if(resultY&&!resultX){ resultX=SetX(pos.x); } return resultX|resultY; } vf2d&Player::GetPos(){ return pos; } float Player::GetX(){ return pos.x; } float Player::GetY(){ return pos.y; } float Player::GetZ(){ return z; } int Player::GetHealth(){ return hp; } int Player::GetMaxHealth(){ return maxhp; } int Player::GetMana(){ return mana; } int Player::GetMaxMana() { return maxmana; } int Player::GetAttack(){ float mod_atk=atk; for(Buff&b:GetBuffs(BuffType::ATTACK_UP)){ mod_atk+=atk*b.intensity; } return int(mod_atk); } float Player::GetMoveSpdMult(){ float mod_moveSpd=moveSpd; for(Buff&b:GetBuffs(BuffType::SLOWDOWN)){ mod_moveSpd-=mod_moveSpd*b.intensity; } for(Buff&b:GetBuffs(BuffType::BLOCK_SLOWDOWN)){ mod_moveSpd-=mod_moveSpd*b.intensity; } return mod_moveSpd; } float Player::GetSizeMult(){ return size; } float Player::GetAttackRangeMult(){ return attack_range; } float Player::GetSpinAngle(){ return spin_angle; } State::State Player::GetState(){ return state; } void Player::Knockback(vf2d vel){ this->vel+=vel; } void Player::Update(float fElapsedTime){ Ability&rightClickAbility=GetRightClickAbility(), &ability=GetAbility1(), &ability2=GetAbility2(), &ability3=GetAbility3(), &ability4=GetAbility4(); attack_cooldown_timer=std::max(0.f,attack_cooldown_timer-fElapsedTime); iframe_time=std::max(0.f,iframe_time-fElapsedTime); notEnoughManaDisplay.second=std::max(0.f,notEnoughManaDisplay.second-fElapsedTime); notificationDisplay.second=std::max(0.f,notificationDisplay.second-fElapsedTime); lastHitTimer=std::max(0.f,lastHitTimer-fElapsedTime); blockTimer=std::max(0.f,blockTimer-fElapsedTime); manaTickTimer-=fElapsedTime; if(castInfo.castTimer>0){ castInfo.castTimer-=fElapsedTime; if(castInfo.castTimer<=0){ if(castPrepAbility->action(this,castInfo.castPos)){ castPrepAbility->cooldown=castPrepAbility->COOLDOWN_TIME; ConsumeMana(castPrepAbility->manaCost); } castInfo.castTimer=0; SetState(State::NORMAL); } } while(manaTickTimer<=0){ manaTickTimer+=0.2; RestoreMana(1); } 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; } } //Class-specific update events. OnUpdate(fElapsedTime); switch(state){ case State::SPIN:{ switch(facingDirection){ case UP:{ if(lastAnimationFlip==0){ lastAnimationFlip=0.03; facingDirection=DOWN; animation.ChangeState(internal_animState,"WARRIOR_WALK_S"); } }break; case DOWN:{ if(lastAnimationFlip==0){ lastAnimationFlip=0.03; facingDirection=UP; animation.ChangeState(internal_animState,"WARRIOR_WALK_N"); } }break; } if(facingDirection==RIGHT){ spin_angle+=spin_spd*fElapsedTime; } else { spin_angle-=spin_spd*fElapsedTime; } if(spin_attack_timer>0){ z=float("Warrior.Ability 2.SpinMaxHeight"_I)*sin(3.3*(GROUND_SLAM_SPIN_TIME-spin_attack_timer)/GROUND_SLAM_SPIN_TIME); spin_attack_timer=std::max(0.f,spin_attack_timer-fElapsedTime); } else { SetState(State::NORMAL); spin_angle=0; z=0; float numb=4; game->HurtEnemies(pos,"Warrior.Ability 2.Range"_F/100*12,GetAttack()*"Warrior.Ability 2.DamageMult"_F,OnUpperLevel(),0); game->AddEffect(std::make_unique(GetPos(),"Warrior.Ability 2.EffectLifetime"_F,"ground-slam-attack-front.png",upperLevel,"Warrior.Ability 2.Range"_F/300*1.33f,"Warrior.Ability 2.EffectFadetime"_F),std::make_unique(GetPos(),"Warrior.Ability 2.EffectLifetime"_F,"ground-slam-attack-back.png",upperLevel,"Warrior.Ability 2.Range"_F/300*1.33f,"Warrior.Ability 2.EffectFadetime"_F)); } if(lastAnimationFlip>0){ lastAnimationFlip=std::max(0.f,lastAnimationFlip-fElapsedTime); } animation.UpdateState(internal_animState,fElapsedTime); }break; case State::BLOCK:{ if(blockTimer<=0){ SetState(State::NORMAL); } }break; case State::SWING_SONIC_SWORD:{ if(ability3.COOLDOWN_TIME-ability3.cooldown>0.5){ SetState(State::NORMAL); switch(facingDirection){ case DOWN:{ UpdateAnimation("WARRIOR_IDLE_S"); }break; case RIGHT:{ UpdateAnimation("WARRIOR_IDLE_E"); }break; case LEFT:{ UpdateAnimation("WARRIOR_IDLE_W"); }break; case UP:{ UpdateAnimation("WARRIOR_IDLE_N"); }break; } } animation.UpdateState(internal_animState,fElapsedTime); }break; case State::TELEPORT:{ teleportAnimationTimer=std::max(0.f,teleportAnimationTimer-fElapsedTime); if(teleportAnimationTimer<=0){ SetPos(teleportTarget); SetState(State::NORMAL); } animation.UpdateState(internal_animState,fElapsedTime); }break; default:{ //Update animations normally. animation.UpdateState(internal_animState,fElapsedTime); } } rightClickAbility.cooldown-=fElapsedTime; ability.cooldown-=fElapsedTime; ability2.cooldown-=fElapsedTime; ability3.cooldown-=fElapsedTime; ability4.cooldown-=fElapsedTime; if(rightClickAbility.cooldown<0){ rightClickAbility.cooldown=0; } if(ability.cooldown<0){ ability.cooldown=0; } if(ability2.cooldown<0){ ability2.cooldown=0; } if(ability3.cooldown<0){ ability3.cooldown=0; } if(ability4.cooldown<0){ ability4.cooldown=0; } for(Monster&m:MONSTER_LIST){ if(!HasIframes()&&abs(m.GetZ()-GetZ())<=1&&OnUpperLevel()==m.OnUpperLevel()&&geom2d::overlaps(geom2d::circle(pos,12*size/2),geom2d::circle(m.GetPos(),12*m.GetSizeMult()/2))){ if(m.IsAlive()){ m.Collision(this); } geom2d::line line(pos,m.GetPos()); float dist = line.length(); if(dist<=0.001){ m.SetPos(m.GetPos()+vf2d{util::random(2)-1,util::random(2)-1}); }else{ m.SetPos(line.rpoint(dist*1.1)); } if(m.IsAlive()){ vel=line.vector().norm()*-128; } } } 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}){ float newX=pos.x+vel.x*fElapsedTime; float newY=pos.y+vel.y*fElapsedTime; SetX(newX); SetY(newY); } if(Menu::stack.empty()){ if(CanAct()&&attack_cooldown_timer==0&&Crawler::KEY_ATTACK.Held()){ AutoAttack(); } auto AllowedToCast=[&](Ability&ability){return !ability.precastInfo.precastTargetingRequired&&GetState()!=State::ANIMATION_LOCK;}; //If pressed is set to false, uses held instead. auto CheckAndPerformAbility=[&](Ability&ability,InputGroup key){ if(ability.name!="???"){ if(CanAct(ability)){ if(ability.cooldown==0&&GetMana()>=ability.manaCost){ if(key.Held()||key.Released()&&&ability==castPrepAbility&&GetState()==State::PREP_CAST){ if(GetState()==State::CASTING){ SetState(State::NORMAL); castInfo.castTimer=castInfo.castTotalTime=0; } if(AllowedToCast(ability)&&ability.action(this,{})){ ability.cooldown=ability.COOLDOWN_TIME; ConsumeMana(ability.manaCost); }else if(ability.precastInfo.precastTargetingRequired&&GetState()==State::NORMAL){ PrepareCast(ability); } } } else if(ability.cooldown==0&&GetMana()0){ SetZ(6*sin(PI/RETREAT_TIME*retreatTimer)); retreatTimer-=fElapsedTime; if(retreatTimer<=0){ SetVelocity({}); SetZ(0); SetState(State::NORMAL); } } if(ghostRemoveTimer>0){ ghostRemoveTimer-=fElapsedTime; if(ghostRemoveTimer<=0){ if(ghostPositions.size()>0){ ghostPositions.erase(ghostPositions.begin()); if(ghostPositions.size()>0){ ghostRemoveTimer=RETREAT_GHOST_FRAME_DELAY; } } } } if(ghostFrameTimer>0){ ghostFrameTimer-=fElapsedTime; if(ghostFrameTimer<=0&&GetState()==State::RETREAT){ ghostPositions.push_back(GetPos()+vf2d{0,-GetZ()}); ghostFrameTimer=RETREAT_GHOST_FRAME_DELAY; } } if(rapidFireTimer>0){ rapidFireTimer-=fElapsedTime; if(rapidFireTimer<=0){ if(remainingRapidFireShots>0){ remainingRapidFireShots--; geom2d::line pointTowardsCursor(GetPos(),game->GetWorldMousePos()); vf2d extendedLine=pointTowardsCursor.upoint(1.1); float angleToCursor=atan2(extendedLine.y-GetPos().y,extendedLine.x-GetPos().x); attack_cooldown_timer=ARROW_ATTACK_COOLDOWN; BULLET_LIST.push_back(std::make_unique(Arrow(GetPos(),extendedLine,vf2d{cos(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F,float(sin(angleToCursor)*"Ranger.Ability 1.ArrowSpd"_F-PI/8*"Ranger.Ability 1.ArrowSpd"_F)}+movementVelocity,12*"Ranger.Ability 1.ArrowRadius"_F/100,GetAttack()*"Ranger.Ability 1.DamageMult"_F,OnUpperLevel(),true))); SetAnimationBasedOnTargetingDirection(angleToCursor); rapidFireTimer=RAPID_FIRE_SHOOT_DELAY; }else{ SetState(State::NORMAL); } } } #pragma endregion } float Player::GetSwordSwingTimer(){ return swordSwingTimer; } void Player::SetSwordSwingTimer(float val){ swordSwingTimer=val; } void Player::SetState(State::State newState){ if(GetState()==State::BLOCK){ RemoveAllBuffs(BuffType::BLOCK_SLOWDOWN); } state=newState; } vf2d Player::GetVelocity(){ return vel; } bool Player::CanMove(){ return state!=State::CASTING&&state!=State::ANIMATION_LOCK; } bool Player::CanAct(){ Ability dummyAbility; return CanAct(dummyAbility); } bool Player::CanAct(Ability&ability){ return (ability.canCancelCast||state!=State::CASTING)&&state!=State::ANIMATION_LOCK&&GameState::STATE==GameState::states[States::GAME_RUN]; } bool Player::HasIframes(){ return iframe_time>0; } bool Player::Hurt(int damage,bool onUpperLevel,float z){ if(hp<=0||HasIframes()||OnUpperLevel()!=onUpperLevel||abs(GetZ()-z)>1) return false; if(GetState()==State::BLOCK)damage*=1-"Warrior.Right Click Ability.DamageReduction"_F; 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),true); DAMAGENUMBER_LIST.push_back(damageNumberPtr); } lastHitTimer=0.05; return true; } void Player::AddAnimation(std::string state){ animation.AddState(state,ANIMATION_DATA.at(state)); } void Player::UpdateAnimation(std::string animState,int specificClass){ if(specificClass==ANY||specificClass&GetClass()){ animation.ChangeState(internal_animState,animState); } } Animate2D::Frame Player::GetFrame(){ return animation.GetFrame(internal_animState); } void Player::SetLastReleasedMovementKey(Key k){ lastReleasedMovementKey=k; } Key Player::GetLastReleasedMovementKey(){ return lastReleasedMovementKey; } void Player::SetFacingDirection(Key direction){ facingDirection=direction; } Key Player::GetFacingDirection(){ return facingDirection; } void Player::Moved(){ for(MonsterSpawner&spawner:SPAWNER_LIST){ if(!spawner.SpawnTriggered()&&spawner.DoesUpperLevelSpawning()==OnUpperLevel()&&geom2d::contains(geom2d::rect{spawner.GetPos(),spawner.GetRange()},pos)){ spawner.SetTriggered(true); } } 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; } } } void Player::Spin(float duration,float spinSpd){ SetState(State::SPIN); spin_attack_timer=duration; spin_spd=spinSpd; spin_angle=0; } void Player::UpdateWalkingAnimation(Key direction){ std::string anim; switch(direction){ case UP:anim=GetWalkNAnimation();break; case RIGHT:anim=GetWalkEAnimation();break; case DOWN:anim=GetWalkSAnimation();break; case LEFT:anim=GetWalkWAnimation();break; } UpdateAnimation(anim); } void Player::UpdateIdleAnimation(Key direction){ std::string anim; switch(direction){ case UP:anim=GetIdleNAnimation();break; case RIGHT:anim=GetIdleEAnimation();break; case DOWN:anim=GetIdleSAnimation();break; case LEFT:anim=GetIdleWAnimation();break; } UpdateAnimation(anim); } void Player::AddBuff(BuffType type,float duration,float intensity){ buffList.push_back(Buff{type,duration,intensity}); } bool Player::OnUpperLevel(){ return upperLevel; } std::vectorPlayer::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; } void Player::RemoveBuff(BuffType buff){ for(auto it=buffList.begin();it!=buffList.end();++it){ Buff&b=*it; if(b.type==buff){ buffList.erase(it); return; } } } void Player::RemoveAllBuffs(BuffType buff){ std::erase_if(buffList,[&](Buff&b){return b.type==buff;}); } void Player::RemoveAllBuffs(){ buffList.clear(); } void Player::CastSpell(Ability&ability){ vf2d castPosition=game->GetWorldMousePos(); float distance=sqrt(pow(GetX()-game->GetWorldMousePos().x,2)+pow(GetY()-game->GetWorldMousePos().y,2)); if(distance>ability.precastInfo.range){//Clamp the distance. vf2d pointToCursor = {game->GetWorldMousePos().x-GetX(),game->GetWorldMousePos().y-GetY()}; pointToCursor=pointToCursor.norm()*ability.precastInfo.range; castPosition=GetPos()+pointToCursor; } castInfo={ability.name,ability.precastInfo.castTime,ability.precastInfo.castTime,castPosition}; SetState(State::CASTING); } CastInfo&Player::GetCastInfo(){ return castInfo; } bool Player::CanPathfindTo(vf2d pos,vf2d targetPos,float range){ if(targetPos.x<0||targetPos.y<0||targetPos.x>game->GetCurrentMap().width*game->GetCurrentMap().tilewidth||targetPos.y>game->GetCurrentMap().height*game->GetCurrentMap().tileheight)return false; std::vectorpathing=game->pathfinder.Solve_AStar(pos,targetPos,range,upperLevel); return pathing.size()>0&&pathing.size()0){ SetState(State::PREP_CAST); }else{ CastSpell(ability); } } void Player::SetVelocity(vf2d vel){ this->vel=vel; } void Player::SetAnimationBasedOnTargetingDirection(float targetDirection){ if(GetClass()==Class::RANGER){ if(targetDirection<=PI/4&&targetDirection>-PI/4){ UpdateAnimation("RANGER_SHOOT_E"); } else if(targetDirection>=3*PI/4||targetDirection<-3*PI/4){ UpdateAnimation("RANGER_SHOOT_W"); } else if(targetDirection<=3*PI/4&&targetDirection>PI/4){ UpdateAnimation("RANGER_SHOOT_S"); } else if(targetDirection>=-3*PI/4&&targetDirection<-PI/4){ UpdateAnimation("RANGER_SHOOT_N"); } } } void Player::SetIframes(float duration){ iframe_time=duration; } bool Player::Heal(int damage){ hp=std::clamp(hp+damage,0,maxhp); return true; } void Player::RestoreMana(int amt){ mana=std::clamp(mana+amt,0,maxmana); } void Player::ConsumeMana(int amt){ mana=std::clamp(mana-amt,0,maxmana); } void Player::SetSizeMult(float size){ this->size=size; } Item&Player::GetLoadoutItem(int slot){ if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+")."); return loadout[slot]; } void Player::SetLoadoutItem(int slot,std::string itemName){ if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+")."); if(Inventory::GetItemCount(itemName)>0){ GetLoadoutItem(slot)=Inventory::GetItem(itemName); GetLoadoutItem(slot).amt=std::min((uint32_t)"Player.Item Loadout Limit"_I,GetLoadoutItem(slot).Amt()); //We are only allowed to have up to 10 maximum of an item on a journey. }else{ ERR("Trying to set item "+itemName+" in Loadout slot "+std::to_string(slot)+" when said item does not exist in our inventory!"); } } void Player::UseLoadoutItem(int slot){ if(slot<0||slot>loadout.size()-1)ERR("Invalid inventory slot "+std::to_string(slot)+", please choose a slot in range (0-"+std::to_string(loadout.size()-1)+")."); if(GetLoadoutItem(slot).Amt()>0){ Inventory::UseItem(loadout[slot].Name()); GetLoadoutItem(slot).OnUseAction(); GetLoadoutItem(slot).amt--; } }