#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_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::rect<int>collisionRect=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::rect<float>collision={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::rect<int>collisionRect=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::rect<float>collision={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<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;
		}
	}
	//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<Effect>(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<Effect>(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()<ability.manaCost&&key.Pressed()){
						notEnoughManaDisplay={ability.name,1};
					}
				}
			}
		};
		CheckAndPerformAbility(GetAbility1(),Player::KEY_ABILITY1);
		CheckAndPerformAbility(GetAbility2(),Player::KEY_ABILITY2);
		CheckAndPerformAbility(GetAbility3(),Player::KEY_ABILITY3);
		CheckAndPerformAbility(GetAbility4(),Player::KEY_ABILITY4);
		CheckAndPerformAbility(GetRightClickAbility(),Player::KEY_DEFENSIVE);

		if(GetState()==State::PREP_CAST){
			#define CheckAbilityKeyReleasedAndCastSpell(ability,releaseState) \
				if(&ability==castPrepAbility&&releaseState){CastSpell(ability);}

			CheckAbilityKeyReleasedAndCastSpell(rightClickAbility,Player::KEY_DEFENSIVE.Released())
				else
			CheckAbilityKeyReleasedAndCastSpell(ability,Player::KEY_ABILITY1.Released())
				else
			CheckAbilityKeyReleasedAndCastSpell(ability2,Player::KEY_ABILITY2.Released())
				else
			CheckAbilityKeyReleasedAndCastSpell(ability3,Player::KEY_ABILITY3.Released())
				else
			CheckAbilityKeyReleasedAndCastSpell(ability4,Player::KEY_ABILITY4.Released())
		}

	}

	#pragma region Warrior
		switch(GetState()){
		case State::SWING_SWORD:{
				switch(GetFacingDirection()){
					case UP:{
						UpdateAnimation("WARRIOR_SWINGSWORD_N");
					}break;
					case DOWN:{
						UpdateAnimation("WARRIOR_SWINGSWORD_S");
					}break;
					case LEFT:{
						UpdateAnimation("WARRIOR_SWINGSWORD_W");
					}break;
					case RIGHT:{
						UpdateAnimation("WARRIOR_SWINGSWORD_E");
					}break;
				}
				SetSwordSwingTimer(GetSwordSwingTimer()-fElapsedTime);
				if(GetSwordSwingTimer()<=0){
					SetSwordSwingTimer(0);
					SetState(State::NORMAL); 
				}
			}break;
		}
	#pragma endregion

	#pragma region Ranger
		if(GetState()==State::SHOOT_ARROW){
			if(attack_cooldown_timer<=ARROW_ATTACK_COOLDOWN-0.3){
				SetState(State::NORMAL);
			}
		}
		if(retreatTimer>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>(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;
}

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<DamageNumber>(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<float>{spawner.GetPos(),spawner.GetRange()},pos)){
			spawner.SetTriggered(true);
		}
	}
	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;
		}
	}
}

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::vector<Buff>Player::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;
}

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::vector<vf2d>pathing=game->pathfinder.Solve_AStar(pos,targetPos,range,upperLevel);
	return pathing.size()>0&&pathing.size()<range;//We'll say 7 tiles or less is close enough to 650 range. Have a little bit of wiggle room.
}

void Player::PrepareCast(Ability&ability){
	castPrepAbility=&ability;
	if(ability.precastInfo.range>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);
}