SpelunkyRemake / Level.cpp
Level.cpp
Raw
#include "pch.h"
#include "Level.h"
#include "GroundTile.h"
#include "WoodTile.h"
#include "Item.h"
#include <fstream>
#include <algorithm>

class IsOverlappingHitbox
{
public:
	IsOverlappingHitbox(const Rectf& hitbox)
		: m_Hitbox(hitbox)
	{

	}

	bool operator()(const Tile* tile)
	{
		return (utils::IsOverlapping(Rectf{ tile->GetVertices()[0].x, tile->GetVertices()[0].y, Tile::m_Size, Tile::m_Size }, m_Hitbox));
	}

private:
	const Rectf& m_Hitbox;
};

Level::Level(int world, int stage)
	: m_BackgroundTexture{ "Resources/Sprites/minebg.jpg" }
	, m_BorderBgTexture{ "Resources/Sprites/borderbg.png" }
	, m_BorderEdgesTexture{ "Resources/Sprites/border_edges.png" }
	, m_World{ world }
	, m_Stage{ stage }
	, m_Timer{ }
	, m_DoorTextures{ "Resources/Sprites/TU_exitdoors.png" }
	, m_EntryDoorPos{ }
	, m_ExitDoorPos{ }
	, m_EntryDoorSrc{ 256, 256, 256, 256 }
	, m_ExitDoorSrc{ 0, 256, 256, 256 }
{
	m_LevelVertices.push_back(Point2f{ 0, 0 });
	m_LevelVertices.push_back(Point2f{ Tile::m_Size * m_NrTilesX, 0 });
	m_LevelVertices.push_back(Point2f{ Tile::m_Size * m_NrTilesX, Tile::m_Size * m_NrTilesY });
	m_LevelVertices.push_back(Point2f{ 0, Tile::m_Size * m_NrTilesY });

	LoadLevel(m_World, m_Stage);

	m_Boundaries = Rectf{ Tile::m_Size * -m_BorderSize, Tile::m_Size * -m_BorderSize, Tile::m_Size * (m_NrTilesX + m_BorderSize), Tile::m_Size * (m_NrTilesY + m_BorderSize) };

	InitTiles();
}

void Level::InitTiles()
{
	for (Tile* tile : m_pTilesList)
	{
		tile->Init(m_pTiles, m_NrTilesX, m_NrTilesY);
	}
}

void Level::UpdateTilesBorders()
{
	for (Tile* tile : m_pTilesList)
	{
		tile->UpdateBorder(m_pTiles, m_NrTilesX, m_NrTilesY);
	}
}

void Level::Update(float elapsedSec)
{
	m_Timer += elapsedSec;
}

Level::Info Level::GetInfo() const
{
	return Info{ m_World, m_Stage, m_Timer };
}

unsigned int Level::GetTileIdx(int x, int y) const
{
	return (x + m_NrTilesX * y);
}

void Level::CreateTile(int x, int y, Tile::Type tileType, int itemType)
{
	switch (tileType)
	{
	case Tile::Type::Ground:
		m_pTiles[GetTileIdx(x, y)] = new GroundTile(x, y, itemType);
		break;
	case Tile::Type::Wood:
		m_pTiles[GetTileIdx(x, y)] = new WoodTile(x, y, itemType);
		break;
	}
	
	m_pTilesList.push_back(m_pTiles[GetTileIdx(x, y)]);
}

void Level::LoadLevel(int world, int stage)
{
	std::ifstream levelFile{ "Resources/Levels/" + std::to_string(world) + "-" + std::to_string(stage) + "/level.lvl" };

	int y{ m_NrTilesY - 1 };
	while (levelFile)
	{
		std::string str{};
		std::getline(levelFile, str);

		for (size_t x{}; x < str.length(); x++)
		{
			switch (str[x])
			{
			case '0':
				break;
			case '1':
				CreateTile(static_cast<int>(x), static_cast<int>(y), Tile::Type::Ground);
				break;
			case '2':
				CreateTile(static_cast<int>(x), static_cast<int>(y), Tile::Type::Wood);
				break;
			case 'g':
				CreateTile(static_cast<int>(x), static_cast<int>(y), Tile::Type::Ground, int(Item::Type::GOLD_CHUNK_SMALL));
				break;
			case 'G':
				CreateTile(static_cast<int>(x), static_cast<int>(y), Tile::Type::Ground, int(Item::Type::GOLD_CHUNK_BIG));
				break;
			case 'd':
				m_EntryDoorPos.x = (x * Tile::m_Size) - (m_EntryDoorSrc.width / 2 - Tile::m_Size / 2);
				m_EntryDoorPos.y = (y * Tile::m_Size) - (m_EntryDoorSrc.height / 2 - Tile::m_Size / 2);
				break;
			case 'D':
				m_ExitDoorPos.x = (x * Tile::m_Size) - (m_ExitDoorSrc.width / 2 - Tile::m_Size / 2);
				m_ExitDoorPos.y = (y * Tile::m_Size) - (m_ExitDoorSrc.height / 2 - Tile::m_Size / 2);
				break;
			case 'l':
				CreateLadder(static_cast<int>(x), static_cast<int>(y));
				break;
			case 'L':
				CreateLadder(static_cast<int>(x), static_cast<int>(y), WITH_PLATFORM);
				break;
			default:
				break;
			}
		}
		
		y--;
	}
}

