SpelunkyRemake / Player.cpp
Player.cpp
Raw
#include "pch.h"
#include "Player.h"
#include "Game.h"
#include <algorithm>

int Player::m_Score{ 0 };

Player::Player(const Point2f startPos)
	: m_Hitbox{ startPos.x, startPos.y, 48, 56 }
	, m_HitBoxHeight{ 56 }
	, m_SpriteRect {0, 0, 80, 80}
	, m_ClimbSpeed{ 160.f }
	, m_CrawlSpeed{ 80.f }
	, m_WalkSpeed{ 240.0f }
	, m_RunSpeed { 480.0f }
	, m_MoveAcceleration{ 4000.f }
	, m_BrakeSpeed{ 4000.f }
	, m_BrakeSpeedHurt{ 400.f }
	, m_SpeedLimit { 200.f }
	, m_JumpSpeed{ 620.0f }
	, m_StandUpTimer { }
	, m_StandUpDelay { 0.1f }
	, m_LookTimer { }
	, m_LookDelay { 1.f }
	, m_JumpTimer{ }
	, m_MinJumpDuration{ 0.08f }
	, m_NormalJumpDuration{ 0.08f }
	, m_JumpOnEnemyDuration{ 0.24f }
	, m_Velocity{ }
	, m_GravityAcceleration{ 0, -981.0f * 2 }
	, m_ActionState{ ActionState::WALKING }
	, m_LastActionState{ ActionState::WALKING }
	, m_HurtTimer{ 0.0f }
	, m_HurtDuration{ 1.25f }
	, m_Power{ }
	, m_SpritesTexture { "Resources/Sprites/char_orange.png" }
	, m_ClipHeight{ 80.0f }
	, m_ClipWidth{ 80.0f }
	, m_AnimFirstFrameId{ 0 }
	, m_AnimLength{ 10 }
	, m_AnimLoop{ true }
	, m_NrFramesPerSec{ 20 }
	, m_AnimTime{ }
	, m_AnimFrame{ }
	, m_Direction { Direction::LEFT }
	, m_Controller { nullptr }
	, m_GrabbedTile{ nullptr }
	, m_LastGrabbedTile{ nullptr }
	, m_HitSFX{ "Resources/Sounds/hit.wav" }
	, m_JumpSFX{ "Resources/Sounds/jump.wav" }
	, m_LandSFX{ "Resources/Sounds/land.wav" }
	, m_GrabLedgeSFX{ "Resources/Sounds/grab.wav" }
	, m_GoldSmallSFX{ "Resources/Sounds/chime.wav" }
	, m_GoldBigSFX{ "Resources/Sounds/chime3.wav" }
	, m_GreenGem{ "Resources/Sounds/gem2.wav" }
	, m_BlueGem{ "Resources/Sounds/gem3.wav" }
	, m_RedGem{ "Resources/Sounds/gem4.wav" }
	, m_Lives{ 4 }
	, m_Bombs{ 5 }
	, m_Ropes{ 6 }
{
	for (int i{}; i < int(Buttons::LENGTH); i++)
	{
		m_HeldInputs[i] = false;
	}

	m_LandSFX.SetVolume(0.5f);
	m_GrabLedgeSFX.SetVolume(0.5f);
}

Player::Player(SDL_GameController* gameController, const Point2f startPos)
	: Player{ startPos }
{
	m_Controller = gameController;
}

Player::~Player()
{
	//Controller is closed(deleted) in Game.cpp destructor.
	m_Controller = nullptr;
}

void Player::SetPosition(const Point2f& pos)
{
	m_Hitbox.left = pos.x;
	m_Hitbox.bottom = pos.y;
}

std::vector<Point2f> Player::GetTopVertices() const
{
	std::vector<Point2f> topVertices{ Point2f{ m_Hitbox.left, m_Hitbox.bottom + m_Hitbox.height }, Point2f{ m_Hitbox.left + m_Hitbox.width, m_Hitbox.bottom + m_Hitbox.height } };
	return topVertices;
}

