// 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 #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 #include #include #include #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(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