void Level::CreateLadder(int x, int y, bool isPlatform)
{
	m_pLadders.push_back(new Ladder{ x, y, isPlatform });
}

Level::~Level()
{
	for (size_t i{}; i < m_pTilesList.size(); i++)
	{
		delete m_pTilesList[i];
		m_pTilesList[i] = nullptr;
	}

	for (size_t i{}; i < m_pLadders.size(); i++)
	{
		delete m_pLadders[i];
		m_pLadders[i] = nullptr;
	}
}

Rectf Level::GetBoundaries() const
{
	return m_Boundaries;
}

Point2f Level::GetStartPos(float playerWidth) const
{
	return Point2f{ (Tile::m_Size - playerWidth) / 2 + m_EntryDoorPos.x + m_EntryDoorSrc.width / 2 - Tile::m_Size / 2, m_EntryDoorPos.y + m_EntryDoorSrc.height / 2 -Tile::m_Size / 2 };
}

void Level::DrawBackground() const
{
	// background
	Point2f bgPos{ 0, 0 };
	for (int i{}; i < (m_NrTilesX / 4); i++)
	{
		for (int j{}; j < (m_NrTilesY / 4); j++)
		{
			m_BackgroundTexture.Draw(Point2f{ bgPos.x + m_BackgroundTexture.GetWidth() * i, bgPos.y + m_BackgroundTexture.GetHeight() * j });
		}
	}

	// doors
	m_DoorTextures.Draw(Point2f{ m_EntryDoorPos.x, m_EntryDoorPos.y }, m_EntryDoorSrc);
	m_DoorTextures.Draw(Point2f{ m_ExitDoorPos.x, m_ExitDoorPos.y }, m_ExitDoorSrc);

	// ladders
	for (Ladder* pLadder : m_pLadders)
	{
		pLadder->Draw();
	}

	// border
	Rectf srcRect{ 64, 0, 128, m_BorderBgTexture.GetHeight() / 2 };
	for (int i{}; i < m_NrTilesX / 2; i++)
	{
		// top
		m_BorderBgTexture.Draw(Point2f{ i * srcRect.width, Tile::m_Size * m_NrTilesY }, srcRect);

		// bottom
		m_BorderBgTexture.Draw(Point2f{ i * srcRect.width, Tile::m_Size * - m_BorderSize }, srcRect);
	}

	srcRect = Rectf{ 0, 0, m_BorderBgTexture.GetWidth(), m_BorderBgTexture.GetHeight() };
	for (int i{}; i < m_NrTilesY / 4; i++)
	{
		// left
		m_BorderBgTexture.Draw(Point2f{ Tile::m_Size * -m_BorderSize, i * m_BorderBgTexture.GetHeight() - (Tile::m_Size * m_BorderSize) });

		// right
		m_BorderBgTexture.Draw(Point2f{ Tile::m_Size * m_NrTilesX, i * m_BorderBgTexture.GetHeight() - (Tile::m_Size * m_BorderSize) }, srcRect);
	}
}

void Level::DrawVertices() const
{
	float vertexSize = 4;

	glLineWidth(1);
	glColor4f(.5f, .5f, .5f, 1);
	glBegin(GL_LINE_LOOP);
	for (Point2f vertex : m_LevelVertices)
	{
		glVertex2f(vertex.x, vertex.y);
	}
	glEnd();

	glColor4f(0, 0, 1, 1);
	for (Point2f vertex : m_LevelVertices)
	{
		utils::FillRect(Point2f{ vertex.x - vertexSize / 2, vertex.y - vertexSize / 2 }, vertexSize, vertexSize);
	}

	for (Tile* tile : m_pTilesList)
	{
		tile->DrawVertices();
	}
}

