InventoryGame / InventoryGame.cpp
InventoryGame.cpp
Raw
// Fedrau, Christian & Yomtov, Nir - 1DAE09

// precompiled header file
#include "pch.h"

#pragma region generalDirectives
// SDL libs
#pragma comment(lib, "sdl2.lib")
#pragma comment(lib, "SDL2main.lib")

// OpenGL libs
#pragma comment (lib,"opengl32.lib")
#pragma comment (lib,"Glu32.lib")

// SDL extension libs 
#pragma comment(lib, "SDL2_image.lib") // Library to load image files
#pragma comment(lib, "SDL2_ttf.lib") // Library to use fonts

// SDL and OpenGL Includes
#include "Lib\SDL2-2.0.10\include\SDL.h"
#include "Lib\SDL2-2.0.10\include\SDL_opengl.h"
#include <GL\GLU.h>

#include "Lib\SDL2_image-2.0.5\include\SDL_image.h"
#include "Lib\SDL2_ttf-2.0.15\include\SDL_ttf.h"
#pragma endregion generalDirectives

#include <iostream>
#include <string>
#include <ctime>
#include <chrono>

#include "structs.h"
#include "utils.h"

#pragma region windowInformation
const float g_WindowWidth{ 600.f };
const float g_WindowHeight{ 580.f };
const std::string g_WindowTitle{ "TeamGame - Fedrau, Christian & Yomtov, Nir - 1DAE09" };
bool g_IsVSyncOn{ true };
#pragma endregion windowInformation

#pragma region textureDeclarations
struct Texture
{
	GLuint id;
	float width;
	float height;
};

bool TextureFromFile(const std::string& path, Texture & texture);
bool TextureFromString(const std::string & text, TTF_Font *pFont, const Color4f & textColor, Texture & texture);
bool TextureFromString(const std::string & text, const std::string& fontPath, int ptSize, const Color4f & textColor, Texture & texture);
void TextureFromSurface(const SDL_Surface *pSurface, Texture & textureData);
void DrawTexture(const Texture & texture, const Point2f& bottomLeftVertex, const Rectf & sourceRect = {});
void DrawTexture(const Texture & texture, const Rectf & destinationRect, const Rectf & sourceRect = {});
void DeleteTexture(Texture & texture);
#pragma endregion textureDeclarations

struct Player;

const int g_PunchMinDamage{ 1 };
const int g_PunchMaxDamage{ 3 };

const float g_CellSize{ 56.f };

enum class ActionTypes { None, Attack, Steal, Trade, Talk, Count, Leave };
enum class BarTextures { Background, Red, Green, Blue, Text, Count };

enum class NPCTypes
{
	Scavenger,
	Trader,
	Guard,
	Rat,
	Wolf,
	Bear,

	Count,
	None,
	Player
};

enum class LootTypes { None, Weapon, Drink, Food, Ammo, Gold, Count };
enum class LootTextures { GLOCK17, USP, MACHETE, WATER, APPLE, GOLD, Count };
enum class Rarity { Normal, Rare, Legendary, Count };
enum class EventTypes { Town, Encounter, Scavenge, Count, None };
enum class EntityTypes { NPC, Item, Container, Location, Count, None };

//g_CurrentDay++;
//EnterEvent(g_ExplorationEvents[g_CurrentDay]);

// TODO think of another solution
// min included, max Excluded
int GetRandom(int min, int max);
// min and max included
NPCTypes GetRandom(NPCTypes min, NPCTypes max);

//void AddToUITextArray(UIText*& pUITextArray, int &uiTextArrayLength, UIText &newUIElement);

const int g_NumberOfEncounterPlaces{ 5 };
const int g_NumberOfScavengePlaces{ 5 };
const int g_NumberOfTowns{ 5 };
const int g_NPCNamesCount{ 11 };

//TODO aeiuo

const std::string g_EncounterPlaces[g_NumberOfEncounterPlaces]{ "Forest", "Field", "Old Military Base", "Abandoned Camp", "Destroyed Town" };
const std::string g_ScavengePlaces[g_NumberOfScavengePlaces]{ "Abandoned House", "Old Military Base", "Abandoned Camp", "Destroyed Town", "Devastated Factory" };
const std::string g_Towns[g_NumberOfTowns]{ "Dietzenbach", "Offenbach", "Reinheim", "Versmold", "Erfurt" };
const std::string g_NPCNames[g_NPCNamesCount]{ "Mitch", "Daniel", "Cthulhu", "Brian", "Matt" , "John", "Patrick", "Clint", "Jordan", "Domenik", "Nicklas" };

// NPC VALUES
const int g_GuardMinDefence{ 5 };
const int g_GuardMaxDefence{ 10 };
const int g_GuardMinHP{ 25 };
const int g_GuardMaxHP{ 35 };

const int g_TraderMinDefence{ 6 };
const int g_TraderMaxDefence{ 12 };
const int g_TraderMinHP{ 35 };
const int g_TraderMaxHP{ 45 };

const int g_ScavengerMinDefence{ 3 };
const int g_ScavengerMaxDefence{ 6 };
const int g_ScavengerMinHP{ 15 };
const int g_ScavengerMaxHP{ 25 };

const int g_RatMinDefence{ 0 };
const int g_RatMaxDefence{ 1 };
const int g_RatMinHP{ 5 };
const int g_RatMaxHP{ 15 };

const int g_WolfMinDefence{ 0 };
const int g_WolfMaxDefence{ 5 };
const int g_WolfMinHP{ 10 };
const int g_WolfMaxHP{ 15 };

const int g_BearMinDefence{ 5 };
const int g_BearMaxDefence{ 15 };
const int g_BearMinHP{ 20 };
const int g_BearMaxHP{ 25 };

struct Loot
{
	LootTypes type;
	std::string name;
	std::string description;
	Rarity rarity;
	LootTextures texture;

	int width;
	int height;

	bool isRotated;

	int x;
	int y;

	int v1;
	int v2;

	int value;

	Loot() : type{ LootTypes::None }, isRotated{ }, x{ }, y{ }, v1{ }, v2{ }, value{ }
	{
		
	}

	Loot(LootTextures loot) : type{ LootTypes::None }, texture{ loot }, isRotated{ }, x{ }, y{ }, v1{ }, v2{ }, value{ }
	{
		switch (loot)
		{
		case LootTextures::GLOCK17:
			type = LootTypes::Weapon;
			rarity = Rarity::Normal;
			name = "Glock 17";
			description = "\"Reliable pistol\nused by police\nforces before\nthe war.\"";
			width = 3;
			height = 2;
			v1 = GetRandom(10, 16);
			v2 = GetRandom(15, 31);
			value = 200;
			break;

		case LootTextures::USP:
			type = LootTypes::Weapon;
			rarity = Rarity::Normal;
			name = "USP";
			description = "\"Reliable pistol\nused by police\nforces before\nthe war.\"";
			width = 3;
			height = 2;
			v1 = GetRandom(15, 21);
			v2 = GetRandom(20, 41);
			value = 250;
			break;

		case LootTextures::MACHETE:
			type = LootTypes::Weapon;
			name = "Machete";
			description = "\"A trusty weapon.\"";
			width = 1;
			height = 3;
			value = 100;
			v1 = GetRandom(6, 10);
			v2 = GetRandom(11, 16);
			Rotate();
			break;

		case LootTextures::WATER:
			type = LootTypes::Drink;
			name = "Water Bottle";
			description = "\"Water. You\ndrink it!\"";
			width = 1;
			height = 2;
			value = 80;
			v1 = GetRandom(5, 16);
			break;

		case LootTextures::APPLE:
			type = LootTypes::Food;
			name = "An apple.";
			description = "\"Apple. You\nteat it!\"";
			width = 1;
			height = 1;
			value = 40;
			v1 = GetRandom(5, 16);
			break;

		case LootTextures::GOLD:
			type = LootTypes::Gold;
			name = "Gold";
			description = "\"Gold, it's money!\"";
			width = 1;
			height = 1;
			value = 1;
			break;
		}
	}

	Point2f GetCenter(const Point2f& point) const
	{
		float xMargin{}, yMargin{};

		if (width % 2 != 0)
		{
			xMargin += (g_CellSize / 2) + g_CellSize * ((width - 1) / 2);
		}
		else
		{
			xMargin += g_CellSize * (width / 2);
		}

		if (height % 2 != 0)
		{
			yMargin += (g_CellSize / 2) + g_CellSize * ((height - 1) / 2);
		}
		else
		{
			yMargin += g_CellSize * (height / 2);
		}

		return Point2f{ point.x + xMargin, point.y + yMargin };
	}

	void Rotate()
	{
		if (height * width > 1)
		{
			isRotated = !isRotated;

			int temp{ width };
			width = height;
			height = temp;
		}
		else
		{
			std::cout << "Warning: can't rotate a 1x1 item..\n";
		}
		//std::cout << "isRotated: " << isRotated << ", w: " << width << " , h: " << height << '\n';
	}
};

struct Inventory
{
	int count;

	Point2f pos{};

	int cols;
	int rows;

	int equippedId;

	Loot* pItems;
	int* pGrid;

	bool isVisible;

	int hoveredX;
	int hoveredY;

	Rectf GetCloseButtonRect() const
	{
		Rectf rect{ Rectf{ pos.x + Width() - 58, pos.y + Height(), 60, 24 } };

		return rect;
	}

	bool HasEquipped() const
	{
		if (equippedId > -1)
		{
			return true;
		}

		return false;
	}

	int Size() const
	{
		return cols * rows;
	}

	float Height() const
	{
		return rows * g_CellSize;
	}

	float Width() const
	{
		return cols * g_CellSize;
	}

	bool InBounds(Loot item, int xPos, int yPos) const
	{
		if (xPos + item.width <= cols && yPos + item.height <= rows)
		{
			return true;
		}

		return false;
	}

	bool GetBestWeapon(Loot*& pItem)
	{
		pItem = nullptr;

		for (int i = 0; i < count; i++)
		{
			if (pItems[i].type == LootTypes::Weapon)
			{
				if (pItem != nullptr)
				{
					float itemDamage { (pItems->v1 + pItems->v2) / 2.f };
					float newItemDamage{ (pItems->v1 + pItems->v2) / 2.f };

					if (newItemDamage > itemDamage)
					{
						pItem = &pItems[i];
					}
				}
				else
				{
					pItem = &pItems[i];
				}
			}
		}

		if (pItem != nullptr)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	void Equip(Loot* item)
	{
		if (item != nullptr)
		{
			if (item->type == LootTypes::Weapon)
			{
				for (int i = 0; i < count; i++)
				{
					if (&pItems[i] == item)
					{
						equippedId = i;
						return;
					}
				}

				//std::cout << "Warning: tried to equip an item from another inventory!\n";
			}
			else
			{
				std::cout << "Warning: tried to equip an item that's not a weapon!\n";
			}
		}
		else
		{
			std::cout << "Warning: tried to equip non existent item!\n";
		}
	}

	void Unequip()
	{
		equippedId = -1;
	}

	void Remove(Loot* pItem)
	{
		int id{ -1 };

		for (int i = 0; i < Size(); i++)
		{
			if (id == -1)
			{
				if (&pItems[i] == pItem)
				{
					id = i;

					if (id == equippedId)
					{
						Unequip();
					}

					pItems[i].type = LootTypes::None;
					for (int j = 0; j < Size(); j++)
					{
						if (pGrid[j] != -1)
						{
							if (pGrid[j] == id)
							{
								pGrid[j] = -1;
							}
							else if (pGrid[j] > id)
							{
								pGrid[j] -= 1;
							}
						}
					}
				}
			}
			else if (i > id)
			{
				if (i == equippedId)
				{
					equippedId--;
				}

				pItems[i - 1] = pItems[i];
			}
		}

		if (id != -1)
		{
			pItems[Size() - 1].type = LootTypes::None;
			count--;
		}
		else
		{
			std::cout << "Warning: Tried to remove item from inventory that doesn't contain it!\n";
		}
	}

	Inventory() : Inventory(1, 1)
	{

	}

	Inventory(int c, int r) : equippedId{ -1 }, count{}, cols{ c }, rows{ r }
	{
		pItems = new Loot[Size()]{};
		pGrid = new int[Size()]{};

		for (int i = 0; i < Size(); i++)
		{
			pGrid[i] = -1;
		}
	}

	~Inventory()
	{
		//delete[] pItems;
		//pItems = nullptr;

		//delete[] pGrid;
		//pGrid = nullptr;
	}

	bool Add(Loot item, int x, int y, bool equipAfterAdding = false)
	{
		if (count < Size())
		{
			if (InBounds(item, x, y))
			{
				count++;
				int id{ count - 1 };

				pItems[id] = item;
				pItems[id].x = x;
				pItems[id].y = y;

				for (int r = y; r < y + pItems[id].height; r++)
				{
					for (int c = x; c < x + pItems[id].width; c++)
					{
						pGrid[utils::GetIndex(r, c, cols)] = id;
					}
				}

				if (equipAfterAdding)
				{
					equippedId = id;
				}

				return true;
			}
			else
			{
				std::cout << "Warning: tried to add item where part of it is out of bounds!\n";
			}
		}
		else
		{
			std::cout << "Warning: tried to add item to inventory but inventory is full!\n";
		}

		return false;
	}

	void DebugPrint()
	{
		for (int i = 0; i < Size(); i++)
		{
			if (pItems[i].type != LootTypes::None)
				std::cout << i << ", " << pItems[i].name << '\n';
		}
		std::cout << '\n';

		for (int r = 0; r < rows; r++)
		{
			for (int c = 0; c < cols; c++)
			{
				std::cout << pGrid[utils::GetIndex(r, c, cols)] << " ";
			}
			std::cout << '\n';
		}
		std::cout << '\n';
	}
};

struct NPC
{
	std::string name;
	NPCTypes type;

	int nrOfHostileTowards;
	NPC** pHostileTowards;

	Inventory inventory;

	int hp;
	int maxHp;
	int defense;

