 Player = Entity:New(x,y)


 function Player:New(x,y)
 	local o = Entity:New(x,y)

  o.type = "player"
 	-- physics
  o.moveSpeed = 1.3 -- gameworld pixels
  o.zeroSpeed = 0.01 -- gameworld pixels
  o.move_x = 0 -- gameworld pixels

  o.airFriction = 0.01 -- gameworld pixels
  o.groundFriction = 0.3 -- gameworld pixels

  o.jumpImpulse = 3.5 -- gameworld pixels

  o.coyoteAmount = 5 -- int
 	o.coyoteValue = 5 -- frames

  o.dashCooldownTime = 0.1 -- seconds
  o.dashCooldownTimer = 0 -- seconds

  -- dash values
  o.dashTimer = 0 -- seconds
  o.dashTime = 0.15 -- seconds
  o.dashDistance = 40 -- gameworld pixels
  o.dashSpeed = o.dashDistance / (o.dashTime*60) -- pixels
  o.dashCount = 1 -- int
  o.dashAmount = 10 -- int

  -- hook values
  o.hookAnchor = {
    x = nil,
    y = nil
  }

  o.boxCollision = {
  	from = {x = -8, y = -16}, --gameworld pixels
  	to = {x = 8, y = 0} -- gameworld pixels
  }

 	o.lightRange = 10 -- screen pixels

 	-- status
  o.isDashing = false
 	o.isJumping = false
  o.isHooked = false
 	o.isOnGround = true
  o.isOnLadder = false
 	o.canJump = true
 	o.canFall = true
 	o.canFriction = true
	o.maskType = animation.moth_mask

  o.anchorRespawn = {
    x = o.pos.x,
    y = o.pos.y
  }

	-- sprite
	o.target_offset = {x = 0, y = 0}
	o.body = Animation:New(animation.nancy.idle)
	o.mask = Animation:New(animation.moth_mask.idle)
  o:centerOffset(o.body)
  o:getBoundingBox(o.body,0,3,-1,-3)

  -- lights
	o.light = CreateLight(o.pos.x,o.pos.y,o.lightRange)

  table.insert(LoadedObjects.Entities,o)
  o.id = #LoadedObjects.Entities

 	setmetatable(o, self)
 	self.__index = self
 	return o
 end

function Player:Smart()
  self:LightAdjust(self.target_offset.x,self.target_offset.y)

  -- reset coyoteValue
  if self.isOnGround then
    self.coyoteValue = self.coyoteAmount
  elseif self.coyoteValue > 0 then
    self.coyoteValue = self.coyoteValue - 1
  end

  if self.dashTimer <= 0 then
    -- horizontal movement
    if Keybind:CheckDown(Keybind.move.left) then
      self.move_x = -self.moveSpeed
    elseif Keybind:CheckDown(Keybind.move.right) then
      self.move_x = self.moveSpeed
    end

    -- jump if on ground (coyotevalue)
    if Keybind:CheckDown(Keybind.move.jump) then
      if self.coyoteValue > 0 then
        self.vel.y = -self.jumpImpulse
        self.coyoteValue = 0
      end
    end
  end

  -- dash timer
  self.dashCooldownTimer = math.max(0,self.dashCooldownTimer - current_dt)

  -- try to dash
  if Keybind:CheckDown(Keybind.move.dash) then
    if self.dashCooldownTimer == 0
    and not self.isDashing
    and self.dashCount > 0 then
      self:Unhook()

      -- state player
      self.isDashing = true
      self.dashCount = self.dashCount - 1

      -- get dash direction
      local vertical = 0
      if Keybind:CheckDown(Keybind.move.down) then vertical = vertical + 1 end
      if Keybind:CheckDown(Keybind.move.up) then vertical = vertical - 1 end
      local horizontal = 0
      if Keybind:CheckDown(Keybind.move.right) then horizontal = horizontal + 1 end
      if Keybind:CheckDown(Keybind.move.left) then horizontal = horizontal - 1 end

      -- if no direction, then dash forward
      if horizontal == 0 and vertical == 0 then
        horizontal = self.sprite_flip.x
      end

      -- set dash values
      self.dashDirection = GetAngleFromVector(horizontal, vertical)
      self.dashTimer = self.dashTime
    end
  else
    -- not dashing!
    self.isDashing = false
  end

  if Keybind:CheckPressed(Keybind.move.hook) then
    if self.isHooked then
      self:Unhook()
    else
      local anchor = self:CheckNearest("hook_anchor",self.hookDistance)
      if anchor then
        self.isHooked = true
        self.hookDistance = anchor.hookDistance
        self.hookAnchor = {
          x = anchor.pos.x,
          y = anchor.pos.y
        }
      end
    end
  end
end