void Level::DrawForeground() const
{
	// tiles
	for (const Tile* tile : m_pTilesList)
	{
		tile->Draw();
	}
	for (const Tile* tile : m_pTilesList)
	{
		tile->DrawBorder();
		tile->DrawItem();
	}

	// border overlay
	Rectf srcTopBorder{ 128, 64, 64, 64 };
	Rectf srcBtmBorder{ 192, 64, 64, 64 };

	for (int i{}; i < m_NrTilesX; i++)
	{
		// top
		m_BorderEdgesTexture.Draw(Point2f{ i * Tile::m_Size, Tile::m_Size * m_NrTilesY - 14 }, srcTopBorder);

		// bottom
		m_BorderEdgesTexture.Draw(Point2f{ i * Tile::m_Size, -24 }, srcBtmBorder);
	}

	Rectf srcLeftBorder{ 64, 64, 64, 64 };
	Rectf srcRightBorder{ 0, 64, 64, 64 };
	float edgeMargin{ 16 };

	for (int i{}; i < m_NrTilesY; i++)
	{
		// left
		m_BorderEdgesTexture.Draw(Point2f{ -edgeMargin, i * Tile::m_Size }, srcLeftBorder);

		// right
		m_BorderEdgesTexture.Draw(Point2f{ Tile::m_Size * (m_NrTilesX - 1) + edgeMargin, i * Tile::m_Size }, srcRightBorder);
	}
}

Tile* Level::IsGrabbingLedge(Rectf& hitbox, Vector2f& velocity, int playerDir) const
{
	float grabDistance{ 4 };
	float halfWidth{ hitbox.width * 0.5f };

	Point2f topRight{ hitbox.left + hitbox.width, hitbox.bottom + hitbox.height };
	Point2f topLeft{ hitbox.left, hitbox.bottom + hitbox.height };

	utils::HitInfo hitInfo{};

	std::vector<Tile*> pNearbyTiles;
	/*
	auto IsOverlappingLambda = [hitbox](Tile* tile)
	{
		return (utils::IsOverlapping(Rectf{ tile->GetVertices()[0].x, tile->GetVertices()[0].y, Tile::m_Size, Tile::m_Size }, hitbox));
	};
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingLambda) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingLambda))
	*/
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if ((tile->GetY() < (m_NrTilesY - 1)) && (m_pTiles[GetTileIdx(tile->GetX(), tile->GetY() + 1)] == nullptr))
		{
			switch (playerDir)
			{
			case 0: //left
				if (utils::Raycast(tile->GetLedgeVertices(), Point2f{ topLeft.x + halfWidth , topLeft.y }, Point2f{ topLeft.x - grabDistance, topLeft.y }, hitInfo)) return tile;
				break;

			case 1: //right
				if (utils::Raycast(tile->GetLedgeVertices(), Point2f{ topRight.x - halfWidth , topRight.y }, Point2f{ topRight.x + grabDistance, topRight.y }, hitInfo)) return tile;
				break;

			default:
				break;
			}
		}
	}

	return nullptr;
}