	void Init(NPCTypes npcType)
	{
		int npcDefense{};
		int npcHp{};
		std::string npcName{};
		Inventory npcInventory;

		switch (npcType)
		{
		case NPCTypes::Guard:
			npcDefense = GetRandom(g_GuardMinDefence, g_GuardMaxDefence);
			npcHp = GetRandom(g_GuardMinHP, g_GuardMaxHP);
			npcName = g_NPCNames[GetRandom(0, g_NPCNamesCount)];
			npcInventory = Inventory{ 6, 3 };
			break;
		case NPCTypes::Trader:
			npcDefense = GetRandom(g_TraderMinDefence, g_TraderMaxDefence);
			npcHp = GetRandom(g_TraderMinHP, g_TraderMaxHP);
			npcName = g_NPCNames[GetRandom(0, g_NPCNamesCount)];
			npcInventory = Inventory{ 8, 4 };
			break;
		case NPCTypes::Scavenger:
			npcDefense = GetRandom(g_ScavengerMinDefence, g_ScavengerMaxDefence);
			npcHp = GetRandom(g_ScavengerMinHP, g_ScavengerMaxHP);
			npcName = g_NPCNames[GetRandom(0, g_NPCNamesCount)];
			npcInventory = Inventory{ 6, 3 };
			break;
		case NPCTypes::Rat:
			npcDefense = GetRandom(g_RatMinDefence, g_RatMaxDefence);
			npcHp = GetRandom(g_RatMinHP, g_RatMaxHP);
			npcName = "Rat";
			npcInventory = Inventory{ 4, 2 };
			break;
		case NPCTypes::Wolf:
			npcDefense = GetRandom(g_WolfMinDefence, g_WolfMaxDefence);
			npcHp = GetRandom(g_WolfMinHP, g_WolfMaxHP);
			npcName = "Wolf";
			npcInventory = Inventory{ 4, 2 };
			break;
		case NPCTypes::Bear:
			npcDefense = GetRandom(g_BearMinDefence, g_BearMaxDefence);
			npcHp = GetRandom(g_BearMinHP, g_BearMaxHP);
			npcName = "Bear";
			npcInventory = Inventory{ 4, 2 };
			break;
		}

		type = npcType;
		defense = npcDefense;
		hp = npcHp;
		maxHp = npcHp;
		name = npcName;
		inventory = npcInventory;
	}

	NPC() : NPC("no_name")
	{

	}

	NPC(std::string n, NPCTypes t = NPCTypes::None, int invCols = 1, int invRows = 1, int health = 100, int def = 0) :
		name{ n }, inventory{ Inventory(invCols, invRows) }, hp{ health }, maxHp{ health }, defense{ def }, nrOfHostileTowards{ 0 }, pHostileTowards{ nullptr }
	{
		//inventory = Inventory(invCols, invRows);
	}

	NPC(std::string n, NPCTypes t, Inventory inv, int health, int def) :
		name{ n }, inventory{ inv }, hp{ health }, maxHp{ health }, defense{ def }, nrOfHostileTowards{ 0 }, pHostileTowards{ nullptr }
	{

	}

	~NPC()
	{
		delete[] pHostileTowards;

		//inventory = nullptr;
	}

	int GetGold()
	{
		return 666; // TODO
	}

	bool isAnimal()
	{
		if (type == NPCTypes::Bear || type == NPCTypes::Rat || type == NPCTypes::Wolf)
		{
			return true;
		}

		return false;
	}

	bool IsHostileTowards(NPC* npc)
	{
		for (int i = 0; i < nrOfHostileTowards; i++)
		{
			if (pHostileTowards[i] == npc)
			{
				return true;
			}
		}

		return false;
	}

	void AddHostile(NPC* npc)
	{
		if (npc != this)
		{
			bool isInList{};

			for (int i = 0; i < nrOfHostileTowards; i++)
			{
				if (pHostileTowards[i] == npc)
				{
					isInList = true;
					break;
				}
			}

			if (!isInList)
			{
				nrOfHostileTowards++;

				if (pHostileTowards != nullptr)
				{
					NPC** pNew = new NPC*[nrOfHostileTowards];
					for (int i = 0; i < nrOfHostileTowards - 1; i++)
					{
						pNew[i] = pHostileTowards[i];
					}
					pNew[nrOfHostileTowards - 1] = npc;

					delete[] pHostileTowards;
					pHostileTowards = nullptr;

					pHostileTowards = pNew;
				}
				else
				{
					pHostileTowards = new NPC*[nrOfHostileTowards] {};
					pHostileTowards[nrOfHostileTowards - 1] = npc;
				}
			}
			else
			{
				std::cout << "Warning: tried to add npc to list of hostileTowards that was already in the list!\n";
			}
		}
		else
		{
			//std::cout << name << " tried to add himself to list of hostileTowards!\n";
			std::cout << "Warning: npc tried to add himself to list of hostileTowards!\n";
		}
	}

	void RemoveHostile(NPC* npc)
	{
		if (pHostileTowards != nullptr)
		{
			bool isInList{};

			for (int i = 0; i < nrOfHostileTowards; i++)
			{
				if (isInList)
				{
					pHostileTowards[i - 1] = pHostileTowards[i];
				}
				else if (pHostileTowards[i] == npc)
				{
					pHostileTowards[i] = nullptr;

					isInList = true;
				}
			}

			if (isInList)
			{
				pHostileTowards[nrOfHostileTowards - 1] = nullptr;
				nrOfHostileTowards--;

				NPC** pNew = new NPC*[nrOfHostileTowards];
				for (int i = 0; i < nrOfHostileTowards; i++)
				{
					pNew[i] = pHostileTowards[i];
				}

				delete[] pHostileTowards;
				pHostileTowards = nullptr;

				pHostileTowards = pNew;
			}
			else
			{
				std::cout << "Warning: tried to add npc to list of hostileTowards that was already in the list!\n";
			}
		}
		else
		{
			std::cout << "Warning: tried to remove npc from an empty hostileTowards list!\n";
		}
	}

	void ResetHostiles()
	{
		delete[] pHostileTowards;
		pHostileTowards = nullptr;

		nrOfHostileTowards = 0;
	}

	void PrintHostiles() const
	{
		if (pHostileTowards != nullptr)
		{
			std::cout << " | &pHostileTowards: " << &pHostileTowards << " | \n";

			for (int i = 0; i < nrOfHostileTowards; i++)
			{
				std::cout << name << "'s pHostileTowards[" << i << "]: " << (pHostileTowards[i])->name << '\n';
			}
		}
		else
		{
			std::cout << "Warning: tried to call PrintHostiles() but pHostileTowards is a null pointer!\n";
		}
	}

	bool isAlive() const
	{
		if (hp <= 0)
		{
			return false;
		}

		return true;
	}
};

struct Player
{
	NPC npc;

	int hunger;
	int maxHunger;

	int thirst;
	int maxThirst;

	Player(std::string name, int invCols, int invRows) : hunger{ 50 }, thirst{ 50 }, maxHunger{ 50 }, maxThirst{ 50 }
	{
		npc = NPC(name, NPCTypes::Player, invCols, invRows);
	}

	int GetGold()
	{
		return npc.GetGold();
	}

	Inventory& GetInventory()
	{
		return npc.inventory;
	}
};

Player g_Player("Player", 9, 3);

struct Container
{
	std::string name;
	Inventory inventory;

	void Init(std::string n, Inventory inv)
	{
		name = n;
		inventory = inv;
	}

	Container() : name{ "container_no_name" }, inventory{ Inventory() }
	{

	}

	Container(std::string n, Inventory inv) : name{ n }, inventory{ inv }
	{

	}

	Container(std::string n, int cols, int rows) : name{ n }, inventory{ Inventory(cols, rows) }
	{
		
	}
};

struct UIText
{
	std::string text;
	std::string space;

	Point2f pos;

	Texture texture;
	EntityTypes type;

	Container* pContainer;
	Loot* pItem;
	NPC* pNpc;
	//Event* pEvent;

