From f4ea9a854b1b940082a37aa0a24614ee3b31b517 Mon Sep 17 00:00:00 2001 From: Attenzes Date: Sat, 31 Mar 2018 15:00:36 +0800 Subject: [PATCH] 0.4.0 Events Update --- CHANGELOG.txt | 101 ++- Data.dat | 32 +- Events.dat | 73 +++ Quest!/DataLoader.cpp | 29 +- Quest!/DataLoader.h | 19 +- Quest!/EventMsgHandler.cpp | 21 +- Quest!/EventMsgHandler.h | 4 +- Quest!/Events.cpp | 120 ++++ Quest!/Events.h | 96 ++- Quest!/Game.cpp | 1090 ++++++++++++++++++++++----------- Quest!/Game.h | 41 +- Quest!/Inventory.cpp | 88 +++ Quest!/Inventory.h | 26 + Quest!/ItemsAndEntities.cpp | 156 ----- Quest!/ItemsAndEntities.h | 76 +-- Quest!/Merchant.cpp | 10 + Quest!/{NPCs.h => Merchant.h} | 30 +- Quest!/MiscFunctions.cpp | 2 +- Quest!/NPCs.cpp | 64 -- Quest!/Player.cpp | 96 +++ Quest!/Player.h | 36 ++ 21 files changed, 1439 insertions(+), 771 deletions(-) create mode 100644 Events.dat create mode 100644 Quest!/Events.cpp create mode 100644 Quest!/Inventory.cpp create mode 100644 Quest!/Inventory.h create mode 100644 Quest!/Merchant.cpp rename Quest!/{NPCs.h => Merchant.h} (56%) delete mode 100644 Quest!/NPCs.cpp create mode 100644 Quest!/Player.cpp create mode 100644 Quest!/Player.h diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 32d93fa..fe3461b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,75 +1,46 @@ -Version 0.3.0 RPG Update -01 Feb 2018 +Version 0.4.0 Events Update +06 Feb 2018 -------------------------------------------------- -Description: Third major version of the game. -This update will introduce many features typical of -an RPG game, such as levels, defense values, a basic -economy and NPCs, as well as customizable options +Description: Fourth major version of the game. +This update adds customization of event messages, +when interacting with items/entities, and allows +defining custom descriptions upon inspection. +This update also features a better atk/def algorithm. Data: -- Additions: Items now have a new data member "value" to add (Default value of item in shop) -- Additions: Mobs now have a new data member "gold" to add (Amount it will drop on death) -- Changes: Item type should now be placed before the name +- Additions: Threats now have an atk stat +- Changes: Player can now have a variable number of inventory slots +- Changes: Starting Exp is now evaluated and the player levelled up accordingly +- Fixes: Typo in comment description of min/max dmg for mobs and threats Options: -- Additions: New file for custom starting options like map size, position of magical potion/player, max time to complete game +- Additions: Magic potion can now be defined to have a random starting position determined by the game +- Additions: Player can now be defined to have a random starting position determined by the game NPCs: -- Additions: New file for creating NPCs in the game (Only the merchant for now) -Notes: Merchants can have a maximum of 9 inventory slots +- Additions: NPCs can now be defined to have a random starting position determined by the game -Gameplay: -- Additions: Items can now be inspected to yield a description (To be expanded on in future Events update) -- Additions: Players now have exp and levels that scale their stats -- Additions: Attack value of attacker is compared to defence value of defender to determine effective damage -(^^ Atk and def must be defined to be at least 1) -- Additions: Items now have a functioning success rate (Can now fail to be used) -- Additions: Mobs now drop gold and exp -- Additions: There is now a merchant NPC that will buy and sell items (Basic trading) -- Additions: Unusable item types (Can only be sold) -- Additions: Game now tells the player what his/her roll on whether his/her use of item is successful is (Not for running) -- Changes: UI Overhaul, with more sub-menus for each type of action as opposed to a specific number for each specific action -- Changes: Game now prints map after defeating a mob -- Changes: Changed text describing number of uses left for an item -- Changes: Minor text alignment changes -- Fixes: Typo when printing that player will constantly take damage while on same tile as a threat -- Fixes: Undefined behaviour when choosing an item slot with an item that is not a healing item in encounter with threat -- Fixes: Player character did not appear on screen after defeating a mob, then checking surroundings -- Fixes: Player can try to move right or down out of the map, resulting in a crash -- Fixes: Picking up the magical potion resulted in the game displaying as having medicine in the inventory -- Fixes: Inputting invalid option in swap item menu, game returns to movement/inventory/etc menu without telling user -- Fixes: Game did not print player position if player chooses to go back from swap item menu -- Fixes: Threat can no longer spawn on the same tile as the magical potion - -Additional notes: -- Initial values of player's atk and def can be changed in Data.dat but currently the levelled up values are fixed -- If the player starts with more than 5 exp, the level will not updated until the player gains some exp -- The current atk/def algorithm needs work, a simple one has currently been implemented as a trial -- Position of merchant is not checked if it has a conflict with other merchants/players -- There is a chance that if the number of entities to be generated is directly equal to that of the mapsize, and the last -entity to be generated is a threat, and the last tile without an entity is the magical potion, game could infinite loop (unconfirmed) -- All these will be fixed in a future update - -Version 0.3.1 Code Patch -02 Feb 2018 --------------------------------------------------- -Description: Small patch with a few minor fixes, -mostly not intended to add or change features, -focuses on rebuilding some parts of the game with -revised source code - -Data: -- Fixes: Will not infinite loop if number of entities to be generated equal to mapsize - -NPCs: -- Fixes: Items generated in inventories were mistakenly added to the check if there were too many items to generate for mapsize -- Fixes: Conflicts in coordinates of NPCs will now be flagged up +Events: +- Additions: Events file where specific event messages can be defined for each item/npc/mob/threat Gameplay: -- Changes: Some messages such as printing what objects are on the same tile as the player are changed -- Changes: Game now prints if there is nothing on the tile -- Changes: Game now prints time passed before any other events -- Changes: Game now prints more specific event messages when picking up/swapping/dropping items -- Changes: Game used to display only how much a healing item would have healed, not actually heal (If player at/near max health) -- Fixes: Game message says that there is no item in that inventory slot if user inspects a tile with nothing on it -- Fixes: Game did not reprint map after using item despite not being in encounter \ No newline at end of file +- Additions: Can now inspect entities, threats and NPCs +- Additions: Mobs and threats' min dmg and max dmg, def, level details now available to be seen in-game through inspection +- Additions: Can now inspect items in merchants' inventories in the buy menu +- Additions: New atk/def dmg bonus calculation algorithm +- Changes: Inventory is now listed from top down then left to right instead of left to right then top down +- Changes: Inventory is now printed in the buy menu +- Fixes: The atk/def mechanism did not include when mobs attack the player +- Fixes: Typo in message displaying additional damage done due to greater attack stat +- Fixes: No longer displays old menu in encounter with threat despite taking inputs for the new menu +- Fixes: No longer asks if player wants to use inventory slot 1, 2, 3 or 4 but which inventory slot to use when picking up item +- Fixes: Game does not reprint map after going use inventory/inspect menu +- Fixes: Small typo in game over message +- Fixes: Player could use the save option and "escape" an encounter, being on same tile but able to move and not receive damage + +Bugs: +- If the end code is missed out in the data files, data loader will try to load same object as previous one loaded + +Additional notes: +- Mobs each have a "level" but it currently doesn't do anything and thats as intended for now. +- This will be worked on in the future. \ No newline at end of file diff --git a/Data.dat b/Data.dat index 2ab5b40..b11a5cc 100644 --- a/Data.dat +++ b/Data.dat @@ -61,11 +61,10 @@ within the slashes/ //Starting Defense value (At least 1) //Starting EXP (At least 0) //Starting level (At least 1) -//Starting inventory slot 1 item ID (-1 for nothing, 0 for first item defined above, item_typeid) -//Starting inventory slot 2 item ID (-1 for nothing, 0 for first item defined above, item_typeid) -//Starting inventory slot 3 item ID (-1 for nothing, 0 for first item defined above, item_typeid) -//Starting inventory slot 4 item ID (-1 for nothing, 0 for first item defined above, item_typeid) -//Starting gold +//Starting gold (At least 0) +//Number of inventory slots +//Starting inventory slot 1 item ID (-1 for nothing, 0 for first baseitem/item defined above, item_typeid) +//Other inventory slot item IDs here... /Player code/ 1 /Name of Player/ "Player" @@ -75,11 +74,14 @@ within the slashes/ /Def/ 1 /EXP/ 0 /Level/ 1 -/Inv slot 1 item ID/ -1 -/Inv slot 1 item ID/ 2 -/Inv slot 1 item ID/ 0 -/Inv slot 1 item ID/ 1 /Gold/ 30 +/Inventory size/ 6 +/Inv slot 1 item ID/ 1 +/Inv slot 2 item ID/ 1 +/Inv slot 3 item ID/ 2 +/Inv slot 4 item ID/ 2 +/Inv slot 5 item ID/ 0 +/Inv slot 6 item ID/ -1 //Syntax for Mobs: //Code 2 @@ -88,8 +90,8 @@ within the slashes/ //Starting HP //Starting Attack value //Starting Defense value (At least 1) -//Min heal/damage amount -//Max heal/damage amount +//Min damage amount +//Max damage amount //Starting EXP (At least 0) //Starting level (At least 1) //Player's run chance from this mob (From 0 to 100 up to 5 dp) @@ -101,7 +103,7 @@ within the slashes/ /Max HP/ 50 /HP/ 50 /Atk/ 1 -/Def/ 1 +/Def/ 2 /Min damage/ 12 /Max damage/ 20 /EXP/ 5 @@ -113,13 +115,15 @@ within the slashes/ //Syntax for Threats: //Code 3 //Name of threat within double quotes -//Min heal/damage amount -//Max heal/damage amount +//Attack value +//Min damage amount +//Max damage amount //Player's run chance from this threat (From 0 to 100 up to 5 dp) //Number of this threat to spawn on the map /Threat code/ 3 /Name of Threat/ "Threat" +/Atk/ 3 /Min damage/ 10 /Max damage/ 10 /Run chance/ 60.0 diff --git a/Events.dat b/Events.dat new file mode 100644 index 0000000..edcc25d --- /dev/null +++ b/Events.dat @@ -0,0 +1,73 @@ +//Comments: +//Double slashes will comment out everything until the start of next line +/Double slashes with at least one character between then ignore everything +within the slashes/ + +//Data.dat +// +//Define all the event messages for each object (Both items and entities) here in order +//Order for each class of object (Code): BaseItem, Item, Mob, Threat, Merchant +//Order for each type of each object: Follow the order of declaration in Data.dat/NPCs.dat +//Comments in this file cannot be placed between individual event messages, only between each object's event messages block +//Note: Comments using /this syntax/ cannot be used on the same line as the event messages (At most beside code number). +//Only the message itself should be on the line or it will be read as part of the event message +//Only sequence character supported is \n + +//BaseItem code 0 +//Use +//Inspect description + +//Gold Ring +0 +This item has no use. +A golden ring that can be worth quite some gold.\nHowever, it cannot be equipped for any stat boost or used for any reason. + +//Item code 1 +//Use success +//Use failure +//Item used up +//Inspect description + +1 /Medicine/ +You apply the unknown liquid stored in this Medicine successfully! +You tried to use the Medicine but failed to apply it properly! +The Medicine runs out of healing liquid. It can no longer be used. +The Medicine comes in a small container of some weird unknown liquid with medical properties.\nThere seems store only just enough for one use. + +1 /Weapon/ +You swing the Weapon at your foe! +You tried to use the Weapon but stumbled and failed! +The Weapon breaks! It has run out of durability. +The Weapon is a rusty iron sword that can be used to kill your enemies.\nBut, it has low durability and will definitely break after one use. + +//Mob code 2 +//Attack +//Death +//Inspect description + +2 /Mob/ +The Mob uses its claws to, shockingly, claw you! +The Mob dies! +The Mob is a mob. It is a generic mob. It is just a mob. + +//Threat code 3 +//Attack +//Inspect description + +3 /Threat/ +The Threat threatens you! +The Threat is well, a threat. Some kind of threat. You should be careful of it because it's a threat. + +//Merchant code 4 +//Inspect description + +4 /Travelling merchant/ +A merchant who travels! He who goes across the world in search of goods to buy and sell!\nA nice chap to do some business with. + +4 /Dummy merchant/ +A dummy, he doesn't do anything and he can't.\nHe might as well not be in the game but is, for some inexplicable reason. + +4 /Weapons merchant/ +A merchant who specializes in weapon-related goods.\nHe sells more weapon items than other merchants, but also at a higher price. + +5 /End code/ diff --git a/Quest!/DataLoader.cpp b/Quest!/DataLoader.cpp index 586d687..5bd24f4 100644 --- a/Quest!/DataLoader.cpp +++ b/Quest!/DataLoader.cpp @@ -1,6 +1,12 @@ #include "DataLoader.h" -void DataLoader::open(std::string& filename) +void DataLoader::open(const char* filename) +{ + m_filereader.open(filename); + m_filename = filename; +} + +void DataLoader::open(const std::string& filename) { m_filereader.open(filename); m_filename = filename; @@ -79,3 +85,24 @@ int DataLoader::getWithinQuotes(std::string& string) m_filereader.get(); //Discard the closing double quotation mark return 0; } + +void DataLoader::handleNewlines(std::string& string) const +{ + std::string::size_type pos = 0; + while ((pos = string.find("\\n")) != std::string::npos) + { + string.replace(pos, 2, "\n"); + } +} + +void DataLoader::checkStatus() const +{ + if (m_filereader.eof()) + { + throw std::runtime_error(m_filename + ": Data loader unexpectedly reached end of file. File is incomplete/Syntax is wrong."); + } + if (m_filereader.fail()) + { + throw std::runtime_error(m_filename + ": Data loader encountered unexpected input. Data is defined wrongly/Syntax is wrong."); + } +} \ No newline at end of file diff --git a/Quest!/DataLoader.h b/Quest!/DataLoader.h index 5956200..5d7594c 100644 --- a/Quest!/DataLoader.h +++ b/Quest!/DataLoader.h @@ -12,6 +12,10 @@ class DataLoader public: + DataLoader(const char* filename) + : m_filename{ filename }, m_filereader{ filename } + {} + DataLoader(const std::string& filename) : m_filename{ filename }, m_filereader{ filename } {} @@ -23,7 +27,8 @@ class DataLoader m_filereader.close(); } - void open(std::string& filename); + void open(const char* filename); + void open(const std::string& filename); bool is_open() const { return m_filereader.is_open(); } void close(){m_filereader.close();} @@ -51,6 +56,18 @@ class DataLoader //Reasons for failure: Reached eof/next character after ignoring whitespace and comments is not double quotation mark int getWithinQuotes(std::string& string); + //Gets a line of characters and sets the string passed in to be equal to it + void getLine(std::string& string) { std::getline(m_filereader, string); } + void ignore(std::streamsize count, int delim) { m_filereader.ignore(count, delim); } + + //When "\n" is read from a file, it is read as two characters, '\\' and 'n' + //This function removes \\n from a string and replaces it with \n so that newlines can be streamed properly + void handleNewlines(std::string& string) const; + + //Throws if the filereader failed or is at eof + //Note: This function is designed to specifically throw, use only if you are sure that you can't continue if filereader fails or eof + void checkStatus() const; + template friend DataLoader& operator>>(DataLoader& data_loader, T& storage_variable) { diff --git a/Quest!/EventMsgHandler.cpp b/Quest!/EventMsgHandler.cpp index 04856bb..adc29fb 100644 --- a/Quest!/EventMsgHandler.cpp +++ b/Quest!/EventMsgHandler.cpp @@ -1,19 +1,20 @@ #include "EventMsgHandler.h" +void EventMsgHandler::addEventMsg(const std::string& new_event_msg) +{ + m_event_msgs.push_back(new_event_msg); +} + void EventMsgHandler::addEventMsg(std::string&& new_event_msg) { - if (m_event_msg.size() > 0) //Already has an event message being held - { - m_event_msg += new_event_msg + '\n'; //Append - } - else - { - m_event_msg = new_event_msg + '\n'; - } + m_event_msgs.push_back(std::move(new_event_msg)); } void EventMsgHandler::printMsgs() { - std::cout << m_event_msg; - m_event_msg.clear(); + for (const auto& i : m_event_msgs) + { + std::cout << i << '\n'; + } + m_event_msgs.clear(); } \ No newline at end of file diff --git a/Quest!/EventMsgHandler.h b/Quest!/EventMsgHandler.h index 9b41e8f..f9e1905 100644 --- a/Quest!/EventMsgHandler.h +++ b/Quest!/EventMsgHandler.h @@ -1,14 +1,16 @@ #pragma once #include #include +#include class EventMsgHandler { - std::string m_event_msg; + std::vector m_event_msgs; public: //Add an event message to be held + void addEventMsg(const std::string& new_event_msg); void addEventMsg(std::string&& new_event_msg); //Prints all event messages being held then clears them diff --git a/Quest!/Events.cpp b/Quest!/Events.cpp new file mode 100644 index 0000000..f7e3b46 --- /dev/null +++ b/Quest!/Events.cpp @@ -0,0 +1,120 @@ +#include "Events.h" + +void EventList::loadEvents(const char* filename) +{ + DataLoader data_loader{ filename }; + + if (!data_loader.is_open()) + { + throw std::runtime_error(std::string(filename) + ": Unable to load events, please make sure that the event file is in the same folder as the executable"); + } + + int code{ -1 }, current_code{ 0 }; + int object_typeid{ 0 }; + std::string event_message; + while (data_loader()) + { + data_loader.clearWhitespaceAndComments(); //Clear whitespace and comments before each set of event messages + data_loader >> code; + data_loader.clearWhitespaceAndComments(); //And after each code + if (code < current_code || (code - 1) > current_code) + { + throw std::runtime_error(std::string(filename) + ": Incorrect code number or code order read, game data cannot be loaded."); + } + if (code > current_code) + { + object_typeid = 0; + current_code = code; + } + + m_event_messages.resize(5); // Number of distinct classes of objects + + switch (current_code) + { + case 0: //Load events for a BaseItem + { + std::vector base_item_event_messages; + base_item_event_messages.reserve(static_cast(BaseItemEvent::NUMBER_OF_EVENTS)); + std::string event_message; + for (int i{ 0 }; i < static_cast(BaseItemEvent::NUMBER_OF_EVENTS); ++i) + { + data_loader.getLine(event_message); + data_loader.handleNewlines(event_message); + base_item_event_messages.push_back(event_message); + } + data_loader.checkStatus(); //Throws if error + m_event_messages[0].push_back(std::move(base_item_event_messages)); + break; + } + case 1: //Load events for an Item + { + std::vector item_event_messages; + item_event_messages.reserve(static_cast(ItemEvent::NUMBER_OF_EVENTS)); + std::string event_message; + for (int i{ 0 }; i < static_cast(ItemEvent::NUMBER_OF_EVENTS); ++i) + { + data_loader.getLine(event_message); + data_loader.handleNewlines(event_message); + item_event_messages.push_back(event_message); + } + data_loader.checkStatus(); //Throws if error + m_event_messages[1].push_back(std::move(item_event_messages)); + break; + } + case 2: //Load events for a Mob + { + std::vector mob_event_messages; + mob_event_messages.reserve(static_cast(MobEvent::NUMBER_OF_EVENTS)); + std::string event_message; + for (int i{ 0 }; i < static_cast(MobEvent::NUMBER_OF_EVENTS); ++i) + { + data_loader.getLine(event_message); + data_loader.handleNewlines(event_message); + mob_event_messages.push_back(event_message); + } + data_loader.checkStatus(); //Throws if error + m_event_messages[2].push_back(std::move(mob_event_messages)); + break; + } + case 3: //Load events for a Threat + { + std::vector threat_event_messages; + threat_event_messages.reserve(static_cast(ThreatEvent::NUMBER_OF_EVENTS)); + std::string event_message; + for (int i{ 0 }; i < static_cast(ThreatEvent::NUMBER_OF_EVENTS); ++i) + { + data_loader.getLine(event_message); + data_loader.handleNewlines(event_message); + threat_event_messages.push_back(event_message); + } + data_loader.checkStatus(); //Throws if error + m_event_messages[3].push_back(std::move(threat_event_messages)); + break; + } + case 4: //Load events for a Merchant + { + std::vector merchant_event_messages; + merchant_event_messages.reserve(static_cast(MerchantEvent::NUMBER_OF_EVENTS)); + std::string event_message; + for (int i{ 0 }; i < static_cast(MerchantEvent::NUMBER_OF_EVENTS); ++i) + { + data_loader.getLine(event_message); + data_loader.handleNewlines(event_message); + merchant_event_messages.push_back(event_message); + } + data_loader.checkStatus(); //Throws if error + m_event_messages[4].push_back(std::move(merchant_event_messages)); + break; + } + case 5: + //Does not check if correct number of object's messages loaded, should be checked by the handler, Game class using getNumberOfObjectTypesLoaded + //End of file, success load + data_loader.close(); + return; + default: + throw std::runtime_error(std::string(filename) + ": Unknown code read, events cannot be loaded."); + } + ++object_typeid; + } + throw std::runtime_error(std::string(filename) + ": Data loader has failed, events data cannot be loaded."); //Should return from function from loop if successful +} \ No newline at end of file diff --git a/Quest!/Events.h b/Quest!/Events.h index 85c4eb2..acd010f 100644 --- a/Quest!/Events.h +++ b/Quest!/Events.h @@ -1,6 +1,13 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include "DataLoader.h" -enum class GameState +enum class GameState : int { ONGOING, SAVING, @@ -12,7 +19,7 @@ enum class GameState LOST }; -enum class Action +enum class Action : int { MOVE_UP, MOVE_LEFT, @@ -22,32 +29,89 @@ enum class Action INVENTORY2, INVENTORY3, INVENTORY4, + INVENTORY5, + INVENTORY6, + INVENTORY7, + INVENTORY8, + INVENTORY9, SWAP_ITEM1, SWAP_ITEM2, SWAP_ITEM3, SWAP_ITEM4, + SWAP_ITEM5, + SWAP_ITEM6, + SWAP_ITEM7, + SWAP_ITEM8, + SWAP_ITEM9, CHECK_SURROUNDINGS, SAVE, EXIT, RUN }; -//not used -enum class EncounterType +enum class BaseItemEvent : int { - NONE, - SHOP, - MOB, - THREAT + INSPECT_DESCRIPTION = 0, + USE, + NUMBER_OF_EVENTS }; -//Future implement, customised messages from attacking/defending/etc -class EventList +enum class ItemEvent : int { - //encounter - //kill - //killed player - //kill (miss) - //attacked player - //load from file + USE_SUCCESS = 0, + USE_FAILURE, + ITEM_USED_UP, + INSPECT_DESCRIPTION, + NUMBER_OF_EVENTS +}; + +enum class MobEvent : int +{ + ATTACK = 0, + DEATH, + INSPECT_DESCRIPTION, + NUMBER_OF_EVENTS +}; + +enum class ThreatEvent : int +{ + ATTACK = 0, + INSPECT_DESCRIPTION, + NUMBER_OF_EVENTS +}; + +enum class MerchantEvent : int +{ + INSPECT_DESCRIPTION = 0, + NUMBER_OF_EVENTS +}; + + +class EventList final +{ +private: + std::vector>> m_event_messages; + +public: + + template + const std::string& getMessage(T1 object_type, int type_id, T2 event_type) const //Gets the corresponding string for an object of type_id's event + { + static_assert(std::is_enum::value, "EventList.getMessage(T1, int, T2): T1 object_type parameter must be of enumeration type"); + static_assert(std::is_enum::value, "EventList.getMessage(T1, int, T2): T2 event_type parameter must be of enumeration type"); + + return m_event_messages[static_cast(object_type)][type_id][static_cast(event_type)]; + } + + template + int getNumberOfObjectTypesLoaded(T object_type) const //Returns number of object types loaded for a particular object for Game class to verify + { + static_assert(std::is_enum::value, "EventList.getNumberOfObjectTypesLoaded(T): T object_type parameter must be of enumeration type"); + + return m_event_messages[static_cast(object_type)].size(); + } + + //Loads all the event messages associated with each object (item/entity) + void loadEvents(const char* filename); + }; \ No newline at end of file diff --git a/Quest!/Game.cpp b/Quest!/Game.cpp index 07a3c19..cfafe02 100644 --- a/Quest!/Game.cpp +++ b/Quest!/Game.cpp @@ -1,12 +1,14 @@ #include #include #include +#include +#include #include "Game.h" //Default values to be initialized (Options are loaded from Options.dat) void Game::initializeDefaultValues() { - Game::need_update_map = false; + Game::need_update_map = true; Game::game_state = GameState::ONGOING; Game::time_left = max_time; Game::current_time = 0; @@ -23,7 +25,7 @@ void Game::start() do { Game::printTimeLeft(); - Game::printPlayerDetails(Game::player[0]); + Game::printPlayerDetails(Game::player); if (game_state == GameState::WON) { Game::event_message_handler.printMsgs(); @@ -38,9 +40,9 @@ void Game::start() } bool input_valid; - Game::printPlayerPosition(Game::player[0]); + Game::printPlayerPosition(Game::player); Game::event_message_handler.printMsgs(); - Game::printObjectsOnTileDetails(Game::player[0].getXCoord(), Game::player[0].getYCoord()); //Print what is on the same tile at the player (Item/entity) + Game::printObjectsOnTileDetails(Game::player.getXCoord(), Game::player.getYCoord()); //Print what is on the same tile at the player (Item/entity) Game::printAvailablePlayerActions(); //Move, inventory, check surroundings, save, exit do { @@ -54,10 +56,10 @@ void Game::start() Game::printMap(); //Reprint Note: This portion of code is up for revision } Game::printTimeLeft(); - Game::printPlayerDetails(Game::player[0]); - Game::printPlayerPosition(Game::player[0]); + Game::printPlayerDetails(Game::player); + Game::printPlayerPosition(Game::player); Game::event_message_handler.printMsgs(); - Game::printObjectsOnTileDetails(Game::player[0].getXCoord(), Game::player[0].getYCoord()); //Print objects on same tile as player (Item/entity) + Game::printObjectsOnTileDetails(Game::player.getXCoord(), Game::player.getYCoord()); //Print objects on same tile as player (Item/entity) Game::printAvailablePlayerActions(); //Move, inventory, check surroundings, save, exit } //Else, the message telling the player that he/she chose an invalid option and why will be printed by isPlayerActionValid @@ -74,7 +76,7 @@ void Game::start() } else if (Game::game_state == GameState::SAVING) { - std::cout << "Sorry, this has not been implemented yet\n"; + //Will currently never reach here } else { @@ -87,11 +89,11 @@ void Game::start() //Clears all variables defined at the start of the game to get ready for the next time its run void Game::cleanUpGame() { - Game::player.clear(); - Game::items.clear(); Game::base_items.clear(); + Game::items.clear(); Game::mobs.clear(); Game::merchants.clear(); + Game::inventories.clear(); Game::map.clear(); Game::used_items_id.clear(); Game::dead_mobs_id.clear(); @@ -138,40 +140,47 @@ void Game::loadOptions() { throw std::runtime_error("Options.dat: Data is defined wrongly. Max time must be at least 1 day long"); } - if (player_start_xcoord < 0) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of player cannot be negative"); - } - if (player_start_ycoord < 0) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of player cannot be negative"); - } - if (player_start_xcoord >= Game::map_xsize) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of player cannot be greater than or equal to size of map (0 to (xsize - 1))"); - } - if (player_start_ycoord >= Game::map_ysize) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of player cannot be greater than or equal to size of map (0 to (ysize - 1))"); - } - if (Game::magical_potion_xcoord < 0) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of magical potion cannot be negative"); - } - if (Game::magical_potion_ycoord < 0) - { - throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of magical potion cannot be negative"); - } - if (Game::magical_potion_xcoord >= Game::map_xsize) + if (player_start_xcoord == -1 && player_start_ycoord == -1) {} //Ignore this case, this tells the item/entity generator to place the player randomly + else { - throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of magical potion cannot be greater than or equal to size of map (0 to (xsize - 1))"); + if (player_start_xcoord < 0) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of player cannot be negative"); + } + if (player_start_ycoord < 0) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of player cannot be negative"); + } + if (player_start_xcoord >= Game::map_xsize) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of player cannot be greater than or equal to size of map (0 to (xsize - 1))"); + } + if (player_start_ycoord >= Game::map_ysize) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of player cannot be greater than or equal to size of map (0 to (ysize - 1))"); + } } - if (Game::magical_potion_ycoord >= Game::map_ysize) + if (Game::magical_potion_xcoord == -1 && Game::magical_potion_ycoord == -1) {} //Ignore this case, this tells the item/entity generator to place the potion randomly + else { - throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of magical potion cannot be greater than or equal to size of map (0 to (ysize - 1))"); + if (Game::magical_potion_xcoord < 0) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of magical potion cannot be negative"); + } + if (Game::magical_potion_ycoord < 0) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of magical potion cannot be negative"); + } + if (Game::magical_potion_xcoord >= Game::map_xsize) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. X coordinate of magical potion cannot be greater than or equal to size of map (0 to (xsize - 1))"); + } + if (Game::magical_potion_ycoord >= Game::map_ysize) + { + throw std::runtime_error("Options.dat: Data is defined wrongly. Y coordinate of magical potion cannot be greater than or equal to size of map (0 to (ysize - 1))"); + } } - - Game::player_data[0].setCoords(player_start_xcoord, player_start_ycoord); + Game::player_data.setCoords(player_start_xcoord, player_start_ycoord); } //Loads data for items, mobs and threats from Data file into vectors (For new entities to be copy constructed from) @@ -202,8 +211,11 @@ void Game::loadData() while (data_loader()) { data_loader >> code; + data_loader.clearWhitespace(); if (code < current_code || (code - 1) > current_code) + { throw std::runtime_error("Data.dat: Incorrect code number or code order read, game data cannot be loaded."); + } if (code > current_code) { object_typeid = 0; @@ -224,8 +236,8 @@ void Game::loadData() } data_loader >> value >> number_to_place; - checkDataLoaderStatus(data_loader, "Data.dat"); - BaseItem base_item(static_cast(item_type), name, value); + data_loader.checkStatus(); //Throws if error + BaseItem base_item(static_cast(item_type), name, value, object_typeid); if (!base_item.valid()) { throw std::runtime_error("Data.dat: Item data with object_typeid " + std::to_string(object_typeid) + " is invalid. A member variable value has been defined wrongly."); @@ -245,6 +257,7 @@ void Game::loadData() } else //Load a usable item { + int item_object_typeid = object_typeid - Game::base_item_data.size(); //Get a name from within double quotes if (Game::getName(data_loader, name) < 0) //Returns -1 if error { @@ -253,11 +266,11 @@ void Game::loadData() data_loader >> min_hp_change >> max_hp_change >> uses >> success_rate >> value >> number_to_place; - checkDataLoaderStatus(data_loader, "Data.dat"); - Item item(static_cast(item_type), name, min_hp_change, max_hp_change, uses, success_rate, value); + data_loader.checkStatus(); //Throws if error + Item item(static_cast(item_type), name, min_hp_change, max_hp_change, uses, success_rate, value, item_object_typeid); if (!item.valid()) //Validate data, if invalid, throw (Passes size of item_data for validating inventory ids) { - throw std::runtime_error("Data.dat: Item data with object_typeid " + std::to_string(object_typeid) + " is invalid. A member variable value has been defined wrongly."); + throw std::runtime_error("Data.dat: Item data with object_typeid " + std::to_string(item_object_typeid) + " is invalid. A member variable value has been defined wrongly."); } if (number_to_place >= 0) @@ -266,7 +279,7 @@ void Game::loadData() } else { - throw std::runtime_error("Data.dat: Item data with object_typeid " + std::to_string(object_typeid) + " has an invalid number of instances to place."); + throw std::runtime_error("Data.dat: Item data with object_typeid " + std::to_string(item_object_typeid) + " has an invalid number of instances to place."); } Game::item_data.push_back(item); @@ -276,7 +289,7 @@ void Game::loadData() case 1: { //Load player, code 1 - if (Game::player_data.size() > 0) + if (Game::player_data.getMaxHealth() != 0) //Already loaded a player asset { throw std::runtime_error("Data.dat: More than one player type not allowed."); } @@ -287,17 +300,29 @@ void Game::loadData() throw std::runtime_error("Data.dat: " + data_loader.getErrorMsg()); } - int inventory_id[4]; - data_loader >> max_hp >> hp >> atk >> def >> exp >> level >> inventory_id[0] >> inventory_id[1] >> inventory_id[2] >> inventory_id[3] >> gold; + int inventory_size; + data_loader >> max_hp >> hp >> atk >> def >> exp >> level >> gold >> inventory_size; - checkDataLoaderStatus(data_loader, "Data.dat"); - Player player(name, max_hp, hp, atk, def, exp, level, inventory_id[0], inventory_id[1], inventory_id[2], inventory_id[3], gold); - if (!player.valid(Game::base_item_data.size() + Game::item_data.size()))//validate data, if invalid, throw + data_loader.checkStatus(); //Throws if error + Inventory inventory(inventory_size); + int inventory_id; + for (int i{ 0 }; i < inventory_size; ++i) //Load inventory { - throw std::runtime_error("Data.dat: Player data is invalid. A member variable value has been defined wrongly."); + data_loader >> inventory_id; + inventory.setItemID(i, inventory_id); } + if (!inventory.valid(Game::base_item_data.size() + Game::item_data.size())) + { + throw std::runtime_error("Data.dat: Player inventory data is invalid. Please check the typeids, and if you entered the correct number of ids."); + } + Game::inventory_data.push_back(inventory); - Game::player_data.push_back(player); + Game::player_data = Player(name, max_hp, hp, atk, def, exp, level, gold); + data_loader.checkStatus(); //Throws if error + if (!Game::player_data.valid())//validate data, if invalid, throw + { + throw std::runtime_error("Data.dat: Player data is invalid. A member variable value has been defined wrongly."); + } break; } case 2: @@ -311,7 +336,7 @@ void Game::loadData() data_loader >> max_hp >> hp >> atk >> def >> min_dmg >> max_dmg >> exp >> level >> run_chance >> gold >> number_to_place; - checkDataLoaderStatus(data_loader, "Data.dat"); + data_loader.checkStatus(); //Throws if error Mob mob(name, max_hp, hp, atk, def, min_dmg, max_dmg, exp, level, run_chance, gold, object_typeid); if (!mob.valid()) //Validate data, if invalid, throw { @@ -339,10 +364,10 @@ void Game::loadData() throw std::runtime_error("Data.dat: " + data_loader.getErrorMsg()); } - data_loader >> min_dmg >> max_dmg >> run_chance >> number_to_place; + data_loader >> atk >> min_dmg >> max_dmg >> run_chance >> number_to_place; - checkDataLoaderStatus(data_loader, "Data.dat"); - Threat threat(name, min_dmg, max_dmg, run_chance); + data_loader.checkStatus(); //Throws if error + Threat threat(name, atk, min_dmg, max_dmg, run_chance, object_typeid); if (!threat.valid()) //Validate data, if invalid, throw { throw std::runtime_error("Data.dat: Threat data with object_typeid " + std::to_string(object_typeid) + " is invalid. Data.dat is in a wrong format."); @@ -366,7 +391,7 @@ void Game::loadData() data_loader.close(); return; default: - throw std::runtime_error("Data.dat: Invalid code read, game data cannot be loaded."); + throw std::runtime_error("Data.dat: Unknown code read, game data cannot be loaded."); } ++object_typeid; } @@ -391,19 +416,6 @@ int Game::getName(DataLoader& data_loader, std::string& name) } } -//Throws std::runtime_error if there is an error with the data_loader -void Game::checkDataLoaderStatus(const DataLoader& data_loader, const std::string& filename) const -{ - if (data_loader.eof()) - { - throw std::runtime_error(filename + "Data loader unexpectedly reached end of file. File is incomplete/Syntax is wrong."); - } - if (data_loader.fail()) - { - throw std::runtime_error(filename + "Data loader encountered unexpected input. Data is defined wrongly/Syntax is wrong."); - } -} - //Loads all the NPCs to be generated in the game //May throw std::runtime_error (Should be caught by std::exception handler) void Game::loadNPCs() @@ -421,14 +433,16 @@ void Game::loadNPCs() std::string name; bool can_be_bought_from, can_be_sold_to; int buy_price_percent, sell_price_percent; - int number_of_inventory_slots; + int inventory_size; int xcoord, ycoord; while (data_loader()) { data_loader >> code; if (code < current_code || (code - 1) > current_code) + { throw std::runtime_error("NPCs.dat: Incorrect code number or code order read, game data cannot be loaded."); + } if (code > current_code) { npc_typeid = 0; @@ -444,41 +458,37 @@ void Game::loadNPCs() throw std::runtime_error("NPCs.dat: " + data_loader.getErrorMsg()); } - data_loader >> can_be_bought_from >> can_be_sold_to >> buy_price_percent >> sell_price_percent >> number_of_inventory_slots; + data_loader >> can_be_bought_from >> can_be_sold_to >> buy_price_percent >> sell_price_percent >> inventory_size; + + data_loader.checkStatus(); //Throws if error - Game::checkDataLoaderStatus(data_loader, "NPCs.dat"); - Merchant merchant(name, can_be_bought_from, can_be_sold_to, buy_price_percent, sell_price_percent); - for (int i{ 0 }; i < number_of_inventory_slots; ++i) + Inventory inventory(inventory_size); + int inventory_id; + for (int i{ 0 }; i < inventory_size; ++i) //Load inventory { - int item_typeid; - data_loader >> item_typeid; - Game::checkDataLoaderStatus(data_loader, "NPCs.dat"); - merchant.addNewItemSlot(ItemType::PLACEHOLDER, item_typeid); + data_loader >> inventory_id; + inventory.setItemID(i, inventory_id); } - - if (!merchant.valid(Game::base_item_data.size() + Game::item_data.size())) + if (!inventory.valid(Game::base_item_data.size() + Game::item_data.size())) { - throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. A member variable has been defined wrongly."); + throw std::runtime_error("NPCs.dat: Merchant inventory data is invalid. Please check the typeids, and if you entered the correct number of ids."); } + Game::inventory_data.push_back(inventory); - data_loader >> xcoord >> ycoord; - Game::checkDataLoaderStatus(data_loader, "NPCs.dat"); + data_loader.checkStatus(); //Throws if error + Merchant merchant(name, can_be_bought_from, can_be_sold_to, buy_price_percent, sell_price_percent, npc_typeid); - if (xcoord < 0) + if (!merchant.valid()) { - throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. X coordinate of NPC cannot be negative"); - } - if (ycoord < 0) - { - throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. Y coordinate of NPC cannot be negative"); - } - if (xcoord >= Game::map_xsize) - { - throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. X coordinate of NPC cannot be greater than or equal to size of map (0 to (xsize - 1))"); + throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. A member variable has been defined wrongly."); } - if (ycoord >= Game::map_ysize) + + data_loader >> xcoord >> ycoord; + data_loader.checkStatus(); //Throws if error + if (xcoord == -1 && ycoord == -1) {} //Ignore this case, this tells the item/entity generator to place this merchant randomly + else if (Game::coordsOutOfBounds(xcoord, ycoord)) { - throw std::runtime_error("NPCs.dat: Merchant with npc_typeid " + std::to_string(npc_typeid) + " is invalid. Y coordinate of NPC cannot be greater than or equal to size of map (0 to (ysize - 1))"); + throw std::runtime_error("NPCs.dat: The coordinates of merchant with npc_typeid " + std::to_string(npc_typeid) + " are out of bounds. Please check again."); } merchant.setCoords(xcoord, ycoord); @@ -498,7 +508,7 @@ void Game::loadNPCs() throw std::runtime_error("NPCs.dat: Coordinates collision between NPCs with merchant_id " + std::to_string(i) + " and " + std::to_string(j)); } } - if (Game::merchant_data[i].getXCoord() == Game::player_data[0].getXCoord() && Game::merchant_data[i].getYCoord() == Game::player_data[0].getYCoord()) + if (Game::merchant_data[i].getXCoord() == Game::player_data.getXCoord() && Game::merchant_data[i].getYCoord() == Game::player_data.getYCoord()) { throw std::runtime_error("NPCs.dat: Coordinates collision between NPC with merchant_id " + std::to_string(i) + " and player"); } @@ -542,21 +552,70 @@ void Game::loadNPCs() throw std::runtime_error("NPCs.dat: Data loader has failed, game data cannot be loaded."); //Should return from function from loop if successful } +bool Game::coordsOutOfBounds(int xcoord, int ycoord) +{ + if (xcoord < 0) + return true; + if (ycoord < 0) + return true; + if (xcoord >= Game::map_xsize) + return true; + if (ycoord >= Game::map_ysize) + return true; + return false; +} + +void Game::verifyEvents() const +{ + if (Game::base_item_data.size() != Game::event_list.getNumberOfObjectTypesLoaded(ObjectType::BASEITEM)) + { + throw std::runtime_error("Too many or too few sets of events defined for base items. Did you mistype an object code?"); + } + if (Game::item_data.size() != Game::event_list.getNumberOfObjectTypesLoaded(ObjectType::ITEM)) + { + throw std::runtime_error("Too many or too few sets of events defined for items. Did you mistype an object code?"); + } + if (Game::mob_data.size() != Game::event_list.getNumberOfObjectTypesLoaded(ObjectType::MOB)) + { + throw std::runtime_error("Too many or too few sets of events defined for mobs. Did you mistype an object code?"); + } + if (Game::threat_data.size() != Game::event_list.getNumberOfObjectTypesLoaded(ObjectType::THREAT)) + { + throw std::runtime_error("Too many or too few sets of events defined for threats. Did you mistype an object code?"); + } + if (Game::merchant_data.size() != Game::event_list.getNumberOfObjectTypesLoaded(ObjectType::MERCHANT)) + { + throw std::runtime_error("Too many or too few sets of events defined for merchants. Did you mistype an object code?"); + } +} + //Places the items and entities on the map //Should only be called ONCE, at the start of the game void Game::placeItemsAndEntities() { + int xcoord, ycoord; + //Place player - Game::map(Game::player_data[0].getXCoord(), Game::player_data[0].getYCoord()).setEntity(EntityType::PLAYER, 0); //entity_id = 0 - Game::player.push_back(Game::player_data[0]); - Game::player[0].setCoords(Game::player_data[0].getXCoord(), Game::player_data[0].getYCoord()); - Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).setExplored(); - Game::setVisibilityAround(Game::player[0], true); + Game::player = Game::player_data; + if (Game::player.getXCoord() == -1 && Game::player.getYCoord() == -1) + { + Game::map.getRandomTileWithoutEntityCoords(xcoord, ycoord); + Game::player.setCoords(xcoord, ycoord); //Place on a random tile + } + Game::map(Game::player.getXCoord(), Game::player.getYCoord()).setEntity(EntityType::PLAYER, 0); //entity_id = 0 + Game::map(Game::player.getXCoord(), Game::player.getYCoord()).setExplored(); + Game::setVisibilityAround(Game::player, true); //Place magical potion - Game::map(Game::magical_potion_xcoord, Game::magical_potion_ycoord).setItem(ItemType::MAGICALPOTION, 0); - - int xcoord, ycoord; + if (Game::magical_potion_xcoord == -1 && Game::magical_potion_ycoord == -1) + { + Game::map.getRandomTileWithoutItemCoords(xcoord, ycoord); + Game::map(xcoord, ycoord).setItem(ItemType::MAGICALPOTION, 0); //Place on random tile + } + else + { + Game::map(Game::magical_potion_xcoord, Game::magical_potion_ycoord).setItem(ItemType::MAGICALPOTION, 0); + } int baseitem_id = 0; //Place baseitems @@ -587,29 +646,36 @@ void Game::placeItemsAndEntities() } } + //Generate inventories + for (const auto& inventory : Game::inventory_data) + { + Game::inventories.push_back(inventory); + } + //Generate the items in player's starting inventory - for (int i{ 1 }; i < 5; ++i) //Inventory slot 1 to 4 + Game::player.m_inventory = &Game::inventories[0]; + for (size_t i{ 0 }; i < Game::player.m_inventory->size(); ++i) //Inventory slot 1 to 4 { - if (Game::player[0].getInventorySlotItemID(i) != -1) + if (Game::player.m_inventory->getItemID(i) != -1) { - if (Game::player[0].getInventorySlotItemID(i) < static_cast(Game::base_item_data.size())) //Is an ID for base item + if (Game::player.m_inventory->getItemID(i) < static_cast(Game::base_item_data.size())) //Is an ID for base item { - Game::base_items.push_back(Game::base_item_data[Game::player[0].getInventorySlotItemID(i)]); - Game::player[0].setInventorySlotItem(i, Game::base_items[baseitem_id].getItemType(), baseitem_id); + Game::base_items.push_back(Game::base_item_data[Game::player.m_inventory->getItemID(i)]); + Game::player.m_inventory->setItem(i, Game::base_items[baseitem_id].getItemType(), baseitem_id); ++baseitem_id; } else //Is an ID for usable item { - int item_typeid = Game::player[0].getInventorySlotItemID(i) - Game::base_item_data.size(); + int item_typeid = Game::player.m_inventory->getItemID(i) - Game::base_item_data.size(); Game::items.push_back(Game::item_data[item_typeid]); - Game::player[0].setInventorySlotItem(i, Game::items[item_id].getItemType(), item_id); + Game::player.m_inventory->setItem(i, Game::items[item_id].getItemType(), item_id); //Game::items[item_id].setCoords(-1, -1); ++item_id; } } else //Nothing to generate in that slot { - Game::player[0].setInventorySlotItem(i, ItemType::NOTHING, -1); + Game::player.m_inventory->clearItem(i); } } @@ -618,31 +684,36 @@ void Game::placeItemsAndEntities() for (auto& i : Game::merchant_data) { Game::merchants.push_back(i); + merchants[merchant_id].m_inventory = &Game::inventories[merchant_id + 1]; //Merchants' inventories indexes start at 1 onwards (0 is held by player) //Generate items in merchant's inventory - int max_inventory_number = merchants[merchant_id].getInventorySize() + 1; - for (int j{ 1 }; j < max_inventory_number; ++j) //j is the inventory slot number (Not index) + for (size_t j{ 0 }; j < merchants[merchant_id].m_inventory->size(); ++j) //j is the inventory slot number (Not index) { - if (merchants[merchant_id].getInventorySlotItemID(j) != -1) //If there is an item to be generated + if (merchants[merchant_id].m_inventory->getItemID(j) != -1) //If there is an item to be generated { - if (merchants[merchant_id].getInventorySlotItemID(j) < static_cast(Game::base_item_data.size())) //Is an ID for base item (Intentional despite unsigned/signed warning) + if (merchants[merchant_id].m_inventory->getItemID(j) < static_cast(Game::base_item_data.size())) //Is an ID for base item (Intentional despite unsigned/signed warning) { - Game::base_items.push_back(Game::base_item_data[merchants[merchant_id].getInventorySlotItemID(j)]); - merchants[merchant_id].setItemSlot(j, Game::base_items[baseitem_id].getItemType(), baseitem_id); + Game::base_items.push_back(Game::base_item_data[merchants[merchant_id].m_inventory->getItemID(j)]); + merchants[merchant_id].m_inventory->setItem(j, Game::base_items[baseitem_id].getItemType(), baseitem_id); ++baseitem_id; } else //Is an ID for usable item { - int item_typeid = merchants[merchant_id].getInventorySlotItemID(j) - Game::base_item_data.size(); + int item_typeid = merchants[merchant_id].m_inventory->getItemID(j) - Game::base_item_data.size(); Game::items.push_back(Game::item_data[item_typeid]); - merchants[merchant_id].setItemSlot(j, Game::item_data[item_typeid].getItemType(), item_id); + merchants[merchant_id].m_inventory->setItem(j, Game::item_data[item_typeid].getItemType(), item_id); ++item_id; } } else //Nothing to generate in that slot { - merchants[merchant_id].setItemSlot(j, ItemType::NOTHING, -1); + merchants[merchant_id].m_inventory->clearItem(j); } } + if (merchants[merchant_id].getXCoord() == -1 && merchants[merchant_id].getYCoord() == -1) + { + Game::map.getRandomTileWithoutEntityCoords(xcoord, ycoord); + Game::merchants[merchant_id].setCoords(xcoord, ycoord); //Place on random tile + } Game::map(merchants[merchant_id].getXCoord(), merchants[merchant_id].getYCoord()).setEntity(EntityType::MERCHANT, merchant_id); //Do for all NPCs ++merchant_id; } @@ -684,7 +755,7 @@ void Game::placeItemsAndEntities() //If entity present, set to entity character, else if no entity, but item present, set to item character, else nothing void Game::updateMapTileCharacter(int x, int y) { - if (Game::player[0].getXCoord() == x && Game::player[0].getYCoord() == y) + if (Game::player.getXCoord() == x && Game::player.getYCoord() == y) { Game::map(x, y).setCharacter('P'); } @@ -766,42 +837,85 @@ void Game::printPlayerDetails(Player& player) const std::cout << "Level: " << player.getLevel() << "\t\t\tAtk: " << player.getAtk() << "\t\tExp: " << player.getExp() << '\n'; std::cout << "Health: " << player.getHealth() << '/' << player.getMaxHealth() << "\t\t\tDef: " << player.getDef(); std::cout << "\t\tGold: " << player.getGold() << '\n'; - Game::printInventory(); + Game::printInventoryTopDown(player.m_inventory); } //Prints all the items in the player's inventory //When inventory is a class of its own, this function should be changed to accept any inventory object -void Game::printInventory() const +void Game::printInventoryLeftRight(Inventory* inventory) const { - for (int i{ 1 }; i < 5; ++i) //Print details of inventory slots 1 - 4 + int inventory_index = 0; + for (size_t i{ 1 }; i < (inventory->size() + 1); ++i, ++inventory_index) //Print details of inventory slots 1 - 4 { - if (Game::player[0].getInventorySlotItemType(i) == ItemType::MAGICALPOTION) + if (inventory->getItemType(inventory_index) == ItemType::MAGICALPOTION) { std::cout << "Inventory slot " << i << ":Magical Potion\t\t"; } - else if (Game::player[0].getInventorySlotItemType(i) != ItemType::NOTHING) //Has an item other than magical potion in that slot + else if (inventory->getItemType(inventory_index) != ItemType::NOTHING) //Has an item other than magical potion in that slot { - if (Game::player[0].getInventorySlotItemType(i) == ItemType::BASE) + if (inventory->getItemType(inventory_index) == ItemType::BASE) { - std::cout << "Inventory slot " << i << ':' << Game::base_items[Game::player[0].getInventorySlotItemID(i)].getName() << "\t\t"; + std::cout << "Inventory slot " << i << ':' << Game::base_items[inventory->getItemID(inventory_index)].getName() << "\t\t"; } else { - std::cout << "Inventory slot " << i << ':' << Game::items[Game::player[0].getInventorySlotItemID(i)].getName() - << "(Uses left: " << Game::items[Game::player[0].getInventorySlotItemID(i)].getUses() << ")\t"; + std::cout << "Inventory slot " << i << ':' << Game::items[inventory->getItemID(inventory_index)].getName() + << "(Uses left: " << Game::items[inventory->getItemID(inventory_index)].getUses() << ")\t"; } } else { std::cout << "Inventory slot " << i << ":NOTHING\t\t"; } - if (i == 2 || i == 4) + if (i % 2 == 0) //Is even number, newline { std::cout << '\n'; } } } +void Game::printInventoryTopDown(Inventory* inventory) const +{ + int half_of_inventory_size_ceil = static_cast(std::ceil(inventory->size() / 2.0)); + int inventory_index = 0; + for (size_t i{ 0 }; i < inventory->size(); ++i) + { + if (inventory->getItemType(inventory_index) == ItemType::MAGICALPOTION) + { + std::cout << "Inventory slot " << (inventory_index + 1) << ":Magical Potion\t\t"; + } + else if (inventory->getItemType(inventory_index) != ItemType::NOTHING) //Has an item other than magical potion in that slot + { + if (inventory->getItemType(inventory_index) == ItemType::BASE) + { + std::cout << "Inventory slot " << (inventory_index + 1) << ':' << Game::base_items[inventory->getItemID(inventory_index)].getName() << "\t\t"; + } + else + { + std::cout << "Inventory slot " << (inventory_index + 1) << ':' << Game::items[inventory->getItemID(inventory_index)].getName() + << "(Uses left: " << Game::items[inventory->getItemID(inventory_index)].getUses() << ")\t"; + } + } + else + { + std::cout << "Inventory slot " << (inventory_index + 1) << ":NOTHING\t\t"; + } + if (inventory_index < half_of_inventory_size_ceil) //Just printed the left side. Next, print the right side + { + inventory_index += half_of_inventory_size_ceil; + } + else //Just printed right side. Next, print left side + { + std::cout << '\n'; + inventory_index -= (half_of_inventory_size_ceil - 1); + } + } + if (inventory->size() % 2 == 1) + { + std::cout << '\n'; + } +} + //Prints the position of the player void Game::printPlayerPosition(Player& player) const { @@ -820,7 +934,7 @@ void Game::printVictoryMessage() const //Should be called when game over condition met (Not finding magical potion in time or dying) void Game::printGameOverMessage() const { - std::cout << "Too bad! Game over!"; + std::cout << "Too bad! Game over! "; if (Game::time_left == 0) { std::cout << "You couldn't find the magical potion in time to save your grandfather!\n"; @@ -890,7 +1004,7 @@ void Game::printAvailablePlayerActions() const if (Game::game_state == GameState::ONGOING) { //m) Move u)Use inventory(Healing items) p)Pick up/etc c)Check surroundings i)Inspect s)Save e)Exit - std::cout << "m)Move\nu)Use inventory\np)Pick up/swap/drop items\nc)Check surroundings\ni)Inspect items\ns)Save\ne)Exit\n"; + std::cout << "m)Move\nu)Use inventory\np)Pick up/swap/drop items\nc)Check surroundings\ni)Inspect\ns)Save\ne)Exit\n"; } else if(Game::game_state == GameState::ENCOUNTER_MOB) //Use inventory slot 1-4, swap item, run @@ -898,24 +1012,24 @@ void Game::printAvailablePlayerActions() const //u)Use inventory(Healing/weapon) p)Pick up/etc r)Run i)Inspect s)Save e)Exit std::cout << "u)Use inventory\np)Pick up/swap/drop items\n"; int xcoord, ycoord; - Game::player[0].getCoords(xcoord, ycoord); + Game::player.getCoords(xcoord, ycoord); std::cout << "r)Run! (Chance of failure: " << Game::mobs[Game::map(xcoord, ycoord).getEntityID()].getRunChance() - << "%)\ni)Inspect items\ns)Save\ne)Exit\n"; + << "%)\ni)Inspect\ns)Save\ne)Exit\n"; } else if (Game::game_state == GameState::ENCOUNTER_THREAT) { //u)Use inventory(Healing) p)Pick up/etc r)Run i)Inspect s)Save e)Exit - std::cout << "1)Use inventory slot 1\n2)Use inventory slot 2\n3)Use inventory slot 3\n4)Use inventory slot 4\n5)Pick up/swap/drop items\n"; + std::cout << "u)Use inventory\np)Pick up/swap/drop\n"; int xcoord, ycoord; - Game::player[0].getCoords(xcoord, ycoord); - std::cout << "6)Run! (Chance of failure: " << Game::threat_data[Game::map(xcoord, ycoord).getEntityID()].getRunChance() - << "%)\ni)Inspect items\ns)Save\ne)Exit\n" ; + Game::player.getCoords(xcoord, ycoord); + std::cout << "r)Run! (Chance of failure: " << Game::threat_data[Game::map(xcoord, ycoord).getEntityID()].getRunChance() + << "%)\ni)Inspect\ns)Save\ne)Exit\n" ; } else//if Game::game_state == GameState::ENCOUNTER_MERCHANT); { //m)Move u)Use inventory(Healing) p)Pick up/etc c)Check surroundings i)Inspect t)Talk s)Save e)Exit - std::cout << "m)Move\nu)Use inventory\np)Pick up/swap/drop items\nc)Check surroundings\ni)Inspect items\nt)Talk\ns)Save\ne)Exit\n"; + std::cout << "m)Move\nu)Use inventory\np)Pick up/swap/drop items\nc)Check surroundings\ni)Inspect\nt)Talk\ns)Save\ne)Exit\n"; } } @@ -940,26 +1054,26 @@ bool Game::isPlayerActionValid() return Game::swapItemMenu(); //Will output its own invalid option message case 'c': //Check surroundings (Always possible when not in encounter) case 'C': - Game::player[0].setAction(Action::CHECK_SURROUNDINGS); + Game::player.setAction(Action::CHECK_SURROUNDINGS); return true; case 'i': case 'I': - Game::inspectMenu(); //Menu to choose which item from inventory or tile to inspect (Get details of) + Game::inspectMenu(Game::player.m_inventory, Game::player.getXCoord(), Game::player.getYCoord()); //Menu to choose which item from inventory or tile to inspect (Get details of) return false; //Not an actual action, prompt for one again case 's': case 'S': - Game::player[0].setAction(Action::SAVE); - return true; + std::cout << "Sorry, this has not been implemented yet\n"; + return false; case 'e': case 'E': - Game::player[0].setAction(Action::EXIT); + Game::player.setAction(Action::EXIT); return true; default: std::cout << "Invalid option! Option is unrecognised.\n"; return false; } } - else if (Game::game_state == GameState::ENCOUNTER_MOB) //Use inventory slot 1, 2, 3, 4 (Weapon/Healing), swap item or run + else if (Game::game_state == GameState::ENCOUNTER_MOB) //Use inventory (Weapon/Healing), swap item or run { switch (Game::user_input) { @@ -971,19 +1085,19 @@ bool Game::isPlayerActionValid() return Game::swapItemMenu(); //Will output its own invalid option message case 'r': //Run case 'R': - Game::player[0].setAction(Action::RUN); + Game::player.setAction(Action::RUN); return true; case 'i': case 'I': - Game::inspectMenu(); //Menu to choose which item from inventory or tile to inspect (Get details of) + Game::inspectMenu(Game::player.m_inventory, Game::player.getXCoord(), Game::player.getYCoord()); //Menu to choose which item from inventory or tile to inspect (Get details of) return false; //Not an actual action, prompt for one again case 's': case 'S': - Game::player[0].setAction(Action::SAVE); - return true; + std::cout << "Sorry, this has not been implemented yet\n"; + return false; case 'e': case 'E': - Game::player[0].setAction(Action::EXIT); + Game::player.setAction(Action::EXIT); return true; default: std::cout << "Invalid option! Option is unrecognised.\n"; @@ -1002,19 +1116,19 @@ bool Game::isPlayerActionValid() return Game::swapItemMenu(); //Will output its own invalid option message case 'r': //Run case 'R': - Game::player[0].setAction(Action::RUN); + Game::player.setAction(Action::RUN); return true; case 'i': case 'I': - Game::inspectMenu(); //Menu to choose which item from inventory or tile to inspect (Get details of) + Game::inspectMenu(Game::player.m_inventory, Game::player.getXCoord(), Game::player.getYCoord()); //Menu to choose which item from inventory or tile to inspect (Get details of) return false; //Not an actual action, prompt for one again case 's': case 'S': - Game::player[0].setAction(Action::SAVE); - return true; + std::cout << "Sorry, this has not been implemented yet\n"; + return false; case 'e': case 'E': - Game::player[0].setAction(Action::EXIT); + Game::player.setAction(Action::EXIT); return true; default: std::cout << "Invalid option! Option is unrecognised.\n"; @@ -1036,11 +1150,11 @@ bool Game::isPlayerActionValid() return Game::swapItemMenu(); //Will output its own invalid option message case 'c': //Check surroundings (Always possible when not in encounter) case 'C': - Game::player[0].setAction(Action::CHECK_SURROUNDINGS); + Game::player.setAction(Action::CHECK_SURROUNDINGS); return true; case 'i': case 'I': - Game::inspectMenu(); //Menu to choose which item from inventory or tile to inspect (Get details of) + Game::inspectMenu(Game::player.m_inventory, Game::player.getXCoord(), Game::player.getYCoord()); //Menu to choose which item from inventory or tile to inspect (Get details of) return false; //Not an actual action, prompt for one again case 't': case 'T': @@ -1048,11 +1162,11 @@ bool Game::isPlayerActionValid() return false; //Not an actual action, prompt for one again case 's': case 'S': - Game::player[0].setAction(Action::SAVE); - return true; + std::cout << "Sorry, this has not been implemented yet\n"; + return false; case 'e': case 'E': - Game::player[0].setAction(Action::EXIT); + Game::player.setAction(Action::EXIT); return true; default: std::cout << "Invalid option! Option is unrecognised.\n"; @@ -1077,9 +1191,9 @@ bool Game::playerMoveMenu() case '1': //Move up case 'w': case 'W': - if (Game::canMoveUp(Game::player[0])) + if (Game::canMoveUp(Game::player)) { - Game::player[0].setAction(Action::MOVE_UP); + Game::player.setAction(Action::MOVE_UP); return true; } else @@ -1090,9 +1204,9 @@ bool Game::playerMoveMenu() case '2': //Move left case 'a': case 'A': - if (Game::canMoveLeft(Game::player[0])) + if (Game::canMoveLeft(Game::player)) { - Game::player[0].setAction(Action::MOVE_LEFT); + Game::player.setAction(Action::MOVE_LEFT); return true; } { @@ -1102,9 +1216,9 @@ bool Game::playerMoveMenu() case '3': //Move right case 'd': case 'D': - if (Game::canMoveRight(Game::player[0])) + if (Game::canMoveRight(Game::player)) { - Game::player[0].setAction(Action::MOVE_RIGHT); + Game::player.setAction(Action::MOVE_RIGHT); return true; } { @@ -1114,9 +1228,9 @@ bool Game::playerMoveMenu() case '4': //Move down case 's': case 'S': - if (Game::canMoveDown(Game::player[0])) + if (Game::canMoveDown(Game::player)) { - Game::player[0].setAction(Action::MOVE_DOWN); + Game::player.setAction(Action::MOVE_DOWN); return true; } { @@ -1137,7 +1251,7 @@ bool Game::playerMoveMenu() //Also accept player object as parameter in future bool Game::useItemMenu() { - std::cout << "Which inventory slot's item would you like to use: 1, 2, 3 or 4? (Enter 'b' to go back)\n"; + std::cout << "Which inventory slot's item would you like to use? (Enter 'b' to go back)\n"; while (true) //Does not exit until valid option chosen or player decides to return from this menu { std::cin >> Game::user_input; @@ -1148,19 +1262,31 @@ bool Game::useItemMenu() case '2': case '3': case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + int inventory_index = Game::user_input - 49; + if (inventory_index >= static_cast(Game::player.m_inventory->size())) + { + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } //Base items have no use associated with them (Only used for selling for gold) - if (Game::player[0].getInventorySlotItemType(user_input - 48) == ItemType::BASE) + if (Game::player.m_inventory->getItemType(inventory_index) == ItemType::BASE) { std::cout << "Invalid option! This item has no use.\n"; continue; } //Healing items can always be used. Weapons can only be used against mob - else if (Game::player[0].getInventorySlotItemType(user_input - 48) == ItemType::HEALING) //Healing items can always be used + else if (Game::player.m_inventory->getItemType(inventory_index) == ItemType::HEALING) //Healing items can always be used { - Game::player[0].setAction(static_cast(static_cast(Action::INVENTORY1) + Game::user_input - 49)); + Game::player.setAction(static_cast(static_cast(Action::INVENTORY1) + Game::user_input - 49)); return true; } - else if (Game::player[0].getInventorySlotItemType(user_input - 48) == ItemType::WEAPON) //Weapons can only be used during encounter with mob + else if (Game::player.m_inventory->getItemType(inventory_index) == ItemType::WEAPON) //Weapons can only be used during encounter with mob { if (Game::game_state == GameState::ONGOING) //Invalid { @@ -1168,7 +1294,7 @@ bool Game::useItemMenu() } else if (Game::game_state == GameState::ENCOUNTER_MOB) //Weapon is valid to use only during encounter with mob { - Game::player[0].setAction(static_cast(static_cast(Action::INVENTORY1) + Game::user_input - 49)); + Game::player.setAction(static_cast(static_cast(Action::INVENTORY1) + Game::user_input - 49)); return true; } else if (Game::game_state == GameState::ENCOUNTER_THREAT) //Invalid @@ -1186,6 +1312,7 @@ bool Game::useItemMenu() std::cout << "Invalid option! There is nothing in that inventory slot to use!\n"; continue; } + } case 'b': case 'B': return false; //Return to caller, will not print invalid option (Backed by caller) @@ -1200,7 +1327,7 @@ bool Game::useItemMenu() //Also accept player object as parameter in future bool Game::swapItemMenu() { - std::cout << "Pick up/swap/drop item using inventory slot: 1, 2, 3 or 4? (Enter 'b' to go back)\n"; + std::cout << "Pick up/swap/drop item using which inventory slot? (Enter 'b' to go back)\n"; while(true) //Does not exit until valid option chosen or player decides to return from this menu { std::cin >> Game::user_input; @@ -1208,13 +1335,25 @@ bool Game::swapItemMenu() switch (Game::user_input) { case '1': //Pick up/swap/drop item with inventory slot 1 - case '2': //Pick up/swap/drop item with inventory slot 2 - case '3': //Pick up/swap/drop item with inventory slot 3 - case '4': //Pick up/swap/drop item with inventory slot 4 - if (Game::map(player[0].getXCoord(), player[0].getYCoord()).getItemType() != ItemType::NOTHING || - Game::player[0].getInventorySlotItemType(user_input - 48) != ItemType::NOTHING) //An item either on the tile or in player's inventory, allow action + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + size_t inventory_index = Game::user_input - 49; + if (inventory_index >= Game::player.m_inventory->size()) { - Game::player[0].setAction(static_cast(static_cast(Action::SWAP_ITEM1) + (Game::user_input - 49))); //;) reducing repeated code + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } + if (Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getItemType() != ItemType::NOTHING || + Game::player.m_inventory->getItemType(inventory_index) != ItemType::NOTHING) //An item either on the tile or in player's inventory, allow action + { + Game::player.setAction(static_cast(static_cast(Action::SWAP_ITEM1) + (Game::user_input - 49))); //;) reducing repeated code return true; } else @@ -1222,6 +1361,7 @@ bool Game::swapItemMenu() std::cout << "Invalid option! There is nothing that can be picked up/swapped/dropped.\n"; continue; //Loop this menu again } + } case 'b': case 'B': return false; //Return to caller, will not print invalid option (Backed by caller) @@ -1232,16 +1372,16 @@ bool Game::swapItemMenu() } } -//The sub-menu that pops up when user decides to inspect an item (Can inspect own inventory, NPC's inventory, and the tile the player is on) -//Accept an inventory object in the future. Three parameters, one for the inventory, second and third for x/ycoords for the tile -//Default second and third variables to negative. If negative, don't allow inspecting of item/entity on tile -void Game::inspectMenu() +//The sub-menu that pops up when user decides to inspect something (Can inspect own inventory, and the tile the player is on) +//Three parameters, first for the inventory, second and third for x/ycoords for the tile +//Don't pass in xcoord and ycoord to get the version with inspecting item/entity on tile disabled +void Game::inspectMenu(const Inventory* const inventory, int xcoord, int ycoord) { while (true) { //Note: This menu is not a menu to choose a action, this is deliberately reprinted - std::cout << "Which item would you like to inspect? The item in inventory slot 1, 2, 3, or 4?\n" - << "Enter 't' to inspect the item on the current tile (Enter 'b' to go back)\n"; + std::cout << "What would you like to inspect? Enter the number of the inventory slot to inspect the item in that slot,\n" + << "'t' for the item on the current tile, or 'e' for the entity on the current tile (Enter 'b' to go back)\n"; std::cin >> Game::user_input; if (!validInput()) continue; //Loop this menu again if invalid input switch (Game::user_input) @@ -1250,45 +1390,134 @@ void Game::inspectMenu() case '2': case '3': case '4': - if (Game::player[0].getInventorySlotItemType(user_input - 48) != ItemType::NOTHING) + case '5': + case '6': + case '7': + case '8': + case '9': + { + size_t inventory_index = Game::user_input - 49; + if (inventory_index >= inventory->size()) { - if (Game::player[0].getInventorySlotItemType(user_input - 48) == ItemType::BASE) //Is a non-usable item + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } + if (inventory->getItemType(inventory_index) != ItemType::NOTHING) + { + if (inventory->getItemType(inventory_index) == ItemType::BASE) //Is a non-usable item { - printItemDetails(Game::base_items[Game::player[0].getInventorySlotItemID(user_input - 48)]); + printItemDetails(Game::base_items[inventory->getItemID(inventory_index)]); } else //Is a usable item { - printItemDetails(Game::items[Game::player[0].getInventorySlotItemID(user_input - 48)]); + printItemDetails(Game::items[inventory->getItemID(inventory_index)]); } } else { - std::cout << "Invalid option! There is no item in that inventory slot to inspect.\n"; + std::cout << "Invalid option! There is no item in that inventory slot to inspect.\n\n"; } - continue; //Loop in case player wants to read details of another item + continue; //Loop in case player wants to read inspect another item/entity + } case 't': case 'T': - if (Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getItemType() != ItemType::NOTHING) + if (Game::map(xcoord, ycoord).getItemType() != ItemType::NOTHING) { - if (Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getItemType() == ItemType::MAGICALPOTION) + if (Game::map(xcoord, ycoord).getItemType() == ItemType::MAGICALPOTION) { std::cout << "\nMagical Potion details:\n" << "The ultimate potion! The best! The most wondrous! Perfection!\nJust take it already.\nEnd of details.\n\n"; } - else if (Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getItemType() == ItemType::BASE) //Is a non-usable item + else if (Game::map(xcoord, ycoord).getItemType() == ItemType::BASE) //Is a non-usable item { - printItemDetails(Game::base_items[Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getItemID()]); + printItemDetails(Game::base_items[Game::map(xcoord, ycoord).getItemID()]); } else //If a usable item { - printItemDetails(Game::items[Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getItemID()]); + printItemDetails(Game::items[Game::map(xcoord, ycoord).getItemID()]); } } else { - std::cout << "Invalid option! There is no item in on this tile to inspect.\n"; + std::cout << "Invalid option! There is no item in on this tile to inspect.\n\n"; } - continue; //Loop in case player wants to read details of another item + continue; //Loop in case player wants to read inspect another item/entity + case 'e': + case 'E': + if (Game::map(xcoord, ycoord).getEntityType() != EntityType::PLAYER) //Tile that player is on cannot be EntityType::NOTHING + { + if (Game::map(xcoord, ycoord).getEntityType() == EntityType::MOB) + { + printEntityDetails(Game::mobs[Game::map(xcoord, ycoord).getEntityID()]); + } + else if (Game::map(xcoord, ycoord).getEntityType() == EntityType::THREAT) + { + printEntityDetails(Game::threat_data[Game::map(xcoord, ycoord).getEntityID()]); + } + else //if (Game::map(xcoord, ycoord).getEntityType == EntityType::MERCHANT) + { + printEntityDetails(Game::merchants[Game::map(xcoord, ycoord).getEntityID()]); + } + } + else + { + std::cout << "Invalid option! There is no entity other than yourself on this tile to inspect.\n\n"; + } + continue; //Loop in case player wants to read inspect another item/entity + case 'b': + case 'B': + return; //Return to caller + default: + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } + } +} + +//Specialized sub-menu for inspecting only an inventory (Like the merchant's inventory, with inspecting item/entity on tile disabled) +void Game::inspectMenu(const Inventory* const inventory) +{ + while (true) + { + //Note: This menu is not a menu to choose a action, this is deliberately reprinted + std::cout << "What would you like to inspect? Enter the number of the inventory slot to inspect the item in that slot,\n"; + std::cin >> Game::user_input; + if (!validInput()) continue; //Loop this menu again if invalid input + switch (Game::user_input) + { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + size_t inventory_index = Game::user_input - 49; + if (inventory_index >= inventory->size()) + { + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } + if (inventory->getItemType(inventory_index) != ItemType::NOTHING) + { + if (inventory->getItemType(inventory_index) == ItemType::BASE) //Is a non-usable item + { + printItemDetails(Game::base_items[inventory->getItemID(inventory_index)]); + } + else //Is a usable item + { + printItemDetails(Game::items[inventory->getItemID(inventory_index)]); + } + } + else + { + std::cout << "Invalid option! There is no item in that inventory slot to inspect.\n\n"; + } + continue; //Loop in case player wants to read inspect another item/entity + } case 'b': case 'B': return; //Return to caller @@ -1299,38 +1528,105 @@ void Game::inspectMenu() } } +//Prints the details of an un-usable item +void Game::printItemDetails(BaseItem& base_item) const +{ + std::cout << '\n' << base_item.getName() << " details:\n" + << Game::event_list.getMessage(ObjectType::BASEITEM, base_item.getObjectTypeID(), BaseItemEvent::INSPECT_DESCRIPTION) + << "\nType: Trading\nValue: " << base_item.getValue() + << "\nEnd of details.\n\n"; +} + //Prints the details of a usable item void Game::printItemDetails(Item& item) const { - std::cout << "\n" << item.getName() << " details:\nType: "; + std::cout << '\n' << item.getName() << " details:\n" + << Game::event_list.getMessage(ObjectType::ITEM, item.getObjectTypeID(), ItemEvent::INSPECT_DESCRIPTION); if (item.getItemType() == ItemType::HEALING) { - std::cout << "Healing\n"; + std::cout << "\nType: Healing\n"; } else if (item.getItemType() == ItemType::WEAPON) { - std::cout << "Weapon\n"; + std::cout << "\nType: Weapon\n"; } std::cout << "Min HP change: " << item.getMinHpChange() << "\nMax HP change: " << item.getMaxHpChange() << "\nUses: " << item.getUses() - << "\nSuccess rate: " << item.getSuccessRate() << '%' - << "\nValue: " << item.getValue() + << "\nSuccess rate: " << item.getSuccessRate() + << "%\nValue: " << item.getValue() << "\nEnd of details.\n\n"; } -//Prints the details of an un-usable item -void Game::printItemDetails(BaseItem& base_item) const +//Prints details of a mob +void Game::printEntityDetails(Mob& mob) const { - std::cout << "\n" << base_item.getName() << " details:\nType: "; - std::cout << "Trading\nValue: " << base_item.getValue() << "\nEnd of details.\n\n"; + std::cout << '\n' << mob.getName() << " details:\n" + << Game::event_list.getMessage(ObjectType::MOB, mob.getObjectTypeID(), MobEvent::INSPECT_DESCRIPTION) + << "\nLevel: " << mob.getLevel() + << "\nMax Health: " << mob.getMaxHealth() + << "\nHealth: " << mob.getHealth() + << "\nAtk: " << mob.getAtk() + << "\nDef: " << mob.getDef() + << "\nMin damage: " << mob.getMinDmg() + << "\nMax damage: " << mob.getMaxDmg() + << "\nExp drop: " << mob.getExp() + << "\nGold drop: " << mob.getGold() + << "\nRun chance from mob: " << mob.getRunChance() + << "\nEnd of details.\n\n"; +} + +//Prints details of a threat +void Game::printEntityDetails(Threat& threat) const +{ + std::cout << '\n' << threat.getName() << " details:\n" + << Game::event_list.getMessage(ObjectType::THREAT, threat.getObjectTypeID(), ThreatEvent::INSPECT_DESCRIPTION) + << "\nAtk: " << threat.getAtk() + << "\nMin damage: " << threat.getMinDmg() + << "\nMax damage: " << threat.getMaxDmg() + << "\nRun chance from threat: " << threat.getRunChance() + << "\nEnd of details.\n\n"; +} + +//Prints details of a merchant NPC +void Game::printEntityDetails(Merchant& merchant) const +{ + std::cout << '\n' << merchant.getName() << " details:\n" + << Game::event_list.getMessage(ObjectType::MERCHANT, merchant.getObjectTypeID(), MerchantEvent::INSPECT_DESCRIPTION) + << "\nCan be bought from: "; + if (merchant.canBeBoughtFrom()) + { + std::cout << "Yes"; + } + else + { + std::cout << "No"; + } + std::cout << "\nCan be sold to: "; + if (merchant.CanBeSoldTo()) + { + std::cout << "Yes"; + } + else + { + std::cout << "No"; + } + if (merchant.canBeBoughtFrom()) + { + std::cout << "\nBuy price as percent of item value: " << merchant.getBuyPricePercent() << '%'; + } + if (merchant.CanBeSoldTo()) + { + std::cout << "\nSell price as percent of item value: " << merchant.getSellPricePercent() << '%'; + } + std::cout << "\nEnd of details.\n\n"; } //Selects the appropriate NPC talk menu based on what NPC the player is talking to void Game::talkToNPCMenu() { - int npc_id = Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getEntityID(); - switch (Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getEntityType()) + int npc_id = Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityID(); + switch (Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityType()) { case EntityType::MERCHANT: Game::merchantTalkMenu(Game::merchants[npc_id]); @@ -1345,7 +1641,7 @@ void Game::merchantTalkMenu(Merchant& merchant) while (true) { //Note: This menu is not a menu to choose an action, this is deliberately reprinted - std::cout << merchant.getName() << ": Welcome to my shop! What can I do for you? (You have " << Game::player[0].getGold() << " gold)\n"; + std::cout << merchant.getName() << ": Welcome to my shop! What can I do for you? (You have " << Game::player.getGold() << " gold)\n"; if (merchant.canBeBoughtFrom()) { std::cout << "p)Purchase items\n"; @@ -1405,27 +1701,28 @@ void Game::npcBuyMenu(Merchant& merchant) { while (true) { - std::cout << "What would you like to buy? (You have " << Game::player[0].getGold() << " gold)\n"; - for (size_t i{ 1 }; i < (merchant.getInventorySize() + 1); ++i)//Prints all the items for sale + std::cout << "What would you like to buy? (You have " << Game::player.getGold() << " gold)\n"; + Game::printInventoryTopDown(Game::player.m_inventory); + for (size_t i{ 0 }; i < merchant.m_inventory->size(); ++i)//Prints all the items for sale { - int item_id = merchant.getInventorySlotItemID(i); + int item_id = merchant.m_inventory->getItemID(i); //change to index if (item_id == -1) { - std::cout << i << ")NOTHING\n"; + std::cout << (i + 1) << ")NOTHING\n"; } else { - if (merchant.getInventorySlotItemType(i) == ItemType::BASE) + if (merchant.m_inventory->getItemType(i) == ItemType::BASE) { - std::cout << i << ")" << Game::base_items[item_id].getName() << " (Cost: " << merchant.getItemBuyPrice(Game::base_items[item_id].getValue()) << " gold)\n"; + std::cout << (i + 1) << ")" << Game::base_items[item_id].getName() << " (Cost: " << merchant.getItemBuyPrice(Game::base_items[item_id].getValue()) << " gold)\n"; } else { - std::cout << i << ")" << Game::items[item_id].getName() << " (Cost: " << merchant.getItemBuyPrice(Game::items[item_id].getValue()) << " gold)\n"; + std::cout << (i + 1) << ")" << Game::items[item_id].getName() << " (Cost: " << merchant.getItemBuyPrice(Game::items[item_id].getValue()) << " gold)\n"; } } } - std::cout << "b)Back\n"; + std::cout << "i)Inspect\nb)Back\n"; std::cin >> Game::user_input; if (!validInput()) continue; //Loop this menu again if invalid input switch (user_input) @@ -1440,48 +1737,48 @@ void Game::npcBuyMenu(Merchant& merchant) case '8': case '9': { - size_t merchant_inventory_slot = Game::user_input - 48; - if (merchant_inventory_slot > merchant.getInventorySize()) //If merchant has no such chosen inventory slot + size_t merchant_inventory_index = Game::user_input - 49; + if (merchant_inventory_index >= merchant.m_inventory->size()) //If merchant has no such chosen inventory slot { std::cout << "Invalid option! Option is unrecognised.\n"; continue; } - if (merchant.getInventorySlotItemType(merchant_inventory_slot) != ItemType::NOTHING) //If merchant has no item in that slot + if (merchant.m_inventory->getItemType(merchant_inventory_index) != ItemType::NOTHING) //If merchant has no item in that slot { - if (Game::player[0].isInventoryFull()) //Player has no free inventory slot + if (Game::player.m_inventory->isFull()) //Player has no free inventory slot { std::cout << "Invalid option! Your inventory is full.\n"; continue; } - ItemType item_type = merchant.getInventorySlotItemType(merchant_inventory_slot); //ItemType of item being bought - int item_id = merchant.getInventorySlotItemID(merchant_inventory_slot); //ID of the item being bought + ItemType item_type = merchant.m_inventory->getItemType(merchant_inventory_index); //ItemType of item being bought + int item_id = merchant.m_inventory->getItemID(merchant_inventory_index); //ID of the item being bought if (item_type == ItemType::BASE) { int item_buy_price = merchant.getItemBuyPrice(Game::base_items[item_id].getValue()); //Price of the item being bought - if (Game::player[0].getGold() < item_buy_price) //Player has not enough gold to afford item + if (Game::player.getGold() < item_buy_price) //Player has not enough gold to afford item { std::cout << "Invalid option! You cannot afford that item.\n"; continue; } - Game::player[0].loseGold(item_buy_price); - int player_inventory_slot = Game::player[0].getEmptyInventorySlot(); - Game::player[0].setInventorySlotItem(player_inventory_slot, Game::base_items[item_id].getItemType(), item_id); - merchant.clearItemSlot(merchant_inventory_slot); + Game::player.loseGold(item_buy_price); + int player_inventory_index = Game::player.m_inventory->getEmptyIndex(); + Game::player.m_inventory->setItem(player_inventory_index, Game::base_items[item_id].getItemType(), item_id); + merchant.m_inventory->clearItem(merchant_inventory_index); std::cout << "You buy the " << Game::base_items[item_id].getName() << " for " << item_buy_price << " gold.\n"; } else //Usable item { int item_buy_price = merchant.getItemBuyPrice(Game::items[item_id].getValue()); //Price of the item being bought - if (Game::player[0].getGold() < item_buy_price) //Player has not enough gold to afford item + if (Game::player.getGold() < item_buy_price) //Player has not enough gold to afford item { std::cout << "Invalid option! You cannot afford that item.\n"; continue; } - Game::player[0].loseGold(item_buy_price); - int player_inventory_slot = Game::player[0].getEmptyInventorySlot(); - Game::player[0].setInventorySlotItem(player_inventory_slot, Game::items[item_id].getItemType(), item_id); - merchant.clearItemSlot(merchant_inventory_slot); + Game::player.loseGold(item_buy_price); + int player_inventory_index = Game::player.m_inventory->getEmptyIndex(); + Game::player.m_inventory->setItem(player_inventory_index, Game::items[item_id].getItemType(), item_id); + merchant.m_inventory->clearItem(merchant_inventory_index); std::cout << "You buy the " << Game::items[item_id].getName() << " for " << item_buy_price << " gold.\n"; } } @@ -1493,6 +1790,10 @@ void Game::npcBuyMenu(Merchant& merchant) continue; } + case 'i': + case 'I': + inspectMenu(merchant.m_inventory); + continue; case 'b': case 'B': return; //Return to caller @@ -1509,26 +1810,26 @@ void Game::npcSellMenu(Merchant& merchant) { while (true) { - std::cout << "What would you like to sell? (You have " << Game::player[0].getGold() << " gold)\n"; - for (int i{ 1 }; i < 5; ++i) //Print details of inventory slots 1 - 4 + std::cout << "What would you like to sell? (You have " << Game::player.getGold() << " gold)\n"; + for (size_t i{ 0 }; i < Game::player.m_inventory->size(); ++i) //Print details of inventory slots 1 - 4 { - if (Game::player[0].getInventorySlotItemType(i) != ItemType::NOTHING) //Has an item in that slot + if (Game::player.m_inventory->getItemType(i) != ItemType::NOTHING) //Has an item in that slot { - if (Game::player[0].getInventorySlotItemType(i) == ItemType::BASE) //If is a non-usable item + if (Game::player.m_inventory->getItemType(i) == ItemType::BASE) //If is a non-usable item { - std::cout << i << ')' << Game::base_items[Game::player[0].getInventorySlotItemID(i)].getName() << "\tValue: " - << merchant.getItemSellPrice(Game::base_items[Game::player[0].getInventorySlotItemID(i)].getValue()) << " gold\n"; + std::cout << (i + 1) << ')' << Game::base_items[Game::player.m_inventory->getItemID(i)].getName() << "\tValue: " + << merchant.getItemSellPrice(Game::base_items[Game::player.m_inventory->getItemID(i)].getValue()) << " gold\n"; } else { - std::cout << i << ')' << Game::items[Game::player[0].getInventorySlotItemID(i)].getName() - << "(Uses left: " << Game::items[Game::player[0].getInventorySlotItemID(i)].getUses() << ")\tValue: " - << merchant.getItemSellPrice(Game::items[Game::player[0].getInventorySlotItemID(i)].getValue()) << " gold\n"; + std::cout << (i + 1) << ')' << Game::items[Game::player.m_inventory->getItemID(i)].getName() + << "(Uses left: " << Game::items[Game::player.m_inventory->getItemID(i)].getUses() << ")\tValue: " + << merchant.getItemSellPrice(Game::items[Game::player.m_inventory->getItemID(i)].getValue()) << " gold\n"; } } else { - std::cout << i << ")NOTHING\n"; + std::cout << (i + 1) << ")NOTHING\n"; } } std::cout << "b)Back\n"; @@ -1540,27 +1841,37 @@ void Game::npcSellMenu(Merchant& merchant) case '2': case '3': case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { - size_t player_inventory_slot = Game::user_input - 48; - ItemType item_type = Game::player[0].getInventorySlotItemType(player_inventory_slot); + size_t player_inventory_index = Game::user_input - 49; + if (player_inventory_index >= static_cast(Game::player.m_inventory->size())) //If player has no such inventory slot + { + std::cout << "Invalid option! Option is unrecognised.\n"; + continue; //Loop this menu again + } + ItemType item_type = Game::player.m_inventory->getItemType(player_inventory_index); if (item_type != ItemType::NOTHING) //Player has an item in that slot { if (item_type == ItemType::BASE) //If non-usable item { - int item_sell_price = merchant.getItemSellPrice(Game::base_items[Game::player[0].getInventorySlotItemID(player_inventory_slot)].getValue()); - Game::player[0].gainGold(item_sell_price); - std::cout << "You sell the " << Game::base_items[Game::player[0].getInventorySlotItemID(player_inventory_slot)].getName() + int item_sell_price = merchant.getItemSellPrice(Game::base_items[Game::player.m_inventory->getItemID(player_inventory_index)].getValue()); + Game::player.gainGold(item_sell_price); + std::cout << "You sell the " << Game::base_items[Game::player.m_inventory->getItemID(player_inventory_index)].getName() << " for " << item_sell_price << " gold.\n"; - Game::player[0].setInventorySlotItem(player_inventory_slot, ItemType::NOTHING, -1); + Game::player.m_inventory->clearItem(player_inventory_index); continue; } else { - int item_sell_price = merchant.getItemSellPrice(Game::items[Game::player[0].getInventorySlotItemID(player_inventory_slot)].getValue()); - Game::player[0].gainGold(item_sell_price); - std::cout << "You sell the " << Game::items[Game::player[0].getInventorySlotItemID(player_inventory_slot)].getName() + int item_sell_price = merchant.getItemSellPrice(Game::items[Game::player.m_inventory->getItemID(player_inventory_index)].getValue()); + Game::player.gainGold(item_sell_price); + std::cout << "You sell the " << Game::items[Game::player.m_inventory->getItemID(player_inventory_index)].getName() << " for " << item_sell_price << " gold.\n"; - Game::player[0].setInventorySlotItem(player_inventory_slot, ItemType::NOTHING, -1); + Game::player.m_inventory->clearItem(player_inventory_index); continue; } } @@ -1584,7 +1895,7 @@ void Game::npcSellMenu(Merchant& merchant) void Game::evaluatePlayerAction() { //During encounter with mob, use inventory option is implemented together with that of an encounter with nothing - switch (Game::player[0].getAction()) + switch (Game::player.getAction()) { case Action::MOVE_UP: case Action::MOVE_LEFT: @@ -1592,7 +1903,7 @@ void Game::evaluatePlayerAction() case Action::MOVE_DOWN: { Game::advanceTime(1); - Game::playerMove(Game::player[0]); + Game::playerMove(Game::player); Game::evaluatePossibleEncounter(); //Handles need_update_map break; } @@ -1600,17 +1911,22 @@ void Game::evaluatePlayerAction() case Action::INVENTORY2: case Action::INVENTORY3: case Action::INVENTORY4: + case Action::INVENTORY5: + case Action::INVENTORY6: + case Action::INVENTORY7: + case Action::INVENTORY8: + case Action::INVENTORY9: { Game::advanceTime(0.25); - //Converts INVENTORY1 to 1, INVENTORY4 to 4 - int inventory_slot_number = static_cast(Game::player[0].getAction()) - static_cast(Action::INVENTORY1) + 1; - if (Game::player[0].getInventorySlotItemType(inventory_slot_number) == ItemType::HEALING) + //Converts INVENTORY1 to 0, INVENTORY4 to 3 + int inventory_index = static_cast(Game::player.getAction()) - static_cast(Action::INVENTORY1); + if (Game::player.m_inventory->getItemType(inventory_index) == ItemType::HEALING) { - Game::useHealingItemSlot(inventory_slot_number); //Handles need_update_map + Game::useHealingItem(inventory_index); //Handles need_update_map } - else //if( Game::player[0].getInventorySlotItemType(inventory_slot_number) == ItemType::WEAPON) + else //if( Game::player.getInventorySlotItemType(inventory_index) == ItemType::WEAPON) { //If is weapon, and execution reached here, that means player is definitely in encounter with mob and not in any other game state - Game::useWeaponItemSlot(inventory_slot_number); //Handles need_update_map + Game::useWeaponItem(inventory_index); //Handles need_update_map } break; } @@ -1618,21 +1934,25 @@ void Game::evaluatePlayerAction() case Action::SWAP_ITEM2: case Action::SWAP_ITEM3: case Action::SWAP_ITEM4: + case Action::SWAP_ITEM5: + case Action::SWAP_ITEM6: + case Action::SWAP_ITEM7: + case Action::SWAP_ITEM8: + case Action::SWAP_ITEM9: { Game::advanceTime(0.5); - //Converts SWAP_ITEM1 to 1, SWAP_ITEM4 to 4 - int inventory_slot_number = static_cast(Game::player[0].getAction()) - static_cast(Action::SWAP_ITEM1) + 1; - Game::swapItems(Game::player[0], inventory_slot_number); + //Converts SWAP_ITEM1 to 0, SWAP_ITEM4 to 3 + int inventory_index = static_cast(Game::player.getAction()) - static_cast(Action::SWAP_ITEM1); + Game::swapItems(Game::player, inventory_index); break; } case Action::CHECK_SURROUNDINGS: Game::advanceTime(3); - Game::checkSurroundings(Game::player[0]); + Game::checkSurroundings(Game::player); Game::event_message_handler.addEventMsg("You checked your surroundings carefully..."); Game::need_update_map = true; break; case Action::SAVE: - Game::game_state = GameState::SAVING; break; case Action::EXIT: Game::game_state = GameState::EXITING; @@ -1641,14 +1961,14 @@ void Game::evaluatePlayerAction() { advanceTime(1); int xcoord, ycoord; - Game::player[0].getCoords(xcoord, ycoord); + Game::player.getCoords(xcoord, ycoord); int entity_id = Game::map(xcoord, ycoord).getEntityID(); if (Game::map(xcoord, ycoord).getEntityType() == EntityType::MOB) { - if (Game::player[0].runFrom(Game::mobs[entity_id])) //If run successful + if (Game::player.runFrom(Game::mobs[entity_id])) //If run successful { Game::event_message_handler.addEventMsg("You ran away successfully."); - playerMoveRandomDirection(Game::player[0]); + playerMoveRandomDirection(Game::player); evaluatePossibleEncounter(); //Handles need_update_map } else @@ -1659,10 +1979,10 @@ void Game::evaluatePlayerAction() } else if (Game::map(xcoord, ycoord).getEntityType() == EntityType::THREAT) { - if (Game::player[0].runFrom(Game::threat_data[entity_id])) //If run successful + if (Game::player.runFrom(Game::threat_data[entity_id])) //If run successful { Game::event_message_handler.addEventMsg("You ran away successfully."); - playerMoveRandomDirection(Game::player[0]); + playerMoveRandomDirection(Game::player); evaluatePossibleEncounter(); //Handles need_update_map } else @@ -1681,8 +2001,8 @@ void Game::evaluatePlayerAction() //Will set if the game needs to update the map void Game::evaluatePossibleEncounter() { - EntityType entity_on_tile_type = Game::map(player[0].getXCoord(), player[0].getYCoord()).getEntityType(); - int entity_id = Game::map(player[0].getXCoord(), player[0].getYCoord()).getEntityID(); + EntityType entity_on_tile_type = Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityType(); + int entity_id = Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityID(); if (entity_on_tile_type == EntityType::MOB) { Game::game_state = GameState::ENCOUNTER_MOB; @@ -1764,28 +2084,28 @@ void Game::playerMoveRandomDirection(Player& player) switch (rand) { case 1: - valid_move = canMoveUp(Game::player[0]); + valid_move = canMoveUp(Game::player); if(valid_move) - Game::player[0].setAction(Action::MOVE_UP); + Game::player.setAction(Action::MOVE_UP); break; case 2: - valid_move = canMoveLeft(Game::player[0]); + valid_move = canMoveLeft(Game::player); if (valid_move) - Game::player[0].setAction(Action::MOVE_LEFT); + Game::player.setAction(Action::MOVE_LEFT); break; case 3: - valid_move = canMoveRight(Game::player[0]); + valid_move = canMoveRight(Game::player); if (valid_move) - Game::player[0].setAction(Action::MOVE_RIGHT); + Game::player.setAction(Action::MOVE_RIGHT); break; case 4: - valid_move = canMoveDown(Game::player[0]); + valid_move = canMoveDown(Game::player); if (valid_move) - Game::player[0].setAction(Action::MOVE_DOWN); + Game::player.setAction(Action::MOVE_DOWN); break; } } while (valid_move == false); - playerMove(Game::player[0]); + playerMove(Game::player); } //Sets the visibility of all 8 surrounding tiles and the tile that the entity is on to new_visibility @@ -1815,9 +2135,9 @@ void Game::setVisibilityAround(const Entity& entity, bool new_visibility) } //Uses item (Confirmed ItemType::HEALING) in the inventory slot to heal player -void Game::useHealingItemSlot(int inventory_slot_number) +void Game::useHealingItem(int inventory_index) { - int item_id = Game::player[0].getInventorySlotItemID(inventory_slot_number); + int item_id = Game::player.m_inventory->getItemID(inventory_index); double success_rate = static_cast(Game::items[item_id].getSuccessRate() * 100'000); int random_number = getRandomInt(0, 10'000'000); @@ -1825,20 +2145,21 @@ void Game::useHealingItemSlot(int inventory_slot_number) if (random_number < success_rate) //If random number is within success rate, successful at using item { int heal_amount = getRandomInt(Game::items[item_id].getMinHpChange(), Game::items[item_id].getMaxHpChange()); - Game::player[0].heal(heal_amount); + Game::player.heal(heal_amount); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::USE_SUCCESS)); Game::event_message_handler.addEventMsg("You heal for " + std::to_string(heal_amount) + " points of health"); } - else + else //Use item failed { - Game::event_message_handler.addEventMsg("You tried to use the " + Game::items[item_id].getName() + " but you failed!"); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::USE_FAILURE)); } Game::items[item_id].decrementUses(); if (Game::items[item_id].getUses() == 0) { //Manage used up items - Game::player[0].setInventorySlotItem(inventory_slot_number, ItemType::NOTHING, -1); + Game::player.m_inventory->clearItem(inventory_index); logUsedItem(item_id); - Game::event_message_handler.addEventMsg("The " + Game::items[item_id].getName() + " has been used up."); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::ITEM_USED_UP)); } if (Game::game_state == GameState::ONGOING) @@ -1851,47 +2172,10 @@ void Game::useHealingItemSlot(int inventory_slot_number) } } -//Allows player to pick up/drop/swap items with the item on the tile that the player is on using the specified inventory slot -//Note: Does not check if inventory slot is out of bounds -void Game::swapItems(Player& player, int inventory_slot_number) -{ - //Store item held by player temporarily - int inventory_item_id = player.getInventorySlotItemID(inventory_slot_number); - ItemType inventory_item_type = Game::player[0].getInventorySlotItemType(inventory_slot_number); - int xcoord, ycoord; - player.getCoords(xcoord, ycoord); - - if (player.getInventorySlotItemType(inventory_slot_number) == ItemType::NOTHING) //Nothing in inventory, i.e. picking up item - { - Game::event_message_handler.addEventMsg("Successfully picked up item."); - } - else if (Game::map(xcoord, ycoord).getItemType() == ItemType::NOTHING) //Nothing on tile, i.e. dropping an item - { - Game::event_message_handler.addEventMsg("Successfully dropped item."); - } - else //Something in both inventory and on tile, i.e. swapping items - { - Game::event_message_handler.addEventMsg("Successfully swapped items."); - } - - player.setInventorySlotItem(inventory_slot_number, Game::map(xcoord, ycoord).getItemType(), Game::map(xcoord, ycoord).getItemID()); - Game::map(xcoord, ycoord).setItem(inventory_item_type, inventory_item_id); - - - if (Game::game_state == GameState::ONGOING) - { - Game::need_update_map = true; - } - else //In any encounter - { - Game::need_update_map = false; - } -} - //Uses item (Confirmed ItemType::WEAPON) in the inventory slot to attack mob -void Game::useWeaponItemSlot(int inventory_slot_number) +void Game::useWeaponItem(int inventory_index) { - int item_id = Game::player[0].getInventorySlotItemID(inventory_slot_number); + int item_id = Game::player.m_inventory->getItemID(inventory_index); double success_rate = static_cast(Game::items[item_id].getSuccessRate() * 100'000); int random_number = getRandomInt(0, 10'000'000); @@ -1900,30 +2184,23 @@ void Game::useWeaponItemSlot(int inventory_slot_number) { int expected_damage = getRandomInt(Game::items[item_id].getMinHpChange(), Game::items[item_id].getMaxHpChange()); int xcoord, ycoord; - Game::player[0].getCoords(xcoord, ycoord); + Game::player.getCoords(xcoord, ycoord); int entity_id = Game::map(xcoord, ycoord).getEntityID(); - int actual_damage; - if (Game::player[0].getAtk() > Game::mobs[entity_id].getDef() || Game::player[0].getAtk() < Game::mobs[entity_id].getDef()) - { - actual_damage = static_cast(static_cast(Game::player[0].getAtk()) / Game::mobs[entity_id].getDef() * expected_damage); - } - else //if (Game::player[0].getAtk() == Game::mobs[entity_id].getDef()) - { - actual_damage = expected_damage; //Damage amount does not change - } + int actual_damage = Game::evaluateActualDamage(expected_damage, player.getAtk(), Game::mobs[entity_id].getDef()); int damage_discrepency = actual_damage - expected_damage; Game::mobs[entity_id].takeDamage(actual_damage); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::USE_SUCCESS)); Game::event_message_handler.addEventMsg("You attack the " + Game::mobs[entity_id].getName() + " for " + std::to_string(actual_damage) + " points of damage!"); if (damage_discrepency < 0) //Took less damage than expected { - Game::event_message_handler.addEventMsg(Game::mobs[entity_id].getName() + " blocked " + std::to_string(damage_discrepency) + " points of damage."); + Game::event_message_handler.addEventMsg(Game::mobs[entity_id].getName() + "\'s greater defense allowed it to block " + std::to_string(-damage_discrepency) + " points of damage."); } else if (damage_discrepency > 0) { - Game::event_message_handler.addEventMsg("You greater attack allowed you to deal " + std::to_string(damage_discrepency) + " more points of damage."); + Game::event_message_handler.addEventMsg("Your greater attack allowed you to deal " + std::to_string(damage_discrepency) + " more points of damage."); } if (Game::mobs[entity_id].isDead()) @@ -1932,15 +2209,15 @@ void Game::useWeaponItemSlot(int inventory_slot_number) Game::map(xcoord, ycoord).setEntity(EntityType::PLAYER, 0); updateMapTileCharacter(xcoord, ycoord); logDeadMob(entity_id); - Game::event_message_handler.addEventMsg("The " + Game::mobs[entity_id].getName() + " dies!"); - int player_initial_level = Game::player[0].getLevel(); - Game::player[0].gainExp(Game::mobs[entity_id].getExp()); //Note: Levelling implementation has to be relooked in the future - Game::player[0].gainGold(Game::mobs[entity_id].getGold()); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::MOB, Game::mobs[entity_id].getObjectTypeID(), MobEvent::DEATH)); + int player_initial_level = Game::player.getLevel(); + Game::player.gainExp(Game::mobs[entity_id].getExp()); //Note: Levelling implementation has to be relooked in the future + Game::player.gainGold(Game::mobs[entity_id].getGold()); Game::event_message_handler.addEventMsg("You gain " + std::to_string(Game::mobs[entity_id].getExp()) + " EXP and " + std::to_string(Game::mobs[entity_id].getGold()) + " gold"); - if (Game::player[0].getLevel() > player_initial_level) + if (Game::player.getLevel() > player_initial_level) { - Game::event_message_handler.addEventMsg("You levelled up!"); //Has to be reworked in the future + Game::event_message_handler.addEventMsg("You feel additional strength surging forth from gaining experience... You levelled up!"); //Has to be reworked in the future } Game::need_update_map = true; } @@ -1948,7 +2225,7 @@ void Game::useWeaponItemSlot(int inventory_slot_number) } else //Use item failed { - Game::event_message_handler.addEventMsg("You tried to use the " + Game::items[item_id].getName() + " but you failed!"); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::USE_FAILURE)); Game::need_update_map = false; } @@ -1956,9 +2233,64 @@ void Game::useWeaponItemSlot(int inventory_slot_number) if (Game::items[item_id].getUses() == 0) { //Manage used up items - Game::player[0].setInventorySlotItem(inventory_slot_number, ItemType::NOTHING, -1); + Game::player.m_inventory->clearItem(inventory_index); logUsedItem(item_id); - Game::event_message_handler.addEventMsg("The " + Game::items[item_id].getName() + " has been used up."); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::ITEM, Game::items[item_id].getObjectTypeID(), ItemEvent::ITEM_USED_UP)); + } +} + +int Game::evaluateActualDamage(int expected_damage, int attacker_atk, int defender_def) +{ + if (attacker_atk > defender_def) + { + return static_cast(((std::cbrt(attacker_atk - defender_def) + 0.35) * expected_damage)); + } + else if (attacker_atk == defender_def) + { + return expected_damage; + } + else //if(attacker_atk < defender_def) + { + int actual_damage = static_cast((0.83 - ((std::cbrt(defender_def - attacker_atk) - 1) / 2.0)) * expected_damage); + if (actual_damage < 0) return 0; + else return actual_damage; + } +} + +//Allows player to pick up/drop/swap items with the item on the tile that the player is on using the specified inventory slot +//Note: Does not check if inventory slot is out of bounds +void Game::swapItems(Player& player, int inventory_index) +{ + //Store item held by player temporarily + int inventory_item_id = player.m_inventory->getItemID(inventory_index); + ItemType inventory_item_type = player.m_inventory->getItemType(inventory_index); + int xcoord, ycoord; + player.getCoords(xcoord, ycoord); + + if (player.m_inventory->getItemType(inventory_index) == ItemType::NOTHING) //Nothing in inventory, i.e. picking up item + { + Game::event_message_handler.addEventMsg("Successfully picked up item."); + } + else if (Game::map(xcoord, ycoord).getItemType() == ItemType::NOTHING) //Nothing on tile, i.e. dropping an item + { + Game::event_message_handler.addEventMsg("Successfully dropped item."); + } + else //Something in both inventory and on tile, i.e. swapping items + { + Game::event_message_handler.addEventMsg("Successfully swapped items."); + } + + player.m_inventory->setItem(inventory_index, Game::map(xcoord, ycoord).getItemType(), Game::map(xcoord, ycoord).getItemID()); + Game::map(xcoord, ycoord).setItem(inventory_item_type, inventory_item_id); + + + if (Game::game_state == GameState::ONGOING) + { + Game::need_update_map = true; + } + else //In any encounter + { + Game::need_update_map = false; } } @@ -2009,19 +2341,43 @@ void Game::evaluateEvents() { if (!Game::first_time_in_encounter_mob) //Note: If it is actually the first time, we want to do nothing as the mob should not attack yet { - int entity_id = Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getEntityID(); - int damage_amount = getRandomInt(Game::mobs[entity_id].getMinDmg(), Game::mobs[entity_id].getMaxDmg()); - Game::player[0].takeDamage(damage_amount); - Game::event_message_handler.addEventMsg("The " + Game::mobs[entity_id].getName() + " attacks you for " + std::to_string(damage_amount) + " points of damage!"); + int entity_id = Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityID(); + int expected_damage = getRandomInt(Game::mobs[entity_id].getMinDmg(), Game::mobs[entity_id].getMaxDmg()); + int actual_damage = Game::evaluateActualDamage(expected_damage, Game::mobs[entity_id].getAtk(), player.getDef()); + int damage_discrepency = actual_damage - expected_damage; + + Game::player.takeDamage(actual_damage); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::MOB, Game::mobs[entity_id].getObjectTypeID(), MobEvent::ATTACK)); + Game::event_message_handler.addEventMsg("You take " + std::to_string(actual_damage) + " points of damage!"); + if (damage_discrepency < 0) //Took less damage than expected + { + Game::event_message_handler.addEventMsg("Your greater defense allowed you to block " + std::to_string(-damage_discrepency) + " points of damage."); + } + else if (damage_discrepency > 0) + { + Game::event_message_handler.addEventMsg(Game::mobs[entity_id].getName() + "\'s greater attack allowed it to deal you " + std::to_string(damage_discrepency) + " more points of damage."); + } } Game::first_time_in_encounter_mob = false; } else if (Game::game_state == GameState::ENCOUNTER_THREAT) //Note: Threat will always attack { - int entity_id = Game::map(Game::player[0].getXCoord(), Game::player[0].getYCoord()).getEntityID(); - int damage_amount = getRandomInt(Game::threat_data[entity_id].getMinDmg(), Game::threat_data[entity_id].getMaxDmg()); - Game::player[0].takeDamage(damage_amount); - Game::event_message_handler.addEventMsg("The " + Game::threat_data[entity_id].getName() + " causes you to lose " + std::to_string(damage_amount) + " points of health!"); + int entity_id = Game::map(Game::player.getXCoord(), Game::player.getYCoord()).getEntityID(); + int expected_damage = getRandomInt(Game::threat_data[entity_id].getMinDmg(), Game::threat_data[entity_id].getMaxDmg()); + int actual_damage = Game::evaluateActualDamage(expected_damage, Game::threat_data[entity_id].getAtk(), player.getDef()); + int damage_discrepency = actual_damage - expected_damage; + + Game::player.takeDamage(actual_damage); + Game::event_message_handler.addEventMsg(Game::event_list.getMessage(ObjectType::THREAT, Game::threat_data[entity_id].getObjectTypeID(), ThreatEvent::ATTACK)); + Game::event_message_handler.addEventMsg("You take " + std::to_string(actual_damage) + " points of damage!"); + if (damage_discrepency < 0) //Took less damage than expected + { + Game::event_message_handler.addEventMsg("Your greater defense allowed you to block " + std::to_string(-damage_discrepency) + " points of damage."); + } + else if (damage_discrepency > 0) + { + Game::event_message_handler.addEventMsg(Game::threat_data[entity_id].getName() + "\'s greater attack allowed it to deal you " + std::to_string(damage_discrepency) + " more points of damage."); + } } if (playerDied()) @@ -2057,7 +2413,7 @@ bool Game::noTimeLeft() const //Checks if the player has died bool Game::playerDied() const { - if (Game::player[0].isDead()) + if (Game::player.isDead()) return true; //else return false; @@ -2068,7 +2424,7 @@ bool Game::playerHasMagicalPotion() const { for (int i{ 0 }; i < 4; ++i) { - if (Game::player[0].getInventorySlotItemType(i) == ItemType::MAGICALPOTION) + if (Game::player.m_inventory->getItemType(i) == ItemType::MAGICALPOTION) return true; } //if all checks above false, diff --git a/Quest!/Game.h b/Quest!/Game.h index a6f791f..d64e9d5 100644 --- a/Quest!/Game.h +++ b/Quest!/Game.h @@ -1,12 +1,12 @@ #pragma once #include -#include #include "MiscFunctions.h" #include "DataLoader.h" #include "EventMsgHandler.h" #include "ItemsAndEntities.h" -#include "NPCs.h" +#include "Player.h" +#include "Merchant.h" #include "Events.h" #include "Map.h" @@ -16,19 +16,25 @@ class Game final Map map; std::vector base_item_number_to_place, item_number_to_place, mob_number_to_place, threat_number_to_place; //Stores data for number to place for each item/mob/threat - std::vector player_data; //Stores data for player base stats + Player player_data; //Stores data for player base stats std::vector base_item_data;//For BaseItem assets loaded at runtime to be copy constructed from std::vector item_data; //For Item assets std::vector mob_data; //For Mob assets std::vector threat_data; //For Threat assets std::vector merchant_data;//For Merchant assets - std::vector player; //Stores the actual player objects in the game + std::vector inventory_data; + + Player player; //Stores the actual player objects in the game std::vector base_items;//For actual baseitem objects std::vector items; //For actual item objects std::vector mobs; //For actual mob objects std::vector merchants; + std::vector inventories; + + EventList event_list; + //Not actually done anything with for the moment std::vector used_items_id; //Keeps track of the IDs of items that have been completely used up std::vector dead_mobs_id; //Keeps track of the IDs of dead mobs @@ -46,11 +52,12 @@ class Game final EventMsgHandler event_message_handler; //Holds messages from previous events (Should be cleared after being printed) - void loadOptions(); //Loads default options. Throws if error loading/validating default options values void loadData(); //Loads items/entities data. Throws if error loading/validating items and entites int getName(DataLoader& data_loader, std::string& name); - void checkDataLoaderStatus(const DataLoader& data_loader, const std::string& filename = "Unknown file") const; //Throws if the filereader failed or is at eof void loadNPCs(); + void loadOptions(); //Loads default options. Throws if error loading/validating default options values + bool coordsOutOfBounds(int xcoord, int ycoord); + void verifyEvents() const; void placeItemsAndEntities(); //Throws if not enough tiles to place items/entities //Map "refreshing" functions @@ -61,11 +68,12 @@ class Game final void printMap() const; //Shows the game board void printTimeLeft() const; //Prints the time left to complete the game void printPlayerDetails(Player& player) const; //Prints the hero's hp, exp, level, equipped weapon, inventory slots - void printInventory() const; + void printInventoryLeftRight(Inventory* inventory) const; //Prints the inventory slots and their items w/ no. of uses from left to right, then top to down + void printInventoryTopDown(Inventory* inventory) const; //Prints the inventory slots and their items w/ no. of uses from top to down, then left to right void printPlayerPosition(Player& player) const; void printVictoryMessage() const; //Congratulates player on winning void printGameOverMessage() const; //Game over message - void printObjectsOnTileDetails(int xcoord, int ycoord) const; //Prints what(Item/entity) is on the same tile as the player + void printObjectsOnTileDetails(int xcoord, int ycoord) const; //Prints what(Item/entity) is on the same tile as the player void printAvailablePlayerActions() const; //Print available options the player can take //Functions that deal with player action @@ -77,9 +85,13 @@ class Game final bool playerMoveMenu(); //Note: Changes the action of the player if valid (Specific case of isPlayerActionValid() bool useItemMenu(); bool swapItemMenu(); //Note: Changes the action of the player if valid (Specific case of isPlayerActionValid() - void inspectMenu(); //Note: Not an actual player action, only tells the player details about items - void printItemDetails(Item& item) const; + void inspectMenu(const Inventory* const inventory, int xcoord, int ycoord); //Note: Not an actual player action, only tells the player details about items + void inspectMenu(const Inventory* const inventory); //Version of inspectMenu that has inspect item/entity on tile disabled void printItemDetails(BaseItem& base_item) const; + void printItemDetails(Item& item) const; + void printEntityDetails(Mob& mob) const; + void printEntityDetails(Threat& threat) const; + void printEntityDetails(Merchant& merchant) const; void talkToNPCMenu(); void merchantTalkMenu(Merchant& merchant); void npcBuyMenu(Merchant& merchant); @@ -88,11 +100,12 @@ class Game final void playerMove(Player& player); //Not only moves the player but also sets visibility and explored elements of maptiles void playerMoveRandomDirection(Player& player); void setVisibilityAround(const Entity& entity, bool new_visibility); - void useHealingItemSlot(int inventory_slot_number); - void useWeaponItemSlot(int inventory_slot_number); + void useHealingItem(int inventory_index); + void useWeaponItem(int inventory_index); + int evaluateActualDamage(int expected_damage, int attacker_atk, int defender_def); void logUsedItem(int item_id) { used_items_id.push_back(item_id); } //Note: This logs to the game for it to note which items are used, not for player void logDeadMob(int entity_id) { dead_mobs_id.push_back(entity_id); } //Note: This logs to the game for it to note which mobs are dead, not for player - void swapItems(Player& player, int inventory_slot_number); + void swapItems(Player& player, int inventory_index); void checkSurroundings(const Entity& entity); //Non-player event functions @@ -117,6 +130,8 @@ class Game final Game::loadData(); //Load Data.dat for entities and items Game::loadOptions(); //Load Options.dat for default options Game::loadNPCs(); //Load NPCs.dat for NPCs + Game::event_list.loadEvents("Events.dat"); + Game::verifyEvents(); } void startNewGame() diff --git a/Quest!/Inventory.cpp b/Quest!/Inventory.cpp new file mode 100644 index 0000000..b60ff29 --- /dev/null +++ b/Quest!/Inventory.cpp @@ -0,0 +1,88 @@ +#include "Inventory.h" + +Inventory::Inventory(int size) +{ + m_item_types.resize(size); + for (int i{ 0 }; i < size; ++i) + { + m_item_types[i] = ItemType::PLACEHOLDER; + } + m_item_ids.resize(size); +} + +void Inventory::setItem(int inventory_slot_index, ItemType new_item_type, int new_item_id) +{ + m_item_types[inventory_slot_index] = new_item_type; + m_item_ids[inventory_slot_index] = new_item_id; +} + +void Inventory::clearItem(int inventory_slot_index) +{ + m_item_types[inventory_slot_index] = ItemType::NOTHING; + m_item_ids[inventory_slot_index] = -1; +} + +bool Inventory::valid(int item_size) const +{ + if (m_item_types.size() == 0) //Empty inventory + return true; + if (m_item_types.size() > 9) + return false; + if (m_item_types[0] != ItemType::PLACEHOLDER) //If not placeholder, we are loading a saved game, check validity of item type + { + for (int i{ 0 }; i < static_cast(m_item_types.size()); ++i) + { + //NOTHING is defined to be the first possible item type with an int value of -1, while magical potion should be the last possible type + if (static_cast(m_item_types[i]) < static_cast(ItemType::NOTHING) || + static_cast(m_item_types[i]) >= static_cast(ItemType::MAGICALPOTION)) + { + return false; + } + //ID is defined to be -1 if there is nothing in the inventory + if (m_item_types[i] == ItemType::NOTHING && m_item_ids[i] != -1) + { + return false; + } + //If there is an item, check if its item ID is correct (0 to Game::items.size() - 1) + if (m_item_types[i] != ItemType::NOTHING && (m_item_ids[i] < 0 || m_item_ids[i] > (item_size - 1))) + { + return false; + } + } + return true; + } + + for (int item_id : m_item_ids) //Since item type is placeholder, we are only loading game data + { + //ID is valid if either nothing(-1) or within the indexes of item_data (0 to item_data.size() - 1) + if (item_id < -1 || item_id >= item_size) + { + return false; + } + } + return true; +} + +bool Inventory::isFull() const +{ + for (int i : m_item_ids) + { + if (i == -1) //An inventory slot is empty + { + return false; + } + } + return true; //All slots are not empty and thus full +} + +int Inventory::getEmptyIndex() const //Returns the first available inventory slot (-1 if no free inventory slot) +{ + for (size_t i{ 0 }; i < m_item_ids.size(); ++i) + { + if (m_item_ids[i] == -1) + { + return i; + } + } + return -1; +} \ No newline at end of file diff --git a/Quest!/Inventory.h b/Quest!/Inventory.h new file mode 100644 index 0000000..9737c9f --- /dev/null +++ b/Quest!/Inventory.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include "ItemsAndEntities.h" + +class Inventory final +{ +private: + std::vector m_item_types; + std::vector m_item_ids; + + +public: + Inventory(int size); + + size_t size() const { return m_item_types.size(); } + ItemType getItemType(int inventory_slot_index) const { return m_item_types[inventory_slot_index]; } + int getItemID(int inventory_slot_index) const { return m_item_ids[inventory_slot_index]; } + + void setItemID(int inventory_slot_index, int new_item_id) { m_item_ids[inventory_slot_index] = new_item_id; } + void setItem(int inventory_slot_index, ItemType new_item_type, int new_item_id); + void clearItem(int inventory_slot_index); //Does not erase the item slot + + bool valid(int item_size) const; //For use during loading inventory IDs + bool isFull() const; + int getEmptyIndex() const; //Returns the first available inventory slot (-1 if no free inventory slot) +}; diff --git a/Quest!/ItemsAndEntities.cpp b/Quest!/ItemsAndEntities.cpp index a6d3bd5..1e8e679 100644 --- a/Quest!/ItemsAndEntities.cpp +++ b/Quest!/ItemsAndEntities.cpp @@ -44,163 +44,7 @@ void Entity::takeDamage(int damage_amount) } } -void Player::gainExp(int exp_to_add) -{ - m_exp += exp_to_add; - if (m_exp >= 5) - { - while (m_exp >= 5) //Currently, 5 exp to level up - { - m_exp -= 5; - levelUp(); - } - } - else - { - return; - } -} - -//Level up, atk/def/mindmg/maxdmg/maxhp will increase -void Player::levelUp() -{ - //For now, atk and def = level - ++m_level; - m_atk = m_level; - m_def = m_level; - - if (m_level < 6) //For levels 1 - 5 - { - m_max_hp += static_cast((10.0 - m_level) / 100.0 * m_max_hp); - m_min_dmg += static_cast((10.0 - m_level) / 100.0 * m_min_dmg); - m_max_dmg += static_cast((10.0 - m_level) / 100.0 * m_max_dmg); - } - else - { - m_max_hp += static_cast(5.0 / 100.0 * m_max_hp); - m_min_dmg += static_cast(5.0 / 100.0 * m_min_dmg); - m_max_dmg += static_cast(5.0 / 100.0 * m_max_dmg); - } -} - -//Future use scaleStatsToLevel() -//Checks if the data inside is valid or not (e.g. no negative hp values) -//Should be passed size of Game::item_data when loading game data -//Should be passed size of Game::items when loading saved game -bool Player::valid(int item_size) -{ - if (m_max_hp <= 0 || m_hp > m_max_hp) - return false; - if (m_atk < 1) - return false; - if (m_def < 1) - return false; - if (m_min_dmg < 0 || m_max_dmg < m_min_dmg) - return false; - if (m_exp < 0) - return false; - if (m_level < 0) - return false; - if (m_gold < 0) - return false; - - if (m_inventory_item_type[1] != ItemType::PLACEHOLDER) //If not placeholder, we are loading a saved game, check validity of item type - { - for (int i{ 0 }; i < 4; ++i) - { - //NOTHING is defined to be the first possible item type with an int value of -1, while magical potion should be the last possible type - if (static_cast(m_inventory_item_type[i]) < static_cast(ItemType::NOTHING) || - static_cast(m_inventory_item_type[i]) >= static_cast(ItemType::MAGICALPOTION)) - { - return false; - } - //ID is defined to be -1 if there is nothing in the inventory - if (m_inventory_item_type[i] == ItemType::NOTHING && m_inventory_id[i] != -1) - { - return false; - } - //If there is an item, check if its item ID is correct (0 to Game::items.size() - 1) - if (m_inventory_item_type[i] != ItemType::NOTHING && (m_inventory_id[i] < 0 || m_inventory_id[i] > (item_size - 1))) - { - return false; - } - } - return true; - } - - for (int i{ 0 }; i < 4; ++i) //Since item type is placeholder, we are only loading game data - { - //ID is valid if either nothing(-1) or within the indexes of item_data (0 to item_data.size() - 1) - if (m_inventory_id[i] < -1 || m_inventory_id[i] >= item_size) - { - return false; - } - } - return true; -} - -bool Player::isInventoryFull() -{ - for (int i{ 0 }; i < 4; ++i) - { - if (m_inventory_id[i] == -1) //An inventory slot is empty - { - return false; - } - } - return true; //All slots are not empty and thus full -} - -int Player::getEmptyInventorySlot() //Returns the first available inventory slot (-1 if no free inventory slot) -{ - for (int i{ 0 }; i < 4; ++i) - { - if (m_inventory_id[i] == -1) - { - return (i + 1); - } - } - return -1; -} - -void Player::setInventorySlotItem(int inventory_slot_number, ItemType new_item_type, int new_item_id) -{ - m_inventory_id[inventory_slot_number - 1] = new_item_id; - m_inventory_item_type[inventory_slot_number - 1] = new_item_type; -} - -bool Player::runFrom(Mob& mob) -{ - //Run_chance has a limit of up to 5 decimal places, any more are ignored - //Success rate will be counted as run_chance / 10'000'000 - int run_chance = static_cast(mob.getRunChance() * 100'000); - int random_number = getRandomInt(0, 10'000'000); - if (random_number < run_chance) //If random number is within success rate, successful at running - return true; - //else - return false; -} - -bool Player::runFrom(Threat& threat) -{ - //Run_chance has a limit of up to 5 decimal places, any more are ignored - //Success rate will be counted as run_chance / 10'000'000 - int run_chance = static_cast(threat.getRunChance() * 100'000); - int random_number = getRandomInt(0, 10'000'000); - if (random_number < run_chance) //If random number is within success rate, successful at running - return true; - //else - return false; -} - -//void Player::scaleStatsToLevel() -//{ -// m_max_hp = 100 + (m_level - 1) * 10; -// m_hp = m_max_hp; -// -// m_def = m_level; -//} //Checks if the data inside is valid or not (e.g. no negative hp values) bool Mob::valid() diff --git a/Quest!/ItemsAndEntities.h b/Quest!/ItemsAndEntities.h index 6030278..afac682 100644 --- a/Quest!/ItemsAndEntities.h +++ b/Quest!/ItemsAndEntities.h @@ -2,9 +2,10 @@ #include #include "MiscFunctions.h" -#include "Events.h" -enum class ItemType +class Inventory; + +enum class ItemType : int { PLACEHOLDER = -2, NOTHING = -1, @@ -14,7 +15,7 @@ enum class ItemType MAGICALPOTION }; -enum class EntityType +enum class EntityType : int { NOTHING = -1, PLAYER, @@ -23,6 +24,16 @@ enum class EntityType MERCHANT, }; +enum class ObjectType : int //Collection of objects (Items/entities) with event messages tied to them +{ + BASEITEM = 0, + ITEM, + MOB, + THREAT, + MERCHANT, + NUMBER_OF_OBJECTS +}; + class BaseItem { protected: @@ -34,9 +45,11 @@ class BaseItem int m_xcoord; //So far items havent had to have their coordinates used though, since all maptiles hold itemtype and their ids int m_ycoord; + int m_object_typeid; + public: - BaseItem(ItemType item_type, const std::string& name, int value) - : m_item_type{ item_type }, m_name{ name }, m_value{ value } + BaseItem(ItemType item_type, const std::string& name, int value, int object_typeid) + : m_item_type{ item_type }, m_name{ name }, m_value{ value }, m_object_typeid{ object_typeid } {} bool valid(); @@ -47,6 +60,7 @@ class BaseItem int getYCoord() const { return m_ycoord; } int getCoords(int& xcoord, int& ycoord) const { xcoord = m_xcoord; ycoord = m_ycoord; } int getValue() const { return m_value; } + int getObjectTypeID() const { return m_object_typeid; } void setXCoord(int new_xcoord) { m_xcoord = new_xcoord; } void setYCoord(int new_ycoord) { m_ycoord = new_ycoord; } @@ -69,8 +83,8 @@ class Item final : public BaseItem //Usable item public: - Item(ItemType item_type, const std::string& name, int min_hp_change, int max_hp_change, int uses, double success_rate, int value) - : BaseItem(item_type, name, value), m_min_hp_change{ min_hp_change }, m_max_hp_change{ max_hp_change }, m_uses{ uses }, + Item(ItemType item_type, const std::string& name, int min_hp_change, int max_hp_change, int uses, double success_rate, int value, int object_typeid) + : BaseItem(item_type, name, value, object_typeid), m_min_hp_change{ min_hp_change }, m_max_hp_change{ max_hp_change }, m_uses{ uses }, m_success_rate{ success_rate } {} @@ -112,6 +126,8 @@ class Entity public: + Entity() {} + Entity(EntityType entity_type, const std::string& name, int max_hp, int hp,int atk, int def, int min_dmg, int max_dmg, int exp, int level, bool is_dead, int gold, int object_typeid) : m_entity_type{ entity_type }, m_name{ name }, m_max_hp{ max_hp }, m_hp{ hp }, m_atk{ atk }, m_def{ def }, m_min_dmg{ min_dmg }, m_max_dmg{ max_dmg }, @@ -134,6 +150,7 @@ class Entity int getXCoord() const { return m_xcoord; } int getYCoord() const { return m_ycoord; } void getCoords(int& xcoord, int& ycoord) const { xcoord = m_xcoord; ycoord = m_ycoord; } + int getObjectTypeID() const { return m_object_typeid; } void setXCoord(int new_xcoord) { m_xcoord = new_xcoord; } void setYCoord(int new_ycoord) { m_ycoord = new_ycoord; } @@ -165,58 +182,27 @@ class Threat final private: std::string m_name; + int m_atk; int m_min_dmg; int m_max_dmg; double m_run_chance; + int m_object_typeid; + public: - Threat(const std::string& name, int min_dmg, int max_dmg, double run_chance) - : m_name{ name }, m_min_dmg{ min_dmg }, m_max_dmg{ max_dmg }, m_run_chance{ run_chance } + Threat(const std::string& name, int atk, int min_dmg, int max_dmg, double run_chance, int object_typeid) + : m_name{ name }, m_atk{ atk }, m_min_dmg{ min_dmg }, m_max_dmg{ max_dmg }, m_run_chance{ run_chance }, m_object_typeid{ object_typeid } {} bool valid(); const std::string& getName() const { return m_name; } + int getAtk() const { return m_atk; } int getMinDmg() const { return m_min_dmg; } int getMaxDmg() const { return m_max_dmg; } double getRunChance() const { return m_run_chance; } + int getObjectTypeID() const { return m_object_typeid; } }; -class Player final : public Entity -{ -private: - ItemType m_inventory_item_type[4]; - int m_inventory_id[4]; - //Add equipped slots in the future - - Action m_action; -public: - Player(const std::string& name, int max_hp, int hp, int atk, int def, int exp, int level, - int inventory1_id, int inventory2_id, int inventory3_id, int inventory4_id, int gold, int object_typeid = 0, bool is_dead = false, - ItemType inventory1_item_type = ItemType::PLACEHOLDER, ItemType inventory2_item_type = ItemType::PLACEHOLDER, - ItemType inventory3_item_type = ItemType::PLACEHOLDER, ItemType inventory4_item_type = ItemType::PLACEHOLDER) - : Entity(EntityType::PLAYER, name, max_hp, hp, atk, def, 0, 0, exp, level, is_dead, gold, object_typeid), //Initially 0 min dmg and 0 max dmg, only one type of player - m_inventory_item_type{ inventory1_item_type ,inventory2_item_type ,inventory3_item_type ,inventory4_item_type }, - m_inventory_id{ inventory1_id ,inventory2_id, inventory3_id, inventory4_id } - { - //Scale level - } - - bool valid(int item_size); - - bool isInventoryFull(); - int getInventorySlotItemID(int inventory_slot_number) const { return m_inventory_id[inventory_slot_number - 1]; } //Insert inventory slot number, not index - ItemType getInventorySlotItemType(int inventory_slot_number) const { return m_inventory_item_type[inventory_slot_number - 1]; } - int getEmptyInventorySlot(); - Action getAction() const { return m_action; } - - void setAction(Action action) { m_action = action; } - void setInventorySlotItem(int inventory_slot_number, ItemType new_item_type, int new_item_id); - bool runFrom(Mob& mob); - bool runFrom(Threat& threat); - void gainExp(int exp_to_add); - void levelUp(); - -}; diff --git a/Quest!/Merchant.cpp b/Quest!/Merchant.cpp new file mode 100644 index 0000000..336eddc --- /dev/null +++ b/Quest!/Merchant.cpp @@ -0,0 +1,10 @@ +#include "Merchant.h" + +bool Merchant::valid() +{ + if (m_buy_price_percent < 0) + return false; + if (m_sell_price_percent < 0) + return false; + return true; +} \ No newline at end of file diff --git a/Quest!/NPCs.h b/Quest!/Merchant.h similarity index 56% rename from Quest!/NPCs.h rename to Quest!/Merchant.h index bc5bc74..b7d5254 100644 --- a/Quest!/NPCs.h +++ b/Quest!/Merchant.h @@ -2,6 +2,7 @@ #include #include #include "ItemsAndEntities.h" +#include "Inventory.h" //NOTE: Check const std::string& name or std::string&& @@ -21,16 +22,18 @@ class NPC int m_xcoord; int m_ycoord; + int m_object_typeid; + public: - NPC(NPCType npc_type, const std::string& name) - : m_npc_type{ npc_type }, m_name{ name } + NPC(NPCType npc_type, const std::string& name, int object_typeid) + : m_npc_type{ npc_type }, m_name{ name }, m_object_typeid{ object_typeid } {} - virtual ~NPC() {}; const std::string& getName() const { return m_name; } NPCType getNPCType() const { return m_npc_type; } int getXCoord() const { return m_xcoord; } int getYCoord() const { return m_ycoord; } + int getObjectTypeID() const { return m_object_typeid; } void setCoords(int xcoord, int ycoord) { m_xcoord = xcoord; m_ycoord = ycoord; } }; @@ -43,30 +46,23 @@ class Merchant final : public NPC int m_buy_price_percent; //Buy price as percent of value int m_sell_price_percent; //Sell price as percent of value - std::vector m_inventory_item_type; - std::vector m_inventory_item_ids;//Size of this vector tracks how many item slots the merchant has - public: - Merchant(const std::string& name, bool can_be_bought_from, bool can_be_sold_to, int buy_price_percent, int sell_price_percent) - : NPC(NPCType::MERCHANT, name), m_can_be_bought_from{ can_be_bought_from }, m_can_be_sold_to{ can_be_sold_to }, + Merchant(const std::string& name, bool can_be_bought_from, bool can_be_sold_to, int buy_price_percent, int sell_price_percent, int object_typeid) + : NPC(NPCType::MERCHANT, name, object_typeid), m_can_be_bought_from{ can_be_bought_from }, m_can_be_sold_to{ can_be_sold_to }, m_buy_price_percent{ buy_price_percent }, m_sell_price_percent{ sell_price_percent } {} + Inventory* m_inventory; + //Should be passed size of Game::item_data when loading game data //Should be passed size of Game::items when loading saved game - bool valid(int item_size); + bool valid(); bool canBeBoughtFrom() const { return m_can_be_bought_from; } bool CanBeSoldTo() const { return m_can_be_sold_to; } + int getBuyPricePercent() const { return m_buy_price_percent; } + int getSellPricePercent() const { return m_sell_price_percent; } int getItemBuyPrice(int item_value) const { return static_cast(item_value * m_buy_price_percent/100.0); } int getItemSellPrice(int item_value) const { return static_cast(item_value * m_sell_price_percent / 100.0); } - //Inventory management - size_t getInventorySize() { return m_inventory_item_ids.size(); } - int getInventorySlotItemID(int inventory_slot) const { return m_inventory_item_ids[inventory_slot - 1]; } - ItemType getInventorySlotItemType(int inventory_slot) const { return m_inventory_item_type[inventory_slot - 1]; } - const std::vector& getInventoryItemIDs() const { return m_inventory_item_ids; } //Not used yet - void addNewItemSlot(ItemType item_type, int item_id); - void clearItemSlot(int inventory_slot); //Note: Does not decrease slot count - void setItemSlot(int inventory_slot, ItemType new_item_type, int new_item_id); }; \ No newline at end of file diff --git a/Quest!/MiscFunctions.cpp b/Quest!/MiscFunctions.cpp index 35e211d..fce086f 100644 --- a/Quest!/MiscFunctions.cpp +++ b/Quest!/MiscFunctions.cpp @@ -11,7 +11,7 @@ // namespace CONSTANTS { - constexpr char* VERSION_NUMBER = "v0.3.1"; + constexpr char* VERSION_NUMBER = "v0.4.0"; } //Utility functions diff --git a/Quest!/NPCs.cpp b/Quest!/NPCs.cpp deleted file mode 100644 index 8db9cda..0000000 --- a/Quest!/NPCs.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "NPCs.h" - -bool Merchant::valid(int item_size) -{ - if (m_buy_price_percent < 0) - return false; - if (m_sell_price_percent < 0) - return false; - if (m_inventory_item_ids.size() > 9) //Max 9 slots - return false; - - if (m_inventory_item_type.size() != 0) //Then execute below - if (m_inventory_item_type[1] != ItemType::PLACEHOLDER) //If not placeholder, we are loading a saved game, check validity of item type - { - for (size_t i{ 0 }; i < m_inventory_item_ids.size(); ++i) - { - //NOTHING is defined to be the first possible item type with an int value of -1, while magical potion should be the last possible type - if (static_cast(m_inventory_item_type[i]) < static_cast(ItemType::NOTHING) || - static_cast(m_inventory_item_type[i]) >= static_cast(ItemType::MAGICALPOTION)) - { - return false; - } - //ID is defined to be -1 if there is nothing in the inventory - if (m_inventory_item_type[i] == ItemType::NOTHING && m_inventory_item_ids[i] != -1) - { - return false; - } - //If there is an item, check if its item ID is correct (0 to Game::items.size() - 1) - if (m_inventory_item_type[i] != ItemType::NOTHING && (m_inventory_item_ids[i] < 0 || m_inventory_item_ids[i] > (item_size - 1))) - { - return false; - } - } - return true; - } - - for (int i : m_inventory_item_ids) - { - //ID is valid if either nothing(-1) or within the indexes of item_data (0 to item_data.size() - 1) - if (i < -1 || i >= item_size) - { - return false; - } - } - return true; -} - -void Merchant::addNewItemSlot(ItemType item_type, int item_id) -{ - m_inventory_item_type.push_back(item_type); - m_inventory_item_ids.push_back(item_id); -} - -void Merchant::clearItemSlot(int inventory_slot) //Note: Does not decrease slot count -{ - m_inventory_item_type[inventory_slot - 1] = ItemType::NOTHING; - m_inventory_item_ids[inventory_slot - 1] = -1; -} - -void Merchant::setItemSlot(int inventory_slot, ItemType new_item_type, int new_item_id) -{ - m_inventory_item_type[inventory_slot - 1] = new_item_type; - m_inventory_item_ids[inventory_slot - 1] = new_item_id; -} \ No newline at end of file diff --git a/Quest!/Player.cpp b/Quest!/Player.cpp new file mode 100644 index 0000000..afb9570 --- /dev/null +++ b/Quest!/Player.cpp @@ -0,0 +1,96 @@ +#include "Player.h" + +void Player::gainExp(int exp_to_add) +{ + m_exp += exp_to_add; + if (m_exp >= 5) + { + while (m_exp >= 5) //Currently, 5 exp to level up + { + m_exp -= 5; + levelUp(); + } + } + else + { + return; + } +} + +//Level up, atk/def/mindmg/maxdmg/maxhp will increase +void Player::levelUp() +{ + //For now, atk and def = level + ++m_level; + m_atk = m_level; + m_def = m_level; + + if (m_level < 6) //For levels 1 - 5 + { + m_max_hp += static_cast((10.0 - m_level) / 100.0 * m_max_hp); + m_min_dmg += static_cast((10.0 - m_level) / 100.0 * m_min_dmg); + m_max_dmg += static_cast((10.0 - m_level) / 100.0 * m_max_dmg); + } + else + { + m_max_hp += static_cast(5.0 / 100.0 * m_max_hp); + m_min_dmg += static_cast(5.0 / 100.0 * m_min_dmg); + m_max_dmg += static_cast(5.0 / 100.0 * m_max_dmg); + } +} + +//Future use scaleStatsToLevel() + +//Checks if the data inside is valid or not (e.g. no negative hp values) +//Should be passed size of Game::item_data when loading game data +//Should be passed size of Game::items when loading saved game +bool Player::valid() +{ + if (m_max_hp <= 0 || m_hp > m_max_hp) + return false; + if (m_atk < 1) + return false; + if (m_def < 1) + return false; + if (m_min_dmg < 0 || m_max_dmg < m_min_dmg) + return false; + if (m_exp < 0) + return false; + if (m_level < 0) + return false; + if (m_gold < 0) + return false; + return true; +} + +bool Player::runFrom(Mob& mob) +{ + //Run_chance has a limit of up to 5 decimal places, any more are ignored + //Success rate will be counted as run_chance / 10'000'000 + int run_chance = static_cast(mob.getRunChance() * 100'000); + int random_number = getRandomInt(0, 10'000'000); + if (random_number < run_chance) //If random number is within success rate, successful at running + return true; + //else + return false; +} + +bool Player::runFrom(Threat& threat) +{ + //Run_chance has a limit of up to 5 decimal places, any more are ignored + //Success rate will be counted as run_chance / 10'000'000 + int run_chance = static_cast(threat.getRunChance() * 100'000); + int random_number = getRandomInt(0, 10'000'000); + if (random_number < run_chance) //If random number is within success rate, successful at running + return true; + //else + return false; +} + +//void Player::scaleStatsToLevel() +//{ +// m_max_hp = 100 + (m_level - 1) * 10; +// m_hp = m_max_hp; +// +// m_def = m_level; +//} \ No newline at end of file diff --git a/Quest!/Player.h b/Quest!/Player.h new file mode 100644 index 0000000..b100cef --- /dev/null +++ b/Quest!/Player.h @@ -0,0 +1,36 @@ +#pragma once +#include "Events.h" +#include "ItemsAndEntities.h" +#include "Inventory.h" + +class Player final : public Entity +{ +private: + //Add equipped slots in the future + + Action m_action; + +public: + Player() {} + + Player(const std::string& name, int max_hp, int hp, int atk, int def, int exp, int level, int gold, int object_typeid = 0, bool is_dead = false) + : Entity(EntityType::PLAYER, name, max_hp, hp, atk, def, 0, 0, exp, level, is_dead, gold, object_typeid) + //Initially 0 min dmg and 0 max dmg, only one type of player + { + gainExp(0); + m_hp = m_max_hp; + } + + Inventory* m_inventory; //Public member variable with its own public functions and private members + + bool valid(); + + Action getAction() const { return m_action; } + + void setAction(Action action) { m_action = action; } + bool runFrom(Mob& mob); + bool runFrom(Threat& threat); + void gainExp(int exp_to_add); + void levelUp(); + +}; \ No newline at end of file