void Level::HandleCollision(Rectf& hitbox, Vector2f& velocity) const
{
	float halfWidth{ hitbox.width * 0.5f };
	float halfHeight{ hitbox.height * 0.5f };
	float marginSides{ 12 };
	float marginTop{ 12 };

	Point2f btmLeft{ hitbox.left, hitbox.bottom };
	Point2f btmRight{ hitbox.left + hitbox.width, hitbox.bottom };
	Point2f topRight{ hitbox.left + hitbox.width, hitbox.bottom + hitbox.height };
	Point2f topLeft{ hitbox.left, hitbox.bottom + hitbox.height };

	utils::HitInfo hitInfo{};
	
	// Collision with level boundaries
	if ((hitbox.left <= 0) || (hitbox.bottom <= 0) || ((hitbox.left + hitbox.width) >= m_NrTilesX * Tile::m_Size) || ((hitbox.bottom + hitbox.height) >= m_NrTilesY * Tile::m_Size))
	{
		if (utils::Raycast(m_LevelVertices, Point2f{ topLeft.x + halfWidth, topLeft.y }, Point2f{ topLeft.x + halfWidth, topLeft.y - halfHeight }, hitInfo))
		{	// top
			hitbox.bottom = hitInfo.intersectPoint.y - hitbox.height;
			velocity.y = 0;
		}
		else if (utils::Raycast(m_LevelVertices, Point2f{ btmLeft.x + halfWidth, btmLeft.y }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight }, hitInfo))
		{	// bottom
			hitbox.bottom = hitInfo.intersectPoint.y;
			velocity.y = 0;
		}

		if (utils::Raycast(m_LevelVertices, Point2f{ topLeft.x , topLeft.y - halfHeight }, Point2f{ topLeft.x + halfWidth, topLeft.y - halfHeight }, hitInfo))
		{	//left
			hitbox.left = hitInfo.intersectPoint.x;
			velocity.x = 0;
		}
		else if (utils::Raycast(m_LevelVertices, Point2f{ topRight.x, topRight.y - halfHeight }, Point2f{ topRight.x - halfWidth, topRight.y - halfHeight }, hitInfo))
		{	// right
			hitbox.left = hitInfo.intersectPoint.x - hitbox.width;
			velocity.x = 0;
		}
	}

	// Collision with level tiles
	std::vector<Tile*> pNearbyTiles;
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if (utils::IsOverlapping(Rectf{ tile->GetVertices()[0].x, tile->GetVertices()[0].y, Tile::m_Size, Tile::m_Size }, hitbox))
		{
			if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + marginTop, btmLeft.y }, Point2f{ btmLeft.x + marginTop, btmLeft.y + halfHeight }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x - marginTop, btmRight.y }, Point2f{ btmRight.x - marginTop, btmRight.y + halfHeight }, hitInfo))
			{
				// bottom
				hitbox.bottom = hitInfo.intersectPoint.y;
				velocity.y = 0;
			}
			else if (utils::Raycast(tile->GetVertices(), Point2f{ topLeft.x + marginTop, topLeft.y }, Point2f{ topLeft.x + marginTop, topLeft.y - halfHeight }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topRight.x - marginTop, topRight.y }, Point2f{ topRight.x - marginTop, topRight.y - halfHeight }, hitInfo))
			{	// top
				hitbox.bottom = hitInfo.intersectPoint.y - hitbox.height;
				velocity.y = 0;
			}

			if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x, btmLeft.y + marginSides }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + marginSides }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topLeft.x, topLeft.y - marginSides }, Point2f{ topLeft.x + halfWidth, topLeft.y - marginSides }, hitInfo))
			{
				// btm left
				hitbox.left = hitInfo.intersectPoint.x;
				velocity.x = 0;
			}
			else if (utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x, btmRight.y + marginSides }, Point2f{ btmRight.x - halfWidth, btmRight.y + marginSides }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topRight.x, topRight.y - marginSides }, Point2f{ topRight.x - halfWidth, topRight.y - marginSides }, hitInfo))
			{
				// btm right
				hitbox.left = hitInfo.intersectPoint.x - hitbox.width;
				velocity.x = 0;
			}
		}
	}
}