	void GenerateTexture()
	{
		//DeleteTexture(texture);

		Color4f npcColor{ 0 / 255.f, 175 / 255.f, 0 / 255.f, 1 };

		switch (type)
		{
		case EntityTypes::None:
			TextureFromString(text + space, "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, texture);
			break;
		case EntityTypes::Location:
			// hack to show any location as current event
			//TextureFromString(g_pEvent->name + " ", "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, texture);

			TextureFromString(text + space, "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, texture);
			break;
		case EntityTypes::NPC:
			if (pNpc->IsHostileTowards(&g_Player.npc))
			{
				npcColor = { 175 / 255.f, 0 / 255.f, 0 / 255.f, 1 };
			}
			TextureFromString(pNpc->name + space, "Resources/ShortStack-Regular.ttf", 16, npcColor, texture);
			break;
		case EntityTypes::Container:
			TextureFromString(pContainer->name + space, "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 75 / 255.f, 0 / 255.f, 1 }, texture);
			break;
		case EntityTypes::Item:
			TextureFromString(pItem->name + space, "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, texture);
			break;
		}
	}

	UIText(std::string txt, bool noSpace = false) : text{ txt }, type{ EntityTypes::None }, space{ " " }
	{
		if (noSpace)
		{
			space = "";
		}

		GenerateTexture();
	}
	
	UIText(Container* container, bool noSpace = false) : pContainer{ container }, type{ EntityTypes::Container }, space{ " " }
	{
		if (noSpace)
		{
			space = "";
		}

		GenerateTexture();
	}

	UIText(Loot* loot, bool noSpace = false) : pItem{ loot }, type{ EntityTypes::Item }, space{ " " }
	{
		if (noSpace)
		{
			space = "";
		}

		GenerateTexture();
	}

	UIText(NPC* npc, bool noSpace = false) : pNpc{ npc }, type{ EntityTypes::NPC }, space{ " " }
	{
		if (noSpace)
		{
			space = "";
		}

		GenerateTexture();
	}

	//UIText(Event* event) : pEvent{ event }, type{ EntityTypes::Location }
	//{

	//}

	UIText()
	{
		//std::cout << "Warning: empty UIText() constructor called!";
	}

	~UIText()
	{
		DeleteTexture(texture);
	}
};

struct Action
{
	ActionTypes type;
	bool didExecute;

	int nrOfActiveNpcs;
	NPC* pActiveNpcs;

	int nrOfPassiveNpcs;
	NPC* pPassiveNpcs;

	int nrOfItems;
	Loot* pItems;

	Action() : type{ ActionTypes::None }
	{

	}

	Action(ActionTypes actionType, NPC* activeNpcs, int activeNpcsCount, NPC* passiveNpcs, int passiveNpcsCount, Loot* items, int itemsCount) : type{ actionType }, nrOfActiveNpcs{ activeNpcsCount }, nrOfPassiveNpcs{ passiveNpcsCount }, nrOfItems{ itemsCount }
	{
		pActiveNpcs = activeNpcs;
		pPassiveNpcs = passiveNpcs;
		pItems = items;

		// Don't actually need to copy them since we don't delete Events and the pointers are stored there..
		// And even if we did delete the event, it contains this Action so it would get deleted anyway.
		/*
		pActiveNpcs = new NPC[nrOfActiveNpcs];
		for (int i = 0; i < nrOfActiveNpcs; i++)
		{
			pActiveNpcs[i] = activeNpcs[i];
		}

		pPassiveNpcs = new NPC[nrOfPassiveNpcs];
		for (int i = 0; i < nrOfPassiveNpcs; i++)
		{
			pPassiveNpcs[i] = passiveNpcs[i];
		}

		pItems = new Loot[nrOfItems];
		for (int i = 0; i < nrOfItems; i++)
		{
			pItems[i] = items[i];
		}
		*/
	}

	void Execute()
	{
		if (!didExecute)
		{
			switch (type)
			{
			case ActionTypes::Attack:

				break;
			case ActionTypes::Talk:

				break;
			case ActionTypes::Trade:

				break;
			case ActionTypes::Steal:

				break;
			}

			didExecute = true;
		}
		else
		{
			std::cout << "Warning: tried to execute Action that was already executed once!\n";
		}
	}
};

const bool NO_SPACE = true;

struct Event
{
	std::string name;
	EventTypes type;

	int nrNPCs;
	NPC* pNpcs;

	int nrContainers;
	Container* pContainers;

	//Inventory inventory;
	Inventory* pDisplayedInventory;

	Action currentAction;
	Action lastAction;

	int nrOfUiTexts;
	UIText* pUiTexts;

	int nrOfNPCReactions;
	UIText* pNPCReactions;

	bool IsInTown()
	{
		if (type == EventTypes::Town)
		{
			return true;
		}

		return false;
	}

	void AddUIText(UIText& newUIElement)
	{
		nrOfUiTexts++;

		UIText *pNew = new UIText[nrOfUiTexts];
		for (int i = 0; i < nrOfUiTexts - 1; i++)
		{
			pNew[i] = pUiTexts[i];
		}
		pNew[nrOfUiTexts - 1] = newUIElement;

		delete[] pUiTexts;
		pUiTexts = pNew;

		for (int i = 0; i < nrOfUiTexts; i++)
		{
			pUiTexts[i].GenerateTexture();
		}
		pUiTexts[nrOfUiTexts - 1].GenerateTexture();
	}

	void AddNPCReaction(UIText& newNPCReaction)
	{
		nrOfNPCReactions++;

		UIText *pNew = new UIText[nrOfNPCReactions];
		for (int i = 0; i < nrOfNPCReactions - 1; i++)
		{
			pNew[i] = pNPCReactions[i];
		}
		pNew[nrOfNPCReactions - 1] = newNPCReaction;

		delete[] pNPCReactions;
		pNPCReactions = pNew;
	}

	void ResetNPCReaction()
	{
		nrOfNPCReactions = 0;
		delete[] pNPCReactions;
		pNPCReactions = new UIText[0];
	}

	void GenerateText()
	{
		if (pUiTexts != nullptr)
		{
			delete[] pUiTexts;
			pUiTexts = nullptr;

			nrOfUiTexts = 0;
		}

		UIText toAdd;

		int count{};
		switch (type)
		{
		case EventTypes::Town:
			toAdd = UIText("You are in");
			AddUIText(toAdd);

			//toAdd = UIText(this);
			toAdd = UIText(name + ".");
			AddUIText(toAdd);

			toAdd = UIText("There are currently " + std::to_string(nrNPCs));
			AddUIText(toAdd);

			toAdd = UIText("people");
			AddUIText(toAdd);

			toAdd = UIText("outside.");
			AddUIText(toAdd);

			for (int i = 0; i < nrNPCs; i++)
			{
				switch (pNpcs[i].type)
				{
				case NPCTypes::Trader:
					if (pNpcs[i].pHostileTowards == nullptr)
					{
						toAdd = UIText("You see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("selling");
						AddUIText(toAdd);

						toAdd = UIText("some");
						AddUIText(toAdd);

						toAdd = UIText("goods.");
						AddUIText(toAdd);
					}
					else
					{
						std::string target{};

						if (pNpcs[i].pHostileTowards[0] == &g_Player.npc)
						{
							target = "you";
						}
						else
						{
							target = pNpcs[i].pHostileTowards[0]->name;
						}

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("is attacking " + target + ".");
						AddUIText(toAdd);
					}
					break;
				case NPCTypes::Guard:
					if (pNpcs[i].pHostileTowards == nullptr)
					{
						toAdd = UIText("You see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("keeping");
						AddUIText(toAdd);

						toAdd = UIText("a");
						AddUIText(toAdd);

						toAdd = UIText("watch");
						AddUIText(toAdd);

						toAdd = UIText("on");
						AddUIText(toAdd);

						toAdd = UIText("the");
						AddUIText(toAdd);

						toAdd = UIText("city.");
						AddUIText(toAdd);
					}
					else
					{
						std::string target{};

						if (pNpcs[i].pHostileTowards[0] == &g_Player.npc)
						{
							target = "you";
						}
						else
						{
							target = pNpcs[i].pHostileTowards[0]->name;
						}

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("is attacking " + target + ".");
						AddUIText(toAdd);
					}
					break;
				case NPCTypes::Scavenger:
					if (pNpcs[i].pHostileTowards == nullptr)
					{
						toAdd = UIText("You see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("his");
						AddUIText(toAdd);

						toAdd = UIText("his");
						AddUIText(toAdd);

						toAdd = UIText("business.");
						AddUIText(toAdd);

					}
					else
					{
						std::string target{};

						if (pNpcs[i].pHostileTowards[0] == &g_Player.npc)
						{
							target = "you";
						}
						else
						{
							target = pNpcs[i].pHostileTowards[0]->name;
						}

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("is attacking " + target + ".");
						AddUIText(toAdd);
					}
					break;
				}
			}
			break;
		case EventTypes::Encounter:
			if (currentAction.type == ActionTypes::None)
			{
				toAdd = UIText("While exploring the");
				AddUIText(toAdd);

				//toAdd = UIText(this);
				toAdd = UIText(name);
				AddUIText(toAdd);

				toAdd = UIText("you");
				AddUIText(toAdd);

				toAdd = UIText("run");
				AddUIText(toAdd);

				toAdd = UIText("into");
				AddUIText(toAdd);

				for (int i = 0; i < nrNPCs; i++)
				{
					if (pNpcs[i].isAnimal())
					{
						toAdd = UIText("a");
						AddUIText(toAdd);
					}

					if (i < nrNPCs - 2)
					{
						toAdd = UIText(&pNpcs[i], NO_SPACE);
						AddUIText(toAdd);

						toAdd = UIText(",");
						AddUIText(toAdd);
					}
					else if (nrNPCs > 1 && i < nrNPCs - 1)
					{
						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("and");
						AddUIText(toAdd);
					}
					else
					{
						toAdd = UIText(&pNpcs[i], NO_SPACE);
						AddUIText(toAdd);

						toAdd = UIText(".");
						AddUIText(toAdd);
					}
				}

				if (pNpcs[0].pHostileTowards == nullptr)
				{
					if (nrNPCs > 1)
					{
						toAdd = UIText("They");
						AddUIText(toAdd);

						toAdd = UIText("are");
						AddUIText(toAdd);

						toAdd = UIText("minding");
						AddUIText(toAdd);

						toAdd = UIText("their");
						AddUIText(toAdd);

						toAdd = UIText("own");
						AddUIText(toAdd);

						toAdd = UIText("business.");
						AddUIText(toAdd);
					}
					else
					{
						toAdd = UIText("He");
						AddUIText(toAdd);

						toAdd = UIText("is");
						AddUIText(toAdd);

						toAdd = UIText("minding");
						AddUIText(toAdd);

						toAdd = UIText("his");
						AddUIText(toAdd);

						toAdd = UIText("own");
						AddUIText(toAdd);

						toAdd = UIText("business.");
						AddUIText(toAdd);
					}
				}
				else if (pNpcs[0].IsHostileTowards(&g_Player.npc))
				{
					if (nrNPCs > 1)
					{
						toAdd = UIText("They are attacking you.");
						AddUIText(toAdd);
					}
					else
					{
						toAdd = UIText("He is attacking you.");
						AddUIText(toAdd);
					}
				}
				else
				{
					if (nrNPCs > 1)
					{
						toAdd = UIText("They are fighting.");
						AddUIText(toAdd);
					}
					else
					{
						toAdd = UIText("He is fighting.");
						AddUIText(toAdd);
					}
				}
			}
			else
			{
				toAdd = UIText("You are in");
				AddUIText(toAdd);

				//toAdd = UIText(this);
				toAdd = UIText(name, NO_SPACE);
				AddUIText(toAdd);

				toAdd = UIText(".");
				AddUIText(toAdd);

				for (int i = 0; i < nrNPCs; i++)
				{
					if (pNpcs[i].isAlive())
					{
						toAdd = UIText("You see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						if (pNpcs[i].nrOfHostileTowards > 0)
						{
							toAdd = UIText("is fighting");
							AddUIText(toAdd);

							for (int j = 0; j < pNpcs[i].nrOfHostileTowards; j++)
							{
								toAdd = UIText(pNpcs[i].pHostileTowards[j]->name, NO_SPACE);
								AddUIText(toAdd);

								if (j < pNpcs[i].nrOfHostileTowards - 2)
								{
									toAdd = UIText(",");
									AddUIText(toAdd);
								}
								else if (pNpcs[i].nrOfHostileTowards > 1 && j < pNpcs[i].nrOfHostileTowards - 1)
								{
									toAdd = UIText("and");
									AddUIText(toAdd);
								}
								else
								{
									toAdd = UIText(".");
									AddUIText(toAdd);
								}
							}
						}
						else
						{
							toAdd = UIText(".");
							AddUIText(toAdd);

							toAdd = UIText("He");
							AddUIText(toAdd);

							toAdd = UIText("is");
							AddUIText(toAdd);

							toAdd = UIText("minding");
							AddUIText(toAdd);

							toAdd = UIText("his");
							AddUIText(toAdd);

							toAdd = UIText("own");
							AddUIText(toAdd);

							toAdd = UIText("business.");
							AddUIText(toAdd);
						}
					}
					else
					{
						toAdd = UIText("You");
						AddUIText(toAdd);

						toAdd = UIText("see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("Dead");
						AddUIText(toAdd);

						toAdd = UIText("on");
						AddUIText(toAdd);

						toAdd = UIText("the");
						AddUIText(toAdd);

						toAdd = UIText("ground.");
						AddUIText(toAdd);
					}
				}
				/*for (int i = 0; i < nrNPCs; i++)
				{
					if (!pNpcs[i].isAlive())
					{
						toAdd = UIText("You");
						AddUIText(toAdd);

						toAdd = UIText("see");
						AddUIText(toAdd);

						toAdd = UIText(&pNpcs[i]);
						AddUIText(toAdd);

						toAdd = UIText("Dead");
						AddUIText(toAdd);

						toAdd = UIText("on");
						AddUIText(toAdd);

						toAdd = UIText("the");
						AddUIText(toAdd);

						toAdd = UIText("ground.");
						AddUIText(toAdd);
					}
				}
				*/
			}
			break;
		case EventTypes::Scavenge:

			toAdd = UIText("While searching the");
			AddUIText(toAdd);

			//toAdd = UIText(this);
			toAdd = UIText(name);
			AddUIText(toAdd);

			toAdd = UIText("you find a");
			AddUIText(toAdd);

			if (nrContainers > 1)
			{
				for (int i = 0; i < nrContainers; i++)
				{
					if (i == nrContainers - 1)
					{
						toAdd = UIText(&pContainers[i]);
						AddUIText(toAdd);

						switch (GetRandom(0, 3))
						{
						case 0:
							toAdd = UIText("on the ground.");
							AddUIText(toAdd);
							break;
						case 1:
							toAdd = UIText("infront of you.");
							AddUIText(toAdd);
							break;
						case 2:
							toAdd = UIText("right there.");
							AddUIText(toAdd);
							break;
						}
					}
					else
					{
						toAdd = UIText(&pContainers[i], NO_SPACE);
						AddUIText(toAdd);

						toAdd = UIText(", and a");
						AddUIText(toAdd);
					}
				}

				if (nrNPCs > 0)
				{
					if (nrNPCs > 1)
					{
						toAdd = UIText("Next to it are");
						AddUIText(toAdd);
					}
					else if (nrNPCs == 1)
					{
						if (pNpcs[0].isAnimal())
						{
							toAdd = UIText("Next to it is a");
							AddUIText(toAdd);
						}
						else
						{
							toAdd = UIText("Next to it is");
							AddUIText(toAdd);
						}
					}
				}
			}
			else
			{
				toAdd = UIText(&pContainers[0]);
				AddUIText(toAdd);

				switch (GetRandom(0, 3))
				{
				case 0:
					toAdd = UIText("on the ground.");
					AddUIText(toAdd);
					break;
				case 1:
					toAdd = UIText("infront of you.");
					AddUIText(toAdd);
					break;
				case 2:
					toAdd = UIText("right there.");
					AddUIText(toAdd);
					break;
				}

				if (nrNPCs > 0)
				{
					if (nrNPCs > 1)
					{
						toAdd = UIText("Next to it are");
						AddUIText(toAdd);
					}
					else
					{
						if (pNpcs[0].isAnimal())
						{
							toAdd = UIText("Next to it is a");
							AddUIText(toAdd);
						}
						else
						{
							toAdd = UIText("Next to it is");
							AddUIText(toAdd);
						}
					}
				}
			}

			for (int i = 0; i < nrNPCs; i++)
			{
				if (i < nrNPCs - 2)
				{
					toAdd = UIText(&pNpcs[i], NO_SPACE);
					AddUIText(toAdd);

					toAdd = UIText(",");
					AddUIText(toAdd);
				}
				else if (nrNPCs > 1 && i < nrNPCs - 1)
				{
					toAdd = UIText(&pNpcs[i]);
					AddUIText(toAdd);

					toAdd = UIText("and");
					AddUIText(toAdd);
				}
				else
				{
					toAdd = UIText(&pNpcs[i], NO_SPACE);
					AddUIText(toAdd);

					toAdd = UIText(".");
					AddUIText(toAdd);
				}
			}
			if (nrNPCs > 0)
			{
				if (nrNPCs > 1)
				{
					if (pNpcs[0].IsHostileTowards(&g_Player.npc))
					{
						toAdd = UIText("They are attacking you!");
						AddUIText(toAdd);
					}
				}
				else
				{
					if (pNpcs[0].IsHostileTowards(&g_Player.npc))
					{
						toAdd = UIText("He is attacking you!");
						AddUIText(toAdd);
					}
				}
			}
			break;
		}

		for (int i = 0; i < nrOfNPCReactions; i++)
		{
			AddUIText(pNPCReactions[i]);
		}
	}
};

#pragma region coreDeclarations
// Functions
void Initialize();
void Run();
void Cleanup();
void QuitOnSDLError();
void QuitOnOpenGlError();
void QuitOnImageError();
void QuitOnTtfError();

// Variables
SDL_Window* g_pWindow{ nullptr }; // The window we'll be rendering to
SDL_GLContext g_pContext; // OpenGL context
Uint32 g_MilliSeconds{};
const Uint32 g_MaxElapsedTime{ 100 };
#pragma endregion coreDeclarations

#pragma region gameDeclarations
// Functions
void Update(float elapsedSec);
void Draw();
void ClearBackground();

void InitGameResources();
void FreeGameResources();

void ProcessKeyDownEvent(const SDL_KeyboardEvent  & e);
void ProcessKeyUpEvent(const SDL_KeyboardEvent  & e);
void ProcessMouseMotionEvent(const SDL_MouseMotionEvent & e);
void ProcessMouseDownEvent(const SDL_MouseButtonEvent & e);
void ProcessMouseUpEvent(const SDL_MouseButtonEvent & e);

void DrawInventory(Inventory& inventory, const Point2f& pos);
void DrawInventory(Inventory& inventory, float y);
void DrawInventory(const Inventory& inventory);
void DrawBar(Point2f pos, float width, int value, int maxValue, BarTextures color, std::string name = "");
void DrawOverlay(Point2f pos, NPC &npc, float width = 130.f);
void DrawOverlay(Point2f pos, Loot &item, float width = 130.f);
void DrawPlayer();
void DrawEvent();
void DrawCursor();

void PickupLoot(Loot* pItem, Inventory* pInventory);
void GetHoveredInventoryAndItem(const Point2f& point, Inventory& inventory, bool& isHoveringItem, bool& isHoveringInventory);
void GetHeldItemHoverResult(const Inventory& inventory, bool& isOutOfBounds, bool& severalItems, int& itemId);
void GetHoveredUIText(const Point2f& point, Event* pEvent, bool& isHoveringUIText);

void Attack(NPC& attacker, NPC& attacked);
void TakeDamage(NPC& npc, int damage);

void CreateNewEvent(Event &event, EventTypes type);

void NextEvent();
void Talk(NPC &npc);
void Trade(NPC &npc);
void Steal(NPC &npc);

void Eat(Loot &food);
void Drink(Loot &drink);

void Heal(NPC& npc, int amount);

void ProcessNPCActs();
void CreateNPCs(Event &event, int nrOfNPCs);
void CreateContainers(Event& event, int nrContainers);

void InitiateGameEnd(bool hasSurvived);

void ShowInformations();

// Variables
//Inventory g_Inventory2(2, 3);
//Inventory g_Inventory3(7, 2);

Texture g_LootTextures[int(LootTextures::Count)];

const float g_CellBorder{ 1.5f };
Texture g_CellTexture;
Texture g_CellDirtOverlay;

Texture g_EquippedTexture;
Texture g_InvCloseButtonTexture;
Texture g_InvCloseButtonHoverTexture;

Point2f g_MousePos;
Texture g_CursorTextures[int(ActionTypes::Count)];
Texture g_BarTextures[int(BarTextures::Count)];

ActionTypes g_PlayerIntent{ ActionTypes::Attack };

Loot* g_pHoveredItem;
Inventory* g_pHoveredInventory;

Loot g_HeldItem;
Inventory* g_pHeldItemSourceInventory;
bool g_HeldItemWasEquipped;

UIText* g_pHoveredUIText;

bool g_HoveringInvCloseButton;

Texture g_EventStringTexture;

const int g_MaxDays{ 30 };

Event g_ExplorationEvents[g_MaxDays];
Event g_TownEvents[g_MaxDays];
Event* g_pEvent;

int g_CurrentDay{ 0 };

const bool PLAYER_SURVIVED = true;
const bool PLAYER_DIED = false;

const bool EQUIP_AFTER_ADDING = true;

//bool g_IsInTown{true}; // TODO Update in main code

#pragma endregion gameDeclarations

int main(int argc, char* args[])
{
	// seed the pseudo random number generator
	srand(unsigned int(time(nullptr)));

	// Initialize SDL and OpenGL
	Initialize();

	SDL_ShowCursor(SDL_DISABLE);

	Loot lootGlock(LootTextures::GLOCK17);
	Loot lootWater(LootTextures::WATER);
	Loot lootUsp(LootTextures::USP);
	Loot lootGold(LootTextures::GOLD);
	lootGold.value = 30;

	g_Player.GetInventory().Add(lootGlock, 0, 1);
	g_Player.GetInventory().Add(lootWater, 3, 0);
	g_Player.GetInventory().Add(lootWater, 4, 1);
	g_Player.GetInventory().Add(lootUsp, 6, 0);
	g_Player.GetInventory().Add(lootGold, g_Player.GetInventory().cols - 1, g_Player.GetInventory().rows - 1);

	//g_Player.GetInventory().DebugPrint();
	g_Player.npc.inventory.equippedId = 0;

	NextEvent();

	// Event loop
	Run();

	// Clean up SDL and OpenGL
	Cleanup();

	return 0;
}

#pragma region gameImplementations
void InitGameResources()
{
	//Invetory cells textures
	TextureFromFile("Resources/Cell.png", g_CellTexture);
	TextureFromFile("Resources/Cell_Dirt_Overlay.png", g_CellDirtOverlay);

	//Inventory equipped "E" texture
	TextureFromFile("Resources/Equipped.png", g_EquippedTexture);

	//Inventory close button
	TextureFromFile("Resources/InventoryCloseButton.png", g_InvCloseButtonTexture);
	TextureFromFile("Resources/InventoryCloseButton_Hover.png", g_InvCloseButtonHoverTexture);

	//Mouse cursor
	TextureFromFile("Resources/Cursor.png", g_CursorTextures[0]);
	TextureFromFile("Resources/Cursor_Attack.png", g_CursorTextures[1]);
	TextureFromFile("Resources/Cursor_Steal.png", g_CursorTextures[2]);
	TextureFromFile("Resources/Cursor_Trade.png", g_CursorTextures[3]);
	TextureFromFile("Resources/Cursor_Talk.png", g_CursorTextures[4]);

	//Bar textures (e.g: hp, thirst, hunger)
	TextureFromFile("Resources/Bar_Background.png", g_BarTextures[int(BarTextures::Background)]);
	TextureFromFile("Resources/Bar_Red.png", g_BarTextures[int(BarTextures::Red)]);
	TextureFromFile("Resources/Bar_Green.png", g_BarTextures[int(BarTextures::Green)]);
	TextureFromFile("Resources/Bar_Blue.png", g_BarTextures[int(BarTextures::Blue)]);
	TextureFromString("-1", "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, g_BarTextures[int(BarTextures::Text)]);

	//Items textures
	TextureFromFile("Resources/GLOCK17.png", g_LootTextures[int(LootTextures::GLOCK17)]);
	TextureFromFile("Resources/USP.png", g_LootTextures[int(LootTextures::USP)]);
	TextureFromFile("Resources/MACHETE.png", g_LootTextures[int(LootTextures::MACHETE)]);
	TextureFromFile("Resources/WATER.png", g_LootTextures[int(LootTextures::WATER)]);
	TextureFromFile("Resources/APPLE.png", g_LootTextures[int(LootTextures::APPLE)]);
	TextureFromFile("Resources/GOLD.png", g_LootTextures[int(LootTextures::GOLD)]);

	//Fonts
	TextureFromString("While exploring the outlands you stumble into 2 Scavengers.", "Resources/ShortStack-Regular.ttf", 16, Color4f{ 175 / 255.f, 175 / 255.f, 175 / 255.f, 1 }, g_EventStringTexture);
}

void FreeGameResources()
{
	DeleteTexture(g_CellTexture);
	DeleteTexture(g_CellDirtOverlay);

	DeleteTexture(g_EquippedTexture);
	DeleteTexture(g_InvCloseButtonTexture);
	DeleteTexture(g_InvCloseButtonHoverTexture);

	for (int i = 0; i < int(ActionTypes::Count); i++)
	{
		DeleteTexture(g_CursorTextures[i]);
	}

	for (int i = 0; i < int(BarTextures::Count); i++)
	{
		DeleteTexture(g_BarTextures[i]);
	}

	DeleteTexture(g_LootTextures[int(LootTextures::GLOCK17)]);
	DeleteTexture(g_LootTextures[int(LootTextures::WATER)]);

	DeleteTexture(g_EventStringTexture);
}

void ProcessKeyDownEvent(const SDL_KeyboardEvent  & e)
{

}

void ProcessKeyUpEvent(const SDL_KeyboardEvent  & e)
{
	switch (e.keysym.sym)
	{
	case SDLK_LEFT:
		//std::cout << "Left arrow key released\n";
		break;
	case SDLK_i:
		ShowInformations();
		break;
	case SDLK_n:
		g_Player.hunger -= GetRandom(2, 8);
		g_Player.thirst -= GetRandom(2, 8);
		NextEvent();
		break;
	case SDLK_KP_1:
		//std::cout << "Key 1 released\n";
		break; Texture* texture = &g_LootTextures[int(g_HeldItem.texture)];
	}
}

void ProcessMouseMotionEvent(const SDL_MouseMotionEvent & e)
{
	Point2f point{};

	g_MousePos = { float(e.x), float(g_WindowHeight - e.y) };
	Point2f heldItemPos{ g_MousePos };

	if (g_HeldItem.type != LootTypes::None)
	{
		if (g_HeldItem.width % 2 == 0)
		{
			heldItemPos.x -= (g_CellTexture.width / 2) + g_CellTexture.width * ((g_HeldItem.width - 1) / 2);
		}
		else
		{
			heldItemPos.x -= g_CellTexture.width * (g_HeldItem.width / 2);
		}

		if (g_HeldItem.height % 2 == 0)
		{
			heldItemPos.y += (g_CellTexture.height / 2) + g_CellTexture.height * ((g_HeldItem.height - 1) / 2);
		}
		else
		{
			heldItemPos.y += g_CellTexture.height * (g_HeldItem.height / 2);
		}

		point = heldItemPos;
	}
	else
	{
		point = g_MousePos;
	}

	bool isHoveringItem{ false };
	bool isHoveringInventory{ false };

	// Check if hovering player inventory and any item in it
	GetHoveredInventoryAndItem(point, g_Player.GetInventory(), isHoveringItem, isHoveringInventory);

	// Check if hovering displayed inventory and any item in it
	if (g_pEvent->pDisplayedInventory != nullptr)
	{
		GetHoveredInventoryAndItem(point, *g_pEvent->pDisplayedInventory, isHoveringItem, isHoveringInventory);
	}

	if (!isHoveringItem)
	{
		g_pHoveredItem = nullptr;
	}

	if (!isHoveringInventory)
	{
		g_pHoveredInventory = nullptr;
	}
	
	g_HoveringInvCloseButton = false;
	if (g_pEvent->pDisplayedInventory != nullptr)
	{
		Rectf closeButtonRect = g_pEvent->pDisplayedInventory->GetCloseButtonRect();
		if (utils::IsPointInRect(g_MousePos, closeButtonRect))
		{
			g_HoveringInvCloseButton = true;
		}
	}

	bool isHoveringUIText{ false };

	if (!isHoveringInventory && !isHoveringItem)
	{
		GetHoveredUIText(point, g_pEvent, isHoveringUIText);
	}

	if (!isHoveringUIText)
	{
		g_pHoveredUIText = nullptr;
	}
}

void GetHoveredInventoryAndItem(const Point2f& point, Inventory& inventory, bool& isHoveringItem, bool& isHoveringInventory)
{
	if (utils::IsPointInRect(point, Rectf{ inventory.pos.x, inventory.pos.y, inventory.Width(), inventory.Height() }))
	{
		g_pHoveredInventory = &inventory;
		isHoveringInventory = true;

		Point2f curPos{ inventory.pos.x, inventory.pos.y + inventory.Height() };

		for (int r = 0; r < inventory.rows && !isHoveringItem; r++)
		{
			curPos.y -= g_CellTexture.height;

			for (int c = 0; c < inventory.cols && !isHoveringItem; c++)
			{
				if (utils::IsPointInRect(point, Rectf{ curPos.x - g_CellBorder / 2, curPos.y - g_CellBorder / 2, g_CellTexture.width + g_CellBorder, g_CellTexture.height + g_CellBorder }))
				{
					inventory.hoveredX = c;
					inventory.hoveredY = r;

					if (inventory.pGrid[utils::GetIndex(r, c, inventory.cols)] != -1)
					{
						g_pHoveredItem = &inventory.pItems[inventory.pGrid[utils::GetIndex(r, c, inventory.cols)]];
						isHoveringItem = true;

						//std::cout << "inventory.pGrid[" << utils::GetIndex(r, c, inventory.cols) << "]:" << inventory.pGrid[utils::GetIndex(r, c, inventory.cols)] << ", inventory.pGrid[" << inventory.pGrid[utils::GetIndex(r, c, inventory.cols)] << "]: " << g_MouseLootHover->name << "\n";
					}
				}

				curPos.x += g_CellTexture.width;
			}

			curPos.x = inventory.pos.x;
		}
	}
}

void GetHoveredUIText(const Point2f& point, Event* pEvent, bool& isHoveringUIText)
{
	for (int i = 0; i < pEvent->nrOfUiTexts; i++)
	{
		if (utils::IsPointInRect(point, Rectf{ pEvent->pUiTexts[i].pos.x, pEvent->pUiTexts[i].pos.y, pEvent->pUiTexts[i].texture.width, pEvent->pUiTexts[i].texture.height }))
		{
			g_pHoveredUIText = &pEvent->pUiTexts[i];
			isHoveringUIText = true;

			break;
		}
	}
}

void ProcessMouseDownEvent(const SDL_MouseButtonEvent & e)
{
	if (e.button == SDL_BUTTON_LEFT)
	{
		if (g_pHoveredItem != nullptr && g_HeldItem.type == LootTypes::None)
		{
			PickupLoot(g_pHoveredItem, g_pHoveredInventory);
		}
		else if (g_HeldItem.type != LootTypes::None)
		{
			if (g_pHoveredInventory != nullptr)
			{
				bool isOutOfBounds{ false };
				bool severalItems{ false };
				int itemId{ -1 };

				GetHeldItemHoverResult(*g_pHoveredInventory, isOutOfBounds, severalItems, itemId);

				if (!isOutOfBounds)
				{
					if (severalItems)
					{

					}
					else if (itemId != -1)
					{
						Loot hoverCopy = g_pHoveredInventory->pItems[itemId];
						bool hoverCopyWasEquipped{};
						if (g_pHoveredInventory->HasEquipped() && itemId == g_pHoveredInventory->equippedId)
						{
							hoverCopyWasEquipped = true;
						}
						Inventory* hoverCopySourceInventory{ g_pHoveredInventory };

						g_pHoveredInventory->Remove(&g_pHoveredInventory->pItems[itemId]);

						if (g_pHoveredInventory == g_pHeldItemSourceInventory && g_HeldItemWasEquipped)
						{
							g_pHoveredInventory->Add(g_HeldItem, g_pHoveredInventory->hoveredX, g_pHoveredInventory->hoveredY, EQUIP_AFTER_ADDING);
						}
						else
						{
							g_pHoveredInventory->Add(g_HeldItem, g_pHoveredInventory->hoveredX, g_pHoveredInventory->hoveredY);
						}

						g_HeldItem = hoverCopy;
						g_HeldItemWasEquipped = hoverCopyWasEquipped;
						g_pHeldItemSourceInventory = hoverCopySourceInventory;
					}
					else
					{
						if (g_pHoveredInventory == g_pHeldItemSourceInventory && g_HeldItemWasEquipped)
						{
							g_pHoveredInventory->Add(g_HeldItem, g_pHoveredInventory->hoveredX, g_pHoveredInventory->hoveredY, EQUIP_AFTER_ADDING);
						}
						else
						{
							g_pHoveredInventory->Add(g_HeldItem, g_pHoveredInventory->hoveredX, g_pHoveredInventory->hoveredY);
						}
						
						g_HeldItem.type = LootTypes::None;
						g_pHeldItemSourceInventory = nullptr;
						g_HeldItemWasEquipped = false;

						//g_pHoveredInventory->DebugPrint();
					}
				}
			}
		}
		else if (g_HoveringInvCloseButton)
		{
			g_pEvent->pDisplayedInventory = nullptr;
		}
		else if (g_pHoveredUIText != nullptr)
		{
			if (g_pHoveredUIText->type == EntityTypes::Container)
			{
				g_pEvent->pDisplayedInventory = &g_pHoveredUIText->pContainer->inventory;
			}
			else if (g_pHoveredUIText->type == EntityTypes::NPC)
			{
				if (g_pHoveredUIText->pNpc->isAlive())
				{
					switch (g_PlayerIntent)
					{
					case ActionTypes::Attack:
						Attack(g_Player.npc, *g_pHoveredUIText->pNpc);

						if (g_pEvent->pDisplayedInventory != nullptr && (g_pEvent->lastAction.type == ActionTypes::Steal || g_pEvent->lastAction.type == ActionTypes::Trade))
						{
							g_pEvent->pDisplayedInventory = nullptr;
						}
						break;

					case ActionTypes::Steal:
						Steal(*g_pHoveredUIText->pNpc);
						break;

					case ActionTypes::Trade:
						Trade(*g_pHoveredUIText->pNpc);
						break;

					case ActionTypes::Talk:
						Talk(*g_pHoveredUIText->pNpc);

						if (g_pEvent->pDisplayedInventory != nullptr && (g_pEvent->lastAction.type == ActionTypes::Steal || g_pEvent->lastAction.type == ActionTypes::Trade))
						{
							g_pEvent->pDisplayedInventory = nullptr;
						}
						break;

					}
				}
				else
				{
					Steal(*g_pHoveredUIText->pNpc);
				}
			}

			g_pHoveredUIText = nullptr;
			ProcessNPCActs();
		}
	}
	else if (e.button == SDL_BUTTON_RIGHT)
	{
		if (g_HeldItem.type != LootTypes::None)
		{
			g_HeldItem.Rotate();
		}
		else if (g_pHoveredItem != nullptr)
		{
			switch (g_pHoveredItem->type)
			{
			case LootTypes::Drink:
				if (g_pHoveredInventory == &g_Player.npc.inventory)
				{
					Drink(*g_pHoveredItem);
					g_pHoveredItem = nullptr;
				}
				break;
			case LootTypes::Weapon:
				g_Player.npc.inventory.Equip(g_pHoveredItem);
				break;
			case LootTypes::Food:
				if (g_pHoveredInventory == &g_Player.npc.inventory)
				{
					Eat(*g_pHoveredItem);
					g_pHoveredItem = nullptr;
				}
				break;
			}
		}
		else if (g_pHoveredUIText != nullptr && g_pHoveredUIText->type != EntityTypes::None)
		{
			if (int(g_PlayerIntent) < int(ActionTypes::Count) - 1)
			{
				g_PlayerIntent = ActionTypes(int(g_PlayerIntent) + 1);
			}
			else
			{
				g_PlayerIntent = ActionTypes::Attack;
			}
		}
	}
}

void ProcessMouseUpEvent(const SDL_MouseButtonEvent & e)
{
	//std::cout << "  [" << e.x << ", " << e.y << "]\n";
	switch (e.button)
	{
	case SDL_BUTTON_LEFT:
	{
		//std::cout << "Left mouse button released\n";
		//Point2f mousePos{ float( e.x ), float( g_WindowHeight - e.y ) };
		break;
	}
	case SDL_BUTTON_RIGHT:
		//std::cout << "Right mouse button released\n";
		break;
	case SDL_BUTTON_MIDDLE:
		//std::cout << "Middle mouse button released\n";
		break;
	}
}

void Update(float elapsedSec)
{
	// Check keyboard state
	//const Uint8 *pStates = SDL_GetKeyboardState( nullptr );
	//if ( pStates[SDL_SCANCODE_RIGHT] )
	//{
	//	std::cout << "Right arrow key is down\n";
	//}
	//if ( pStates[SDL_SCANCODE_LEFT] && pStates[SDL_SCANCODE_UP])
	//{
	//	std::cout << "Left and up arrow keys are down\n";
	//}
}

void Draw()
{
	ClearBackground();

	DrawEvent();
	DrawPlayer();
	DrawCursor();
}

void DrawOverlay(Point2f pos, Loot &item, float width)
{
	if (pos.x + width > g_WindowWidth)
	{
		pos.x = g_WindowWidth - width;
	}
	else if(pos.x < 0)
	{
		pos.x = 0;
	}

	Texture textTexture;
	float height{};

	height = 38;

	// outline
	glColor4f(48 / 255.f, 48 / 255.f, 48 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x, pos.y + 1 }, width, 24);
	utils::FillRect(Point2f{ pos.x + 1, pos.y }, width - 2, 26);

	// background
	glColor4f(24 / 255.f, 24 / 255.f, 24 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x + 1, pos.y + 1 }, width - 2, 24);

	// text
	TextureFromString(item.name, "Resources/ShortStack-Regular.ttf", 14, Color4f{ 1, 1, 1, 1 }, g_BarTextures[int(BarTextures::Text)]);
	DrawTexture(g_BarTextures[int(BarTextures::Text)], Point2f{ pos.x + int(width / 2 - g_BarTextures[int(BarTextures::Text)].width / 2), pos.y + 3 });
	DeleteTexture(g_BarTextures[int(BarTextures::Text)]);

	// outline
	glColor4f(48 / 255.f, 48 / 255.f, 48 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x, pos.y - height + 1 }, width, height);
	utils::FillRect(Point2f{ pos.x + 1, pos.y - height }, width - 2, height);

	// background
	glColor4f(24 / 255.f, 24 / 255.f, 24 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x + 1, pos.y - height + 1 }, width - 2, height - 1);

	// properties
	switch (item.type)
	{
	case LootTypes::Weapon:
		TextureFromString("ATK", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);

		TextureFromString(std::to_string(item.v1) + " -> " + std::to_string(item.v2), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 189 / 255.f, 25 / 255.f, 25 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);
		break;
	case LootTypes::Drink:
		TextureFromString("WATER", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);

		TextureFromString(std::to_string(item.v1), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 25 / 255.f, 121 / 255.f, 189 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);
		break;
	case LootTypes::Food:
		TextureFromString("FOOD", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);

		TextureFromString(std::to_string(item.v1), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 67 / 255.f, 189 / 255.f, 25 / 255.f, 1 }, textTexture);
		DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 19 });
		DeleteTexture(textTexture);
		break;
		break;
	}

	TextureFromString("VAL", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
	DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 35 });
	DeleteTexture(textTexture);

	TextureFromString(std::to_string(item.value), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 237 / 255.f, 182 / 255.f, 15 / 255.f, 1 }, textTexture);
	DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 35 });
	DeleteTexture(textTexture);
}