function Player:DoPhysics()
  if self.dashTimer <= 0 then
    if self.isOnGround then
      self.vel.x = self.vel.x * (1-self.groundFriction)
    else
      self.vel.x = self.vel.x * (1-self.airFriction)
    end

    self.vel.y = self.vel.y * (1-self.airFriction)

    if math.abs(self.vel.x) < self.zeroSpeed then self.vel.x = 0 end
  end

  -- reset state
  self.canFall = true
  self.isOnGround = false
  -- adjust timers
  self.dashTimer = self.dashTimer - current_dt

  -- DASH STATE
  if self.dashTimer > 0 then
    self.canFall = false
    -- dash particle
    local particle_data = {
      animation = self.body,
      sprite_tint = HEX2RGB("#fed100"),
      sprite_alpha = 0.5,
      sprite_flip = {
        x = self.sprite_flip.x,
        y = self.sprite_flip.y
      }
    }
    Particle:New(self.pos.x,self.pos.y,particle_data)
    self.dashCooldownTimer = self.dashCooldownTime
    -- dash movement
    self.vel.x = self.dashSpeed * math.cos(self.dashDirection)
    self.vel.y = self.dashSpeed * math.sin(self.dashDirection)
  end

  -- hook state
  if self.isHooked then
    local hook = Vector(self.pos.x, self.pos.y, self.hookAnchor.x, self.hookAnchor.y)
    if GetVectorValue(hook) > self.hookDistance then
      local particle_data = {
        animation = self.body,
        sprite_tint = HEX2RGB("#fed100"),
        sprite_alpha = 0.5,
        sprite_flip = {
          x = self.sprite_flip.x,
          y = self.sprite_flip.y
        }
      }
      Particle:New(self.pos.x,self.pos.y,particle_data)
      local hook_angle = GetAngleFromVector(hook[1],hook[2])-math.rad(180)
      if Keybind:CheckDown(Keybind.move.right) then
        hook_angle = hook_angle - math.rad(0.05)
        self.move_x = 0
      end
      if Keybind:CheckDown(Keybind.move.left) then
        hook_angle = hook_angle + math.rad(0.05)
        self.move_x = 0
      end
      local pos_x = self.hookAnchor.x + self.hookDistance * math.cos(hook_angle)
      local pos_y = self.hookAnchor.y + self.hookDistance * math.sin(hook_angle)
      self.vel.x = self.vel.x + pos_x - self.pos.x
      self.vel.y = self.vel.y + pos_y - self.pos.y
      self.pos.x = pos_x
      self.pos.y = pos_y
    end
  end


  if self.canFall then
    -- not in dash
    self.dashTimer = 0
    self.vel.y = self.vel.y + gravity
  end

  -- horizontal collision
  if not self:isCollidingAt(self.pos.x + self.vel.x + self.move_x, self.pos.y, LoadedObjects.Collisions) then
    self.pos.x = self.pos.x + self.vel.x + self.move_x
  else
    self.vel.x = 0
  end

  -- vertical collision
  if not self:isCollidingAt(self.pos.x, self.pos.y + self.vel.y, LoadedObjects.Collisions) then
    self.pos.y = self.pos.y + self.vel.y
  else
    if self.vel.y > 0 then
      self.isOnGround = true
      self.dashCount = self.dashAmount
    end
    self.vel.y = 0
  end

  -- if u collision w hazard, respawn
  if self:isCollidingAt(self.pos.x, self.pos.y, LoadedObjects.Hazards) then
    self:Respawn()
  end
end

function Player:Respawn()
  self.pos.x = self.anchorRespawn.x
  self.pos.y = self.anchorRespawn.y
end

function Player:HandleAnimation()
	-- flip sprite to look in the direction is moving
  if self.isHooked then
    if self.vel.x ~= 0 then
      self.sprite_flip.x = math.sign(self.vel.x)
    end
  elseif self.move_x ~= 0 then
    self.sprite_flip.x = math.sign(self.move_x)
  end

	-- animation priority
	if self.vel.y > 1.25 then
    self.body = self.body:ChangeTo(animation.nancy.fall)
  	self.mask = self.mask:ChangeTo(self.maskType.fall)
	elseif self.vel.y < 0 then
		self.body = self.body:ChangeTo(animation.nancy.jump)
		self.mask = self.mask:ChangeTo(self.maskType.jump)
	elseif self.vel.x + self.move_x ~= 0 then
		self.body = self.body:ChangeTo(animation.nancy.run)
		self.mask = self.mask:ChangeTo(self.maskType.run)
	else
		self.body = self.body:ChangeTo(animation.nancy.idle)
		self.mask = self.mask:ChangeTo(self.maskType.idle)
	end

	-- special case: idle animation gets slower by time
	if self.body.anim_path == animation.nancy.idle.path then
		if self.body.anim_speed < 0.5 then
      self.body.anim_speed = self.body.anim_speed + 0.001
    end
	end

  if self.isHooked then
    love.graphics.line(
      -Camera.pos.x + self.pos.x,
      -Camera.pos.y + self.pos.y,
      -Camera.pos.x + self.hookAnchor.x,
      -Camera.pos.y + self.hookAnchor.y
    )
  end

  self.body:Animate()
  self:Draw(self.body)

  if self.dashCount > 0 then
    self:Draw(self.mask)
  end
  self.move_x = 0
end

function Player:Unhook()
  self.isHooked = false
  self.hookAnchor = nil
end