void Level::BounceCollision(Rectf& hitbox, Vector2f& velocity) const
{
	float halfWidth{ hitbox.width * 0.5f };
	float halfHeight{ hitbox.height * 0.5f };
	float marginSides{ 8 };
	float marginTop{ 8 };

	Point2f btmLeft{ hitbox.left, hitbox.bottom };
	Point2f btmRight{ hitbox.left + hitbox.width, hitbox.bottom };
	Point2f topRight{ hitbox.left + hitbox.width, hitbox.bottom + hitbox.height };
	Point2f topLeft{ hitbox.left, hitbox.bottom + hitbox.height };

	utils::HitInfo hitInfo{};

	// Collision with level boundaries
	if ((hitbox.left <= 0) || (hitbox.bottom <= 0) || ((hitbox.left + hitbox.width) >= m_NrTilesX * Tile::m_Size) || ((hitbox.bottom + hitbox.height) >= m_NrTilesY * Tile::m_Size))
	{
		if (utils::Raycast(m_LevelVertices, Point2f{ topLeft.x + halfWidth, topLeft.y }, Point2f{ topLeft.x + halfWidth, topLeft.y - halfHeight }, hitInfo))
		{	// top
			hitbox.bottom = hitInfo.intersectPoint.y - hitbox.height;
			velocity.y = -velocity.y;
		}
		else if (utils::Raycast(m_LevelVertices, Point2f{ btmLeft.x + halfWidth, btmLeft.y }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight }, hitInfo))
		{	// bottom
			hitbox.bottom = hitInfo.intersectPoint.y;
			velocity.y = -velocity.y;
		}

		if (utils::Raycast(m_LevelVertices, Point2f{ topLeft.x , topLeft.y - halfHeight }, Point2f{ topLeft.x + halfWidth, topLeft.y - halfHeight }, hitInfo))
		{	//left
			hitbox.left = hitInfo.intersectPoint.x;
			velocity.x = -velocity.x;
		}
		else if (utils::Raycast(m_LevelVertices, Point2f{ topRight.x, topRight.y - halfHeight }, Point2f{ topRight.x - halfWidth, topRight.y - halfHeight }, hitInfo))
		{	// right
			hitbox.left = hitInfo.intersectPoint.x - hitbox.width;
			velocity.x = -velocity.x;
		}
	}

	// Collision with level tiles
	std::vector<Tile*> pNearbyTiles;
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if (utils::IsOverlapping(Rectf{ tile->GetVertices()[0].x, tile->GetVertices()[0].y, Tile::m_Size, Tile::m_Size }, hitbox))
		{
			if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + marginTop, btmLeft.y }, Point2f{ btmLeft.x + marginTop, btmLeft.y + halfHeight }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x - marginTop, btmRight.y }, Point2f{ btmRight.x - marginTop, btmRight.y + halfHeight }, hitInfo))
			{
				// bottom
				hitbox.bottom = hitInfo.intersectPoint.y;
				velocity.y = -velocity.y;
			}
			else if (utils::Raycast(tile->GetVertices(), Point2f{ topLeft.x + marginTop, topLeft.y }, Point2f{ topLeft.x + marginTop, topLeft.y - halfHeight }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topRight.x - marginTop, topRight.y }, Point2f{ topRight.x - marginTop, topRight.y - halfHeight }, hitInfo))
			{	// top
				hitbox.bottom = hitInfo.intersectPoint.y - hitbox.height;
				velocity.y = -velocity.y;
			}

			if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x, btmLeft.y + marginSides }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + marginSides }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topLeft.x, topLeft.y - marginSides }, Point2f{ topLeft.x + halfWidth, topLeft.y - marginSides }, hitInfo))
			{
				// btm left
				hitbox.left = hitInfo.intersectPoint.x;
				velocity.x = -velocity.x;
			}
			else if (utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x, btmRight.y + marginSides }, Point2f{ btmRight.x - halfWidth, btmRight.y + marginSides }, hitInfo) ||
				utils::Raycast(tile->GetVertices(), Point2f{ topRight.x, topRight.y - marginSides }, Point2f{ topRight.x - halfWidth, topRight.y - marginSides }, hitInfo))
			{
				// btm right
				hitbox.left = hitInfo.intersectPoint.x - hitbox.width;
				velocity.x = -velocity.x;
			}
		}
	}
}

bool Level::IsOverlappingLadder(const Rectf& hitbox, float& maxY, bool& isTopPiece) const
{
	for (Ladder* pLadder : m_pLadders)
	{
		if (utils::IsOverlapping(hitbox, pLadder->GetThinnerHitbox()))
		{
			std::vector<Ladder*>::const_iterator found = std::find_if(m_pLadders.begin(), m_pLadders.end(), [pLadder](Ladder* ladder)
			{
				return ((ladder->GetX() == pLadder->GetX()) && (ladder->GetY() == (pLadder->GetY() + 1)));
			});

			if (found == m_pLadders.end())
			{
				isTopPiece = true;
				maxY = pLadder->GetHitbox().bottom + pLadder->GetHitbox().height;
			}

			return true;
		}
	}

	return false;
}

bool Level::IsOverlappingLadder(const Rectf& hitbox, float& ladderX) const
{
	for (Ladder* pLadder : m_pLadders)
	{
		if (utils::IsOverlapping(hitbox, pLadder->GetThinnerHitbox()))
		{
			ladderX = pLadder->GetHitbox().left;
			return true;
		}
	}

	return false;
}