void DrawOverlay(Point2f pos, NPC &npc, float width)
{
	if (pos.x + width > g_WindowWidth)
	{
		pos.x = g_WindowWidth - width;
		pos.y -= 6;
	}

	Texture textTexture;
	float height{ 38 };

	// npc hp bar
	DrawBar(pos, 130, npc.hp, npc.maxHp, BarTextures::Red, npc.name + " ");

	// outline
	glColor4f(48 / 255.f, 48 / 255.f, 48 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x, pos.y - height + 1 }, width, height);
	utils::FillRect(Point2f{ pos.x + 1, pos.y - height }, width - 2, height);

	// background
	glColor4f(24 / 255.f, 24 / 255.f, 24 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x + 1, pos.y - height + 1 }, width - 2, height - 1);

	// properties
	TextureFromString("ATK", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
	DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 19 });
	DeleteTexture(textTexture);

	if (npc.inventory.equippedId == -1)
	{
		TextureFromString(std::to_string(g_PunchMinDamage) + " -> " + std::to_string(g_PunchMaxDamage), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 189 / 255.f, 25 / 255.f, 25 / 255.f, 1 }, textTexture);
	}
	else
	{
		TextureFromString(std::to_string(npc.inventory.pItems[npc.inventory.equippedId].v1) + " -> " + std::to_string(npc.inventory.pItems[npc.inventory.equippedId].v2), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 189 / 255.f, 25 / 255.f, 25 / 255.f, 1 }, textTexture);
	}
	DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 19 });
	DeleteTexture(textTexture);

	TextureFromString("DEF", "Resources/ShortStack-Regular.ttf", 13, Color4f{ 128 / 255.f, 128 / 255.f, 128 / 255.f, 1 }, textTexture);
	DrawTexture(textTexture, Point2f{ pos.x + int(width / 4 - textTexture.width / 2), pos.y - 35 });
	DeleteTexture(textTexture);

	TextureFromString(std::to_string(npc.defense), "Resources/ShortStack-Regular.ttf", 13, Color4f{ 25 / 255.f, 121 / 255.f, 189 / 255.f, 1 }, textTexture);
	DrawTexture(textTexture, Point2f{ pos.x + int(width - width / 4 - textTexture.width / 2), pos.y - 35 });
	DeleteTexture(textTexture);
}