void Player::Draw() const
{
	if (Game::DrawDebug)
	{
		//draw hitbox
		utils::SetColor(Color4f{ 1, 0, 0, 0.5f });
		utils::FillRect(m_Hitbox);
	}

	Rectf srcRect{ (m_ClipWidth * (m_AnimFirstFrameId % 11)) + (m_ClipWidth * m_AnimFrame), m_ClipHeight + (m_ClipHeight * (m_AnimFirstFrameId / 11)), m_ClipWidth, m_ClipHeight };
	Rectf destRect{ -m_SpriteRect.width / 2, 0, m_SpriteRect.width, m_SpriteRect.height };

	glPushMatrix();
	glTranslatef(m_SpriteRect.left - destRect.left, m_SpriteRect.bottom, 0);

	if (m_Direction == Direction::LEFT)
	{
		glScalef(-1, 1, 1);
	}

	//draw player
	m_SpritesTexture.Draw(destRect, srcRect);

	glPopMatrix();
}

bool Player::GetInput(Buttons button, bool pressedOnce)
{
	bool result{ false };
	const Uint8* pStates = SDL_GetKeyboardState(nullptr);

	if (pressedOnce && m_HeldInputs[int(button)])
	{
		return false;
	}

	switch (button)
	{
	case Buttons::LEFT:
		if (pStates[SDL_SCANCODE_LEFT] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT)) result = true;
		break;
	case Buttons::RIGHT:
		if (pStates[SDL_SCANCODE_RIGHT] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT)) result = true;
		break;
	case Buttons::UP:
		if (pStates[SDL_SCANCODE_UP] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_DPAD_UP)) result = true;
		break;
	case Buttons::DOWN:
		if (pStates[SDL_SCANCODE_DOWN] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN)) result = true;
		break;
	case Buttons::A:
		if (pStates[SDL_SCANCODE_Z] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_A)) result = true;
		break;
	case Buttons::B:
		if (pStates[SDL_SCANCODE_S] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_B)) result = true;
		break;
	case Buttons::X:
		if (pStates[SDL_SCANCODE_X] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_X)) result = true;
		break;
	case Buttons::Y:
		if (pStates[SDL_SCANCODE_A] || SDL_GameControllerGetButton(m_Controller, SDL_CONTROLLER_BUTTON_Y)) result = true;
		break;
	case Buttons::RIGHT_TRIGGER:
		if (pStates[SDL_SCANCODE_LSHIFT] || (SDL_GameControllerGetAxis(m_Controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) > 5000)) result = true;
		break;
	default:
		break;
	}

	m_HeldInputs[int(button)] = result;
	return result;
}

Player::Info Player::GetInfo() const
{
	return Info{ m_Lives, m_Bombs, m_Ropes };
}

Player::ActionState Player::GetState() const
{
	return m_ActionState;
}

bool Player::HandlePickups(std::vector<Item*>& pItems)
{
	bool didPickup{ };
	Rectf& hitbox = m_Hitbox;

	auto IsOverlappingPickup = [hitbox](Item* item)
	{
		return (item->GetTypeHash() == typeid(Gold).hash_code() || item->GetTypeHash() == typeid(Diamond).hash_code())
			&& (utils::IsOverlapping(item->GetHitbox(), hitbox));
	};

	for (std::vector<Item*>::iterator it{ std::find_if(pItems.begin(), pItems.end(), IsOverlappingPickup) }; it != pItems.end(); it = std::find_if(++it, pItems.end(), IsOverlappingPickup))
	{
		if (dynamic_cast<Gold*>(*it) != nullptr)
		{
			m_Score += dynamic_cast<Gold*>(*it)->GetAmount();

			switch (dynamic_cast<Gold*>(*it)->GetType())
			{
			case Gold::Type::THREE_COINS:
			case Gold::Type::BIG_CHUNK:
				m_GoldBigSFX.Play(0);
				break;
			case Gold::Type::COIN:
			case Gold::Type::SMALL_CHUNK:
				m_GoldSmallSFX.Play(0);
				break;
			default:
				break;
			}

			delete *it;
			*it = nullptr;
		}
		else if (dynamic_cast<Diamond*>(*it) != nullptr)
		{
			m_Score += dynamic_cast<Diamond*>(*it)->GetAmount();

			switch (dynamic_cast<Diamond*>(*it)->GetType())
			{
			case Diamond::Type::GREEN:
				m_GreenGem.Play(0);
				break;
			case Diamond::Type::BLUE:
				m_BlueGem.Play(0);
				break;
			case Diamond::Type::RED:
				m_RedGem.Play(0);
				break;
			default:
				break;
			}

			delete *it;
			*it = nullptr;
		}

		didPickup = true;
	}

	return didPickup;
}