bool Level::IsNearlyFalling(const Rectf& hitbox, int playerDir) const
{
	float halfWidth{ hitbox.width / 2 };
	float halfHeight{ hitbox.height / 2 };
	float marginTop{ 8 };

	Point2f btmLeft{ hitbox.left, hitbox.bottom };
	Point2f btmRight{ hitbox.left + hitbox.width, hitbox.bottom };

	utils::HitInfo hitInfo{};

	if (utils::Raycast(m_LevelVertices, Point2f{ btmLeft.x + halfWidth, btmLeft.y }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight }, hitInfo))
	{
		return false;
	}

	std::vector<Tile*> pNearbyTiles;
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight }, Point2f{ btmLeft.x + halfWidth, btmLeft.y - 1 }, hitInfo))
		{
			return false;
		}
		else
		{
			switch (playerDir)
			{
			case 0: //left
				if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + marginTop, btmLeft.y + halfHeight }, Point2f{ btmLeft.x + marginTop, btmLeft.y - 1 }, hitInfo)) return false;
				break;
			case 1: //right
				if (utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x - marginTop, btmRight.y + halfHeight }, Point2f{ btmRight.x - marginTop, btmRight.y - 1 }, hitInfo)) return false;
				break;
			default:
				break;
			}
		}
	}

	return true;
}

bool Level::IsOnSingleTile(const Rectf& hitbox) const
{
	float halfWidth{ hitbox.width / 2 };
	float halfHeight{ hitbox.height / 2 };
	float marginTop{ 8 };

	Point2f btmLeft{ hitbox.left, hitbox.bottom };
	Point2f btmRight{ hitbox.left + hitbox.width, hitbox.bottom };

	utils::HitInfo hitInfo{};

	std::vector<Tile*> pNearbyTiles;
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + marginTop, btmLeft.y + halfHeight }, Point2f{ btmLeft.x + marginTop, btmLeft.y - 1 }, hitInfo) ||
			utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x - marginTop, btmRight.y + halfHeight }, Point2f{ btmRight.x - marginTop, btmRight.y - 1 }, hitInfo))
		{
			bool cantMoveRight{};
			bool cantMoveLeft{};

			if (tile->GetY() < m_NrTilesY - 1)
			{
				if (tile->GetX() < m_NrTilesX - 1)
				{
					if ((m_pTiles[GetTileIdx(tile->GetX() + 1, tile->GetY())] == nullptr) || (m_pTiles[GetTileIdx(tile->GetX() + 1, tile->GetY() + 1)] != nullptr))
					{
						cantMoveRight = true;
					}
				}

				if (tile->GetX() > 0)
				{
					if ((m_pTiles[GetTileIdx(tile->GetX() - 1, tile->GetY())] == nullptr) || (m_pTiles[GetTileIdx(tile->GetX() - 1, tile->GetY() + 1)] != nullptr))
					{
						cantMoveLeft = true;
					}
				}
			}

			//std::cout << "\rleftTile: " << noLeftTile << ", rightTile: " << noRightTile;
			//std::cout << "\rnoLeftTile: " << noLeftTile << ", noRightTile: " << noRightTile << ", tile: [" << tile->GetX() << ", " << tile->GetY() << "]";

			return (cantMoveLeft && cantMoveRight);
		}
	}

	return false;
}

bool Level::IsOnGround(const Rectf& hitbox) const
{
	float halfWidth{ hitbox.width / 2 };
	float halfHeight{ hitbox.height / 2 };
	float marginTop{ 8 };

	Point2f btmLeft{ hitbox.left, hitbox.bottom };
	Point2f btmRight{ hitbox.left + hitbox.width, hitbox.bottom };

	utils::HitInfo hitInfo{};
	
	if (utils::Raycast(m_LevelVertices, Point2f{ btmLeft.x + halfWidth, btmLeft.y }, Point2f{ btmLeft.x + halfWidth, btmLeft.y + halfHeight }, hitInfo))
	{
		return true;
	}

	std::vector<Tile*> pNearbyTiles;
	for (std::vector<Tile*>::const_iterator it{ std::find_if(m_pTilesList.begin(), m_pTilesList.end(), IsOverlappingHitbox(hitbox)) }; it != m_pTilesList.end(); it = std::find_if(++it, m_pTilesList.end(), IsOverlappingHitbox(hitbox)))
	{
		pNearbyTiles.push_back(*it);
	}

	for (Tile* tile : pNearbyTiles)
	{
		if (utils::Raycast(tile->GetVertices(), Point2f{ btmLeft.x + marginTop, btmLeft.y + halfHeight }, Point2f{ btmLeft.x + marginTop, btmLeft.y - 1 }, hitInfo) ||
			utils::Raycast(tile->GetVertices(), Point2f{ btmRight.x - marginTop, btmRight.y + halfHeight }, Point2f{ btmRight.x - marginTop, btmRight.y - 1 }, hitInfo))
		{
			return true;
		}
	}

	return false;
}