void DrawBar(Point2f pos, float width, int value, int maxValue, BarTextures color, std::string name)
{
	if (value > maxValue)
	{
		value = maxValue;
		std::cout << "Warning: tried to DrawBar with value bigger than maxValue!\n";
	}

	// outline
	//glColor4f(36 / 255.f, 36 / 255.f, 36 / 255.f, 1);
	glColor4f(48 / 255.f, 48 / 255.f, 48 / 255.f, 1);
	utils::FillRect(Point2f{ pos.x, pos.y + 1 }, width, 24);
	utils::FillRect(Point2f{ pos.x + 1, pos.y }, width - 2, 26);

	// background
	DrawTexture(g_BarTextures[int(BarTextures::Background)], Point2f{ pos.x + 1, pos.y + 1 }, Rectf{ 0, 0, width - 2, 24 });

	// bar
	if (value > 0)
	{
		DrawTexture(g_BarTextures[int(color)], Point2f{ pos.x + 3, pos.y + 3 }, Rectf{ 0, 0, (value / float(maxValue)) * (width - 6), 20 });
	}

	// text
	TextureFromString(name + std::to_string(value) + " / " + std::to_string(maxValue), "Resources/ShortStack-Regular.ttf", 14, Color4f{ 1, 1, 1, 1 }, g_BarTextures[int(BarTextures::Text)]);
	DrawTexture(g_BarTextures[int(BarTextures::Text)], Point2f{ pos.x + int(width / 2 - g_BarTextures[int(BarTextures::Text)].width / 2), pos.y + 3 });
	DeleteTexture(g_BarTextures[int(BarTextures::Text)]);
}

void PickupLoot(Loot* pItem, Inventory* pInventory)
{
	if (pItem != nullptr && pInventory != nullptr && g_HeldItem.type == LootTypes::None)
	{
		g_HeldItem = *pItem;
		g_pHeldItemSourceInventory = pInventory;
		if (pInventory->HasEquipped() && pItem == &pInventory->pItems[pInventory->equippedId])
		{
			g_HeldItemWasEquipped = true;
		}
		else
		{
			g_HeldItemWasEquipped = false;
		}

		pInventory->Remove(pItem);

		g_pHoveredInventory = nullptr;
		g_pHoveredItem = nullptr;

		//pInventory->DebugPrint();
	}
	else
	{
		std::cout << "Warning: called PickupLoot() but not hovering any loot!";
	}
}

void DrawCursor()
{
	if (g_HeldItem.type != LootTypes::None)
	{
		Texture* texture = &g_LootTextures[int(g_HeldItem.texture)];

		if (!g_HeldItem.isRotated)
		{
			DrawTexture(*texture, Point2f{ g_MousePos.x - texture->width / 2, g_MousePos.y - texture->height / 2 });
		}
		else
		{
			glPushMatrix();

			glTranslatef(g_MousePos.x, g_MousePos.y, 0);
			glRotatef(-90, 0, 0, 1);
			glTranslatef(-g_MousePos.x, -g_MousePos.y, 0);

			DrawTexture(*texture, Point2f{ g_MousePos.x - texture->width / 2, g_MousePos.y - texture->height / 2 });

			glPopMatrix();
		}

		if (g_HeldItemWasEquipped)
		{
			DrawTexture(g_EquippedTexture, Point2f{ g_MousePos.x - g_HeldItem.width * g_CellSize / 2, g_MousePos.y - g_CellSize + g_HeldItem.height * g_CellSize / 2 });
		}
	}
	else if (g_pHoveredUIText != nullptr && g_pHoveredUIText->type != EntityTypes::None)
	{
		if (g_pHoveredUIText->type == EntityTypes::NPC)
		{
			if (g_pHoveredUIText->pNpc->isAlive())
			{
				DrawTexture(g_CursorTextures[int(g_PlayerIntent)], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[int(g_PlayerIntent)].height });
				DrawOverlay(Point2f{ g_MousePos.x + 40, g_MousePos.y - 66.f }, *g_pHoveredUIText->pNpc);
			}
			else
			{
				DrawTexture(g_CursorTextures[int(ActionTypes::Steal)], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[int(ActionTypes::Steal)].height });
				DrawOverlay(Point2f{ g_MousePos.x + 40, g_MousePos.y - 66.f }, *g_pHoveredUIText->pNpc);
			}
		}
		else if (g_pHoveredUIText->type == EntityTypes::Item)
		{
			DrawTexture(g_CursorTextures[0], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[0].height });
			DrawOverlay(Point2f{ g_MousePos.x - 65.f, g_MousePos.y + 40.f }, *g_pHoveredItem);
		}
		else if (g_pHoveredUIText->type == EntityTypes::Container)
		{
			DrawTexture(g_CursorTextures[int(ActionTypes::Steal)], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[int(ActionTypes::Steal)].height });
		}

	}
	else if (g_pHoveredItem != nullptr)
	{
		//DrawOverlay(Point2f{ g_MousePos.x - 65.f, g_MousePos.y + 40.f }, *g_pHoveredItem);
		DrawOverlay(Point2f{ g_pHoveredInventory->pos.x + (g_pHoveredItem->x * g_CellSize) - 65 + g_pHoveredItem->width * g_CellSize / 2, g_pHoveredInventory->pos.y + g_pHoveredInventory->Height() - g_pHoveredItem->y * g_CellSize + 40 + 7 }, *g_pHoveredItem);
		DrawTexture(g_CursorTextures[0], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[0].height });
	}
	else
	{
		DrawTexture(g_CursorTextures[0], Point2f{ g_MousePos.x, g_MousePos.y - g_CursorTextures[0].height });
	}
}