void Player::Hit(int damage, int direction)
{
	m_HitSFX.Play(0);

	bool(direction) ? m_Velocity = Vector2f{ 450.f, 200.f } : m_Velocity = Vector2f{ -450.f, 200.f };
	SetAction(ActionState::HURT);

	m_Lives -= damage;
}

bool Player::DidJumpOnEnemy(const std::vector<Point2f>& topVertices)
{
	if (m_Velocity.y < 0)
	{
		float halfHeight{ m_Hitbox.height * 0.5f };
		Point2f btmLeft{ m_Hitbox.left, m_Hitbox.bottom };
		float halfWidth{ m_Hitbox.width * 0.5f };

		utils::HitInfo hitInfo{};

		if (utils::Raycast(topVertices, Point2f{ btmLeft.x + halfWidth, btmLeft.y - halfHeight / 2 }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight / 2 }, hitInfo))
		{
			if (GetInput(Buttons::A))
			{
				m_Velocity.y = m_JumpSpeed * 1.5f;
			}
			else
			{
				m_Velocity.y = m_JumpSpeed;
			}

			m_MinJumpDuration = m_JumpOnEnemyDuration;
			SetAction(ActionState::JUMPING);
			return true;
		}
	}

	return false;
}

void Player::Update(float elapsedSec, const Level& level, std::vector<Item*>& pItems, Point2f& cameraOffset, float& cameraSmoothing)
{
	/*
	if (GetInput(Buttons::Y, PRESSED_ONCE))
	{
		//bool(m_Direction) ? m_Velocity = Vector2f{ 1000.f, 800.f } : m_Velocity = Vector2f{ -1000.f, 800.f };
		//SetAction(ActionState::HURT);
		SetAction(ActionState::LOST_BALANCE);
	}
	std::cout << "\rm_ActionState: " << int(m_ActionState);
	std::cout << "\r[ " << m_Hitbox.left << ", " << m_Hitbox.bottom << " ]";
	*/

	if (HandlePickups(pItems))
	{
		pItems.erase(remove_if(pItems.begin(), pItems.end(), [](Item* item)
		{
			return item == nullptr;
		}));
	}

	if ((m_ActionState != ActionState::LEDGE_GRAB) && (m_ActionState != ActionState::LADDER_IDLE))
	{
		if (!level.IsOnGround(m_Hitbox)) m_Velocity += elapsedSec * m_GravityAcceleration;

		m_Hitbox.bottom += m_Velocity.y * elapsedSec;
		level.HandleCollision(m_Hitbox, m_Velocity);
		m_Hitbox.left += m_Velocity.x * elapsedSec;

		level.HandleCollision(m_Hitbox, m_Velocity);
	}

	if (m_ActionState != ActionState::CRAWLING) GetInput(Buttons::RIGHT_TRIGGER) ? m_SpeedLimit = m_RunSpeed : m_SpeedLimit = m_WalkSpeed;

	cameraOffset = Point2f{ 0, 0 };
	cameraSmoothing = 6.f;

	switch (m_ActionState)
	{
	case ActionState::IDLE:
		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::WALKING);
			break;
		}
		else
		{
			if (GetInput(Buttons::DOWN))
			{
				SetAction(ActionState::CROUCHING);
				break;
			}
			else if (GetInput(Buttons::UP))
			{
				float ladderX{};
				if (level.IsOverlappingLadder(m_Hitbox, ladderX))
				{
					m_Hitbox.left = ladderX + 8;
					SetAction(ActionState::LADDER_CLIMBING);
					break;
				}
				else
				{
					SetAction(ActionState::STARING_UP);
					break;
				}
			}

			if (level.IsNearlyFalling(m_Hitbox, int(m_Direction)))
			{
				SetAction(ActionState::LOST_BALANCE);
				break;
			}
			
			if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
			{
				(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
			}
			else
			{
				m_Velocity.x = 0;
			}
		}

		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
		}
		break;

	case ActionState::LADDER_IDLE:
		if (GetInput(Buttons::A, PRESSED_ONCE))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT))
		{
			m_Direction = Direction::LEFT;
		}
		else if (GetInput(Buttons::RIGHT))
		{
			m_Direction = Direction::RIGHT;
		}

		if (GetInput(Buttons::DOWN))
		{
			SetAction(ActionState::LADDER_CLIMBING);
			break;
		}
		else if (GetInput(Buttons::UP))
		{
			SetAction(ActionState::LADDER_CLIMBING);
			break;
		}
		break;

	case ActionState::LADDER_CLIMBING:
		if (GetInput(Buttons::A, PRESSED_ONCE))
		{
			if (GetInput(Buttons::DOWN))
			{
				SetAction(ActionState::FALLING);
				break;
			}
			else
			{
				m_Velocity.y = m_JumpSpeed;
				SetAction(ActionState::JUMPING);
				break;
			}
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::LADDER_IDLE);
			break;
		}

		if (GetInput(Buttons::DOWN))
		{
			if (level.IsOnGround(m_Hitbox))
			{
				SetAction(ActionState::CROUCHING);
				break;
			}
			else
			{
				m_Velocity.y = -m_ClimbSpeed;
			}
		}
		else if (GetInput(Buttons::UP))
		{
			bool isTopPiece{};
			float maxY{};
			if (level.IsOverlappingLadder(m_Hitbox, maxY, isTopPiece))
			{
				m_Velocity.y = m_ClimbSpeed;
				if (isTopPiece)
				{
					if (m_Hitbox.bottom + m_Hitbox.height - 12 > maxY)
					{
						m_Velocity.y = 0;
					}
				}
			}
		}
		else
		{
			m_Velocity.y = 0;

			SetAction(ActionState::LADDER_IDLE);
			break;
		}
		break;

	case ActionState::LOST_BALANCE:
		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::WALKING);
			break;
		}
		else
		{
			if (GetInput(Buttons::DOWN))
			{
				SetAction(ActionState::CROUCHING);
				break;
			}
			else if (GetInput(Buttons::UP))
			{
				if (m_LookTimer >= m_LookDelay)
				{
					cameraSmoothing = 9.f;
					cameraOffset = Point2f{ 0, 216 };
				}
				m_LookTimer += elapsedSec;
			}
			else
			{
				m_LookTimer = 0;
			}

			if (!level.IsNearlyFalling(m_Hitbox, int(m_Direction)))
			{
				SetAction(ActionState::IDLE);
				break;
			}

			if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
			{
				(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
			}
			else
			{
				m_Velocity.x = 0;
			}
		}

		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
		}
		break;

	case ActionState::STANDING_UP:
		if (GetInput(Buttons::DOWN))
		{
			SetAction(ActionState::CROUCHING);
			break;
		}

		if (m_StandUpTimer >= m_StandUpDelay)
		{
			if (level.IsNearlyFalling(m_Hitbox, int(m_Direction)))
			{
				SetAction(ActionState::LOST_BALANCE);
				break;
			}
			else
			{
				SetAction(ActionState::IDLE);
				break;
			}
		}
		m_StandUpTimer += elapsedSec;

		break;

	case ActionState::STARING_BACK_DOWN:
		if (GetInput(Buttons::DOWN))
		{
			SetAction(ActionState::CROUCHING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::WALKING);
			break;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (m_LookTimer >= 0.2f)
		{
			SetAction(ActionState::IDLE);
			break;
		}
		m_LookTimer += elapsedSec;

		break;

	case ActionState::STARING_UP:
		if (!GetInput(Buttons::UP))
		{
			SetAction(ActionState::STARING_BACK_DOWN);
			break;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::CRAWLING);
			break;
		}
		else
		{
			if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
			{
				(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
			}
			else
			{
				m_Velocity.x = 0;
			}
		}

		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
		}

		if (m_LookTimer >= m_LookDelay)
		{
			cameraSmoothing = 9.f;
			cameraOffset = Point2f{ 0, 216 };
		}
		m_LookTimer += elapsedSec;

		break;

	case ActionState::CROUCHING:
		if (!GetInput(Buttons::DOWN))
		{
			SetAction(ActionState::STANDING_UP);
			break;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			SetAction(ActionState::CRAWLING);
			break;
		}
		else
		{
			if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
			{
				(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
			}
			else
			{
				m_Velocity.x = 0;
			}
		}

		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
		}

		if (m_LookTimer >= m_LookDelay)
		{
			cameraSmoothing = 9.f;
			cameraOffset = Point2f{ 0, -216 };
		}
		m_LookTimer += elapsedSec;

		break;

	case ActionState::JUMPING:
		m_JumpTimer += elapsedSec;

		if ((m_JumpTimer > m_MinJumpDuration) && !GetInput(Buttons::A))
		{
			SetAction(ActionState::FALLING);
			m_Velocity.y = 0;

			m_MinJumpDuration = m_NormalJumpDuration;
			break;
		}

		if (GetInput(Buttons::UP, PRESSED_ONCE) || GetInput(Buttons::DOWN))
		{
			float ladderX{};
			if (level.IsOverlappingLadder(m_Hitbox, ladderX))
			{
				m_Hitbox.left = ladderX + 8;
				SetAction(ActionState::LADDER_CLIMBING);
				break;
			}
		}

		if (GetInput(Buttons::LEFT))
		{
			(m_Velocity.x > -m_SpeedLimit) ? m_Velocity.x += -m_MoveAcceleration * elapsedSec : m_Velocity.x = -m_SpeedLimit;
			m_Direction = Direction::LEFT;
		}
		else if (GetInput(Buttons::RIGHT))
		{
			(m_Velocity.x < m_SpeedLimit) ? m_Velocity.x += m_MoveAcceleration * elapsedSec : m_Velocity.x = m_SpeedLimit;
			m_Direction = Direction::RIGHT;
		}
		else if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
		{
			(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
		}
		else
		{
			m_Velocity.x = 0;
		}

		if (m_Velocity.y <= 0)
		{
			SetAction(ActionState::FALLING);
			break;
		}
		break;

	case ActionState::FALLING:
		m_GrabbedTile = level.IsGrabbingLedge(m_Hitbox, m_Velocity, int(m_Direction));
		if (m_GrabbedTile != nullptr && m_GrabbedTile != m_LastGrabbedTile)
		{
			m_Velocity.y = 0;
			SetAction(ActionState::LEDGE_GRAB);
			break;
		}

		if (GetInput(Buttons::UP) || GetInput(Buttons::DOWN, PRESSED_ONCE))
		{
			float ladderX{};
			if (level.IsOverlappingLadder(m_Hitbox, ladderX))
			{
				m_Hitbox.left = ladderX + 8;
				SetAction(ActionState::LADDER_CLIMBING);
				break;
			}
		}

		if (GetInput(Buttons::LEFT))
		{
			(m_Velocity.x > -m_SpeedLimit) ? m_Velocity.x += -m_MoveAcceleration * elapsedSec : m_Velocity.x = -m_SpeedLimit;
			m_Direction = Direction::LEFT;
		}
		else if (GetInput(Buttons::RIGHT))
		{
			(m_Velocity.x < m_SpeedLimit) ? m_Velocity.x += m_MoveAcceleration * elapsedSec : m_Velocity.x = m_SpeedLimit;
			m_Direction = Direction::RIGHT;
		}
		else if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
		{
			(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
		}
		else
		{
			m_Velocity.x = 0;
		}

		if (level.IsOnGround(m_Hitbox))
		{
			m_LandSFX.Play(0);

			SetAction(ActionState::IDLE);
			break;
		}

		break;

	case ActionState::CRAWLING:
		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
			break;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			if (!GetInput(Buttons::DOWN))
			{
				SetAction(ActionState::WALKING);
				break;
			}

			if (GetInput(Buttons::LEFT))
			{
				if (m_Velocity.x > -m_CrawlSpeed) m_Velocity.x += -m_MoveAcceleration * elapsedSec;
				m_Direction = Direction::LEFT;
			}
			else if (GetInput(Buttons::RIGHT))
			{
				if (m_Velocity.x < m_CrawlSpeed) m_Velocity.x += m_MoveAcceleration * elapsedSec;
				m_Direction = Direction::RIGHT;
			}
		}
		else
		{
			if (std::fabs(m_Velocity.x) > m_BrakeSpeed * elapsedSec)
			{
				(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeed * elapsedSec : m_Velocity.x += m_BrakeSpeed * elapsedSec;
			}
			else
			{
				m_Velocity.x = 0;
			}

			if (!GetInput(Buttons::DOWN))
			{
				SetAction(ActionState::STANDING_UP);
				break;
			}
			else
			{
				SetAction(ActionState::CROUCHING);
				break;
			}
		}
		break;

	case ActionState::WALKING:
		GetInput(Buttons::RIGHT_TRIGGER) ? m_NrFramesPerSec = 30 : m_NrFramesPerSec = 20;

		if (m_Velocity.y < 0)
		{
			SetAction(ActionState::FALLING);
			break;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE) && level.IsOnGround(m_Hitbox))
		{
			m_Velocity.y = m_JumpSpeed;
			SetAction(ActionState::JUMPING);
			break;
		}

		if (GetInput(Buttons::LEFT) || GetInput(Buttons::RIGHT))
		{
			if (GetInput(Buttons::DOWN))
			{
				m_Velocity.x = 0;
				SetAction(ActionState::CRAWLING);
				break;
			}

			if (GetInput(Buttons::LEFT))
			{
				(m_Velocity.x > -m_SpeedLimit) ? m_Velocity.x += -m_MoveAcceleration * elapsedSec : m_Velocity.x = -m_SpeedLimit;
				m_Direction = Direction::LEFT;
			}
			else if (GetInput(Buttons::RIGHT))
			{
				(m_Velocity.x < m_SpeedLimit) ? m_Velocity.x += m_MoveAcceleration * elapsedSec : m_Velocity.x = m_SpeedLimit;
				m_Direction = Direction::RIGHT;
			}
		}
		else
		{
			SetAction(ActionState::IDLE);
			break;
		}
		break;

	case ActionState::HURT:
		
		if (std::fabs(m_Velocity.x) > m_BrakeSpeedHurt * elapsedSec)
		{
			(m_Velocity.x > 0) ? m_Velocity.x -= m_BrakeSpeedHurt * elapsedSec : m_Velocity.x += m_BrakeSpeedHurt * elapsedSec;
		}
		else
		{
			m_Velocity.x = 0;
			if (std::fabs(m_Velocity.y) < 1) m_HurtTimer += elapsedSec;
		}

		if (m_Lives > 0)
		{
			if (m_HurtTimer >= m_HurtDuration)
			{
				SetAction(ActionState::WALKING);
				break;
			}
		}

		break;

	case ActionState::LEDGE_GRAB:
		if (m_GrabbedTile == nullptr)
		{
			m_LastGrabbedTile = nullptr;

			SetAction(ActionState::FALLING);
			break;
		}

		if (GetInput(Buttons::DOWN))
		{
			if (m_LookTimer >= m_LookDelay)
			{
				cameraSmoothing = 9.f;
				cameraOffset = Point2f{ 0, -216 };
			}
			
			m_LookTimer += elapsedSec;
		}
		else
		{
			cameraSmoothing = 6.f;
			cameraOffset = Point2f{ 0, 0 };

			m_LookTimer = 0;
		}

		if (GetInput(Buttons::A, PRESSED_ONCE))
		{
			if (GetInput(Buttons::DOWN))
			{
				m_LastGrabbedTile = m_GrabbedTile;
				m_GrabbedTile = nullptr;

				SetAction(ActionState::FALLING);
			}
			else
			{
				m_Velocity.y = m_JumpSpeed;
				SetAction(ActionState::JUMPING);
			}
		}
		break;

	default:
		break;
	}

	m_AnimTime += elapsedSec;
	if (m_AnimTime >= 1.f / m_NrFramesPerSec)
	{
		m_AnimFrame++;
		if (m_AnimFrame >= m_AnimLength)
		{
			if (m_AnimLoop)
			{
				m_AnimFrame = 0;
			}
			else
			{
				m_AnimFrame = m_AnimLength;
			}
		}

		m_AnimTime = 0;
	}

	// Check if keys have been released
	UpdateHeldInputs();

	//update Sprite position to fit newly calculated hitbox position
	m_SpriteRect.left = m_Hitbox.left - 16;
	m_SpriteRect.bottom = m_Hitbox.bottom - 8;
}

