diff --git a/client/heartbeat_utility.cpp b/client/heartbeat_utility.cpp new file mode 100644 index 0000000..18fcfee --- /dev/null +++ b/client/heartbeat_utility.cpp @@ -0,0 +1,58 @@ +/* Copyright: (c) Kayne Ruse 2013-2016 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. +*/ +#include "heartbeat_utility.hpp" + +#include "channels.hpp" +#include "ip_operators.hpp" + +//heartbeat system +void HeartbeatUtility::hPing(ServerPacket* const argPacket) { + ServerPacket newPacket; + newPacket.type = SerialPacketType::PONG; + network.SendTo(argPacket->srcAddress, &newPacket); +} + +void HeartbeatUtility::hPong(ServerPacket* const argPacket) { + if (*network.GetIPAddress(Channels::SERVER) != argPacket->srcAddress) { + throw(std::runtime_error("Heartbeat message received from an unknown source")); + } + attemptedBeats = 0; + lastBeat = Clock::now(); +} + +int HeartbeatUtility::CheckHeartBeat() { + //check the connection (heartbeat) + if (Clock::now() - lastBeat > std::chrono::seconds(3)) { + if (attemptedBeats > 2) { + return 1; + } + else { + ServerPacket newPacket; + newPacket.type = SerialPacketType::PING; + network.SendTo(Channels::SERVER, &newPacket); + + attemptedBeats++; + lastBeat = Clock::now(); + } + } + return 0; +} diff --git a/rsc/scripts/setup_database.sql b/client/heartbeat_utility.hpp similarity index 52% rename from rsc/scripts/setup_database.sql rename to client/heartbeat_utility.hpp index f831ead..95ac906 100644 --- a/rsc/scripts/setup_database.sql +++ b/client/heartbeat_utility.hpp @@ -19,27 +19,24 @@ * 3. This notice may not be removed or altered from any source * distribution. */ +#pragma once -------------------------- ---fill the global tables with gameplay data -------------------------- +#include "server_packet.hpp" +#include "udp_network_utility.hpp" -INSERT OR IGNORE INTO InventoryItems (name, type, durability) VALUES - ("sword", "weapon", 100), - ("dagger", "weapon", 100), - ("staff", "weapon", 100), - ("shield", "armour", 100), - ("potion", "consumable", 100) -; +#include ---DEBUG: Test cases -INSERT INTO LiveCharacters +class HeartbeatUtility { +public: + //heartbeat system + void hPing(ServerPacket* const); + void hPong(ServerPacket* const); ---DEBUG: this is supposed to archive the dead characters ---Insert into DeadCharacters From LiveCharacters all characters who's HP has reached zero or below -INSERT INTO DeadCharacters (uid, owner, handle, avatar, birth) - SELECT uid, owner, handle, avatar, birth FROM LiveCharacters WHERE - SELECT character FROM CharacterStatistics WHERE - SELECT uid FROM CombatStatistics WHERE health <= 0; + int CheckHeartBeat(); -DELETE FROM LiveCharacters WHERE uid IN (SELECT uid FROM DeadCharacters); \ No newline at end of file +private: + UDPNetworkUtility& network = UDPNetworkUtility::GetSingleton(); + typedef std::chrono::steady_clock Clock; + Clock::time_point lastBeat = Clock::now(); + int attemptedBeats = 0; +}; \ No newline at end of file diff --git a/client/scenes/world.cpp b/client/scenes/world.cpp index 4c5aa0f..2357614 100644 --- a/client/scenes/world.cpp +++ b/client/scenes/world.cpp @@ -77,12 +77,16 @@ World::World(int* const argClientIndex, int* const argAccountIndex): disconnectButton.SetText(GetRenderer(), font, WHITE, "Disconnect"); shutdownButton.SetBackgroundTexture(GetRenderer(), buttonImage.GetTexture()); shutdownButton.SetText(GetRenderer(), font, WHITE, "Shutdown"); + inventoryButton.SetBackgroundTexture(GetRenderer(), buttonImage.GetTexture()); + inventoryButton.SetText(GetRenderer(), font, WHITE, "Inventory"); //set the button positions disconnectButton.SetX(50); disconnectButton.SetY(50); shutdownButton.SetX(50); shutdownButton.SetY(70); + inventoryButton.SetX(50); + inventoryButton.SetY(90); //load the tilesheet //TODO: (2) Tile size and tile sheet should be loaded elsewhere @@ -161,7 +165,12 @@ void World::Update() { delete reinterpret_cast(packetBuffer); //heartbeat system - CheckHeartBeat(); + if (heartbeatUtility.CheckHeartBeat()) { + //escape to the disconnect screen + SendDisconnectRequest(); + SetSceneSignal(SceneSignal::DISCONNECTEDSCREEN); + ConfigUtility::GetSingleton()["client.disconnectMessage"] = "Error: Lost connection to the server"; + } //update all entities for (auto& it : characterMap) { @@ -261,6 +270,7 @@ void World::RenderFrame(SDL_Renderer* renderer) { //draw UI disconnectButton.DrawTo(renderer); shutdownButton.DrawTo(renderer); + inventoryButton.DrawTo(renderer); //FPS fpsTextLine.DrawTo(renderer); @@ -285,11 +295,13 @@ void World::QuitEvent() { void World::MouseMotion(SDL_MouseMotionEvent const& event) { disconnectButton.MouseMotion(event); shutdownButton.MouseMotion(event); + inventoryButton.MouseMotion(event); } void World::MouseButtonDown(SDL_MouseButtonEvent const& event) { disconnectButton.MouseButtonDown(event); shutdownButton.MouseButtonDown(event); + inventoryButton.MouseButtonDown(event); } void World::MouseButtonUp(SDL_MouseButtonEvent const& event) { @@ -299,6 +311,9 @@ void World::MouseButtonUp(SDL_MouseButtonEvent const& event) { if (shutdownButton.MouseButtonUp(event) == Button::State::RELEASED) { SendAdminShutdownRequest(); } + if (inventoryButton.MouseButtonUp(event) == Button::State::RELEASED) { + //TODO: show the inventory screen + } } void World::MouseWheel(SDL_MouseWheelEvent const& event) { @@ -411,10 +426,10 @@ void World::HandlePacket(SerialPacket* const argPacket) { switch(argPacket->type) { //heartbeat system case SerialPacketType::PING: - hPing(static_cast(argPacket)); + heartbeatUtility.hPing(static_cast(argPacket)); break; case SerialPacketType::PONG: - hPong(static_cast(argPacket)); + heartbeatUtility.hPong(static_cast(argPacket)); break; //game server connections @@ -520,44 +535,6 @@ void World::HandlePacket(SerialPacket* const argPacket) { } } -//------------------------- -//heartbeat system -//------------------------- - -void World::hPing(ServerPacket* const argPacket) { - ServerPacket newPacket; - newPacket.type = SerialPacketType::PONG; - network.SendTo(argPacket->srcAddress, &newPacket); -} - -void World::hPong(ServerPacket* const argPacket) { - if (*network.GetIPAddress(Channels::SERVER) != argPacket->srcAddress) { - throw(std::runtime_error("Heartbeat message received from an unknown source")); - } - attemptedBeats = 0; - lastBeat = Clock::now(); -} - -void World::CheckHeartBeat() { - //check the connection (heartbeat) - if (Clock::now() - lastBeat > std::chrono::seconds(3)) { - if (attemptedBeats > 2) { - //escape to the disconnect screen - SendDisconnectRequest(); - SetSceneSignal(SceneSignal::DISCONNECTEDSCREEN); - ConfigUtility::GetSingleton()["client.disconnectMessage"] = "Error: Lost connection to the server"; - } - else { - ServerPacket newPacket; - newPacket.type = SerialPacketType::PING; - network.SendTo(Channels::SERVER, &newPacket); - - attemptedBeats++; - lastBeat = Clock::now(); - } - } -} - //------------------------- //Connection control //------------------------- diff --git a/client/scenes/world.hpp b/client/scenes/world.hpp index f317e7a..3446a37 100644 --- a/client/scenes/world.hpp +++ b/client/scenes/world.hpp @@ -43,6 +43,7 @@ #include "base_scene.hpp" #include "base_barrier.hpp" #include "base_creature.hpp" +#include "heartbeat_utility.hpp" #include "local_character.hpp" #include "SDL2/SDL.h" @@ -80,12 +81,6 @@ private: //handle incoming traffic void HandlePacket(SerialPacket* const); - //heartbeat system - void hPing(ServerPacket* const); - void hPong(ServerPacket* const); - - void CheckHeartBeat(); - //basic connections void SendLogoutRequest(); void SendDisconnectRequest(); @@ -148,6 +143,7 @@ private: TTF_Font* font = nullptr; Button disconnectButton; Button shutdownButton; + Button inventoryButton; FrameRate fps; TextLine fpsTextLine; @@ -165,11 +161,9 @@ private: LocalCharacter* localCharacter = nullptr; //heartbeat - //TODO: (2) Heartbeat needs it's own utility + HeartbeatUtility heartbeatUtility; typedef std::chrono::steady_clock Clock; - Clock::time_point lastBeat = Clock::now(); Clock::time_point queryTime = Clock::now() - std::chrono::seconds(4); //back 4 seconds to trigger automatically - int attemptedBeats = 0; //ugly references; I hate this ConfigUtility& config = ConfigUtility::GetSingleton(); diff --git a/common/utilities/csv_tool.hpp b/common/utilities/csv_tool.hpp new file mode 100644 index 0000000..784deaf --- /dev/null +++ b/common/utilities/csv_tool.hpp @@ -0,0 +1,125 @@ +/* Copyright: (c) Kayne Ruse 2015 + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. +*/ +#pragma once + +//DOCS: this is a generic CSV reading tool +//DOCS: empty lines and comment lines begining with '#' are ignored +//DOCS: whitespace characters are valid field values +//DOCS: if the file is invalid, then the behavior is undefined + +#include +#include +#include +#include +#include +#include +#include + +//define the container types +template +using CSVElement = std::array; + +template +using CSVObject = std::vector>; + +//read a file into an object +template +CSVObject readCSV(std::string fname, char delim = ',') { + //open the file + std::ifstream is(fname); + + if (!is.is_open()) { + std::ostringstream msg; + msg << "Failed to open file: " << fname; + throw(std::runtime_error(msg.str())); + } + + //build the scanf format + std::ostringstream format; + format << "%[^\0" << delim << "]"; + + //read and store each record (one per line) + CSVObject object; + while(!is.eof()) { + //get a line + std::string tmpLine; + getline(is, tmpLine); + + //skip blank and comment lines + if (tmpLine.size() == 0 || tmpLine[0] == '#') { + continue; + } + + //read and store each field + CSVElement record; + for (int i = 0; i < N; ++i) { + //get a field + char tmpField[256]; + memset(tmpField, 0, 256); + + sscanf(tmpLine.c_str(), format.str().c_str(), tmpField); + + //prune the input + int len = std::min(strlen(tmpField)+1, tmpLine.size()); + tmpLine = tmpLine.substr(len); + + //store the field + record[i] = tmpField; + } + + object.push_back(record); + } + + //finally, close the file + is.close(); + + return object; +} + +template +void writeCSV(std::string fname, CSVObject const& object, char delim = ',') { + //open the file + std::ofstream os(fname); + + if (!os.is_open()) { + std::ostringstream msg; + msg << "Failed to open file: " << fname; + throw(std::runtime_error(msg.str())); + } + + //write each record, one at a time + for(auto& record : object) { + //write each field, one at a time + for (int i = 0; i < N; i++) { + os << record[i]; + + //print delimiter + if (i != N -1) { + os << delim; + } + } + os << std::endl; + } + + //finish + os.close(); +} diff --git a/rsc/data/items.csv b/rsc/data/items.csv new file mode 100644 index 0000000..bdf02e0 --- /dev/null +++ b/rsc/data/items.csv @@ -0,0 +1,5 @@ +#Format (4 columns): "name", type, "sprite.png", stackable +"Sword",weapon,"sword.png",false +"Staff",weapon,"staff.png",false +"Dagger",weapon,"dagger.png",false +"Potion",consumable,"potion.png",true \ No newline at end of file diff --git a/rsc/scripts/setup_server.sql b/rsc/scripts/setup_server.sql index b64848a..afab344 100644 --- a/rsc/scripts/setup_server.sql +++ b/rsc/scripts/setup_server.sql @@ -57,7 +57,23 @@ CREATE TABLE IF NOT EXISTS LiveCharacters ( --physically exists in the world roomIndex INTEGER DEFAULT 0, originX INTEGER DEFAULT 0, - originY INTEGER DEFAULT 0 + originY INTEGER DEFAULT 0, + + --combat stats + level INTEGER DEFAULT 0, + exp INTEGER DEFAULT 0, + maxHP INTEGER DEFAULT 0, + health INTEGER DEFAULT 0, + maxMP INTEGER DEFAULT 0, + mana INTEGER DEFAULT 0, + attack INTEGER DEFAULT 0, + defence INTEGER DEFAULT 0, + intelligence INTEGER DEFAULT 0, + resistance INTEGER DEFAULT 0, + accuracy INTEGER DEFAULT 0, + evasion INTEGER DEFAULT 0, + speed INTEGER DEFAULT 0, + luck INTEGER DEFAULT 0 ); CREATE TABLE IF NOT EXISTS DeadCharacters ( @@ -68,23 +84,21 @@ CREATE TABLE IF NOT EXISTS DeadCharacters ( handle varchar(100), avatar varchar(100), birth timestamp NOT NULL, - death timestamp NOT NULL DEFAULT (datetime()) -); + death timestamp NOT NULL DEFAULT (datetime()), -CREATE TABLE IF NOT EXISTS LiveCreatures ( - uid INTEGER PRIMARY KEY AUTOINCREMENT, - - --metadata - handle varchar(100) UNIQUE, - avatar varchar(100), - - --actions --- script - - --physically exists in the world - roomIndex INTEGER DEFAULT 0, - originX INTEGER DEFAULT 0, - originY INTEGER DEFAULT 0 + --combat stats + level INTEGER DEFAULT 0, + exp INTEGER DEFAULT 0, + maxHP INTEGER DEFAULT 0, + maxMP INTEGER DEFAULT 0, + attack INTEGER DEFAULT 0, + defence INTEGER DEFAULT 0, + intelligence INTEGER DEFAULT 0, + resistance INTEGER DEFAULT 0, + accuracy INTEGER DEFAULT 0, + evasion INTEGER DEFAULT 0, + speed INTEGER DEFAULT 0, + luck INTEGER DEFAULT 0 ); ------------------------- @@ -105,49 +119,4 @@ CREATE TABLE IF NOT EXISTS InventoryItems ( --member tables ------------------------- -CREATE TABLE IF NOT EXISTS CombatStatistics ( - --metadata - uid INTEGER PRIMARY KEY AUTOINCREMENT, - - --general use statistics - level INTEGER DEFAULT 0, - exp INTEGER DEFAULT 0, - maxHP INTEGER DEFAULT 0, - health INTEGER DEFAULT 0, - maxMP INTEGER DEFAULT 0, - mana INTEGER DEFAULT 0, - attack INTEGER DEFAULT 0, - defence INTEGER DEFAULT 0, - intelligence INTEGER DEFAULT 0, - resistance INTEGER DEFAULT 0, - accuracy REAL DEFAULT 0.0, - evasion REAL DEFAULT 0.0, - speed INTEGER DEFAULT 0, - luck REAL DEFAULT 0.0 -); - -------------------------- ---cross reference tables -------------------------- - -CREATE TABLE IF NOT EXISTS CharacterStatistics ( - character INTEGER, - statistic INTEGER, - FOREIGN KEY (character) REFERENCES LiveCharacters(uid), - FOREIGN KEY (statistic) REFERENCES CombatStatistics(uid) -); - -CREATE TABLE IF NOT EXISTS CharacterItems ( - character INTEGER, - item INTEGER, - FOREIGN KEY (character) REFERENCES LiveCharacters(uid), - FOREIGN KEY (item) REFERENCES InventoryItem(uid) -); - -CREATE TABLE IF NOT EXISTS CharacterEquipment ( - character INTEGER, - item INTEGER, - FOREIGN KEY (character) REFERENCES LiveCharacters(uid), - FOREIGN KEY (item) REFERENCES InventoryItem(uid) -); - +--TODO \ No newline at end of file diff --git a/server/Inventory/inventory_manager.cpp b/server/Inventory/inventory_manager.cpp index f4af514..3d30df5 100644 --- a/server/Inventory/inventory_manager.cpp +++ b/server/Inventory/inventory_manager.cpp @@ -21,14 +21,3 @@ */ #include "inventory_manager.hpp" -static const char* CREATE_CHARACTER_ITEMS = "INSERT INTO CharacterItems (character, item) VALUES (?1, ?2);"; - -static const char* DELETE_CHARACTER_ITEMS = "DELETE FROM CharacterItems WHERE (character=?1, item=?2);"; - -static const char* CREATE_CHARACTER_EQUIPMENT = "INSERT INTO CharacterEquipment (character, item) VALUES (?1, ?2);"; - -static const char* DELETE_CHARACTER_EQUIPMENT = "DELETE FROM CharacterEquipment WHERE (character=?1, item=?2);"; - -int InventoryManager::CreateItem(int owner, ItemType itemType) { - sqlite3_stmt* statement = nullptr; -} \ No newline at end of file diff --git a/server/combat/makefile b/server/combat/makefile index ef080f9..169231d 100644 --- a/server/combat/makefile +++ b/server/combat/makefile @@ -1,5 +1,5 @@ #config -INCLUDES+=. .. ../characters ../creatures ../entities ../../common/gameplay ../../common/utilities +INCLUDES+=. .. ../characters ../creatures ../entities ../inventory ../../common/gameplay ../../common/utilities LIBS+= CXXFLAGS+=-std=c++11 $(addprefix -I,$(INCLUDES)) diff --git a/server/rooms/makefile b/server/rooms/makefile index 7f32eee..9b0f275 100644 --- a/server/rooms/makefile +++ b/server/rooms/makefile @@ -1,5 +1,5 @@ #config -INCLUDES+=. .. ../characters ../combat ../creatures ../entities ../monsters ../triggers ../../common/gameplay ../../common/map ../../common/network ../../common/network/packet_types ../../common/utilities +INCLUDES+=. .. ../characters ../combat ../creatures ../entities ../inventory ../monsters ../triggers ../../common/gameplay ../../common/map ../../common/network ../../common/network/packet_types ../../common/utilities LIBS+= CXXFLAGS+=-std=c++11 $(addprefix -I,$(INCLUDES))