void DrawEvent()
{
	float lineSpacing{ 5.f };
	float gap{ 56.f };
	Point2f pos{ gap, g_WindowHeight - gap };

	for (int i = 0; i < g_pEvent->nrOfUiTexts; i++)
	{
		if (pos.x + g_pEvent->pUiTexts[i].texture.width > g_WindowWidth - gap)
		{
			pos.x = gap;
			pos.y -= g_pEvent->pUiTexts[i - 1].texture.height + lineSpacing;
		}
		g_pEvent->pUiTexts[i].pos = pos;

		DrawTexture(g_pEvent->pUiTexts[i].texture, pos);

		pos.x += g_pEvent->pUiTexts[i].texture.width;
	}

	if (g_pEvent->pDisplayedInventory != nullptr)
	{
		DrawInventory(*g_pEvent->pDisplayedInventory, g_Player.npc.inventory.pos.y + g_Player.npc.inventory.Height() + gap / 2);
	}
}

void DrawPlayer()
{
	float gap{ 48.f };
	float barWidth{ 130.f };

	DrawInventory(g_Player.GetInventory(), gap);

	gap = 14;
	DrawBar(Point2f{ gap, 10 }, barWidth, g_Player.hunger, g_Player.maxHunger, BarTextures::Green);
	DrawBar(Point2f{ g_WindowWidth - gap - barWidth, 10 }, barWidth, g_Player.thirst, g_Player.maxThirst, BarTextures::Blue);

	barWidth = 281;
	DrawBar(Point2f{ g_WindowWidth / 2 - barWidth / 2, 10 }, barWidth, g_Player.npc.hp, g_Player.npc.maxHp, BarTextures::Red);
}

void DrawInventory(Inventory& inventory, float y)
{
	inventory.pos = { g_WindowWidth / 2 - (inventory.cols / 2.f * g_CellTexture.width), y };
	DrawInventory(inventory, inventory.pos);
}

void DrawInventory(Inventory& inventory, const Point2f& pos)
{
	inventory.pos = { pos.x, pos.y };
	DrawInventory(inventory);
}

void DrawInventory(const Inventory& inventory)
{
	Point2f curPos{ inventory.pos.x, inventory.pos.y + inventory.Height() };

	glLineWidth(g_CellBorder);
	for (int r = 0; r < inventory.rows; r++)
	{
		curPos.y -= g_CellTexture.height;

		for (int c = 0; c < inventory.cols; c++)
		{
			//grid cell
			DrawTexture(g_CellTexture, curPos);

			//grid cell outline
			glColor3f(142 / 255.f, 142 / 255.f, 142 / 255.f);
			utils::DrawRectOutline(curPos.x - g_CellBorder / 2, curPos.y - g_CellBorder / 2, g_CellTexture.width + g_CellBorder, g_CellTexture.height + g_CellBorder);

			//grid cell dirt overlay
			DrawTexture(g_CellDirtOverlay, Point2f{ curPos.x - g_CellBorder / 2, curPos.y - g_CellBorder / 2 }, Rectf{ c * g_CellTexture.width, r * g_CellTexture.height, g_CellTexture.width, g_CellTexture.height });

			//grid item normal/hover background color
			if (inventory.pGrid[utils::GetIndex(r, c, inventory.cols)] != -1)
			{
				if (g_pHoveredItem != nullptr && g_pHoveredItem == &inventory.pItems[inventory.pGrid[utils::GetIndex(r, c, inventory.cols)]] && g_HeldItem.type == LootTypes::None)
				{
					glColor4f(40 / 255.f, 67 / 255.f, 39 / 255.f, 0.5f); //glColor4f(53 / 255.f, 39 / 255.f, 67 / 255.f, 0.33f);
				}
				else
				{
					glColor4f(39 / 255.f, 40 / 255.f, 70 / 255.f, 0.5f);
				}

				utils::FillRect(curPos.x - g_CellBorder / 2, curPos.y - g_CellBorder / 2, g_CellTexture.width + g_CellBorder, g_CellTexture.height + g_CellBorder);
			}

			curPos.x += g_CellTexture.width;
		}

		curPos.x = inventory.pos.x;
	}

	curPos = { inventory.pos.x, inventory.pos.y + inventory.Height() };

	for (int i = 0; i < inventory.Size(); i++)
	{
		if (inventory.pItems[i].type != LootTypes::None)
		{
			Point2f itemPos{ curPos.x + inventory.pItems[i].x * g_CellTexture.width, (curPos.y - inventory.pItems[i].y * g_CellTexture.height) - (inventory.pItems[i].height * g_CellTexture.height) };

			if (!inventory.pItems[i].isRotated)
			{
				DrawTexture(g_LootTextures[int(inventory.pItems[i].texture)], itemPos);
			}
			else
			{
				glPushMatrix();

				Point2f itemCenter{ inventory.pItems[i].GetCenter(itemPos) };
				glTranslatef(itemCenter.x, itemCenter.y, 0);
				glRotatef(-90, 0, 0, 1);
				glTranslatef(-itemCenter.x, -itemCenter.y, 0);

				DrawTexture(g_LootTextures[int(inventory.pItems[i].texture)], Point2f{ itemCenter.x - g_LootTextures[int(inventory.pItems[i].texture)].width / 2, itemCenter.y - g_LootTextures[int(inventory.pItems[i].texture)].height / 2 });

				glPopMatrix();

				glColor3f(1, 0, 0);
				//utils::FillEllipse(itemPos, 5, 5);
				//utils::FillEllipse(itemCenter, 5, 5);
			}

			if (inventory.equippedId == i)
			{
				DrawTexture(g_EquippedTexture, Point2f{ itemPos.x, itemPos.y + g_CellSize * (inventory.pItems[i].height - 1) });
			}
		}
	}

	bool isOutOfBounds{ false };
	bool severalItems{ false };
	int itemId{ -1 };

	GetHeldItemHoverResult(inventory, isOutOfBounds, severalItems, itemId);

	if (!isOutOfBounds)
	{
		if (severalItems)
		{
			glColor4f(105 / 255.f, 39 / 255.f, 39 / 255.f, 0.5f);
			utils::FillRect(Point2f{ curPos.x + inventory.hoveredX * g_CellTexture.width, (curPos.y - inventory.hoveredY * g_CellTexture.height) - (g_HeldItem.height * g_CellTexture.height) }, g_HeldItem.width * g_CellTexture.width, g_HeldItem.height * g_CellTexture.height);
		}
		else if (itemId != -1)
		{
			glColor4f(0.66f, 0.66f, 0.66f, 0.33f);
			utils::FillRect(Point2f{ curPos.x - g_CellBorder / 2 + inventory.pItems[itemId].x * g_CellTexture.width, (curPos.y - g_CellBorder / 2 - inventory.pItems[itemId].y * g_CellTexture.height) - (inventory.pItems[itemId].height * g_CellTexture.height) }, inventory.pItems[itemId].width * g_CellTexture.width + g_CellBorder, inventory.pItems[itemId].height * g_CellTexture.height + g_CellBorder);
		}
		else
		{
			glColor4f(40 / 255.f, 67 / 255.f, 39 / 255.f, 0.5f);
			utils::FillRect(Point2f{ curPos.x + inventory.hoveredX * g_CellTexture.width, (curPos.y - inventory.hoveredY * g_CellTexture.height) - (g_HeldItem.height * g_CellTexture.height) }, g_HeldItem.width * g_CellTexture.width, g_HeldItem.height * g_CellTexture.height);
		}
	}

	if (&inventory != &g_Player.npc.inventory)
	{
		Rectf closeButtonRect = inventory.GetCloseButtonRect();

		if (utils::IsPointInRect(g_MousePos, closeButtonRect))
		{
			DrawTexture(g_InvCloseButtonHoverTexture, Point2f{ closeButtonRect.left, closeButtonRect.bottom });
		}
		else
		{
			DrawTexture(g_InvCloseButtonTexture, Point2f{ closeButtonRect.left, closeButtonRect.bottom });
		}
	}
}

void GetHeldItemHoverResult(const Inventory& inventory, bool& isOutOfBounds, bool& severalItems, int& itemId)
{
	if (g_pHoveredInventory == &inventory && g_HeldItem.type != LootTypes::None)
	{
		if (inventory.InBounds(g_HeldItem, inventory.hoveredX, inventory.hoveredY))
		{
			isOutOfBounds = false;
			severalItems = false;
			itemId = -1;

			for (int r = inventory.hoveredY; r < inventory.hoveredY + g_HeldItem.height; r++)
			{
				for (int c = inventory.hoveredX; c < inventory.hoveredX + g_HeldItem.width; c++)
				{
					int index{ utils::GetIndex(r, c, inventory.cols) };
					if (inventory.pGrid[index] != -1)
					{
						if (itemId != -1)
						{
							if (itemId != inventory.pGrid[index])
							{
								severalItems = true;
							}
						}
						else
						{
							itemId = inventory.pGrid[index];
						}
					}
				}
			}
		}
		else
		{
			isOutOfBounds = true;
		}
	}
	else
	{
		isOutOfBounds = true;
	}
}

void TakeDamage(NPC& npc, int damage)
{
	npc.hp -= damage;

	if (npc.hp < 0)
	{
		npc.hp = 0;
	}

	if (!npc.isAlive())
	{
		for (int i = 0; i < g_pEvent->nrNPCs; i++)
		{
			if (g_pEvent->pNpcs[i].IsHostileTowards(&npc))
			{
				g_pEvent->pNpcs[i].RemoveHostile(&npc);
			}
		}
	}
}