void Player::UpdateHeldInputs()
{
	for (int i{}; i < int(Buttons::LENGTH); i++)
	{
		if (m_HeldInputs[i])
		{
			if (!GetInput(Buttons(i)))
			{
				m_HeldInputs[i] = false;
			}
		}
	}
}

void Player::SetAction(ActionState action)
{
	m_LastActionState = m_ActionState;
	m_ActionState = action;

	m_Hitbox.height = m_HitBoxHeight;

	m_AnimFrame = 0;
	m_NrFramesPerSec = 20;
	m_AnimLoop = true;

	switch (action)
	{
	case ActionState::LADDER_IDLE:
		m_AnimFirstFrameId = 66;
		m_AnimLength = 1;
		m_NrFramesPerSec = 1;
		break;

	case ActionState::LADDER_CLIMBING:
		m_Velocity.x = 0;
		m_Velocity.y = 0;

		m_AnimFirstFrameId = 66;
		m_AnimLength = 6;
		m_NrFramesPerSec = 15;
		break;

	case ActionState::LOST_BALANCE:
		m_LookTimer = 0;

		m_AnimFirstFrameId = 33;
		m_AnimLength = 8;
		m_NrFramesPerSec = 15;
		break;

	case ActionState::IDLE:
		m_AnimFirstFrameId = 0;
		m_AnimLength = 1;
		break;

	case ActionState::WALKING:
		m_AnimFirstFrameId = 1;
		m_AnimLength = 8;
		break;

	case ActionState::LEDGE_GRAB:
		m_GrabLedgeSFX.Play(0);

		m_LookTimer = 0;

		m_AnimLoop = false;
		m_AnimFirstFrameId = 41;
		m_AnimLength = 3;
		m_NrFramesPerSec = 15;
		break;

	case ActionState::CROUCHING:
		m_Hitbox.height = m_HitBoxHeight / 2;
		m_LookTimer = 0;

		if (m_LastActionState == ActionState::CRAWLING)
		{
			m_AnimLoop = false;
			m_AnimFirstFrameId = 13;
			m_AnimLength = 0;
		}
		else
		{
			m_AnimLoop = false;
			m_AnimFirstFrameId = 11;
			m_AnimLength = 2;
		}
		break;

	case ActionState::STANDING_UP:
		m_StandUpTimer = 0;

		m_AnimLoop = false;
		m_AnimFirstFrameId = 13;
		m_AnimLength = 2;
		break;

	case ActionState::STARING_BACK_DOWN:
		m_LookTimer = 0;

		m_AnimLoop = false;
		m_NrFramesPerSec = 15;
		m_AnimFirstFrameId = 91;
		m_AnimLength = 4;
		break;

	case ActionState::STARING_UP:
		m_LookTimer = 0;

		m_AnimLoop = false;
		m_NrFramesPerSec = 14;
		m_AnimFirstFrameId = 88;
		m_AnimLength = 3;
		break;

	case ActionState::HURT:
		m_HurtTimer = 0;

		m_AnimLoop = false;
		m_AnimFirstFrameId = 9;
		m_AnimLength = 0;
		break;

	case ActionState::FALLING:
		m_AnimLoop = false;
		m_AnimFirstFrameId = 102;
		m_AnimLength = 4;
		m_NrFramesPerSec = 40;
	break;

	case ActionState::JUMPING:
		m_JumpSFX.Play(0);

		m_LastGrabbedTile = nullptr;
		m_JumpTimer = 0;

		m_AnimLoop = false;
		m_AnimFirstFrameId = 99;
		m_AnimLength = 4;
		m_NrFramesPerSec = 40;
		break;

	case ActionState::CRAWLING:
		m_Hitbox.height = m_HitBoxHeight / 2;

		m_AnimFirstFrameId = 16;
		m_AnimLength = 7;
		break;

	default:
		break;
	}
}

void Player::SetController(SDL_GameController* controller)
{
	m_Controller = controller;
}

void Player::PowerUpHit()
{
	m_Power++;
	SetAction(ActionState::HURT);
}

Rectf& Player::GetHitbox()
{
	return m_Hitbox;
}

Vector2f Player::GetVelocity() const
{
	return m_Velocity;
}