void Attack(NPC& attacker, NPC& attacked)
{
	g_pEvent->ResetNPCReaction();

	UIText toAdd;

	//new line hack
	toAdd = UIText("                                                                                                    ");
	g_pEvent->AddNPCReaction(toAdd);

	// calculate damage
	int damage{};
	if (attacker.inventory.equippedId != -1)
	{
		Loot weapon = attacker.inventory.pItems[attacker.inventory.equippedId];

		damage = GetRandom(weapon.v1, weapon.v2 + 1) - attacked.defense;
	}
	else
	{
		damage = GetRandom(g_PunchMinDamage, g_PunchMaxDamage);
	}
	if (damage <= 0)
	{
		damage = 1;
	}

	// inflict damage
	TakeDamage(attacked, damage);

	// create npc response
	if (&attacker == &g_Player.npc)
	{
		if (!attacked.IsHostileTowards(&attacker))
		{
			attacked.AddHostile(&attacker);

			toAdd = UIText(attacked.name + "says:");
			g_pEvent->AddNPCReaction(toAdd);

			switch (attacked.type)
			{
			case NPCTypes::Scavenger:
				toAdd = UIText("\"How dare you attack a fellow adventurer!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Trader:
				toAdd = UIText("\"You shall regret attacking a member of the traders guild!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Guard:
				toAdd = UIText("\"You will not get away for this!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Rat:
				toAdd = UIText("\"Squeak!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Wolf:
				toAdd = UIText("\"GRRRRRR!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Bear:
				toAdd = UIText("\"ROAR!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			}
		}
		else
		{
			UIText toAdd;

			toAdd = UIText(attacked.name + "says:");
			g_pEvent->AddNPCReaction(toAdd);

			switch (attacked.type)
			{

			case NPCTypes::Scavenger:
				toAdd = UIText("\"Argh!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Trader:
				toAdd = UIText("\"Oof!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Guard:
				toAdd = UIText("\"Tch!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Rat:
				toAdd = UIText("\"Squeak!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Wolf:
				toAdd = UIText("\"Howl!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			case NPCTypes::Bear:
				toAdd = UIText("\"Growl(Painfull)!\"");
				g_pEvent->AddNPCReaction(toAdd);
				break;
			}
		}
	}

	if (g_pEvent->type == EventTypes::Town && attacker.type != NPCTypes::Guard)
	{
		for (int i = 0; i < g_pEvent->nrNPCs; i++)
		{
			if (g_pEvent->pNpcs[i].type == NPCTypes::Guard)
			{
				if (g_pEvent->pNpcs[i].IsHostileTowards(&attacker) == false)
				{
					g_pEvent->pNpcs[i].AddHostile(&attacker);
				}
			}
		}
	}

	g_pEvent->lastAction = g_pEvent->currentAction;
	g_pEvent->currentAction = Action(ActionTypes::Attack, &attacker, 1, &attacked, 1, nullptr, 0);

	if (!g_Player.npc.isAlive())
	{
		InitiateGameEnd(PLAYER_DIED);
	}
}

void CreateNewEvent(Event& event, EventTypes type)
{
	std::string name{};
	int nrNPCs{};
	int nrContainers{};

	switch (type)
	{
	case EventTypes::Town:
		name = g_Towns[GetRandom(0, g_NumberOfTowns)];
		nrNPCs = GetRandom(2, 5);
		break;
	case EventTypes::Encounter:
		name = g_EncounterPlaces[GetRandom(0, g_NumberOfEncounterPlaces)];
		nrNPCs = GetRandom(1, 4);
		break;
	case EventTypes::Scavenge:
		name = g_ScavengePlaces[GetRandom(0, g_NumberOfScavengePlaces)];
		nrNPCs = GetRandom(0, 2);
		nrContainers = GetRandom(1, 3);
		break;
	}

	event.type = type;
	event.name = name;

	CreateNPCs(event, nrNPCs);
	CreateContainers(event, nrContainers);
}

void NextEvent() // TODO Update in main code
{
	if (g_CurrentDay == g_MaxDays)
	{
		InitiateGameEnd(PLAYER_SURVIVED);
	}
	else if (g_Player.hunger < 0 || g_Player.thirst < 0)
	{
		InitiateGameEnd(PLAYER_DIED);
	}
	else
	{
		if (g_pEvent != nullptr && g_pEvent->IsInTown())
		{
			g_CurrentDay++;

			g_pEvent = &g_ExplorationEvents[g_CurrentDay];
			CreateNewEvent(*g_pEvent, EventTypes(GetRandom(int(EventTypes::Town) + 1, int(EventTypes::Count))));
		}
		else
		{
			g_pEvent = &g_TownEvents[g_CurrentDay];
			CreateNewEvent(*g_pEvent, EventTypes::Town);
		}

		g_pEvent->GenerateText();
	}
}

void Talk(NPC &npc)
{
	g_pEvent->ResetNPCReaction();

	UIText toAdd;
	int count{};

	//new line hack
	toAdd = UIText("                                                                                                    ");
	g_pEvent->AddNPCReaction(toAdd);

	toAdd = UIText(npc.name + " says:");
	g_pEvent->AddNPCReaction(toAdd);

	switch (npc.type)
	{
	case NPCTypes::Scavenger:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Hello stranger. Nice To meet you.\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"Prepare to meet your end!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Help me to defeat");
			g_pEvent->AddNPCReaction(toAdd);
			for (int i = 0; i < npc.nrOfHostileTowards; i++)
			{
				if (i != 0)
				{
					if (i == npc.nrOfHostileTowards - 1)
					{
						toAdd = UIText("and");
						g_pEvent->AddNPCReaction(toAdd);
					}
					else
					{
						toAdd = UIText(",");
						g_pEvent->AddNPCReaction(toAdd);
					}
				}
				toAdd = UIText(npc.pHostileTowards[i]->name);
				g_pEvent->AddNPCReaction(toAdd);
			}
			toAdd = UIText(".\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	case NPCTypes::Guard:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Don't talk to me. I am on duty!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"You chose the wrong town for your mischiefs!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Lend me your help to protect this town from");
			g_pEvent->AddNPCReaction(toAdd);
			for (int i = 0; i < npc.nrOfHostileTowards; i++)
			{
				if (i != 0)
				{
					if (i == npc.nrOfHostileTowards - 1)
					{
						toAdd = UIText("and");
						g_pEvent->AddNPCReaction(toAdd);
					}
					else
					{
						toAdd = UIText(",");
						g_pEvent->AddNPCReaction(toAdd);
					}
				}
				toAdd = UIText(npc.pHostileTowards[i]->name);
			}
			toAdd = UIText(".\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	case NPCTypes::Trader:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Hello. Would you like to buy something?\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"You shall regret trying to rob me!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Protect my goods from");
			g_pEvent->AddNPCReaction(toAdd);
			for (int i = 0; i < npc.nrOfHostileTowards; i++)
			{
				if (i != 0)
				{
					if (i == npc.nrOfHostileTowards - 1)
					{
						toAdd = UIText("and");
						g_pEvent->AddNPCReaction(toAdd);
					}
					else
					{
						toAdd = UIText(",");
						g_pEvent->AddNPCReaction(toAdd);
					}
				}
				toAdd = UIText(npc.pHostileTowards[i]->name);
				g_pEvent->AddNPCReaction(toAdd);

			}
			toAdd = UIText("!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	case NPCTypes::Rat:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Squeak?\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"SQUEAK!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Squeak Squeak!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	case NPCTypes::Wolf:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Owooooo.\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"Grrrrrrh!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Owooooo!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	case NPCTypes::Bear:
		if (npc.pHostileTowards == nullptr)
		{
			toAdd = UIText("\"Growl.\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else if (npc.IsHostileTowards(&g_Player.npc))
		{
			toAdd = UIText("\"ROAR!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		else
		{
			toAdd = UIText("\"Roar!\"");
			g_pEvent->AddNPCReaction(toAdd);
		}
		break;
	}

	g_pEvent->lastAction = g_pEvent->currentAction;
	g_pEvent->currentAction = Action(ActionTypes::Talk, &g_Player.npc, 1, &npc, 1, nullptr, 0);
}

void Trade(NPC &npc)
{
	g_pEvent->ResetNPCReaction();

	UIText toAdd;
	int count{};

	//new line hack
	toAdd = UIText("                                                                                                    ");
	g_pEvent->AddNPCReaction(toAdd);

	toAdd = UIText(npc.name + " says:");
	g_pEvent->AddNPCReaction(toAdd);

	if (npc.IsHostileTowards(&g_Player.npc))
	{
		switch (npc.type)
		{
		case NPCTypes::Scavenger:
			toAdd = UIText("\"I would never trade with you!\"");
			break;
		case NPCTypes::Guard:
			toAdd = UIText("\"As if i'd trade with a criminal!\"");
			break;
		case NPCTypes::Trader:
			toAdd = UIText("\"You shall never get my goods!\"");
			break;
		case NPCTypes::Rat:
			toAdd = UIText("\"Squeak?\"");
			break;
		case NPCTypes::Wolf:
			toAdd = UIText("\"Bark?\"");
			break;
		case NPCTypes::Bear:
			toAdd = UIText("\"Growl?\"");
			break;
		}
	}
	else
	{
		switch (npc.type)
		{
		case NPCTypes::Scavenger:
			toAdd = UIText("\"Sure let's do it!\"");
			break;
		case NPCTypes::Guard:
			toAdd = UIText("\"As if i'd trade with a criminal!\"");
			break;
		case NPCTypes::Trader:
			toAdd = UIText("\"You shall never get my goods!\"");
			break;
		case NPCTypes::Rat:
			toAdd = UIText("\"Squeak?\"");
			break;
		case NPCTypes::Wolf:
			toAdd = UIText("\"Bark?\"");
			break;
		case NPCTypes::Bear:
			toAdd = UIText("\"Growl?\"");
			break;
		default:
			break;
		}
		g_pEvent->AddNPCReaction(toAdd);
		g_pEvent->pDisplayedInventory = &npc.inventory;
	}

	g_pEvent->lastAction = g_pEvent->currentAction;
	g_pEvent->currentAction = Action(ActionTypes::Trade, &g_Player.npc, 1, &npc, 1, nullptr, 0); // TODO remove empty Loot
}

void Steal(NPC& npc)
{
	g_pEvent->ResetNPCReaction();

	UIText toAdd;
	int count{};

	//new line hack
	toAdd = UIText("                                                                                                    ");
	g_pEvent->AddNPCReaction(toAdd);

	if (!npc.isAlive())
	{
		g_pEvent->pDisplayedInventory = &npc.inventory;
	}
	else if (GetRandom(0, 2))
	{
		toAdd = UIText("Your try to steal was succesfull!");
		g_pEvent->AddNPCReaction(toAdd);

		g_pEvent->pDisplayedInventory = &npc.inventory;
	}
	else
	{
		toAdd = UIText(npc.name + " says:");
		g_pEvent->AddNPCReaction(toAdd);

		switch (npc.type)
		{
		case NPCTypes::Scavenger:
			toAdd = UIText("\"Nobody is gonna steal from me!\"");
			break;
		case NPCTypes::Guard:
			toAdd = UIText("\"This was a bad decision friend!\"");
			break;
		case NPCTypes::Trader:
			toAdd = UIText("\"How dare you! Prepare to die for that!\"");
			break;
		case NPCTypes::Rat:
			toAdd = UIText("\"Squeak!\"");
			break;
		case NPCTypes::Wolf:
			toAdd = UIText("\"Bark!\"");
			break;
		case NPCTypes::Bear:
			toAdd = UIText("\"Growl!\"");
			break;
		default:
			break;
		}
		g_pEvent->AddNPCReaction(toAdd);

		if (!npc.IsHostileTowards(&g_Player.npc))
		{
			npc.AddHostile(&g_Player.npc);
		}
	}

	g_pEvent->lastAction = g_pEvent->currentAction;
	g_pEvent->currentAction = Action(ActionTypes::Steal, &g_Player.npc, 1, &npc, 1, nullptr, 0);
}

void Heal(NPC& npc, int amount)
{
	if (npc.hp + amount > npc.maxHp)
	{
		npc.hp = npc.maxHp;
	}
	else
	{
		npc.hp += amount;
	}
}

void Eat(Loot & food)
{
	if (food.type == LootTypes::Food)
	{
		Heal(g_Player.npc, food.v1);

		if (g_Player.hunger + food.v1 > g_Player.maxHunger)
		{
			g_Player.hunger = g_Player.maxHunger;
		}
		else
		{
			g_Player.hunger += food.v1;
		}

		g_Player.GetInventory().Remove(&food);
	}
}

void Drink(Loot & drink)
{
	if (drink.type == LootTypes::Drink)
	{
		Heal(g_Player.npc, drink.v1);

		if (g_Player.thirst + drink.v1 > g_Player.maxThirst)
		{
			g_Player.thirst = g_Player.maxThirst;
		}
		else
		{
			g_Player.thirst += drink.v1;
		}

		g_Player.GetInventory().Remove(&drink);
	}
}

void ProcessNPCActs()
{
	for (int i = 0; i < g_pEvent->nrNPCs; i++)
	{
		if (g_pEvent->pNpcs[i].isAlive())
		{
			if (g_pEvent->pNpcs[i].nrOfHostileTowards > 0)
			{
				Attack(g_pEvent->pNpcs[i], *g_pEvent->pNpcs[i].pHostileTowards[0]);
			}
		}
	}

	if (g_pEvent->type != EventTypes::None)
	{
		g_pEvent->GenerateText();
	}

	std::string endString{};
	for (int i = 0; i < g_pEvent->nrNPCs; i++)
	{
		if (g_pEvent->pNpcs[i].isAlive())
		{
			if (g_pEvent->pNpcs[i].nrOfHostileTowards > 0)
			{
				if (i == g_pEvent->nrNPCs - 1)
				{
					endString = ".";
				}
				else
				{
					endString = ",";
				}

				UIText toAdd(g_pEvent->pNpcs[i].name + " attacked " + g_pEvent->pNpcs[i].pHostileTowards[0]->name + endString);
				g_pEvent->AddUIText(toAdd);
			}
		}
	}
}

void CreateContainers(Event& event, int nrContainers)
{
	event.nrContainers = nrContainers;
	if (nrContainers > 0)
	{
		event.pContainers = new Container[nrContainers];

		for (int i = 0; i < nrContainers; i++)
		{
			if (GetRandom(0, 2) == 0)
			{
				event.pContainers[i].Init("chest", Inventory(5, 3));

				if (GetRandom(0, 2) == 0)
				{
					Loot waterBottle(LootTextures::WATER);
					if (GetRandom(0, 2) == 0)
					{
						waterBottle.Rotate();
					}
					event.pContainers[i].inventory.Add(waterBottle, 0, 0);
				}
				else
				{
					Loot apple(LootTextures::APPLE);
					event.pContainers[i].inventory.Add(apple, 0, 0);
				}
			}
			else
			{
				event.pContainers[i].Init("garbage bag", Inventory(2, 4));

				if (GetRandom(0, 2) == 0)
				{
					Loot waterBottle(LootTextures::WATER);
					if (GetRandom(0, 2) == 0)
					{
						waterBottle.Rotate();
					}
					event.pContainers[i].inventory.Add(waterBottle, 0, 0);
				}
				else
				{
					Loot apple(LootTextures::APPLE);
					event.pContainers[i].inventory.Add(apple, 0, 0);
				}
			}
		}
	}
}

void CreateNPCs(Event& event, int nrOfNPCs)
{
	event.pNpcs = new NPC[nrOfNPCs];
	event.nrNPCs = nrOfNPCs;

	Loot* bestWeapon;

	switch (event.type)
	{
	case EventTypes::Town:

		event.pNpcs[0].Init(GetRandom(NPCTypes::Scavenger, NPCTypes::Trader));
		for (int i = 1; i < nrOfNPCs; i++)
		{
			event.pNpcs[i].Init(GetRandom(NPCTypes::Scavenger, NPCTypes::Guard));

			Loot loot;
			int random{ GetRandom(0, 5) };
			switch (random)
			{
			case 0:
				loot = Loot(LootTextures::USP);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 1:
				loot = Loot(LootTextures::GLOCK17);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 2:
				loot = Loot(LootTextures::WATER);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 3:
				loot = Loot(LootTextures::APPLE);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 4:
				loot = Loot(LootTextures::MACHETE);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			}

			if (event.pNpcs[i].inventory.GetBestWeapon(bestWeapon))
			{
				event.pNpcs[i].inventory.Equip(bestWeapon);
			}

			Loot gold = Loot(LootTextures::GOLD);
			gold.value = GetRandom(1, 300);
			event.pNpcs[i].inventory.Add(gold, event.pNpcs[i].inventory.cols - 1, event.pNpcs[i].inventory.rows - 1);
		}
		break;
	case EventTypes::Scavenge:
	case EventTypes::Encounter:
		for (int i = 0; i < nrOfNPCs; i++)
		{
			NPCTypes npcType;

			do
			{
				npcType = GetRandom(NPCTypes::Scavenger, NPCTypes::Bear);
			} while (npcType == NPCTypes::Guard);

			event.pNpcs[i].Init(npcType);

			Loot loot;
			int random{ GetRandom(0, 5) };
			switch (random)
			{
			case 0:
				loot = Loot(LootTextures::USP);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 1:
				loot = Loot(LootTextures::GLOCK17);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 2:
				loot = Loot(LootTextures::WATER);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 3:
				loot = Loot(LootTextures::APPLE);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			case 4:
				loot = Loot(LootTextures::MACHETE);
				event.pNpcs[i].inventory.Add(loot, 0, 0);
				break;
			}

			if (event.pNpcs[i].inventory.GetBestWeapon(bestWeapon))
			{
				event.pNpcs[i].inventory.Equip(bestWeapon);
			}

			//Loot gold = Loot(LootTextures::GOLD);
			//gold.value = GetRandom(1, 300);
			//event.pNpcs[i].inventory.Add(gold, event.pNpcs[i].inventory.cols - 1, event.pNpcs[i].inventory.rows - 1);
		}
		break;
	}
}

void InitiateGameEnd(bool hasSurvived)
{
	CreateNewEvent(*g_pEvent, EventTypes::None);

	//new line hack
	UIText toAdd = UIText("                                                                                                    ");
	(*g_pEvent).AddUIText(toAdd);

	if (hasSurvived)
	{
		if (g_pEvent->pUiTexts != nullptr)
		{
			delete[](*g_pEvent).pUiTexts;
			(*g_pEvent).pUiTexts = nullptr;

			(*g_pEvent).nrOfUiTexts = 0;
		}

		toAdd = UIText("Congratulations!");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("reached");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("the");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("end");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("of");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("the");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("Game!");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("survived");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("for");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText(std::to_string(g_CurrentDay));
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("days!");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("Accumulated");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("std::to_string(g_Player.GetGold())"); // TODO Player gold
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("before");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("the");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("end");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("of");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("the");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText(std::to_string(g_MaxDays));
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("days!");
		(*g_pEvent).AddUIText(toAdd);
	}
	else
	{
		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		UIText toAdd = UIText("died.");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("survived");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("for");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText(std::to_string(g_CurrentDay));
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("days.");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("You");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("Accumulated");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText(std::to_string(g_Player.GetGold())); // TODO Player gold
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("before");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("your");
		(*g_pEvent).AddUIText(toAdd);

		toAdd = UIText("death!");
		(*g_pEvent).AddUIText(toAdd);
	}
}

void ShowInformations()
{
	std::cout << " -- GAME INFORMATION --\n";
	std::cout << " This game was made by Nir Yomtov and Christian Fedrau\n\n";
	std::cout << " -- CONTROLS --\n";
	std::cout << " Hover over a colored text to see your options.\n";
	std::cout << " Right-click to rotate through your possible action.\n";
	std::cout << " Left-click to perform your selected action.\n"; // TODO Write more (Equiping/using Items)
	std::cout << " Your actions are: ATTACK, STEAL, TRADE and TALK.\n";
	std::cout << " Press N to advance to the next location.\n\n";
	std::cout << " -- DESCRIPTION --\n";
	std::cout << " In this game your objective is to gain as much money as possible within 30 days.\n";
	std::cout << " You can achieve this by trading, selling things, stealing and killing.\n";
	std::cout << " Other Characters will of course not like it if u do bad things to them.\n";
	//std::cout << " Every day is seperated into 2 Parts.\n";
	//std::cout << " The morning in which you are in a city and the exploration.\n";
	std::cout << " The text on the screen gives you a description of the scene you are in.\n";
	std::cout << " Green names are friendly characters while red ones are hostile towards you.\n";
}

int GetRandom(int min, int max)
{
	return rand() % (max - min) + min;
}

NPCTypes GetRandom(NPCTypes min, NPCTypes max)
{
	return NPCTypes(rand() % (int(max) + 1 - int(min)) + int(min));
}

void ClearBackground()
{
	glClearColor(0, 0, 0, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT);
}
#pragma endregion gameImplementations

#pragma region coreImplementations
void Initialize()
{
	//Initialize SDL
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		QuitOnSDLError();
	}

	//Use OpenGL 2.1
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
	SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

	//Create window
	g_pWindow = SDL_CreateWindow(
		g_WindowTitle.c_str(),
		SDL_WINDOWPOS_CENTERED,
		SDL_WINDOWPOS_CENTERED,
		int(g_WindowWidth),
		int(g_WindowHeight),
		SDL_WINDOW_OPENGL);

	if (g_pWindow == nullptr)
	{
		QuitOnSDLError();
	}

	// Create an opengl context and attach it to the window 
	g_pContext = SDL_GL_CreateContext(g_pWindow);
	if (g_pContext == nullptr)
	{
		QuitOnSDLError();
	}

	if (g_IsVSyncOn)
	{
		// Synchronize buffer swap with the monitor's vertical refresh
		if (SDL_GL_SetSwapInterval(1) < 0)
		{
			QuitOnSDLError();
		}
	}
	else
	{
		SDL_GL_SetSwapInterval(0);
	}

	// Initialize Projection matrix
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	// Set the clipping (viewing) area's left, right, bottom and top
	gluOrtho2D(0, g_WindowWidth, 0, g_WindowHeight);

	// The viewport is the rectangular region of the window where the image is drawn.
	// Set it to the entire client area of the created window
	glViewport(0, 0, int(g_WindowWidth), int(g_WindowHeight));

	//Initialize Modelview matrix
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Enable color blending and use alpha blending
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	//Initialize PNG loading
	int imgFlags = IMG_INIT_PNG;
	if (!(IMG_Init(imgFlags) & imgFlags))
	{
		QuitOnImageError();
	}

	//Initialize SDL_ttf
	if (TTF_Init() == -1)
	{
		QuitOnTtfError();
	}

}
void Run()
{
	//Main loop flag
	bool quit{ false };

	// Set start time
	std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();

	InitGameResources();

	//The event loop
	SDL_Event e{};
	while (!quit)
	{
		// Poll next event from queue
		while (SDL_PollEvent(&e) != 0)
		{
			// Handle the polled event
			switch (e.type)
			{
			case SDL_QUIT:
				//std::cout << "\nSDL_QUIT\n";
				quit = true;
				break;
			case SDL_KEYDOWN:
				ProcessKeyDownEvent(e.key);
				break;
			case SDL_KEYUP:
				ProcessKeyUpEvent(e.key);
				break;
			case SDL_MOUSEMOTION:
				ProcessMouseMotionEvent(e.motion);
				break;
			case SDL_MOUSEBUTTONDOWN:
				ProcessMouseDownEvent(e.button);
				break;
			case SDL_MOUSEBUTTONUP:
				ProcessMouseUpEvent(e.button);
				break;
			default:
				//std::cout << "\nSome other event\n";
				break;
			}
		}

		if (!quit)
		{

			// Calculate elapsed time
			// Get current time
			std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
			// Calculate elapsed time
			float elapsedSeconds = std::chrono::duration<float>(t2 - t1).count();
			// Update current time
			t1 = t2;
			// Prevent jumps in time caused by break points
			if (elapsedSeconds > g_MaxElapsedTime)
			{
				elapsedSeconds = g_MaxElapsedTime;
			}

			// Call update function, using time in seconds (!)
			Update(elapsedSeconds);

			// Draw in the back buffer
			Draw();

			// Update screen: swap back and front buffer
			SDL_GL_SwapWindow(g_pWindow);
		}
	}
	FreeGameResources();
}

void Cleanup()
{
	SDL_GL_DeleteContext(g_pContext);

	SDL_DestroyWindow(g_pWindow);
	g_pWindow = nullptr;

	SDL_Quit();
}

void QuitOnSDLError()
{
	std::cout << "Problem during SDL initialization: ";
	std::cout << SDL_GetError();
	std::cout << std::endl;
	Cleanup();
	exit(-1);
}

void QuitOnOpenGlError()
{
	std::cout << "Problem during OpenGL initialization: ";
	std::cout << SDL_GetError();
	std::cout << std::endl;
	Cleanup();
	exit(-1);
}

void QuitOnImageError()
{
	std::cout << "Problem during SDL_image initialization: ";
	std::cout << IMG_GetError();
	std::cout << std::endl;
	Cleanup();
	exit(-1);
}

void QuitOnTtfError()
{
	std::cout << "Problem during SDL_ttf initialization: ";
	std::cout << TTF_GetError();
	std::cout << std::endl;
	Cleanup();
	exit(-1);
}
#pragma endregion coreImplementations

#pragma region textureImplementations

bool TextureFromFile(const std::string& path, Texture & texture)
{
	//Load file for use as an image in a new surface.
	SDL_Surface* pLoadedSurface = IMG_Load(path.c_str());
	if (pLoadedSurface == nullptr)
	{
		std::cerr << "TextureFromFile: SDL Error when calling IMG_Load: " << SDL_GetError() << std::endl;
		return false;
	}

	TextureFromSurface(pLoadedSurface, texture);

	//Free loaded surface
	SDL_FreeSurface(pLoadedSurface);

	return true;
}

bool TextureFromString(const std::string & text, const std::string& fontPath, int ptSize, const Color4f & textColor, Texture & texture)
{
	// Create font
	TTF_Font *pFont{};
	pFont = TTF_OpenFont(fontPath.c_str(), ptSize);
	if (pFont == nullptr)
	{
		std::cout << "TextureFromString: Failed to load font! SDL_ttf Error: " << TTF_GetError();
		std::cin.get();
		return false;
	}

	// Create texture using this fontand close font afterwards
	bool textureOk = TextureFromString(text, pFont, textColor, texture);
	TTF_CloseFont(pFont);

	return textureOk;
}

bool TextureFromString(const std::string & text, TTF_Font *pFont, const Color4f & color, Texture & texture)
{
	//Render text surface
	SDL_Color textColor{};
	textColor.r = Uint8(color.r * 255);
	textColor.g = Uint8(color.g * 255);
	textColor.b = Uint8(color.b * 255);
	textColor.a = Uint8(color.a * 255);

	SDL_Surface* pLoadedSurface = TTF_RenderText_Blended(pFont, text.c_str(), textColor);
	//SDL_Surface* pLoadedSurface = TTF_RenderText_Solid(pFont, textureText.c_str(), textColor);
	if (pLoadedSurface == nullptr)
	{
		std::cerr << "TextureFromString: Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << '\n';
		return false;
	}

	// copy to video memory
	TextureFromSurface(pLoadedSurface, texture);

	//Free loaded surface
	SDL_FreeSurface(pLoadedSurface);

	return true;
}

void TextureFromSurface(const SDL_Surface *pSurface, Texture & texture)
{
	//Get image dimensions
	texture.width = float(pSurface->w);
	texture.height = float(pSurface->h);

	// Get pixel format information and translate to OpenGl format
	GLenum pixelFormat{ GL_RGB };
	switch (pSurface->format->BytesPerPixel)
	{
	case 3:
		if (pSurface->format->Rmask == 0x000000ff)
		{
			pixelFormat = GL_RGB;
		}
		else
		{
			pixelFormat = GL_BGR;
		}
		break;
	case 4:
		if (pSurface->format->Rmask == 0x000000ff)
		{
			pixelFormat = GL_RGBA;
		}
		else
		{
			pixelFormat = GL_BGRA;
		}
		break;
	default:
		std::cerr << "TextureFromSurface error: Unknow pixel format, BytesPerPixel: " << pSurface->format->BytesPerPixel << "\nUse 32 bit or 24 bit images.\n";;
		texture.width = 0;
		texture.height = 0;
		return;
	}

	//Generate an array of textures.  We only want one texture (one element array), so trick
	//it by treating "texture" as array of length one.
	glGenTextures(1, &texture.id);

	//Select (bind) the texture we just generated as the current 2D texture OpenGL is using/modifying.
	//All subsequent changes to OpenGL's texturing state for 2D textures will affect this texture.
	glBindTexture(GL_TEXTURE_2D, texture.id);

	// check for errors.
	GLenum e = glGetError();
	if (e != GL_NO_ERROR)
	{
		std::cerr << "TextureFromSurface, error binding textures, Error id = " << e << '\n';
		texture.width = 0;
		texture.height = 0;
		return;
	}

	//Specify the texture's data.  This function is a bit tricky, and it's hard to find helpful documentation.  A summary:
	//   GL_TEXTURE_2D:    The currently bound 2D texture (i.e. the one we just made)
	//               0:    The mipmap level.  0, since we want to update the base level mipmap image (i.e., the image itself,
	//                         not cached smaller copies)
	//         GL_RGBA:    Specifies the number of color components in the texture.
	//                     This is how OpenGL will store the texture internally (kinda)--
	//                     It's essentially the texture's type.
	//      surface->w:    The width of the texture
	//      surface->h:    The height of the texture
	//               0:    The border.  Don't worry about this if you're just starting.
	//     pixelFormat:    The format that the *data* is in--NOT the texture! 
	//GL_UNSIGNED_BYTE:    The type the data is in.  In SDL, the data is stored as an array of bytes, with each channel
	//                         getting one byte.  This is fairly typical--it means that the image can store, for each channel,
	//                         any value that fits in one byte (so 0 through 255).  These values are to be interpreted as
	//                         *unsigned* values (since 0x00 should be dark and 0xFF should be bright).
	// surface->pixels:    The actual data.  As above, SDL's array of bytes.
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pSurface->w, pSurface->h, 0, pixelFormat, GL_UNSIGNED_BYTE, pSurface->pixels);

	//Set the minification and magnification filters.  In this case, when the texture is minified (i.e., the texture's pixels (texels) are
	//*smaller* than the screen pixels you're seeing them on, linearly filter them (i.e. blend them together).  This blends four texels for
	//each sample--which is not very much.  Mipmapping can give better results.  Find a texturing tutorial that discusses these issues
	//further.  Conversely, when the texture is magnified (i.e., the texture's texels are *larger* than the screen pixels you're seeing
	//them on), linearly filter them.  Qualitatively, this causes "blown up" (overmagnified) textures to look blurry instead of blocky.
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}

void DeleteTexture(Texture & texture)
{
	glDeleteTextures(1, &texture.id);
}

void DrawTexture(const Texture & texture, const Point2f& bottomLeftVertex, const Rectf & sourceRect)
{
	Rectf destinationRect{ bottomLeftVertex.x, bottomLeftVertex.y, sourceRect.width, sourceRect.height };
	DrawTexture(texture, destinationRect, sourceRect);
}

void DrawTexture(const Texture & texture, const Rectf & destinationRect, const Rectf & sourceRect)
{
	// Determine texture coordinates, default values = draw complete texture
	float textLeft{};
	float textRight{ 1.0f };
	float textTop{};
	float textBottom{ 1.0f };
	if (sourceRect.width > 0.0f && sourceRect.height > 0.0f) // Clip specified, convert them to the range [0.0, 1.0]
	{
		textLeft = sourceRect.left / texture.width;
		textRight = (sourceRect.left + sourceRect.width) / texture.width;
		textTop = (sourceRect.bottom - sourceRect.height) / texture.height;
		textBottom = sourceRect.bottom / texture.height;
	}

	// Determine vertex coordinates
	float vertexLeft{ destinationRect.left };
	float vertexBottom{ destinationRect.bottom };
	float vertexRight{};
	float vertexTop{};
	if (!(destinationRect.width > 0.0f && destinationRect.height > 0.0f)) // If no size specified use size of texture
	{
		vertexRight = vertexLeft + texture.width;
		vertexTop = vertexBottom + texture.height;
	}
	else
	{
		vertexRight = vertexLeft + destinationRect.width;
		vertexTop = vertexBottom + destinationRect.height;

	}

	// Tell opengl which texture we will use
	glBindTexture(GL_TEXTURE_2D, texture.id);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

	// Draw
	glEnable(GL_TEXTURE_2D);
	{
		glBegin(GL_QUADS);
		{
			glTexCoord2f(textLeft, textBottom);
			glVertex2f(vertexLeft, vertexBottom);

			glTexCoord2f(textLeft, textTop);
			glVertex2f(vertexLeft, vertexTop);

			glTexCoord2f(textRight, textTop);
			glVertex2f(vertexRight, vertexTop);

			glTexCoord2f(textRight, textBottom);
			glVertex2f(vertexRight, vertexBottom);
		}
		glEnd();
	}
	glDisable(GL_TEXTURE_2D);

}
#pragma endregion textureImplementations