diff options
author | Freeyorp <Freeyorp101@hotmail.com> | 2010-08-29 19:47:25 +1200 |
---|---|---|
committer | Freeyorp <Freeyorp101@hotmail.com> | 2010-08-29 19:47:25 +1200 |
commit | 7fc50c2d31e1d289e9d2a950271c6d399fe0896a (patch) | |
tree | 1ebff71f7b1526425cc57e2e3b2681297e540f90 /src | |
parent | 853cbb6efdb79f879fabc2133acb8c11d9d4f7b1 (diff) | |
parent | 7db9f6fe36b737d2eec7c6070497035b0834def2 (diff) | |
download | manaserv-7fc50c2d31e1d289e9d2a950271c6d399fe0896a.tar.gz manaserv-7fc50c2d31e1d289e9d2a950271c6d399fe0896a.tar.bz2 manaserv-7fc50c2d31e1d289e9d2a950271c6d399fe0896a.tar.xz manaserv-7fc50c2d31e1d289e9d2a950271c6d399fe0896a.zip |
Merge branch 'testing'
Conflicts:
src/account-server/storage.cpp
src/game-server/being.cpp
src/game-server/being.hpp
src/game-server/character.cpp
src/game-server/character.hpp
src/game-server/gamehandler.cpp
src/game-server/inventory.cpp
src/scripting/lua.cpp
src/sql/mysql/createTables.sql
src/sql/sqlite/createTables.sql
Diffstat (limited to 'src')
58 files changed, 3479 insertions, 1926 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e5da615..20840c04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,6 +194,12 @@ SET(SRCS_MANASERVGAME game-server/accountconnection.cpp game-server/actor.hpp game-server/actor.cpp + game-server/attribute.hpp + game-server/attribute.cpp + game-server/attributemanager.hpp + game-server/attributemanager.cpp + game-server/autoattack.hpp + game-server/autoattack.cpp game-server/being.hpp game-server/being.cpp game-server/buysell.hpp @@ -255,6 +261,8 @@ SET(SRCS_MANASERVGAME utils/base64.cpp utils/mathutils.h utils/mathutils.cpp + utils/speedconv.hpp + utils/speedconv.cpp utils/trim.hpp utils/zlib.hpp utils/zlib.cpp diff --git a/src/Makefile.am b/src/Makefile.am index ff53714f..19390cba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -100,6 +100,12 @@ manaserv_game_SOURCES = \ game-server/accountconnection.cpp \ game-server/actor.hpp \ game-server/actor.cpp \ + game-server/attribute.hpp \ + game-server/attribute.cpp \ + game-server/attributemanager.hpp \ + game-server/attributemanager.cpp \ + game-server/autoattack.hpp \ + game-server/autoattack.cpp \ game-server/being.hpp \ game-server/being.cpp \ game-server/buysell.hpp \ @@ -180,6 +186,8 @@ manaserv_game_SOURCES = \ utils/processorutils.cpp \ utils/string.hpp \ utils/string.cpp \ + utils/speedconv.hpp \ + utils/speedconv.cpp \ utils/stringfilter.h \ utils/stringfilter.cpp \ utils/timer.h \ diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp index 002803e6..99faad62 100644 --- a/src/account-server/accounthandler.cpp +++ b/src/account-server/accounthandler.cpp @@ -29,16 +29,20 @@ #include "account-server/serverhandler.hpp" #include "chat-server/chathandler.hpp" #include "common/configuration.hpp" +#include "common/resourcemanager.hpp" #include "common/transaction.hpp" #include "net/connectionhandler.hpp" #include "net/messagein.hpp" #include "net/messageout.hpp" #include "net/netcomputer.hpp" +#include "utils/functors.h" #include "utils/logger.h" #include "utils/stringfilter.h" #include "utils/tokencollector.hpp" #include "utils/tokendispenser.hpp" #include "utils/sha256.h" +#include "utils/string.hpp" +#include "utils/xml.hpp" static void addUpdateHost(MessageOut *msg) { @@ -46,13 +50,27 @@ static void addUpdateHost(MessageOut *msg) msg->writeString(updateHost); } + +// List of attributes that the client can send at account creation. + +static std::vector< unsigned int > initAttr; + +/* + * Map attribute ids to values that they need to be initialised to at account + * creation. + * The pair contains two elements of the same value (the default) so that the + * iterators can be used to copy a range. + */ + +static std::map< unsigned int, std::pair< double, double> > defAttr; + class AccountHandler : public ConnectionHandler { public: /** * Constructor. */ - AccountHandler(); + AccountHandler(const std::string &attrFile); /** * Called by the token collector in order to associate a client to its @@ -77,6 +95,9 @@ public: */ TokenCollector<AccountHandler, AccountClient *, int> mTokenCollector; + static void sendCharacterData(AccountClient &client, int slot, + const Character &ch); + protected: /** * Processes account related messages. @@ -106,14 +127,54 @@ private: static AccountHandler *accountHandler; -AccountHandler::AccountHandler(): +AccountHandler::AccountHandler(const std::string &attrFile): mTokenCollector(this) { + // Probably not the best place for this, but I don't have a lot of time. + if (initAttr.empty()) + { + std::string absPathFile; + xmlNodePtr node; + + absPathFile = ResourceManager::resolve(attrFile); + if (absPathFile.empty()) { + LOG_FATAL("Account handler: Could not find " << attrFile << "!"); + exit(3); + return; + } + + XML::Document doc(absPathFile, int()); + node = doc.rootNode(); + + if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) + { + LOG_FATAL("Account handler: " << attrFile + << " is not a valid database file!"); + exit(3); + return; + } + for_each_xml_child_node(attributenode, node) + if (xmlStrEqual(attributenode->name, BAD_CAST "stat")) + { + unsigned int id = XML::getProperty(attributenode, "id", 0); + if (!id) continue; + if (utils::toupper(XML::getProperty(attributenode, "modifiable", "false")) == "TRUE") + initAttr.push_back(id); + // Store as string initially to check that the property is defined. + std::string defStr = XML::getProperty(attributenode, "default", ""); + if (!defStr.empty()) + { + double val = string_to<double>()(defStr); + defAttr.insert(std::make_pair(id, std::make_pair(val, val))); + } + } + } } -bool AccountClientHandler::initialize(int port, const std::string &host) +bool AccountClientHandler::initialize(const std::string &configFile, int port, + const std::string &host) { - accountHandler = new AccountHandler; + accountHandler = new AccountHandler(configFile); LOG_INFO("Account handler started:"); return accountHandler->startListen(port, host); @@ -151,7 +212,7 @@ void AccountHandler::computerDisconnected(NetComputer *comp) delete client; // ~AccountClient unsets the account } -static void sendCharacterData(AccountClient &client, int slot, +void AccountHandler::sendCharacterData(AccountClient &client, int slot, const Character &ch) { MessageOut charInfo(APMSG_CHAR_INFO); @@ -163,11 +224,16 @@ static void sendCharacterData(AccountClient &client, int slot, charInfo.writeShort(ch.getLevel()); charInfo.writeShort(ch.getCharacterPoints()); charInfo.writeShort(ch.getCorrectionPoints()); - charInfo.writeLong(ch.getPossessions().money); - for (int j = CHAR_ATTR_BEGIN; j < CHAR_ATTR_END; ++j) + for (AttributeMap::const_iterator it = ch.mAttributes.begin(), + it_end = ch.mAttributes.end(); + it != it_end; + ++it) { - charInfo.writeShort(ch.getAttribute(j)); + // {id, base value in 256ths, modified value in 256ths }* + charInfo.writeLong(it->first); + charInfo.writeLong((int) (it->second.first * 256)); + charInfo.writeLong((int) (it->second.second * 256)); } client.send(charInfo); @@ -552,10 +618,10 @@ void AccountHandler::handleCharacterCreateMessage(AccountClient &client, Message int numHairStyles = Configuration::getValue("char_numHairStyles", 17); int numHairColors = Configuration::getValue("char_numHairColors", 11); int numGenders = Configuration::getValue("char_numGenders", 2); - unsigned minNameLength = Configuration::getValue("char_minNameLength", 4); - unsigned maxNameLength = Configuration::getValue("char_maxNameLength", 25); - unsigned maxCharacters = Configuration::getValue("char_maxCharacters", 3); - unsigned startingPoints = Configuration::getValue("char_startingPoints", 60); + unsigned int minNameLength = Configuration::getValue("char_minNameLength", 4); + unsigned int maxNameLength = Configuration::getValue("char_maxNameLength", 25); + unsigned int maxCharacters = Configuration::getValue("char_maxCharacters", 3); + unsigned int startingPoints = Configuration::getValue("char_startingPoints", 60); MessageOut reply(APMSG_CHAR_CREATE_RESPONSE); @@ -611,16 +677,16 @@ void AccountHandler::handleCharacterCreateMessage(AccountClient &client, Message // LATER_ON: Add race, face and maybe special attributes. // Customization of character's attributes... - int attributes[CHAR_ATTR_NB]; - for (int i = 0; i < CHAR_ATTR_NB; ++i) + std::vector< unsigned int > attributes = std::vector<unsigned int>(initAttr.size(), 0); + for (unsigned int i = 0; i < initAttr.size(); ++i) attributes[i] = msg.readShort(); unsigned int totalAttributes = 0; bool validNonZeroAttributes = true; - for (int i = 0; i < CHAR_ATTR_NB; ++i) + for (unsigned int i = 0; i < initAttr.size(); ++i) { // For good total attributes check. - totalAttributes += attributes[i]; + totalAttributes += attributes.at(i); // For checking if all stats are at least > 0 if (attributes[i] <= 0) validNonZeroAttributes = false; @@ -641,8 +707,12 @@ void AccountHandler::handleCharacterCreateMessage(AccountClient &client, Message else { Character *newCharacter = new Character(name); - for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) - newCharacter->setAttribute(i, attributes[i - CHAR_ATTR_BEGIN]); + for (unsigned int i = 0; i < initAttr.size(); ++i) + newCharacter->mAttributes.insert(std::make_pair( + (unsigned int) (initAttr.at(i)), + std::make_pair((double) (attributes[i]), + (double) (attributes[i])))); + newCharacter->mAttributes.insert(defAttr.begin(), defAttr.end()); newCharacter->setAccount(acc); newCharacter->setLevel(1); newCharacter->setCharacterPoints(0); diff --git a/src/account-server/accounthandler.hpp b/src/account-server/accounthandler.hpp index 5fa2a06f..f1ffcfe2 100644 --- a/src/account-server/accounthandler.hpp +++ b/src/account-server/accounthandler.hpp @@ -28,7 +28,8 @@ namespace AccountClientHandler /** * Creates a connection handler and starts listening on given port. */ - bool initialize(int port, const std::string &host); + bool initialize(const std::string &configFile, int port, + const std::string &host); /** * Stops listening to messages and destroys the connection handler. diff --git a/src/account-server/character.cpp b/src/account-server/character.cpp index e2a18f3a..b6918199 100644 --- a/src/account-server/character.cpp +++ b/src/account-server/character.cpp @@ -36,10 +36,6 @@ Character::Character(const std::string &name, int id): mCorrectionPoints(0), mAccountLevel(0) { - for (int i = 0; i < CHAR_ATTR_NB; ++i) - { - mAttributes[i] = 0; - } } void Character::setAccount(Account *acc) diff --git a/src/account-server/character.hpp b/src/account-server/character.hpp index d96413d4..f9114c7a 100644 --- a/src/account-server/character.hpp +++ b/src/account-server/character.hpp @@ -31,6 +31,9 @@ class Account; class MessageIn; +class MessageOut; + +typedef std::map< unsigned int, std::pair<double, double> > AttributeMap; /** placeholder type needed for include compatibility with game server*/ typedef void Special; @@ -101,13 +104,12 @@ class Character int getLevel() const { return mLevel; } void setLevel(int level) { mLevel = level; } - /** Gets the value of a base attribute of the character. */ - int getAttribute(int n) const - { return mAttributes[n - CHAR_ATTR_BEGIN]; } - /** Sets the value of a base attribute of the character. */ - void setAttribute(int n, int value) - { mAttributes[n - CHAR_ATTR_BEGIN] = value; } + void setAttribute(unsigned int id, double value) + { mAttributes[id].first = value; } + + void setModAttribute(unsigned int id, double value) + { mAttributes[id].second = value; } int getSkillSize() const { return mExperience.size(); } @@ -220,16 +222,29 @@ class Character private: + Character(const Character &); Character &operator=(const Character &); + double getAttrBase(AttributeMap::const_iterator &it) const + { return it->second.first; } + double getAttrMod(AttributeMap::const_iterator &it) const + { return it->second.second; } + Possessions mPossessions; //!< All the possesions of the character. std::string mName; //!< Name of the character. int mDatabaseID; //!< Character database ID. int mAccountID; //!< Account ID of the owner. Account *mAccount; //!< Account owning the character. Point mPos; //!< Position the being is at. - unsigned short mAttributes[CHAR_ATTR_NB]; //!< Attributes. + /** + * Stores attributes. + * The key is an unsigned int which is the id of the attribute. + * The value stores the base value of the attribute in the first part, + * and the modified value in the second. The modified value is only + * used when transmitting to the client. + */ + AttributeMap mAttributes; //!< Attributes. std::map<int, int> mExperience; //!< Skill Experience. std::map<int, int> mStatusEffects; //!< Status Effects std::map<int, int> mKillCount; //!< Kill Count @@ -245,6 +260,11 @@ class Character std::vector<std::string> mGuilds; //!< All the guilds the player //!< belongs to. + friend class AccountHandler; + friend class Storage; + // Set as a friend, but still a lot of redundant accessors. FIXME. + template< class T > + friend void serializeCharacterData(const T &data, MessageOut &msg); }; /** diff --git a/src/account-server/main-account.cpp b/src/account-server/main-account.cpp index 335a872a..9715ed5e 100644 --- a/src/account-server/main-account.cpp +++ b/src/account-server/main-account.cpp @@ -51,9 +51,10 @@ using utils::Logger; // Default options that automake should be able to override. -#define DEFAULT_LOG_FILE "manaserv-account.log" -#define DEFAULT_STATS_FILE "manaserv.stats" -#define DEFAULT_CONFIG_FILE "manaserv.xml" +#define DEFAULT_LOG_FILE "manaserv-account.log" +#define DEFAULT_STATS_FILE "manaserv.stats" +#define DEFAULT_CONFIG_FILE "manaserv.xml" +#define DEFAULT_ATTRIBUTEDB_FILE "attributes.xml" static bool running = true; /**< Determines if server keeps running */ @@ -307,7 +308,8 @@ int main(int argc, char *argv[]) initialize(); std::string host = Configuration::getValue("net_listenHost", std::string()); - if (!AccountClientHandler::initialize(options.port, host) || + if (!AccountClientHandler::initialize(DEFAULT_ATTRIBUTEDB_FILE, + options.port, host) || !GameServerHandler::initialize(options.port + 1, host) || !chatHandler->startListen(options.port + 2, host)) { diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp index a6cf21f0..c68cb623 100644 --- a/src/account-server/serverhandler.cpp +++ b/src/account-server/serverhandler.cpp @@ -116,6 +116,7 @@ NetComputer *ServerHandler::computerConnected(ENetPeer *peer) void ServerHandler::computerDisconnected(NetComputer *comp) { + LOG_INFO("Game-server disconnected."); delete comp; } @@ -562,10 +563,17 @@ void GameServerHandler::syncDatabase(MessageIn &msg) int CharId = msg.readLong(); int CharPoints = msg.readLong(); int CorrPoints = msg.readLong(); - int AttribId = msg.readByte(); - int AttribValue = msg.readLong(); - storage->updateCharacterPoints(CharId, CharPoints, CorrPoints, - AttribId, AttribValue); + storage->updateCharacterPoints(CharId, CharPoints, CorrPoints); + } break; + + case SYNC_CHARACTER_ATTRIBUTE: + { + LOG_DEBUG("received SYNC_CHARACTER_ATTRIBUTE"); + int charId = msg.readLong(); + int attrId = msg.readLong(); + double base = msg.readDouble(); + double mod = msg.readDouble(); + storage->updateAttribute(charId, attrId, base, mod); } break; case SYNC_CHARACTER_SKILL: diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index 4f43ed53..323bc587 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -39,7 +39,7 @@ static const char *DEFAULT_ITEM_FILE = "items.xml"; // defines the supported db version static const char *DB_VERSION_PARAMETER = "database_version"; -static const char *SUPPORTED_DB_VERSION = "10"; +static const char *SUPPORTED_DB_VERSION = "11"; /* * MySQL specificities: @@ -66,24 +66,26 @@ static const char *SUPPORTED_DB_VERSION = "10"; * TODO: Fix problem with PostgreSQL null primary key's. */ -static const char *ACCOUNTS_TBL_NAME = "mana_accounts"; -static const char *CHARACTERS_TBL_NAME = "mana_characters"; -static const char *CHAR_SKILLS_TBL_NAME = "mana_char_skills"; -static const char *CHAR_STATUS_EFFECTS_TBL_NAME = "mana_char_status_effects"; -static const char *CHAR_KILL_COUNT_TBL_NAME = "mana_char_kill_stats"; -static const char *CHAR_SPECIALS_TBL_NAME = "mana_char_specials"; -static const char *INVENTORIES_TBL_NAME = "mana_inventories"; -static const char *ITEMS_TBL_NAME = "mana_items"; -static const char *GUILDS_TBL_NAME = "mana_guilds"; -static const char *GUILD_MEMBERS_TBL_NAME = "mana_guild_members"; -static const char *QUESTS_TBL_NAME = "mana_quests"; -static const char *WORLD_STATES_TBL_NAME = "mana_world_states"; -static const char *POST_TBL_NAME = "mana_post"; -static const char *POST_ATTACHMENTS_TBL_NAME = "mana_post_attachments"; -static const char *AUCTION_TBL_NAME = "mana_auctions"; -static const char *AUCTION_BIDS_TBL_NAME = "mana_auction_bids"; -static const char *ONLINE_USERS_TBL_NAME = "mana_online_list"; -static const char *TRANSACTION_TBL_NAME = "mana_transactions"; +static const char *ACCOUNTS_TBL_NAME = "mana_accounts"; +static const char *CHARACTERS_TBL_NAME = "mana_characters"; +static const char *CHAR_ATTR_TBL_NAME = "mana_char_attr"; +static const char *CHAR_SKILLS_TBL_NAME = "mana_char_skills"; +static const char *CHAR_STATUS_EFFECTS_TBL_NAME = "mana_char_status_effects"; +static const char *CHAR_KILL_COUNT_TBL_NAME = "mana_char_kill_stats"; +static const char *CHAR_SPECIALS_TBL_NAME = "mana_char_specials"; +static const char *CHAR_EQUIPS_TBL_NAME = "mana_char_equips"; +static const char *INVENTORIES_TBL_NAME = "mana_inventories"; +static const char *ITEMS_TBL_NAME = "mana_items"; +static const char *GUILDS_TBL_NAME = "mana_guilds"; +static const char *GUILD_MEMBERS_TBL_NAME = "mana_guild_members"; +static const char *QUESTS_TBL_NAME = "mana_quests"; +static const char *WORLD_STATES_TBL_NAME = "mana_world_states"; +static const char *POST_TBL_NAME = "mana_post"; +static const char *POST_ATTACHMENTS_TBL_NAME = "mana_post_attachments"; +static const char *AUCTION_TBL_NAME = "mana_auctions"; +static const char *AUCTION_BIDS_TBL_NAME = "mana_auction_bids"; +static const char *ONLINE_USERS_TBL_NAME = "mana_online_list"; +static const char *TRANSACTION_TBL_NAME = "mana_transactions"; /** * Constructor. @@ -169,7 +171,7 @@ Account *Storage::getAccountBySQL() // we have no choice but to return nothing. if (accountInfo.isEmpty()) { - return NULL; + return 0; } // specialize the string_to functor to convert @@ -189,7 +191,7 @@ Account *Storage::getAccountBySQL() int level = toUint(accountInfo(0, 4)); // Check if the user is permanently banned, or temporarily banned. if (level == AL_BANNED - || time(NULL) <= (int) toUint(accountInfo(0, 5))) + || time(0) <= (int) toUint(accountInfo(0, 5))) { account->setLevel(AL_BANNED); // It is, so skip character loading. @@ -237,7 +239,7 @@ Account *Storage::getAccountBySQL() catch (const dal::DbSqlQueryExecFailure &e) { LOG_ERROR("DALStorage::getAccountBySQL: " << e.what()); - return NULL; // TODO: Throw exception here + return 0; // TODO: Throw exception here } } @@ -297,13 +299,12 @@ Character *Storage::getCharacterBySQL(Account *owner) // if the character is not even in the database then // we have no choice but to return nothing. if (charInfo.isEmpty()) - { - return NULL; - } + return 0; // specialize the string_to functor to convert // a string to an unsigned short. string_to< unsigned short > toUshort; + string_to< double > toDouble; character = new Character(charInfo(0, 2), toUint(charInfo(0, 0))); character->setGender(toUshort(charInfo(0, 3))); @@ -312,16 +313,10 @@ Character *Storage::getCharacterBySQL(Account *owner) character->setLevel(toUshort(charInfo(0, 6))); character->setCharacterPoints(toUshort(charInfo(0, 7))); character->setCorrectionPoints(toUshort(charInfo(0, 8))); - character->getPossessions().money = toUint(charInfo(0, 9)); - Point pos(toUshort(charInfo(0, 10)), toUshort(charInfo(0, 11))); + Point pos(toUshort(charInfo(0, 9)), toUshort(charInfo(0, 10))); character->setPosition(pos); - for (int i = 0; i < CHAR_ATTR_NB; ++i) - { - character->setAttribute(CHAR_ATTR_BEGIN + i, - toUshort(charInfo(0, 13 + i))); - } - int mapId = toUint(charInfo(0, 12)); + int mapId = toUint(charInfo(0, 11)); if (mapId > 0) { character->setMapId(mapId); @@ -350,12 +345,35 @@ Character *Storage::getCharacterBySQL(Account *owner) character->setAccountLevel(toUint(levelInfo(0, 0)), true); } - // load the skills of the char from CHAR_SKILLS_TBL_NAME std::ostringstream s; - s << "SELECT skill_id, skill_exp " - << "FROM " << CHAR_SKILLS_TBL_NAME << " " + + /* + * Load attributes. + */ + s << "SELECT attr_id, attr_base, attr_mod " + << "FROM " << CHAR_ATTR_TBL_NAME << " " << "WHERE char_id = " << character->getDatabaseID(); + const dal::RecordSet &attrInfo = mDb->execSql(s.str()); + if (!attrInfo.isEmpty()) + { + const unsigned int nRows = attrInfo.rows(); + for (unsigned int row = 0; row < nRows; ++row) + { + unsigned int id = toUint(attrInfo(row, 0)); + character->setAttribute(id, toDouble(attrInfo(row, 1))); + character->setModAttribute(id, toDouble(attrInfo(row, 2))); + } + } + + s.clear(); + s.str(""); + + // load the skills of the char from CHAR_SKILLS_TBL_NAME + + s << "select status_id, status_time FROM " << CHAR_STATUS_EFFECTS_TBL_NAME + << " WHERE char_id = " << character->getDatabaseID(); + const dal::RecordSet &skillInfo = mDb->execSql(s.str()); if (!skillInfo.isEmpty()) { @@ -417,7 +435,28 @@ Character *Storage::getCharacterBySQL(Account *owner) catch (const dal::DbSqlQueryExecFailure &e) { LOG_ERROR("(DALStorage::getCharacter #1) SQL query failure: " << e.what()); - return NULL; + return 0; + } + + Possessions &poss = character->getPossessions(); + + try + { + std::ostringstream sql; + sql << " select * from " << CHAR_EQUIPS_TBL_NAME << " where owner_id = '" + << character->getDatabaseID() << "' order by slot_type desc;"; + + const dal::RecordSet &equipInfo = mDb->execSql(sql.str()); + if (!equipInfo.isEmpty()) + for (int k = 0, size = equipInfo.rows(); k < size; ++k) + poss.equipSlots.insert(std::pair<unsigned int, unsigned int>( + toUint(equipInfo(k, 3)), + toUint(equipInfo(k, 2)))); + } + catch (const dal::DbSqlQueryExecFailure &e) + { + LOG_ERROR("(DALStorage::getCharacter #1) SQL query failure: " << e.what()); + return 0; } try @@ -428,44 +467,19 @@ Character *Storage::getCharacterBySQL(Account *owner) const dal::RecordSet &itemInfo = mDb->execSql(sql.str()); if (!itemInfo.isEmpty()) - { - Possessions &poss = character->getPossessions(); - unsigned nextSlot = 0; - for (int k = 0, size = itemInfo.rows(); k < size; ++k) { - unsigned slot = toUint(itemInfo(k, 2)); - if (slot < EQUIPMENT_SLOTS) - { - poss.equipment[slot] = toUint(itemInfo(k, 3)); - } - else - { - slot -= 32; - if (slot >= INVENTORY_SLOTS || slot < nextSlot) - { - LOG_ERROR("(DALStorage::getCharacter #2) Corrupted inventory."); - break; - } - InventoryItem item; - if (slot != nextSlot) - { - item.itemId = 0; - item.amount = slot - nextSlot; - poss.inventory.push_back(item); - } - item.itemId = toUint(itemInfo(k, 3)); - item.amount = toUint(itemInfo(k, 4)); - poss.inventory.push_back(item); - nextSlot = slot + 1; - } + InventoryItem item; + unsigned short slot = toUint(itemInfo(k, 2)); + item.itemId = toUint(itemInfo(k, 3)); + item.amount = toUint(itemInfo(k, 4)); + poss.inventory[slot] = item; } - } } catch (const dal::DbSqlQueryExecFailure &e) { LOG_ERROR("(DALStorage::getCharacter #2) SQL query failure: " << e.what()); - return NULL; + return 0; } return character; @@ -505,7 +519,7 @@ Character *Storage::getCharacter(const std::string &name) { mDb->bindValue(1, name); } - return getCharacterBySQL(NULL); + return getCharacterBySQL(0); } /** @@ -623,16 +637,9 @@ bool Storage::updateCharacter(Character *character, << "level = '" << character->getLevel() << "', " << "char_pts = '" << character->getCharacterPoints() << "', " << "correct_pts = '"<< character->getCorrectionPoints() << "', " - << "money = '" << character->getPossessions().money << "', " << "x = '" << character->getPosition().x << "', " << "y = '" << character->getPosition().y << "', " - << "map_id = '" << character->getMapId() << "', " - << "str = '" << character->getAttribute(CHAR_ATTR_STRENGTH) << "', " - << "agi = '" << character->getAttribute(CHAR_ATTR_AGILITY) << "', " - << "dex = '" << character->getAttribute(CHAR_ATTR_DEXTERITY) << "', " - << "vit = '" << character->getAttribute(CHAR_ATTR_VITALITY) << "', " - << "`int` = '" << character->getAttribute(CHAR_ATTR_INTELLIGENCE) << "', " - << "will = '" << character->getAttribute(CHAR_ATTR_WILLPOWER) << "' " + << "map_id = '" << character->getMapId() << "' " << "where id = '" << character->getDatabaseID() << "';"; mDb->execSql(sqlUpdateCharacterInfo.str()); @@ -649,13 +656,34 @@ bool Storage::updateCharacter(Character *character, } /** + * Character attributes. + */ + try + { + std::ostringstream sqlAttr; + for (AttributeMap::const_iterator + it = character->mAttributes.begin(), + it_end = character->mAttributes.end(); + it != it_end; + ++it) + updateAttribute(character->getDatabaseID(), it->first, + it->second.first, it->second.second); + } + catch (const dal::DbSqlQueryExecFailure &e) + { + if (startTransaction) + mDb->rollbackTransaction(); + LOG_ERROR("(DALStorage::updateCharacter #2) SQL query failure: " << e.what()); + } + + /** * Character's skills */ try { std::map<int, int>::const_iterator skill_it; - for (skill_it = character->getSkillBegin(); - skill_it != character->getSkillEnd(); skill_it++) + for (skill_it = character->mExperience.begin(); + skill_it != character->mExperience.end(); skill_it++) { updateExperience(character->getDatabaseID(), skill_it->first, skill_it->second); } @@ -667,7 +695,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #2) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #3) SQL query failure: " << e.what()); return false; } @@ -690,7 +718,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #3) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #4) SQL query failure: " << e.what()); return false; } /** @@ -724,7 +752,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #4) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #5) SQL query failure: " << e.what()); return false; } @@ -757,36 +785,39 @@ bool Storage::updateCharacter(Character *character, { std::ostringstream sql; - sql << "insert into " << INVENTORIES_TBL_NAME - << " (owner_id, slot, class_id, amount) values (" + sql << "insert into " << CHAR_EQUIPS_TBL_NAME + << " (owner_id, slot_type, inventory_slot) values (" << character->getDatabaseID() << ", "; std::string base = sql.str(); const Possessions &poss = character->getPossessions(); - - for (int j = 0; j < EQUIPMENT_SLOTS; ++j) + for (EquipData::const_iterator it = poss.equipSlots.begin(), + it_end = poss.equipSlots.end(); + it != it_end; + ++it) { - int v = poss.equipment[j]; - if (!v) continue; - sql.str(std::string()); - sql << base << j << ", " << v << ", 1);"; - mDb->execSql(sql.str()); + sql.str(""); + sql << base << it->first << ", " << it->second << ");"; + mDb->execSql(sql.str()); } - int slot = 32; - for (std::vector< InventoryItem >::const_iterator j = poss.inventory.begin(), + sql.str(""); + + sql << "insert into " << INVENTORIES_TBL_NAME + << " (owner_id, slot, class_id, amount) values (" + << character->getDatabaseID() << ", "; + base = sql.str(); + + for (InventoryData::const_iterator j = poss.inventory.begin(), j_end = poss.inventory.end(); j != j_end; ++j) { - int v = j->itemId; - if (!v) - { - slot += j->amount; - continue; - } - sql.str(std::string()); - sql << base << slot << ", " << v << ", " << unsigned(j->amount) << ");"; + sql.str(""); + unsigned short slot = j->first; + unsigned int itemId = j->second.itemId; + unsigned int amount = j->second.amount; + assert(itemId); + sql << base << slot << ", " << itemId << ", " << amount << ");"; mDb->execSql(sql.str()); - ++slot; } } @@ -797,7 +828,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #4) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #6) SQL query failure: " << e.what()); return false; } @@ -821,7 +852,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #5) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #7) SQL query failure: " << e.what()); return false; } try @@ -840,7 +871,7 @@ bool Storage::updateCharacter(Character *character, { mDb->rollbackTransaction(); } - LOG_ERROR("(DALStorage::updateCharacter #6) SQL query failure: " << e.what()); + LOG_ERROR("(DALStorage::updateCharacter #8) SQL query failure: " << e.what()); return false; } if (startTransaction) @@ -952,8 +983,8 @@ void Storage::flush(Account *account) // uniqueness sqlInsertCharactersTable << "insert into " << CHARACTERS_TBL_NAME - << " (user_id, name, gender, hair_style, hair_color, level, char_pts, correct_pts, money," - << " x, y, map_id, str, agi, dex, vit, `int`, will ) values (" + << " (user_id, name, gender, hair_style, hair_color, level, char_pts, correct_pts," + << " x, y, map_id) values (" << account->getID() << ", \"" << (*it)->getName() << "\", " << (*it)->getGender() << ", " @@ -962,16 +993,9 @@ void Storage::flush(Account *account) << (int)(*it)->getLevel() << ", " << (int)(*it)->getCharacterPoints() << ", " << (int)(*it)->getCorrectionPoints() << ", " - << (*it)->getPossessions().money << ", " << (*it)->getPosition().x << ", " << (*it)->getPosition().y << ", " - << (*it)->getMapId() << ", " - << (*it)->getAttribute(CHAR_ATTR_STRENGTH) << ", " - << (*it)->getAttribute(CHAR_ATTR_AGILITY) << ", " - << (*it)->getAttribute(CHAR_ATTR_DEXTERITY) << ", " - << (*it)->getAttribute(CHAR_ATTR_VITALITY) << ", " - << (*it)->getAttribute(CHAR_ATTR_INTELLIGENCE) << ", " - << (*it)->getAttribute(CHAR_ATTR_WILLPOWER) << " " + << (*it)->getMapId() << ");"; mDb->execSql(sqlInsertCharactersTable.str()); @@ -979,13 +1003,23 @@ void Storage::flush(Account *account) // Update the character ID. (*it)->setDatabaseID(mDb->getLastId()); + // Update all attributes. + std::map<unsigned int, std::pair<double, double> >::const_iterator + attr_it, attr_end; + for (attr_it = (*it)->mAttributes.begin(), + attr_end = (*it)->mAttributes.end(); + attr_it != attr_end; + ++attr_it) + updateAttribute((*it)->getDatabaseID(), attr_it->first, + attr_it->second.first, + attr_it->second.second); + // update the characters skill std::map<int, int>::const_iterator skill_it; - for (skill_it = (*it)->getSkillBegin(); - skill_it != (*it)->getSkillEnd(); skill_it++) - { + for (skill_it = (*it)->mExperience.begin(); + skill_it != (*it)->mExperience.end(); + skill_it++) updateExperience((*it)->getDatabaseID(), skill_it->first, skill_it->second); - } } } // @@ -1076,31 +1110,26 @@ void Storage::updateLastLogin(const Account *account) * @param CharId ID of the character * @param CharPoints Number of character points left for the character * @param CorrPoints Number of correction points left for the character - * @param AttribId ID of the modified attribute - * @param AttribValue New value of the modified attribute */ void Storage::updateCharacterPoints(int charId, - int charPoints, int corrPoints, - int attribId, int attribValue) + int charPoints, int corrPoints) { - std::ostringstream sql; - sql << "UPDATE " << CHARACTERS_TBL_NAME - << " SET char_pts = " << charPoints << ", " - << " correct_pts = " << corrPoints << ", "; + try + { + std::ostringstream sql; + sql << "UPDATE " << CHARACTERS_TBL_NAME + << " SET char_pts = " << charPoints << ", " + << " correct_pts = " << corrPoints << ", " + << " WHERE id = " << charId; - switch (attribId) + mDb->execSql(sql.str()); + } + catch (dal::DbSqlQueryExecFailure &e) { - case CHAR_ATTR_STRENGTH: sql << "`str` = "; break; - case CHAR_ATTR_AGILITY: sql << "`agi` = "; break; - case CHAR_ATTR_DEXTERITY: sql << "`dex` = "; break; - case CHAR_ATTR_VITALITY: sql << "`vit` = "; break; - case CHAR_ATTR_INTELLIGENCE: sql << "`int` = "; break; - case CHAR_ATTR_WILLPOWER: sql << "`will` = "; break; + LOG_ERROR("DALStorage::updateCharacterPoints: " << e.what()); + throw; } - sql << attribValue - << " WHERE id = " << charId; - mDb->execSql(sql.str()); } /** @@ -1156,6 +1185,50 @@ void Storage::updateExperience(int charId, int skillId, int skillValue) } /** + * Write a modification message about character attributes to the database. + * @param charId The Id of the character + * @param attrId The Id of the attribute + * @param base The base value of the attribute for this character + * @param mod The cached modified value for this character. + */ + +void Storage::updateAttribute(int charId, unsigned int attrId, + double base, double mod) +{ + try { + std::ostringstream sql; + sql << "UPDATE " << CHAR_ATTR_TBL_NAME << " " + << "SET " + << "attr_base = '" << base << "', " + << "attr_mod = '" << mod << "' " + << "WHERE " + << "char_id = '" << charId << "' " + << "AND " + << "attr_id = '" << attrId << "';"; + mDb->execSql(sql.str()); + // If this has modified a row, we're done, it updated sucessfully. + if (mDb->getModifiedRows() > 0) + return; + // If it did not change anything, then the record didn't previously exist. + // Create it. + sql.clear(); + sql.str(""); + sql << "INSERT INTO " << CHAR_ATTR_TBL_NAME << " " + << "(char_id, attr_id, attr_base, attr_mod) VALUES ( " + << charId << ", " + << attrId << ", " + << base << ", " + << mod << ")"; + mDb->execSql(sql.str()); + } + catch (const dal::DbSqlQueryExecFailure &e) + { + LOG_ERROR("DALStorage::updateAttribute: " << e.what()); + throw; + } +} + +/** * Write a modification message about character skills to the database. * @param CharId ID of the character * @param monsterId ID of the monster type @@ -1386,7 +1459,7 @@ std::list<Guild*> Storage::getGuildList() i != members.end(); ++i) { - Character *character = getCharacter((*i).first, NULL); + Character *character = getCharacter((*i).first, 0); if (character) { character->addGuild((*itr)->getName()); @@ -1508,7 +1581,7 @@ void Storage::setWorldStateVar(const std::string &name, std::ostringstream updateStateVar; updateStateVar << "UPDATE " << WORLD_STATES_TBL_NAME << " SET value = '" << value << "', " - << " moddate = '" << time(NULL) << "' " + << " moddate = '" << time(0) << "' " << " WHERE state_name = '" << name << "'"; if (mapId >= 0) @@ -1535,10 +1608,10 @@ void Storage::setWorldStateVar(const std::string &name, } else { - insertStateVar << "NULL , "; + insertStateVar << "0 , "; } insertStateVar << "'" << value << "', " - << "'" << time(NULL) << "');"; + << "'" << time(0) << "');"; mDb->execSql(insertStateVar.str()); } catch (const dal::DbSqlQueryExecFailure &e) @@ -1598,7 +1671,7 @@ void Storage::banCharacter(int id, int duration) std::ostringstream sql; sql << "update " << ACCOUNTS_TBL_NAME << " set level = '" << AL_BANNED << "', banned = '" - << time(NULL) + duration * 60 + << time(0) + duration * 60 << "' where id = '" << info(0, 0) << "';"; mDb->execSql(sql.str()); } @@ -1707,7 +1780,7 @@ void Storage::checkBannedAccounts() sql << "update " << ACCOUNTS_TBL_NAME << " set level = " << AL_PLAYER << ", banned = 0" << " where level = " << AL_BANNED - << " AND banned <= " << time(NULL) << ";"; + << " AND banned <= " << time(0) << ";"; mDb->execSql(sql.str()); } catch (const dal::DbSqlQueryExecFailure &e) @@ -1776,7 +1849,7 @@ void Storage::storeLetter(Letter *letter) << letter->getSender()->getDatabaseID() << ", " << letter->getReceiver()->getDatabaseID() << ", " << letter->getExpiry() << ", " - << time(NULL) << ", " + << time(0) << ", " << "?)"; if (mDb->prepareSql(sql.str())) { @@ -1798,7 +1871,7 @@ void Storage::storeLetter(Letter *letter) << " receiver_id = '" << letter->getReceiver()->getDatabaseID() << "', " << " letter_type = '" << letter->getType() << "', " << " expiration_date = '" << letter->getExpiry() << "', " - << " sending_date = '" << time(NULL) << "', " + << " sending_date = '" << time(0) << "', " << " letter_text = ? " << " WHERE letter_id = '" << letter->getId() << "'"; @@ -1846,8 +1919,8 @@ Post *Storage::getStoredPost(int playerId) for (unsigned int i = 0; i < post.rows(); i++ ) { // load sender and receiver - Character *sender = getCharacter(toUint(post(i, 1)), NULL); - Character *receiver = getCharacter(toUint(post(i, 2)), NULL); + Character *sender = getCharacter(toUint(post(i, 1)), 0); + Character *receiver = getCharacter(toUint(post(i, 2)), 0); Letter *letter = new Letter(toUint( post(0,3) ), sender, receiver); @@ -2030,7 +2103,7 @@ void Storage::setOnlineStatus(int charId, bool online) sql.clear(); sql.str(""); sql << "INSERT INTO " << ONLINE_USERS_TBL_NAME - << " VALUES (" << charId << ", " << time(NULL) << ")"; + << " VALUES (" << charId << ", " << time(0) << ")"; mDb->execSql(sql.str()); } else @@ -2060,7 +2133,7 @@ void Storage::addTransaction(const Transaction &trans) << " VALUES (NULL, " << trans.mCharacterId << ", " << trans.mAction << ", " << "?, " - << time(NULL) << ")"; + << time(0) << ")"; if (mDb->prepareSql(sql.str())) { mDb->bindValue(1, trans.mMessage); diff --git a/src/account-server/storage.hpp b/src/account-server/storage.hpp index 989f1b9c..0024fbc6 100644 --- a/src/account-server/storage.hpp +++ b/src/account-server/storage.hpp @@ -61,11 +61,13 @@ class Storage void updateLastLogin(const Account *account); void updateCharacterPoints(int charId, - int charPoints, int corrPoints, - int attribId, int attribValue); + int charPoints, int corrPoints); void updateExperience(int charId, int skillId, int skillValue); + void updateAttribute(int charId, unsigned int attrId, + double base, double mod); + void updateKillCount(int charId, int monsterId, int kills); void insertStatusEffect(int charId, int statusId, int time); diff --git a/src/common/inventorydata.hpp b/src/common/inventorydata.hpp index f1772402..cf7ba1bf 100644 --- a/src/common/inventorydata.hpp +++ b/src/common/inventorydata.hpp @@ -22,6 +22,7 @@ #define COMMON_INVENTORYDATA_HPP #include <vector> +#include <map> /** * Numbers of inventory slots @@ -29,7 +30,6 @@ enum { - EQUIPMENT_SLOTS = 11, INVENTORY_SLOTS = 50 }; @@ -40,20 +40,22 @@ enum struct InventoryItem { - unsigned short itemId; - unsigned char amount; + unsigned int itemId; + unsigned int amount; }; +// slot id -> { item } +typedef std::map< unsigned short, InventoryItem > InventoryData; +// equip slot type -> { slot ids } +// Equipment taking up multiple slots will be referenced multiple times +typedef std::multimap< unsigned int, unsigned int > EquipData; /** * Structure storing the equipment and inventory of a Player. */ struct Possessions { - std::vector< InventoryItem > inventory; - int money; - unsigned short equipment[EQUIPMENT_SLOTS]; - Possessions(): money(0) - { for (int i = 0; i < EQUIPMENT_SLOTS; ++i) equipment[i] = 0; } + InventoryData inventory; + EquipData equipSlots; }; #endif diff --git a/src/defines.h b/src/defines.h index bc32d05c..4f80afaa 100644 --- a/src/defines.h +++ b/src/defines.h @@ -21,6 +21,8 @@ #ifndef DEFINES_H #define DEFINES_H +#define SQRT2 1.4142135623730950488 + /** * Enumeration type for account levels. * A normal player would have permissions of 1 @@ -43,7 +45,7 @@ enum * Guild member permissions * Members with NONE cannot invite users or set permissions * Members with TOPIC_CHANGE can change the guild channel topic - * Members with INVIT can invite other users + * Members with INVITE can invite other users * Memeber with KICK can remove other users * Members with OWNER can invite users and set permissions */ @@ -103,52 +105,67 @@ enum Element }; /** - * Attributes used during combat. Available to all the beings. + * A series of hardcoded attributes that must be defined. + * Much of these serve only to indicate derivatives, and so would not be + * needed once this is no longer a hardcoded system. */ -enum -{ - BASE_ATTR_BEGIN = 0, - BASE_ATTR_PHY_ATK_MIN = BASE_ATTR_BEGIN, - BASE_ATTR_PHY_ATK_DELTA, - /**< Physical attack power. */ - BASE_ATTR_MAG_ATK, /**< Magical attack power. */ - BASE_ATTR_PHY_RES, /**< Resistance to physical damage. */ - BASE_ATTR_MAG_RES, /**< Resistance to magical damage. */ - BASE_ATTR_EVADE, /**< Ability to avoid hits. */ - BASE_ATTR_HIT, /**< Ability to hit stuff. */ - BASE_ATTR_HP, /**< Hit Points (Base value: maximum, Modded value: current) */ - BASE_ATTR_HP_REGEN,/**< number of HP regenerated every 10 game ticks */ - BASE_ATTR_END, - BASE_ATTR_NB = BASE_ATTR_END - BASE_ATTR_BEGIN, - - BASE_ELEM_BEGIN = BASE_ATTR_END, - BASE_ELEM_NEUTRAL = BASE_ELEM_BEGIN, - BASE_ELEM_FIRE, - BASE_ELEM_WATER, - BASE_ELEM_EARTH, - BASE_ELEM_AIR, - BASE_ELEM_SACRED, - BASE_ELEM_DEATH, - BASE_ELEM_END, - BASE_ELEM_NB = BASE_ELEM_END - BASE_ELEM_BEGIN, - - NB_BEING_ATTRIBUTES = BASE_ELEM_END -}; + +#define ATTR_STR 1 +#define ATTR_AGI 2 +#define ATTR_VIT 3 +#define ATTR_INT 4 +#define ATTR_DEX 5 +#define ATTR_WIL 6 + +#define ATTR_ACCURACY 7 +#define ATTR_DEFENSE 8 +#define ATTR_DODGE 9 + +#define ATTR_MAGIC_DODGE 10 +#define ATTR_MAGIC_DEFENSE 11 + +#define ATTR_BONUS_ASPD 12 + +#define ATTR_HP 13 +#define ATTR_MAX_HP 14 +#define ATTR_HP_REGEN 15 + + +// Separate primary movespeed (tiles * second ^-1) and derived movespeed (raw) +#define ATTR_MOVE_SPEED_TPS 16 +#define ATTR_MOVE_SPEED_RAW 17 +#define ATTR_GP 18 +#define ATTR_INV_CAPACITY 19 /** - * Attributes of characters. Used to derive being attributes. + * Temporary attributes. + * @todo Use AutoAttacks instead. */ -enum -{ - CHAR_ATTR_BEGIN = NB_BEING_ATTRIBUTES, - CHAR_ATTR_STRENGTH = CHAR_ATTR_BEGIN, - CHAR_ATTR_AGILITY, - CHAR_ATTR_DEXTERITY, - CHAR_ATTR_VITALITY, - CHAR_ATTR_INTELLIGENCE, - CHAR_ATTR_WILLPOWER, - CHAR_ATTR_END, - CHAR_ATTR_NB = CHAR_ATTR_END - CHAR_ATTR_BEGIN +#define MOB_ATTR_PHY_ATK_MIN 20 +#define MOB_ATTR_PHY_ATK_DELTA 21 +#define MOB_ATTR_MAG_ATK 22 + +/** + * Attribute types. Can be one of stackable, non stackable, or non stackable bonus. + * @todo non-stackable malus layers + */ + +enum AT_TY { + TY_ST, + TY_NST, + TY_NSTB, + TY_NONE // Should only be used on types that have not yet been properly defined +}; + +enum AME_TY { + AME_MULT, + AME_ADD +}; + +struct AttributeInfoType { + AT_TY sType; + AME_TY eType; + AttributeInfoType(AT_TY s, AME_TY e) : sType(s), eType(e) {} }; #endif // DEFINES_H diff --git a/src/game-server/accountconnection.cpp b/src/game-server/accountconnection.cpp index 95f53c97..1a35587e 100644 --- a/src/game-server/accountconnection.cpp +++ b/src/game-server/accountconnection.cpp @@ -71,7 +71,7 @@ bool AccountConnection::start(int gameServerPort) msg.writeString(gameServerAddress); msg.writeShort(gameServerPort); msg.writeString(password); - msg.writeLong(ItemManager::getDatabaseVersion()); + msg.writeLong(itemManager->getDatabaseVersion()); const MapManager::Maps &m = MapManager::getMaps(); for (MapManager::Maps::const_iterator i = m.begin(), i_end = m.end(); i != i_end; ++i) @@ -124,9 +124,6 @@ void AccountConnection::processMessage(MessageIn &msg) { std::string token = msg.readString(MAGIC_TOKEN_LENGTH); Character *ptr = new Character(msg); - ptr->setSpeed(6.0); // The speed is set in tiles per seconds, and transformed by the function - // into the server corresponding internal value. - // TODO: Make this computed somehow... gameHandler->addPendingCharacter(token, ptr); } break; @@ -365,6 +362,18 @@ void AccountConnection::updateCharacterPoints(int charId, int charPoints, syncChanges(); } +void AccountConnection::updateAttributes(int charId, int attrId, double base, + double mod) +{ + ++mSyncMessages; + mSyncBuffer->writeByte(SYNC_CHARACTER_ATTRIBUTE); + mSyncBuffer->writeLong(charId); + mSyncBuffer->writeLong(attrId); + mSyncBuffer->writeDouble(base); + mSyncBuffer->writeDouble(mod); + syncChanges(); +} + void AccountConnection::updateExperience(int charId, int skillId, int skillValue) { diff --git a/src/game-server/accountconnection.hpp b/src/game-server/accountconnection.hpp index 0aed67ea..e2b793b1 100644 --- a/src/game-server/accountconnection.hpp +++ b/src/game-server/accountconnection.hpp @@ -133,6 +133,9 @@ class AccountConnection : public Connection int corrPoints, int attribId, int attribValue); + void updateAttributes(int charId, int attrId, double base, + double mod); + /** * Write a modification message about character skills to the sync * buffer. diff --git a/src/game-server/attribute.cpp b/src/game-server/attribute.cpp new file mode 100644 index 00000000..37ac07b5 --- /dev/null +++ b/src/game-server/attribute.cpp @@ -0,0 +1,333 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "attribute.hpp" +#include "game-server/being.hpp" +#include "utils/logger.h" +#include <cassert> + +AttributeModifiersEffect::AttributeModifiersEffect(AT_TY sType, AME_TY eType) + : mMod(eType == AME_MULT ? 1 : 0), mSType(sType), mEType(eType) +{ + assert(eType == AME_MULT || eType == AME_ADD); + assert(sType == TY_ST || sType == TY_NST || sType == TY_NSTB); + LOG_DEBUG("Layer created with eType " << eType << " and sType " << sType << "."); +} + +AttributeModifiersEffect::~AttributeModifiersEffect() +{ + // ? + /*mStates.clear();*/ + LOG_WARN("DELETION of attribute effect!"); +} + +bool AttributeModifiersEffect::add(unsigned short duration, double value, double prevLayerValue, int level) +{ + LOG_DEBUG("Adding modifier with value " << value << + " with a previous layer value of " << prevLayerValue << ". " + "Current mod at this layer: " << mMod << "."); + bool ret = false; + mStates.push_back(new AttributeModifierState(duration, value, level)); + switch (mSType) { + case TY_ST: + switch (mEType) { + case AME_ADD: + if (value) + { + ret = true; + mMod += value; + mCacheVal = prevLayerValue + mMod; + } + break; + case AME_MULT: + if (value != 1) + { + ret = true; + mMod *= value; + mCacheVal = prevLayerValue * mMod; + } + break; + default: + LOG_FATAL("Attribute modifiers effect: unhandled type '" + << mEType << "' as a stackable!"); + assert(0); + break; + } + break; + case TY_NST: + switch (mEType) { + case AME_ADD: + if (value > mMod) + { + ret = true; + mMod = value; + if (mMod > prevLayerValue) + mCacheVal = mMod; + } + break; + default: + LOG_FATAL("Attribute modifiers effect: unhandled type '" + << mEType << "' as a non-stackable!"); + assert(0); + } + // A multiplicative type would also be nonsensical + break; + case TY_NSTB: + switch (mEType) { + case AME_ADD: + case AME_MULT: + if (value > mMod) + { + ret = true; + mMod = value; + mCacheVal = mEType == AME_ADD ? prevLayerValue + mMod + : prevLayerValue * mMod; + } + break; + default: + LOG_FATAL("Attribute modifiers effect: unhandled type '" + << mEType << "' as a non-stackable bonus!"); + assert(0); + } + break; + default: + LOG_FATAL("Attribute modifiers effect: unknown modifier type '" + << mSType << "'!"); + assert(0); + } + return ret; +} + +bool durationCompare(const AttributeModifierState *lhs, + const AttributeModifierState *rhs) +{ return lhs->mDuration < rhs->mDuration; } + +bool AttributeModifiersEffect::remove(double value, unsigned int id, bool fullCheck) { + /* We need to find and check this entry exists, and erase the entry + from the list too. */ + if (!fullCheck) + mStates.sort(durationCompare); /* Search only through those with a duration of 0. */ + bool ret = false; + for (std::list< AttributeModifierState * >::iterator it = mStates.begin(); + it != mStates.end() && (fullCheck || !(*it)->mDuration); + ++it) + { + /* Check for a match */ + if ((*it)->mValue != value || (*it)->mId != id) + continue; + if (mSType == TY_ST) + updateMod(); + delete *it; + mStates.erase(it); + if (!id) + return true; + else ret = true; + } + if (ret && mSType != TY_ST) + updateMod(); + return ret; +} + +void AttributeModifiersEffect::updateMod(double value) +{ + if (mSType == TY_ST) + { + if (mEType == AME_ADD) + mMod -= value; + else if (mEType == AME_MULT) + mMod /= value; + else LOG_ERROR("Attribute modifiers effect: unhandled type '" + << mEType << "' as a stackable in cache update!"); + } + else if (mSType == TY_NST || mSType == TY_NSTB) + { + if (mMod == value) + { + mMod = 0; + for (std::list< AttributeModifierState * >::const_iterator + it2 = mStates.begin(), + it2_end = mStates.end(); + it2 != it2_end; + ++it2) + if ((*it2)->mValue > mMod) + mMod = (*it2)->mValue; + } + else LOG_ERROR("Attribute modifiers effect: unhandled type '" + << mEType << "' as a non-stackable in cache update!"); + } + else LOG_ERROR("Attribute modifiers effect: unknown modifier type '" + << mSType << "' in cache update!"); +} + +bool AttributeModifiersEffect::recalculateModifiedValue(double newPrevLayerValue) + { + double oldValue = mCacheVal; + switch (mEType) + case AME_ADD: { + switch (mSType) { + case TY_ST: + case TY_NSTB: + mCacheVal = newPrevLayerValue + mMod; + break; + case TY_NST: + mCacheVal = newPrevLayerValue < mMod ? mMod : newPrevLayerValue; + break; + default: + LOG_FATAL("Unknown stack type '" << mEType << "'!"); + assert(0); + } break; + case AME_MULT: + mCacheVal = mSType == TY_ST ? newPrevLayerValue * mMod : newPrevLayerValue * mMod; + break; + default: + LOG_FATAL("Unknown effect type '" << mEType << "'!"); + assert(0); + } + return oldValue != mCacheVal; +} + +bool Attribute::add(unsigned short duration, double value, unsigned int layer, int level) +{ + assert(mMods.size() > layer); + LOG_DEBUG("Adding modifier to attribute with duration " << duration << + ", value " << value << ", at layer " << layer << " with id " + << level); + if (mMods.at(layer)->add(duration, value, + (layer ? mMods.at(layer - 1)->getCachedModifiedValue() + : mBase) + , level)) + { + while (++layer < mMods.size()) + { + if (!mMods.at(layer)->recalculateModifiedValue( + mMods.at(layer - 1)->getCachedModifiedValue())) + { + LOG_DEBUG("Modifier added, but modified value not changed."); + return false; + } + } + LOG_DEBUG("Modifier added. Base value: " << mBase << ", new modified " + "value: " << getModifiedAttribute() << "."); + return true; + } + LOG_DEBUG("Failed to add modifier!"); + return false; +} + +bool Attribute::remove(double value, unsigned int layer, int lvl, bool fullcheck) +{ + assert(mMods.size() > layer); + if (mMods.at(layer)->remove(value, lvl, fullcheck)) + { + while (++layer < mMods.size()) + if (!mMods.at(layer)->recalculateModifiedValue( + mMods.at(layer - 1)->getCachedModifiedValue())) + return false; + return true; + } + return false; +} + +bool AttributeModifiersEffect::tick() +{ + bool ret = false; + std::list<AttributeModifierState *>::iterator it = mStates.begin(); + while (it != mStates.end()) + { + if ((*it)->tick()) + { + double value = (*it)->mValue; + LOG_DEBUG("Modifier of value " << value << " expiring!"); + delete *it; + mStates.erase(it++); + updateMod(value); + ret = true; + } + ++it; + } + return ret; +} + +Attribute::Attribute(const std::vector<struct AttributeInfoType> &type) +{ + LOG_DEBUG("Construction of new attribute with '" << type.size() << "' layers."); + for (unsigned int i = 0; i < type.size(); ++i) + { + LOG_DEBUG("Adding layer with stack type " << type[i].sType << " and effect type " << type[i].eType << "."); + mMods.push_back(new AttributeModifiersEffect(type[i].sType, + type[i].eType)); + LOG_DEBUG("Layer added."); + } +} + +Attribute::~Attribute() +{ + for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(), + it_end = mMods.end(); it != it_end; ++it) + { + // ? + //delete *it; + } +} + +bool Attribute::tick() +{ + bool ret = false; + double prev = mBase; + for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(), + it_end = mMods.end(); it != it_end; ++it) + { + if ((*it)->tick()) + { + LOG_DEBUG("Attribute layer " << mMods.begin() - it + << " has expiring modifiers."); + ret = true; + } + if (ret) + if (!(*it)->recalculateModifiedValue(prev)) ret = false; + prev = (*it)->getCachedModifiedValue(); + } + return ret; +} + +void Attribute::clearMods() +{ + for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(), + it_end = mMods.end(); it != it_end; ++it) + (*it)->clearMods(mBase); +} + +void Attribute::setBase(double base) { + LOG_DEBUG("Setting base attribute from " << mBase << " to " << base << "."); + double prev = mBase = base; + std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(); + while (it != mMods.end()) + if ((*it)->recalculateModifiedValue(prev)) + prev = (*it++)->getCachedModifiedValue(); + else + break; +} + +void AttributeModifiersEffect::clearMods(double baseValue) +{ + mStates.clear(); + mCacheVal = baseValue; + mMod = mEType == AME_ADD ? 0 : 1; +} diff --git a/src/game-server/attribute.hpp b/src/game-server/attribute.hpp new file mode 100644 index 00000000..c5f833f0 --- /dev/null +++ b/src/game-server/attribute.hpp @@ -0,0 +1,178 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ATTRIBUTE_HPP +#define ATTRIBUTE_HPP + +#include "defines.h" +#include <vector> +#include <list> + +class AttributeModifierState +{ + public: + AttributeModifierState(unsigned short duration, double value, + unsigned int id) + : mDuration(duration), mValue(value), mId(id) {} + ~AttributeModifierState() {} + bool tick() { return mDuration ? !--mDuration : false; } + private: + /** Number of ticks (0 means permanent, e.g. equipment). */ + unsigned short mDuration; + const double mValue; /**< Positive or negative amount. */ + /** + * Special purpose variable used to identify this effect to + * dispells or similar. Exact usage depends on the effect, + * origin, etc. + */ + const unsigned int mId; + friend bool durationCompare(const AttributeModifierState*, + const AttributeModifierState*); + friend class AttributeModifiersEffect; +}; + +class AttributeModifiersEffect { + public: + AttributeModifiersEffect(AT_TY sType, AME_TY eType); + ~AttributeModifiersEffect(); + /** + * Recalculates the value for this level. + * @returns True if the value changed, false if it did not change. + * Note that this will not change values at a higher level, nor the + * overall modified value for this attribute. + * If this returns true, the cached values for *all* modifiers of a + * higher level must be recalculated, as well as the final + */ + bool add(unsigned short duration, double value, + double prevLayerValue, int level); + + /** + * remove() - as with Attribute::remove(). + */ + + bool remove(double value, unsigned int id, bool fullCheck); + + /** + * Performs the necessary modifications to mMod when the states change. + */ + + void updateMod(double value = 0); + + /** + * Performs the necessary modifications to mCacheVal when the states + * change. + */ + + bool recalculateModifiedValue(double newPrevLayerValue); + + double getCachedModifiedValue() const { return mCacheVal; } + + bool tick(); + + /** + * clearMods() - removes all modifications present in this layer. + * This only really makes sense when all other layers are being reset too. + * @param baseValue the value to reset to - typically an Attribute's mBase + */ + + void clearMods(double baseValue); + + private: + /** List of all modifications present at this level */ + std::list< AttributeModifierState * > mStates; + /** + * Stores the value that results from mStates. This takes into + * account all previous layers. + */ + double mCacheVal; + /** + * Stores the effective modifying value from mStates. This defaults to + * 0 for additive modifiers and 1 for multiplicative modifiers. + */ + double mMod; + const AT_TY mSType; + const AME_TY mEType; +}; + +class Attribute +{ + public: + Attribute() {throw;} // DEBUG; Find improper constructions + + Attribute(const std::vector<struct AttributeInfoType> &type); + + ~Attribute(); + + void setBase(double base); + double getBase() const { return mBase; } + double getModifiedAttribute() const + { return mMods.empty() ? mBase : + (*mMods.rbegin())->getCachedModifiedValue(); } + + /** + * add() and remove() are the standard functions used to add and + * remove modifiers while keeping track of the modifier state. + */ + + /** + * @param duration The amount of time before the modifier expires + * naturally. + * When set to 0, the effect does not expire. + * @param value The value to be applied as the modifier. + * @param layer The id of the layer with which this modifier is to be + * applied to. + * @param id Used to identify this effect. + * @return Whether the modified attribute value was changed. + */ + + bool add(unsigned short duration, double value, unsigned int layer, int id = 0); + + /** + * @param value The value of the modifier to be removed. + * - When 0, id is used exclusively to identify modifiers. + * @param layer The id of the layer which contains the modifier to be removed. + * @param id Used to identify this effect. + * - When 0, only the first match will be removed. + * - When non-0, all modifiers matching this id and other + * parameters will be removed. + * @param fullcheck Whether to perform a check for all modifiers, + * or only those that are otherwise permanent (ie. duration of 0) + * @returns Whether the modified attribute value was changed. + */ + bool remove(double value, unsigned int layer, int id, bool fullcheck); + + /** + * clearMods() removes *all* modifications present in this Attribute (!) + */ + + void clearMods(); + + /** + * tick() processes all timers associated with modifiers for this attribute. + */ + + bool tick(); + + private: + double mBase; + std::vector< AttributeModifiersEffect * > mMods; +}; + +#endif // ATTRIBUTE_HPP diff --git a/src/game-server/attributemanager.cpp b/src/game-server/attributemanager.cpp new file mode 100644 index 00000000..817eb6d6 --- /dev/null +++ b/src/game-server/attributemanager.cpp @@ -0,0 +1,234 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "game-server/attributemanager.hpp" + +#include "common/resourcemanager.hpp" +#include "utils/string.hpp" +#include "utils/logger.h" +#include "utils/xml.hpp" +#include "defines.h" + +void AttributeManager::initialize() +{ + reload(); +} + +void AttributeManager::reload() +{ + mTagMap.clear(); + mAttributeMap.clear(); + for (unsigned int i = 0; i < ATTR_MAX; ++i) + mAttributeScopes[i].clear(); + + std::string absPathFile; + xmlNodePtr node; + + absPathFile = ResourceManager::resolve(mAttributeReferenceFile); + if (absPathFile.empty()) { + LOG_FATAL("Attribute Manager: Could not find " << mAttributeReferenceFile << "!"); + exit(3); + return; + } + + XML::Document doc(absPathFile, int()); + node = doc.rootNode(); + + if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) + { + LOG_FATAL("Attribute Manager: " << mAttributeReferenceFile + << " is not a valid database file!"); + exit(3); + return; + } + + LOG_INFO("Loading attribute reference..."); + + for_each_xml_child_node(attributenode, node) + { + if (xmlStrEqual(attributenode->name, BAD_CAST "stat")) + { + unsigned int id = XML::getProperty(attributenode, "id", 0); + + mAttributeMap[id] = std::pair< bool , std::vector<struct AttributeInfoType> >(false , std::vector<struct AttributeInfoType>()); + unsigned int layerCount = 0; + for_each_xml_child_node(subnode, attributenode) + { + if (xmlStrEqual(subnode->name, BAD_CAST "modifier")) + { + std::string sType = utils::toupper(XML::getProperty(subnode, "stacktype", "")); + std::string eType = utils::toupper(XML::getProperty(subnode, "modtype", "")); + std::string tag = utils::toupper(XML::getProperty(subnode, "tag", "")); + AT_TY pSType; + AME_TY pEType; + if (!sType.empty()) + { + if (!eType.empty()) + { + bool fail = false; + if (sType == "STACKABLE") + pSType = TY_ST; + else if (sType == "NON STACKABLE") + pSType = TY_NST; + else if (sType == "NON STACKABLE BONUS") + pSType = TY_NSTB; + else + { + LOG_WARN("Attribute manager: attribute '" << id + << "' has unknown stack type '" << sType + << "', skipping modifier!"); + fail = true; + } + if (!fail) + { + if (eType == "ADDITIVE") + pEType = AME_ADD; + else if (eType == "MULTIPLICATIVE") + pEType = AME_MULT; + else + { + LOG_WARN("Attribute manager: attribute '" << id + << "' has unknown modification type '" << sType + << "', skipping modifier!"); + fail = true; + } + if (!fail) + { + mAttributeMap[id].second.push_back(AttributeInfoType(pSType, pEType)); + std::string tag = XML::getProperty(subnode, "tag", ""); + + if (!tag.empty()) + mTagMap.insert(std::make_pair(tag, + std::make_pair(id, layerCount))); + ++layerCount; + } + } + } + else + LOG_WARN("Attribute manager: attribute '" << id << + "' has undefined modification type, skipping modifier!"); + } + else + { + LOG_WARN("Attribute manager: attribute '" << id << + "' has undefined stack type, skipping modifier!"); + } + } + } + std::string scope = utils::toupper(XML::getProperty(attributenode, "scope", std::string())); + if (scope.empty()) + { + // Give a warning unless scope has been explicitly set to "NONE" + LOG_WARN("Attribute manager: attribute '" << id + << "' has no default scope."); + } + else if (scope == "CHARACTER") + { + mAttributeScopes[ATTR_CHAR][id] = &mAttributeMap.at(id).second; + LOG_DEBUG("Attribute manager: attribute '" << id + << "' added to default character scope."); + } + else if (scope == "MONSTER") + { + mAttributeScopes[ATTR_MOB][id] = &mAttributeMap.at(id).second; + LOG_DEBUG("Attribute manager: attribute '" << id + << "' added to default monster scope."); + } + else if (scope == "BEING") + { + mAttributeScopes[ATTR_BEING][id] = &mAttributeMap.at(id).second; + LOG_DEBUG("Attribute manager: attribute '" << id + << "' added to default being scope."); + } + else if (scope == "NONE") + { + LOG_DEBUG("Attribute manager: attribute '" << id + << "' set to have no default scope."); + } + } + } + + LOG_DEBUG("attribute map:"); + LOG_DEBUG("TY_ST is " << TY_ST << ", TY_ NST is " << TY_NST << ", TY_NSTB is " << TY_NSTB << "."); + LOG_DEBUG("AME_ADD is " << AME_ADD << ", AME_MULT is " << AME_MULT << "."); + const std::string *tag; + unsigned int count = 0; + for (AttributeMap::const_iterator i = mAttributeMap.begin(); i != mAttributeMap.end(); ++i) + { + unsigned int lCount = 0; + LOG_DEBUG(" "<<i->first<<" : "); + for (std::vector<struct AttributeInfoType>::const_iterator j = i->second.second.begin(); + j != i->second.second.end(); + ++j) + { + tag = getTagFromInfo(i->first, lCount); + std::string end = tag ? "tag of '" + (*tag) + "'." : "no tag."; + LOG_DEBUG(" sType: " << j->sType << ", eType: " << j->eType << ", " + "and " << end); + ++lCount; + ++count; + } + } + LOG_INFO("Loaded '" << mAttributeMap.size() << "' attributes with '" + << count << "' modifier layers."); + + for(TagMap::const_iterator i = mTagMap.begin(), i_end = mTagMap.end(); + i != i_end; ++i) + { + LOG_DEBUG("Tag '" << i->first << "': '" << i->second.first << "', '" + << i->second.second << "'."); + } + + LOG_INFO("Loaded '" << mTagMap.size() << "' modifier tags."); +} + +const std::vector<struct AttributeInfoType> *AttributeManager::getAttributeInfo(unsigned int id) const +{ + AttributeMap::const_iterator ret = mAttributeMap.find(id); + if (ret == mAttributeMap.end()) return 0; + return &ret->second.second; +} + +const AttributeScopes &AttributeManager::getAttributeInfoForType(SCOPE_TYPES type) const +{ + return mAttributeScopes[type]; +} + +bool AttributeManager::isAttributeDirectlyModifiable(unsigned int id) const +{ + AttributeMap::const_iterator ret = mAttributeMap.find(id); + if (ret == mAttributeMap.end()) return false; + return ret->second.first; +} + +// { attribute id, layer } +std::pair<unsigned int,unsigned int> AttributeManager::getInfoFromTag(const std::string &tag) const +{ + return mTagMap.at(tag); +} + +const std::string *AttributeManager::getTagFromInfo(unsigned int attribute, unsigned int layer) const +{ + for (TagMap::const_iterator it = mTagMap.begin(), + it_end = mTagMap.end(); it != it_end; ++it) + if (it->second.first == attribute && it->second.second == layer) + return &it->first; + return 0; +} diff --git a/src/game-server/attributemanager.hpp b/src/game-server/attributemanager.hpp new file mode 100644 index 00000000..0afac022 --- /dev/null +++ b/src/game-server/attributemanager.hpp @@ -0,0 +1,78 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ATTRIBUTEMANAGER_HPP +#define ATTRIBUTEMANAGER_HPP + +#include <map> +#include <vector> +#include <string> + +typedef struct AttributeInfoType AttributeInfoType_t; + +enum SCOPE_TYPES { + ATTR_BEING = 0, + ATTR_CHAR, + ATTR_MOB, + // Add new types here as needed + ATTR_MAX +}; + +typedef std::map< int, std::vector<struct AttributeInfoType> * > AttributeScopes; + +class AttributeManager +{ + public: + AttributeManager(const std::string &file) : mAttributeReferenceFile(file) {} + /** + * Loads attribute reference file. + */ + void initialize(); + + /** + * Reloads attribute reference file. + */ + void reload(); + const std::vector<struct AttributeInfoType> *getAttributeInfo(unsigned int) const; + + const AttributeScopes &getAttributeInfoForType(SCOPE_TYPES) const; + + bool isAttributeDirectlyModifiable(unsigned int) const; + + std::pair<unsigned int,unsigned int> getInfoFromTag(const std::string &) const; + + const std::string *getTagFromInfo(unsigned int, unsigned int) const; + private: + // attribute id -> { modifiable, { stackable type, effect type }[] } + typedef std::map< int, std::pair< bool, std::vector<struct AttributeInfoType> > > AttributeMap; + // tag name -> { attribute id, layer } + typedef std::map< std::string, std::pair<unsigned int, unsigned int> > TagMap; + + // being type id -> (*{ stackable type, effect type })[] + AttributeScopes mAttributeScopes[ATTR_MAX]; + AttributeMap mAttributeMap; + TagMap mTagMap; + + const std::string mAttributeReferenceFile; +}; + +extern AttributeManager *attributeManager; + +#endif // ATTRIBUTEMANAGER_HPP diff --git a/src/game-server/autoattack.cpp b/src/game-server/autoattack.cpp new file mode 100644 index 00000000..dae6b0ff --- /dev/null +++ b/src/game-server/autoattack.cpp @@ -0,0 +1,39 @@ +#include "autoattack.hpp" + +void AutoAttacks::add(AutoAttack n) +{ + mAutoAttacks.push_back(n); + // Slow, but safe. + mAutoAttacks.sort(); +} + +void AutoAttacks::clear() +{ + mAutoAttacks.clear(); +} + +void AutoAttacks::stop() +{ + for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it) + it->halt(); + mActive = false; +} + +void AutoAttacks::start() +{ + for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it) + it->softReset(); + mActive = true; +} + +void AutoAttacks::tick(std::list<AutoAttack> *ret) +{ + for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it) + if (it->tick()) { + if (mActive) + it->reset(); + else + it->halt(); + } else if (ret && it->isReady()) + ret->push_back(*it); +} diff --git a/src/game-server/autoattack.hpp b/src/game-server/autoattack.hpp new file mode 100644 index 00000000..a931d924 --- /dev/null +++ b/src/game-server/autoattack.hpp @@ -0,0 +1,100 @@ +#ifndef AUTOATTACK_HPP +#define AUTOATTACK_HPP + +#include <list> +#include <limits> + +/** + * Methods of damage calculation + */ +enum DMG_TY +{ + DAMAGE_PHYSICAL = 0, + DAMAGE_MAGICAL, + DAMAGE_DIRECT, + DAMAGE_OTHER = -1 +}; + +/** + * Structure that describes the severity and nature of an attack a being can + * be hit by. + */ +struct Damage +{ + unsigned short base; /**< Base amount of damage. */ + unsigned short delta; /**< Additional damage when lucky. */ + unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */ + unsigned char element; /**< Elemental damage. */ + DMG_TY type; /**< Damage type: Physical or magical? */ + unsigned trueStrike : 1; /**< Override dodge calculation */ + std::list<size_t> usedSkills; /**< Skills used by source (needed for exp calculation) */ + unsigned short range; /**< Maximum distance that this attack can be used from */ + Damage(unsigned short base, unsigned short delta, unsigned short cth, + unsigned char element, DMG_TY type = DAMAGE_OTHER, + unsigned short range = std::numeric_limits<unsigned short>::max()) + : base(base), delta(delta), cth(cth), element(element), type(type), + trueStrike(false), range(range) {} +}; + +/** + * Class that stores information about an auto-attack + */ + +class AutoAttack +{ + public: + AutoAttack(Damage &damage, unsigned int delay, unsigned int warmup) + : mDamage(damage), mTimer(0), mAspd(delay), + mWarmup(warmup && warmup < delay ? warmup : delay >> 2) {} + unsigned short getTimer() const { return mTimer; } + bool tick() { return mTimer ? !--mTimer : false; } + void reset() { mTimer = mAspd; } + bool operator<(const AutoAttack &rhs) const + { return mTimer < rhs.getTimer(); } + bool isReady() const { return !(mTimer - mWarmup); } + void halt() { if (mTimer >= mWarmup) mTimer = 0; } + void softReset() { if (mTimer >= mWarmup) mTimer = mAspd; } + const Damage &getDamage() const { return mDamage; } + private: + Damage mDamage; + /** + * Internal timer that is modified each tick. + * When > warmup, the attack is warming up before a strike + * When = warmup, the attack triggers, dealing damage to the target *if* the target is still in range. + * (The attack is canceled when the target moves out of range before the attack can hit, there should be a trigger for scripts here too) + * (Should the character automatically persue when the target is still visible in this case?) + * When < warmup, the attack is cooling down after a strike. When in cooldown, the timer should not be soft-reset. + * When 0, the attack is inactive (the character is doing something other than attacking and the attack is not in cooldown) + */ + unsigned short mTimer; + unsigned short mAspd; // Value to reset the timer to (warmup + cooldown) + /** + * Pre-attack delay tick. + * This MUST be smaller than or equal to the aspd! + * So the attack triggers where timer == warmup, having gone through aspd - warmup ticks. + */ + unsigned short mWarmup; +}; + +/** + * Helper class for storing multiple auto-attacks. + */ +class AutoAttacks +{ + public: + + /** + * Whether the being has at least one auto attack that is ready. + */ + void add(AutoAttack n); + void clear(); // Wipe the list completely (used in place of remove for now; FIXME) + void start(); + void stop(); // If the character does some action other than attacking, reset all warmups (NOT cooldowns!) + void tick(std::list<AutoAttack> *ret = 0); + private: + bool mActive; /**< Marks whether or not to keep auto-attacking. Cooldowns still need to be processed when false. */ + std::list < AutoAttack > mAutoAttacks; + +}; + +#endif // AUTOATTACK_HPP diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp index fc56dc57..599ca1f1 100644 --- a/src/game-server/being.cpp +++ b/src/game-server/being.cpp @@ -24,6 +24,8 @@ #include "defines.h" #include "common/configuration.hpp" +#include "game-server/attributemanager.hpp" +#include "game-server/character.hpp" #include "game-server/collisiondetection.hpp" #include "game-server/eventlistener.hpp" #include "game-server/mapcomposite.hpp" @@ -36,64 +38,92 @@ Being::Being(ThingType type): Actor(type), mAction(STAND), mTarget(NULL), - mSpeed(0), mDirection(0) { - Attribute attr = { 0, 0 }; - mAttributes.resize(NB_BEING_ATTRIBUTES + CHAR_ATTR_NB, attr); + const AttributeScopes &attr = attributeManager->getAttributeInfoForType(ATTR_BEING); + LOG_DEBUG("Being creation: initialisation of " << attr.size() << " attributes."); + for (AttributeScopes::const_iterator it1 = attr.begin(), + it1_end = attr.end(); + it1 != it1_end; + ++it1) + { + if (mAttributes.count(it1->first)) + LOG_WARN("Redefinition of attribute '" << it1->first << "'!"); + LOG_DEBUG("Attempting to create attribute '" << it1->first << "'."); + mAttributes.insert(std::make_pair(it1->first, + Attribute(*it1->second))); + + } + // TODO: Way to define default base values? + // Should this be handled by the virtual modifiedAttribute? + // URGENT either way +#if 0 // Initialize element resistance to 100 (normal damage). - for (int i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i) + for (i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i) { - mAttributes[i].base = 100; + mAttributes[i] = Attribute(TY_ST); + mAttributes[i].setBase(100); } +#endif } -int Being::damage(Actor *, const Damage &damage) +int Being::damage(Actor *source, const Damage &damage) { if (mAction == DEAD) return 0; int HPloss = damage.base; if (damage.delta) - { - HPloss += rand() / (RAND_MAX / (damage.delta + 1)); - } + HPloss += rand() * (damage.delta + 1) / RAND_MAX; - int hitThrow = rand()%(damage.cth + 1); - int evadeThrow = rand()%(getModifiedAttribute(BASE_ATTR_EVADE) + 1); - if (evadeThrow > hitThrow) - { - HPloss = 0; - } - - /* Elemental modifier at 100 means normal damage. At 0, it means immune. - And at 200, it means vulnerable (double damage). */ - int mod1 = getModifiedAttribute(BASE_ELEM_BEGIN + damage.element); - HPloss = HPloss * (mod1 / 100); - /* Defence is an absolute value which is subtracted from the damage total. */ - int mod2 = 0; + // TODO magical attacks and associated elemental modifiers switch (damage.type) { case DAMAGE_PHYSICAL: - mod2 = getModifiedAttribute(BASE_ATTR_PHY_RES); - HPloss = HPloss - mod2; + if (!damage.trueStrike && + rand()%((int) getModifiedAttribute(ATTR_DODGE) + 1) > + rand()%(damage.cth + 1)) + { + HPloss = 0; + // TODO Process triggers for a dodged physical attack here. + // If there is an attacker included, also process triggers for the attacker (failed physical strike) + } + else + { + HPloss = HPloss * (1.0 - (0.0159375f * + getModifiedAttribute(ATTR_DEFENSE)) / + (1.0 + 0.017 * + getModifiedAttribute(ATTR_DEFENSE))) + + (rand()%((HPloss >> 4) + 1)); + // TODO Process triggers for receiving damage here. + // If there is an attacker included, also process triggers for the attacker (successful physical strike) + } break; case DAMAGE_MAGICAL: - mod2 = getModifiedAttribute(BASE_ATTR_MAG_RES); - HPloss = HPloss / (mod2 + 1); +#if 0 + getModifiedAttribute(BASE_ELEM_BEGIN + damage.element); +#else + LOG_WARN("Attempt to use magical type damage! This has not been" + "implemented yet and should not be used!"); + HPloss = 0; +#endif + case DAMAGE_DIRECT: break; default: + LOG_WARN("Unknown damage type '" << damage.type << "'!"); break; } if (HPloss > 0) { mHitsTaken.push_back(HPloss); - Attribute &HP = mAttributes[BASE_ATTR_HP]; - LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: "<<HP.base + HP.mod<<"/"<<HP.base); - HP.mod -= HPloss; - updateDerivedAttributes(BASE_ATTR_HP); - setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit + Attribute &HP = mAttributes.at(ATTR_HP); + LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: " + << HP.getModifiedAttribute() << "/" + << mAttributes.at(ATTR_MAX_HP).getModifiedAttribute()); + HP.setBase(HP.getBase() - HPloss); + updateDerivedAttributes(ATTR_HP); + setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit if this is set. } else { HPloss = 0; } @@ -103,17 +133,23 @@ int Being::damage(Actor *, const Damage &damage) void Being::heal() { - Attribute &HP = mAttributes[BASE_ATTR_HP]; - HP.mod = HP.base; - updateDerivedAttributes(BASE_ATTR_HP); + Attribute &hp = mAttributes.at(ATTR_HP); + Attribute &maxHp = mAttributes.at(ATTR_MAX_HP); + if (maxHp.getModifiedAttribute() == hp.getModifiedAttribute()) return; // Full hp, do nothing. + hp.clearMods(); // Reset all modifications present in hp + hp.setBase(maxHp.getModifiedAttribute()); + updateDerivedAttributes(ATTR_HP); } -void Being::heal(int hp) +void Being::heal(int gain) { - Attribute &HP = mAttributes[BASE_ATTR_HP]; - HP.mod += hp; - if (HP.mod > HP.base) HP.mod = HP.base; - updateDerivedAttributes(BASE_ATTR_HP); + Attribute &hp = mAttributes.at(ATTR_HP); + Attribute &maxHp = mAttributes.at(ATTR_MAX_HP); + if (maxHp.getModifiedAttribute() == hp.getModifiedAttribute()) return; // Full hp, do nothing. + hp.setBase(hp.getBase() + gain); + if (hp.getModifiedAttribute() > maxHp.getModifiedAttribute()) // Cannot go over maximum hitpoints. + hp.setBase(maxHp.getModifiedAttribute()); + updateDerivedAttributes(ATTR_HP); } void Being::died() @@ -154,18 +190,10 @@ Path Being::findPath() return map->findPath(startX, startY, destX, destY, getWalkMask()); } -void Being::setSpeed(float s) -{ - if (s > 0) - mSpeed = (int)(32000 / (s * (float)DEFAULT_TILE_LENGTH)); - else - mSpeed = 0; -} - void Being::move() { - // Don't deal with not moving beings - if (mSpeed <= 0 && mSpeed >= 32000) + // Immobile beings cannot move. + if (!checkAttributeExists(ATTR_MOVE_SPEED_RAW) || !getModifiedAttribute(ATTR_MOVE_SPEED_RAW)) return; mOld = getPosition(); @@ -233,9 +261,10 @@ void Being::move() { Position next = mPath.front(); mPath.pop_front(); - // 362 / 256 is square root of 2, used for walking diagonally - mActionTime += (prev.x != next.x && prev.y != next.y) - ? mSpeed * 362 / 256 : mSpeed; + // SQRT2 is used for diagonal movement. + mActionTime += (prev.x == next.x || prev.y == next.y) ? + getModifiedAttribute(ATTR_MOVE_SPEED_RAW) : + getModifiedAttribute(ATTR_MOVE_SPEED_RAW) * SQRT2; if (mPath.empty()) { // skip last tile center @@ -264,6 +293,10 @@ int Being::directionToAngle(int direction) } } +int Being::performAttack(Being *target, const Damage &damage) { + return performAttack(target, damage.range, damage); +} + int Being::performAttack(Being *target, unsigned range, const Damage &damage) { // check target legality @@ -281,7 +314,7 @@ int Being::performAttack(Being *target, unsigned range, const Damage &damage) if (maxDist * maxDist < distSquare) return -1; - mActionTime += 1000; // set to 10 ticks wait time + //mActionTime += 1000; // No tick. Auto-attacks should have their own, built-in delays. return (mTarget->damage(this, damage)); } @@ -296,41 +329,60 @@ void Being::setAction(Action action) } } -void Being::applyModifier(int attr, int amount, int duration, int lvl) +void Being::applyModifier(unsigned int attr, double value, unsigned int layer, + unsigned int duration, unsigned int id) { - if (duration) - { - AttributeModifier mod; - mod.attr = attr; - mod.value = amount; - mod.duration = duration; - mod.level = lvl; - mModifiers.push_back(mod); - } - mAttributes[attr].mod += amount; + mAttributes.at(attr).add(duration, value, layer, id); + updateDerivedAttributes(attr); +} + +bool Being::removeModifier(unsigned int attr, double value, unsigned int layer, + unsigned int id, bool fullcheck) +{ + bool ret = mAttributes.at(attr).remove(value, layer, id, fullcheck); updateDerivedAttributes(attr); + return ret; } -void Being::dispellModifiers(int level) +void Being::setAttribute(unsigned int id, double value, bool calc) { - AttributeModifiers::iterator i = mModifiers.begin(); - while (i != mModifiers.end()) + AttributeMap::iterator ret = mAttributes.find(id); + if (ret == mAttributes.end()) { - if (i->level && i->level <= level) - { - mAttributes[i->attr].mod -= i->value; - updateDerivedAttributes(i->attr); - i = mModifiers.erase(i); - continue; - } - ++i; + /* + * The attribute does not yet exist, so we must attempt to create it. + */ + LOG_ERROR("Being: Attempt to access non-existing attribute '" << id << "'!"); + LOG_WARN("Being: Creation of new attributes dynamically is not " + "implemented yet!"); + } + else { + ret->second.setBase(value); + if (calc) + updateDerivedAttributes(id); } } -int Being::getModifiedAttribute(int attr) const +double Being::getAttribute(unsigned int id) const +{ + AttributeMap::const_iterator ret = mAttributes.find(id); + if (ret == mAttributes.end()) return 0; + return ret->second.getBase(); +} + + +double Being::getModifiedAttribute(unsigned int id) const +{ + AttributeMap::const_iterator ret = mAttributes.find(id); + if (ret == mAttributes.end()) return 0; + return ret->second.getModifiedAttribute(); +} + +void Being::setModAttribute(unsigned int id, double value) { - int res = mAttributes[attr].base + mAttributes[attr].mod; - return res <= 0 ? 0 : res; + // No-op to satisfy shared structure. + // The game-server calculates this manually. + return; } void Being::applyStatusEffect(int id, int timer) @@ -389,15 +441,15 @@ void Being::update() if (i->second > -1) i->second--; } - int oldHP = getModifiedAttribute(BASE_ATTR_HP); + int oldHP = getModifiedAttribute(ATTR_HP); int newHP = oldHP; - int maxHP = getAttribute(BASE_ATTR_HP); + int maxHP = getModifiedAttribute(ATTR_MAX_HP); // Regenerate HP if (mAction != DEAD && !isTimerRunning(T_B_HP_REGEN)) { setTimerHard(T_B_HP_REGEN, TICKS_PER_HP_REGENERATION); - newHP += getModifiedAttribute(BASE_ATTR_HP_REGEN); + newHP += getModifiedAttribute(ATTR_HP_REGEN); } // Cap HP at maximum if (newHP > maxHP) @@ -407,24 +459,16 @@ void Being::update() // Only update HP when it actually changed to avoid network noise if (newHP != oldHP) { - applyModifier(BASE_ATTR_HP, newHP - oldHP); + mAttributes.at(ATTR_HP).setBase(newHP); raiseUpdateFlags(UPDATEFLAG_HEALTHCHANGE); } // Update lifetime of effects. - AttributeModifiers::iterator i = mModifiers.begin(); - while (i != mModifiers.end()) - { - --i->duration; - if (!i->duration) - { - mAttributes[i->attr].mod -= i->value; - updateDerivedAttributes(i->attr); - i = mModifiers.erase(i); - continue; - } - ++i; - } + for (AttributeMap::iterator it = mAttributes.begin(); + it != mAttributes.end(); + ++it) + if (it->second.tick()) + updateDerivedAttributes(it->first); // Update and run status effects StatusEffects::iterator it = mStatus.begin(); @@ -432,9 +476,7 @@ void Being::update() { it->second.time--; if (it->second.time > 0 && mAction != DEAD) - { it->second.status->tick(this, it->second.time); - } if (it->second.time <= 0 || mAction == DEAD) { @@ -445,10 +487,8 @@ void Being::update() } // Check if being died - if (getModifiedAttribute(BASE_ATTR_HP) <= 0 && mAction != DEAD) - { + if (getModifiedAttribute(ATTR_HP) <= 0 && mAction != DEAD) died(); - } } void Being::setTimerSoft(TimerID id, int value) @@ -484,3 +524,4 @@ bool Being::isTimerJustFinished(TimerID id) const { return getTimer(id) == 0; } + diff --git a/src/game-server/being.hpp b/src/game-server/being.hpp index e12a5def..5e365f50 100644 --- a/src/game-server/being.hpp +++ b/src/game-server/being.hpp @@ -28,11 +28,15 @@ #include "limits.h" #include "game-server/actor.hpp" +#include "game-server/attribute.hpp" +#include "game-server/autoattack.hpp" class Being; class MapComposite; class StatusEffect; +typedef std::map< unsigned int, Attribute > AttributeMap; + /** * Beings and actors directions * Needs to match client @@ -50,60 +54,10 @@ enum TimerID T_M_STROLL, // time until monster strolls to new location T_M_KILLSTEAL_PROTECTED, // killsteal protection time T_M_DECAY, // time until dead monster is removed - T_B_ATTACK_TIME, // time until being can attack again + T_M_ATTACK_TIME, // time until monster can attack again T_B_HP_REGEN // time until hp is regenerated again }; -/** - * Methods of damage calculation - */ -enum -{ - DAMAGE_PHYSICAL = 0, - DAMAGE_MAGICAL, - DAMAGE_OTHER -}; - -/** - * Structure that describes the severity and nature of an attack a being can - * be hit by. - */ -struct Damage -{ - unsigned short base; /**< Base amount of damage. */ - unsigned short delta; /**< Additional damage when lucky. */ - unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */ - unsigned char element; /**< Elemental damage. */ - unsigned char type; /**< Damage type: Physical or magical? */ - std::list<size_t> usedSkills; /**< Skills used by source (needed for exp calculation) */ -}; - - -/** - * Holds the base value of an attribute and the sum of all its modifiers. - * While base + mod may be negative, the modified attribute is not. - */ -struct Attribute -{ - unsigned short base; - short mod; -}; - -struct AttributeModifier -{ - /**< Number of ticks (0 means permanent, e.g. equipment). */ - unsigned short duration; - short value; /**< Positive or negative amount. */ - unsigned char attr; /**< Attribute to modify. */ - /** - * Strength of the modification. - * - Zero means permanent, e.g. equipment. - * - Non-zero means spell. Can only be removed by a wizard with a - * dispell level higher than this value. - */ - unsigned char level; -}; - struct Status { StatusEffect *status; @@ -111,7 +65,6 @@ struct Status }; typedef std::map< int, Status > StatusEffects; -typedef std::vector< AttributeModifier > AttributeModifiers; /** * Type definition for a list of hits @@ -223,8 +176,6 @@ class Being : public Actor * Gets beings speed. * The speed is given in tiles per second. */ - float getSpeed() const - { return (float)(1000 / (float)mSpeed); } /** * Gets beings speed. @@ -232,7 +183,6 @@ class Being : public Actor * This function automatically transform it * into millsecond per tile. */ - void setSpeed(float s); /** * Gets the damage list. @@ -250,6 +200,7 @@ class Being : public Actor * Performs an attack. * Return Value: damage inflicted or -1 when illegal target */ + int performAttack(Being *target, const Damage &damage); int performAttack(Being *target, unsigned range, const Damage &damage); /** @@ -282,19 +233,32 @@ class Being : public Actor /** * Sets an attribute. */ - void setAttribute(int n, int value) - { mAttributes[n].base = value; } + void setAttribute(unsigned int id, double value, bool calc = true); /** * Gets an attribute. */ - int getAttribute(int n) const - { return mAttributes[n].base; } + double getAttribute(unsigned int id) const; /** * Gets an attribute after applying modifiers. */ - int getModifiedAttribute(int) const; + double getModifiedAttribute(unsigned int id) const; + + /** + * No-op to satisfy shared structure. + * @note The game server calculates this manually, so nothing happens + * here. + */ + void setModAttribute(unsigned int id, double value); + + /** + * Checks whether or not an attribute exists in this being. + * @returns True if the attribute is present in the being, false otherwise. + */ + + bool checkAttributeExists(unsigned int id) const + { return mAttributes.count(id); } /** * Adds a modifier to one attribute. @@ -303,17 +267,16 @@ class Being : public Actor * @param lvl If non-zero, indicates that a temporary modifier can be * dispelled prematuraly by a spell of given level. */ - void applyModifier(int attr, int value, int duration = 0, int lvl = 0); + void applyModifier(unsigned int attr, double value, unsigned int layer, + unsigned int duration = 0, unsigned int id = 0); - /** - * Removes all the modifiers with a level low enough. - */ - void dispellModifiers(int level); + bool removeModifier(unsigned int attr, double value, unsigned int layer, + unsigned int id = 0, bool fullcheck = false); /** * Called when an attribute modifier is changed. */ - virtual void updateDerivedAttributes(int) {} + virtual void updateDerivedAttributes(unsigned int) {} /** * Sets a statuseffect on this being @@ -369,7 +332,8 @@ class Being : public Actor protected: static const int TICKS_PER_HP_REGENERATION = 100; Action mAction; - std::vector< Attribute > mAttributes; + AttributeMap mAttributes; + AutoAttacks mAutoAttacks; StatusEffects mStatus; Being *mTarget; Point mOld; /**< Old coordinates. */ @@ -398,12 +362,10 @@ class Being : public Actor Being &operator=(const Being &rhs); Path mPath; - unsigned int mSpeed; /**< Speed. */ unsigned char mDirection; /**< Facing direction. */ std::string mName; Hits mHitsTaken; /**< List of punches taken since last update. */ - AttributeModifiers mModifiers; /**< Currently modified attributes. */ typedef std::map<TimerID, int> Timers; Timers mTimers; diff --git a/src/game-server/buysell.cpp b/src/game-server/buysell.cpp index 2ae64618..cf5b8fe5 100644 --- a/src/game-server/buysell.cpp +++ b/src/game-server/buysell.cpp @@ -27,10 +27,12 @@ #include "game-server/item.hpp" #include "net/messageout.hpp" +#include "defines.h" + #include <algorithm> BuySell::BuySell(Character *c, bool sell): - mChar(c), mSell(sell) + mCurrencyId(ATTR_GP), mChar(c), mSell(sell) { c->setBuySell(this); } @@ -61,8 +63,18 @@ bool BuySell::registerItem(int id, int amount, int cost) return true; } + int BuySell::registerPlayerItems() { + return 0; // FIXME: STUB + /* + * Replaced with a no-op stub after the equipment slots become softcoded. + * I think this function is meant to fill the sell dialog with player + * items, but it's iterating through the inventory. + * The no-op here is to stop compilation errors while I work on other + * areas. FIXME + */ + /* int nbItemsToSell = 0; if (mSell) { @@ -85,6 +97,7 @@ int BuySell::registerPlayerItems() } } return nbItemsToSell; + */ } bool BuySell::start(Actor *actor) @@ -119,13 +132,17 @@ void BuySell::perform(int id, int amount) if (mSell) { amount -= inv.remove(id, amount); - inv.changeMoney(amount * i->cost); + mChar->setAttribute(mCurrencyId, + mChar->getAttribute(mCurrencyId) + + amount * i->cost); } else { - amount = std::min(amount, mChar->getPossessions().money / i->cost); + amount = std::min(amount, ((int) mChar->getAttribute(mCurrencyId)) / i->cost); amount -= inv.insert(id, amount); - inv.changeMoney(-amount * i->cost); + mChar->setAttribute(mCurrencyId, + mChar->getAttribute(mCurrencyId) - + amount * i->cost); } if (i->amount) { diff --git a/src/game-server/buysell.hpp b/src/game-server/buysell.hpp index 514200ac..a9903bc2 100644 --- a/src/game-server/buysell.hpp +++ b/src/game-server/buysell.hpp @@ -75,6 +75,9 @@ class BuySell typedef std::vector< TradedItem > TradedItems; + /** The attribute ID of the currency to use. Hardcoded for now (FIXME) */ + unsigned int mCurrencyId; + Character *mChar; /**< Character involved. */ TradedItems mItems; /**< Traded items. */ bool mSell; /**< Are items sold? */ diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index 1c96818f..c102700a 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -27,6 +27,7 @@ #include "common/configuration.hpp" #include "game-server/accountconnection.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/buysell.hpp" #include "game-server/eventlistener.hpp" #include "game-server/inventory.hpp" @@ -43,6 +44,7 @@ #include "serialize/characterdata.hpp" #include "utils/logger.h" +#include "utils/speedconv.hpp" // These values should maybe be obtained from the config file const float Character::EXPCURVE_EXPONENT = 3.0f; @@ -67,18 +69,22 @@ Character::Character(MessageIn &msg): mParty(0), mTransaction(TRANS_NONE) { - Attribute attr = { 0, 0 }; - mAttributes.resize(CHAR_ATTR_NB, attr); + const AttributeScopes &attr = attributeManager->getAttributeInfoForType(ATTR_CHAR); + LOG_DEBUG("Character creation: initialisation of " << attr.size() << " attributes."); + for (AttributeScopes::const_iterator it1 = attr.begin(), + it1_end = attr.end(); + it1 != it1_end; + ++it1) + mAttributes.insert(std::make_pair(it1->first, + Attribute(*it1->second))); // Get character data. mDatabaseID = msg.readLong(); setName(msg.readString()); deserializeCharacterData(*this, msg); - for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) - { - updateDerivedAttributes(i); - } + mOld = getPosition(); + Inventory(this).initialise(); + modifiedAllAttribute(); setSize(16); - Inventory(this).initialize(); //give the character some specials for testing. //TODO: get from quest vars and equipment @@ -111,7 +117,7 @@ void Character::update() } if (numRechargeNeeded > 0) { - mRechargePerSpecial = getModifiedAttribute(CHAR_ATTR_INTELLIGENCE) / numRechargeNeeded; + mRechargePerSpecial = getModifiedAttribute(ATTR_INT) / numRechargeNeeded; for (std::list<Special*>::iterator i = rechargeNeeded.begin(); i != rechargeNeeded.end(); i++) { (*i)->currentMana += mRechargePerSpecial; @@ -145,36 +151,11 @@ void Character::perform() return; } - // TODO: Check slot 2 too. - int itemId = mPossessions.equipment[EQUIP_FIGHT1_SLOT]; - ItemClass *ic = ItemManager::getItem(itemId); - int type = ic ? ic->getModifiers().getValue(MOD_WEAPON_TYPE) : 100; - - Damage damage; - damage.base = getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN); - damage.delta = getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA) + - getModifiedAttribute(type); - damage.type = DAMAGE_PHYSICAL; - damage.cth = getModifiedAttribute(BASE_ATTR_HIT) + - getModifiedAttribute(type); - damage.usedSkills.push_back(type); - - if (ic) - { - // weapon fighting - const ItemModifiers &mods = ic->getModifiers(); - damage.element = mods.getValue(MOD_ELEMENT_TYPE); - // todo: get attack range of weapon - // (weapon equipping has to be fixed first) - performAttack(mTarget, 64, damage); - } - else - { - // No-weapon fighting. - damage.element = ELEMENT_NEUTRAL; - performAttack(mTarget, 32, damage); - } - + std::list<AutoAttack> attacks; + mAutoAttacks.tick(&attacks); + if (attacks.empty()) return; // Install default attack? + else for (std::list<AutoAttack>::iterator it = attacks.begin(); it != attacks.end(); ++it) + performAttack(mTarget, it->getDamage()); } void Character::died() @@ -201,8 +182,9 @@ void Character::respawn() { // script-controlled respawning didn't work - fall back to // hardcoded logic - mAttributes[BASE_ATTR_HP].mod = -mAttributes[BASE_ATTR_HP].base + 1; - updateDerivedAttributes(BASE_ATTR_HP); //warp back to spawn point + mAttributes[ATTR_HP].setBase(mAttributes[ATTR_MAX_HP].getModifiedAttribute()); + updateDerivedAttributes(ATTR_HP); + // Warp back to spawn point. int spawnMap = Configuration::getValue("respawnMap", 1); int spawnX = Configuration::getValue("respawnX", 1024); int spawnY = Configuration::getValue("respawnY", 1024); @@ -334,8 +316,8 @@ void Character::sendStatus() { int attr = *i; attribMsg.writeShort(attr); - attribMsg.writeShort(getAttribute(attr)); - attribMsg.writeShort(getModifiedAttribute(attr)); + attribMsg.writeLong(getAttribute(attr) * 256); + attribMsg.writeLong(getModifiedAttribute(attr) * 256); } if (attribMsg.getLength() > 2) gameHandler->sendTo(this, attribMsg); mModifiedAttributes.clear(); @@ -361,95 +343,113 @@ void Character::sendStatus() } } -int Character::getAttribute(int attr) const -{ - if (attr <= CHAR_ATTR_END) - { - return Being::getAttribute(attr); - } - else - { - return Character::levelForExp(mExperience.find(attr)->second); - } -} - -int Character::getModifiedAttribute(int attr) const -{ - if (attr <= CHAR_ATTR_END) - { - return Being::getModifiedAttribute(attr); - } - else - { - //TODO: Find a way to modify skills - return Character::levelForExp(mExperience.find(attr)->second); - } -} - -void Character::updateDerivedAttributes(int attr) -{ - if (attr >= CHAR_ATTR_BEGIN && attr < CHAR_ATTR_END) - { - for (int i = BASE_ATTR_BEGIN; i < BASE_ATTR_END; ++i) +void Character::modifiedAllAttribute() +{ + LOG_DEBUG("Marking all attributes as changed, requiring recalculation."); + for (AttributeMap::iterator it = mAttributes.begin(), + it_end = mAttributes.end(); + it != it_end; ++it) + updateDerivedAttributes(it->first); +} + +void Character::updateDerivedAttributes(unsigned int attr) +{ +// Much of this is remnants from the previous attribute system (placeholder?) +// This could be improved by defining what attributes are derived from others +// in xml or otherwise, so only those that need to be recomputed are. + LOG_DEBUG("Received modified attribute recalculation request for " + << attr << "."); + if (!mAttributes.count(attr)) return; + double newBase = getAttribute(attr); + + std::set< unsigned int > deps; + + switch (attr) { + case ATTR_STR: + deps.insert(ATTR_INV_CAPACITY); + break; + case ATTR_AGI: + deps.insert(ATTR_DODGE); + break; + case ATTR_VIT: + deps.insert(ATTR_MAX_HP); + deps.insert(ATTR_HP_REGEN); + deps.insert(ATTR_DEFENSE); + break; + case ATTR_INT: + break; + case ATTR_DEX: + deps.insert(ATTR_ACCURACY); + break; + case ATTR_WIL: + break; + case ATTR_ACCURACY: + newBase = getModifiedAttribute(ATTR_DEX); // Provisional + break; + case ATTR_DEFENSE: + newBase = 0.3 * getModifiedAttribute(ATTR_VIT); + break; + case ATTR_DODGE: + newBase = getModifiedAttribute(ATTR_AGI); // Provisional + break; + case ATTR_MAGIC_DODGE: + newBase = 1.0; + // TODO + break; + case ATTR_MAGIC_DEFENSE: + newBase = 0.0; + // TODO + break; + case ATTR_BONUS_ASPD: + newBase = 0.0; + // TODO + break; + case ATTR_HP_REGEN: { - int newValue = getAttribute(i); - - if (i == BASE_ATTR_HP_REGEN){ - newValue = (getModifiedAttribute(CHAR_ATTR_VITALITY) + 10) - * (getModifiedAttribute(CHAR_ATTR_VITALITY) + 10) - / (600 / TICKS_PER_HP_REGENERATION); - // formula is in HP per minute. 600 game ticks = 1 minute. - } - else if (i == BASE_ATTR_HP){ - newValue = (getModifiedAttribute(CHAR_ATTR_VITALITY) + 10) - * (mLevel + 10); - } - else if (i == BASE_ATTR_HIT) { - newValue = getModifiedAttribute(CHAR_ATTR_DEXTERITY) - /* + skill in class of currently equipped weapon */; - } - else if (i == BASE_ATTR_EVADE) { - newValue = getModifiedAttribute(CHAR_ATTR_AGILITY); - /* TODO: multiply with 10 / (10 * equip_weight)*/ - } - else if (i == BASE_ATTR_PHY_RES) { - newValue = getModifiedAttribute(CHAR_ATTR_VITALITY); - /* equip defence is through equip modifiers */ - } - else if (i == BASE_ATTR_PHY_ATK_MIN) { - newValue = getModifiedAttribute(CHAR_ATTR_STRENGTH); - /* weapon attack is applied through equip modifiers */ - } - else if (i == BASE_ATTR_PHY_ATK_DELTA) { - newValue = 0; - /* + skill in class of currently equipped weapon ( is - * applied during the damage calculation) - * weapon attack bonus is applied through equip - * modifiers. - */ - } - else if (i == BASE_ATTR_MAG_RES) { - newValue = getModifiedAttribute(CHAR_ATTR_WILLPOWER); - } - else if (i == BASE_ATTR_MAG_ATK) { - newValue = getModifiedAttribute(CHAR_ATTR_WILLPOWER); - } - - if (newValue != getAttribute(i)) - { - setAttribute(i, newValue); - flagAttribute(i); - } + double temp = getModifiedAttribute(ATTR_VIT) * 0.05; + newBase = (temp * TICKS_PER_HP_REGENERATION); } - } + break; + case ATTR_HP: + double diff; + if ((diff = getModifiedAttribute(ATTR_HP) - getModifiedAttribute(ATTR_MAX_HP)) > 0) + newBase -= diff; + break; + case ATTR_MAX_HP: + newBase = ((getModifiedAttribute(ATTR_VIT) + 3) * (getModifiedAttribute(ATTR_VIT) + 20)) * 0.125; + deps.insert(ATTR_HP); + break; + case ATTR_MOVE_SPEED_TPS: + newBase = 3.0 + getModifiedAttribute(ATTR_AGI) * 0.08; // Provisional. + deps.insert(ATTR_MOVE_SPEED_RAW); + break; + case ATTR_MOVE_SPEED_RAW: + newBase = utils::tpsToSpeed(getModifiedAttribute(ATTR_MOVE_SPEED_TPS)); + break; + case ATTR_INV_CAPACITY: + newBase = 2000.0 + getModifiedAttribute(ATTR_STR) * 180.0; // Provisional + break; + default: break; + } + + if (newBase != getAttribute(attr)) + Being::setAttribute(attr, newBase, false); + else + LOG_DEBUG("No changes to sync."); flagAttribute(attr); + for (std::set<unsigned int>::const_iterator it = deps.begin(), + it_end = deps.end(); it != it_end; ++it) + updateDerivedAttributes(*it); } void Character::flagAttribute(int attr) { // Inform the client of this attribute modification. + accountHandler->updateAttributes(getDatabaseID(), attr, + getAttribute(attr), + getModifiedAttribute(attr)); mModifiedAttributes.insert(attr); - if (attr == CHAR_ATTR_INTELLIGENCE) + if (attr == ATTR_INT) { mSpecialUpdateNeeded = true; } @@ -467,47 +467,42 @@ int Character::levelForExp(int exp) void Character::receiveExperience(int skill, int experience, int optimalLevel) { - if (skill >= CHAR_ATTR_END) + // reduce experience when skill is over optimal level + int levelOverOptimum = levelForExp(getExperience(skill)) - optimalLevel; + if (optimalLevel && levelOverOptimum > 0) { - // reduce experience when skill is over optimal level - int levelOverOptimum = getAttribute(skill) - optimalLevel; - if (optimalLevel && levelOverOptimum > 0) - { - experience *= EXP_LEVEL_FLEXIBILITY / (levelOverOptimum + EXP_LEVEL_FLEXIBILITY); - } + experience *= EXP_LEVEL_FLEXIBILITY / (levelOverOptimum + EXP_LEVEL_FLEXIBILITY); + } - // add exp - int oldExp = mExperience[skill]; - long int newExp = mExperience[skill] + experience; - if (newExp < 0) newExp = 0; // avoid integer underflow/negative exp + // add exp + int oldExp = mExperience[skill]; + long int newExp = mExperience[skill] + experience; + if (newExp < 0) newExp = 0; // avoid integer underflow/negative exp - // Check the skill cap - long int maxSkillCap = Configuration::getValue("maxSkillCap", INT_MAX); - assert(maxSkillCap <= INT_MAX); // avoid interger overflow - if (newExp > maxSkillCap) + // Check the skill cap + long int maxSkillCap = Configuration::getValue("maxSkillCap", INT_MAX); + assert(maxSkillCap <= INT_MAX); // avoid interger overflow + if (newExp > maxSkillCap) + { + newExp = maxSkillCap; + if (oldExp != maxSkillCap) { - newExp = maxSkillCap; - if (oldExp != maxSkillCap) - { - LOG_INFO("Player hit the skill cap"); - // TODO: send a message to player leting them know they hit the cap - } + LOG_INFO("Player hit the skill cap"); + // TODO: send a message to player leting them know they hit the cap } - mExperience[skill] = newExp; - mModifiedExperience.insert(skill); + } + mExperience[skill] = newExp; + mModifiedExperience.insert(skill); - // inform account server - if (newExp != oldExp) - accountHandler->updateExperience(getDatabaseID(), skill, newExp); + // inform account server + if (newExp != oldExp) + accountHandler->updateExperience(getDatabaseID(), skill, newExp); - // check for skill levelup - if (Character::levelForExp(newExp) >= Character::levelForExp(oldExp)) - { - updateDerivedAttributes(skill); - } + // check for skill levelup + if (Character::levelForExp(newExp) >= Character::levelForExp(oldExp)) + updateDerivedAttributes(skill); - mRecalculateLevel = true; - } + mRecalculateLevel = true; } void Character::incrementKillCount(int monsterType) @@ -543,7 +538,7 @@ void Character::recalculateLevel() { float expGot = getExpGot(a->first); float expNeed = getExpNeeded(a->first); - levels.push_back(getAttribute(a->first) + expGot / expNeed); + levels.push_back(levelForExp(a->first) + expGot / expNeed); } } levels.sort(); @@ -577,13 +572,13 @@ void Character::recalculateLevel() int Character::getExpNeeded(size_t skill) const { - int level = getAttribute(skill); + int level = levelForExp(getExperience(skill)); return Character::expForLevel(level + 1) - expForLevel(level); } int Character::getExpGot(size_t skill) const { - int level = getAttribute(skill); + int level = levelForExp(getExperience(skill)); return mExperience.at(skill) - Character::expForLevel(level); } @@ -606,11 +601,11 @@ void Character::levelup() AttribmodResponseCode Character::useCharacterPoint(size_t attribute) { - if (attribute < CHAR_ATTR_BEGIN) return ATTRIBMOD_INVALID_ATTRIBUTE; - if (attribute >= CHAR_ATTR_END) return ATTRIBMOD_INVALID_ATTRIBUTE; + if (!attributeManager->isAttributeDirectlyModifiable(attribute)) + return ATTRIBMOD_INVALID_ATTRIBUTE; if (!mCharacterPoints) return ATTRIBMOD_NO_POINTS_LEFT; - mCharacterPoints--; + --mCharacterPoints; setAttribute(attribute, getAttribute(attribute) + 1); updateDerivedAttributes(attribute); return ATTRIBMOD_OK; @@ -618,13 +613,13 @@ AttribmodResponseCode Character::useCharacterPoint(size_t attribute) AttribmodResponseCode Character::useCorrectionPoint(size_t attribute) { - if (attribute < CHAR_ATTR_BEGIN) return ATTRIBMOD_INVALID_ATTRIBUTE; - if (attribute >= CHAR_ATTR_END) return ATTRIBMOD_INVALID_ATTRIBUTE; + if (!attributeManager->isAttributeDirectlyModifiable(attribute)) + return ATTRIBMOD_INVALID_ATTRIBUTE; if (!mCorrectionPoints) return ATTRIBMOD_NO_POINTS_LEFT; if (getAttribute(attribute) <= 1) return ATTRIBMOD_DENIED; - mCorrectionPoints--; - mCharacterPoints++; + --mCorrectionPoints; + ++mCharacterPoints; setAttribute(attribute, getAttribute(attribute) - 1); updateDerivedAttributes(attribute); return ATTRIBMOD_OK; diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp index 98c518a2..5581fc7a 100644 --- a/src/game-server/character.hpp +++ b/src/game-server/character.hpp @@ -29,6 +29,7 @@ #include "game-server/being.hpp" #include "protocol.h" #include "defines.h" +#include "utils/logger.h" class BuySell; class GameClient; @@ -168,6 +169,7 @@ class Character : public Being /* * Character data: * Get and set methods + * Most of this should be accessed directly as a friend */ /** Gets the database id of the character. */ @@ -255,21 +257,14 @@ class Character : public Being void setMapId(int); /** - * Over loads Being::getAttribute, character skills are - * treated as extend attributes + * Marks all attributes as being modified. */ - int getAttribute(int) const; - - /** - * Over loads Being::getModifiedAttribute - * Charcter skills are treated as extend attributes - */ - int getModifiedAttribute(int) const; + void modifiedAllAttribute(); /** * Updates base Being attributes. */ - void updateDerivedAttributes(int); + void updateDerivedAttributes(unsigned int); /** * Calls all the "disconnected" listener. @@ -359,12 +354,6 @@ class Character : public Being int getKillCount(int monsterType) const; /** - * Shortcut to get being's health - */ - int getHealth() const - { return getModifiedAttribute(CHAR_ATTR_VITALITY); } - - /** * Returns the exp needed to reach a specific skill level */ static int expForLevel(int level); @@ -404,7 +393,20 @@ class Character : public Being virtual unsigned char getWalkMask() const { return 0x82; } // blocked by walls and monsters ( bin 1000 0010) + protected: + /** + * Gets the way the actor blocks pathfinding for other objects + */ + virtual Map::BlockType getBlockType() const + { return Map::BLOCKTYPE_CHARACTER; } + private: + + double getAttrBase(AttributeMap::const_iterator it) const + { return it->second.getBase(); } + double getAttrMod(AttributeMap::const_iterator it) const + { return it->second.getModifiedAttribute(); } + Character(const Character &); Character &operator=(const Character &); @@ -483,12 +485,9 @@ class Character : public Being TransactionType mTransaction; /**< Trade/buy/sell action the character is involved in. */ std::map<int, int> mKillCount; /**< How many monsters the character has slain of each type */ - protected: - /** - * Gets the way the actor blocks pathfinding for other objects - */ - virtual Map::BlockType getBlockType() const - { return Map::BLOCKTYPE_CHARACTER; } + // Set as a friend, but still a lot of redundant accessors. FIXME. + template< class T > + friend void serializeCharacterData(const T &data, MessageOut &msg); }; #endif // CHARACTER_HPP diff --git a/src/game-server/command.cpp b/src/game-server/command.cpp index 1a7b4f7c..b421c67d 100644 --- a/src/game-server/command.cpp +++ b/src/game-server/command.cpp @@ -183,10 +183,13 @@ static void item(Character *, Character *q, ItemClass *it, int nb) Inventory(q).insert(it->getDatabaseID(), nb); } +// This no longer works as money is now an attribute. +/* static void money(Character *, Character *q, int nb) { Inventory(q).changeMoney(nb); } +*/ static void drop(Character *from, ItemClass *it, int nb) { @@ -233,11 +236,11 @@ static void reload(Character *, const std::string &db) { if (db == "items") { - ItemManager::reload(); + itemManager->reload(); } else if (db == "monsters") { - MonsterManager::reload(); + monsterManager->reload(); } } @@ -271,7 +274,7 @@ static Command const commands[] = handle("warp", AL_GM, warp), handle("item", AL_GM, item), handle("drop", AL_GM, drop), - handle("money", AL_GM, money), +// handle("money", AL_GM, money), handle("spawn", AL_GM, spawn), handle("goto", AL_GM, goto_), handle("recall", AL_GM, recall), @@ -372,7 +375,7 @@ void runCommand(Character *ch, const std::string &text) break; case 'i': - if (ItemClass *ic = ItemManager::getItem(atoi(arg.c_str()))) + if (ItemClass *ic = itemManager->getItem(atoi(arg.c_str()))) { args[i] = (intptr_t)ic; } @@ -407,7 +410,7 @@ void runCommand(Character *ch, const std::string &text) break; case 'o': - if (MonsterClass *mc = MonsterManager::getMonster(atoi(arg.c_str()))) + if (MonsterClass *mc = monsterManager->getMonster(atoi(arg.c_str()))) { args[i] = (intptr_t)mc; } diff --git a/src/game-server/commandhandler.cpp b/src/game-server/commandhandler.cpp index 0f3bc85b..cf5673ba 100644 --- a/src/game-server/commandhandler.cpp +++ b/src/game-server/commandhandler.cpp @@ -55,7 +55,7 @@ static void handleRecall(Character*, std::string&); static void handleBan(Character*, std::string&); static void handleItem(Character*, std::string&); static void handleDrop(Character*, std::string&); -static void handleMoney(Character*, std::string&); +//static void handleMoney(Character*, std::string&); static void handleSpawn(Character*, std::string&); static void handleAttribute(Character*, std::string&); static void handleReload(Character*, std::string&); @@ -86,8 +86,8 @@ static CmdRef const cmdRef[] = "Creates a number of items in the inventory of a character", &handleItem}, {"drop", "<item id> <amount>", "Drops a stack of items on the ground at your current location", &handleDrop}, - {"money", "<character> <amount>", - "Changes the money a character possesses", &handleMoney}, +/* {"money", "<character> <amount>", + "Changes the money a character possesses", &handleMoney},*/ {"spawn", "<monster id> <number>", "Creates a number of monsters near your location", &handleSpawn}, {"attribute", "<character> <attribute> <value>", @@ -362,7 +362,7 @@ static void handleItem(Character *player, std::string &args) id = utils::stringToInt(itemclass); // check for valid item class - ic = ItemManager::getItem(id); + ic = itemManager->getItem(id); if (!ic) { @@ -421,7 +421,7 @@ static void handleDrop(Character *player, std::string &args) id = utils::stringToInt(itemclass); // check for valid item - ic = ItemManager::getItem(id); + ic = itemManager->getItem(id); if (!ic) { say("Invalid item", player); @@ -448,7 +448,7 @@ static void handleDrop(Character *player, std::string &args) str << "User created item " << ic->getDatabaseID(); accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_DROP, str.str()); } - +/* static void handleMoney(Character *player, std::string &args) { Character *other; @@ -499,6 +499,7 @@ static void handleMoney(Character *player, std::string &args) std::string msg = "User created " + valuestr + " money"; accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_MONEY, msg); } +*/ static void handleSpawn(Character *player, std::string &args) { @@ -530,7 +531,7 @@ static void handleSpawn(Character *player, std::string &args) id = utils::stringToInt(monsterclass); // check for valid monster - mc = MonsterManager::getMonster(id); + mc = monsterManager->getMonster(id); if (!mc) { say("Invalid monster", player); @@ -626,8 +627,8 @@ static void handleRecall(Character *player, std::string &args) static void handleReload(Character *player, std::string &args) { // reload the items and monsters - ItemManager::reload(); - MonsterManager::reload(); + itemManager->reload(); + monsterManager->reload(); } static void handleBan(Character *player, std::string &args) diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 4dbffd5a..fcefa276 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -272,17 +272,17 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) { int slot = message.readByte(); Inventory inv(computer.character); - if (ItemClass *ic = ItemManager::getItem(inv.getItem(slot))) + if (ItemClass *ic = itemManager->getItem(inv.getItem(slot))) { - if (ic->use(computer.character)) + if (ic->hasTrigger(ITT_ACTIVATE)) { - inv.removeFromSlot(slot, 1); - // log transaction std::stringstream str; - str << "User used item " << ic->getDatabaseID() + str << "User activated item " << ic->getDatabaseID() << " from slot " << slot; accountHandler->sendTransaction(computer.character->getDatabaseID(), - TRANS_ITEM_USED, str.str()); + TRANS_ITEM_USED, str.str()); + if (ic->useTrigger(computer.character, ITT_ACTIVATE)) + inv.removeFromSlot(slot, 1); } } } break; @@ -292,7 +292,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) int slot = message.readByte(); int amount = message.readByte(); Inventory inv(computer.character); - if (ItemClass *ic = ItemManager::getItem(inv.getItem(slot))) + if (ItemClass *ic = itemManager->getItem(inv.getItem(slot))) { int nb = inv.removeFromSlot(slot, amount); Item *item = new Item(ic, amount - nb); @@ -329,10 +329,8 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) case PGMSG_UNEQUIP: { int slot = message.readByte(); - if (slot >= 0 && slot < EQUIP_PROJECTILE_SLOT) - { + if (slot >= 0 && slot < INVENTORY_SLOTS) Inventory(computer.character).unequip(slot); - } } break; case PGMSG_MOVE_ITEM: @@ -528,12 +526,12 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) case PGMSG_RAISE_ATTRIBUTE: { - int attribute = message.readByte(); + int attribute = message.readLong(); AttribmodResponseCode retCode; retCode = computer.character->useCharacterPoint(attribute); result.writeShort(GPMSG_RAISE_ATTRIBUTE_RESPONSE); result.writeByte(retCode); - result.writeByte(attribute); + result.writeLong(attribute); if (retCode == ATTRIBMOD_OK ) { @@ -554,12 +552,12 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message) case PGMSG_LOWER_ATTRIBUTE: { - int attribute = message.readByte(); + int attribute = message.readLong(); AttribmodResponseCode retCode; retCode = computer.character->useCorrectionPoint(attribute); result.writeShort(GPMSG_LOWER_ATTRIBUTE_RESPONSE); result.writeByte(retCode); - result.writeByte(attribute); + result.writeLong(attribute); if (retCode == ATTRIBMOD_OK ) { @@ -666,10 +664,7 @@ void GameHandler::tokenMatched(GameClient *computer, Character *character) // Force sending the whole character to the client. Inventory(character).sendFull(); - for (int i = 0; i < CHAR_ATTR_NB; ++i) - { - character->updateDerivedAttributes(i); - } + character->modifiedAllAttribute(); std::map<int, int>::const_iterator skill_it; for (skill_it = character->getSkillBegin(); skill_it != character->getSkillEnd(); skill_it++) { diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp index 33f09264..f560ce50 100644 --- a/src/game-server/inventory.cpp +++ b/src/game-server/inventory.cpp @@ -28,26 +28,27 @@ #include "net/messageout.hpp" #include "utils/logger.h" +// TODO: +// - Inventory::initialise() Usable but could use a few more things +// - Inventory::equip() Usable but last part would be nice + +typedef std::set<unsigned int> ItemIdSet; + Inventory::Inventory(Character *p, bool d): - mPoss(&p->getPossessions()), msg(GPMSG_INVENTORY), mClient(p), - mDelayed(d), mChangedLook(false) + mPoss(&p->getPossessions()), mInvMsg(GPMSG_INVENTORY), + mEqmMsg(GPMSG_EQUIP), mClient(p), mDelayed(d) { } Inventory::~Inventory() { - if (msg.getLength() > 2) - { - update(); - gameHandler->sendTo(mClient, msg); - } + commit(false); } void Inventory::restart() { - msg.clear(); - msg.writeShort(GPMSG_INVENTORY); - mChangedLook = false; + mInvMsg.clear(); + mInvMsg.writeShort(GPMSG_INVENTORY); } void Inventory::cancel() @@ -62,736 +63,702 @@ void Inventory::cancel() restart(); } -void Inventory::update() +void Inventory::commit(bool doRestart) { - if (mDelayed) + Possessions &poss = mClient->getPossessions(); + /* Sends changes, whether delayed or not. */ + if (mInvMsg.getLength() > 2) { - Possessions &poss = mClient->getPossessions(); - if (mPoss != &poss) - { - poss = *mPoss; - delete mPoss; - mPoss = &poss; - } + /* Send the message to the client directly. Perhaps this should be + done through an update flag, too? */ + gameHandler->sendTo(mClient, mInvMsg); } - if (mChangedLook) + if (mPoss != &poss) { - mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); + if (mDelayed) + { + /* + * Search for any and all changes to equipment. + * Search through equipment for changes between old and new equipment. + * Send changes directly when there is a change. + * Even when equipment references to invy slots are the same, it still + * needs to be searched for changes to the internal equiment slot + * usage. + * This is probably the worst part of doing this in delayed mode. + */ + IdSlotMap oldEquip, newEquip; + { + EquipData::const_iterator it1, it2, it1_end, it2_end; + for (it1 = mPoss->equipSlots.begin(), + it1_end = mPoss->equipSlots.end(); + it1 != it1_end; + ++it1) + { +#ifdef INV_CONST_BOUND_DEBUG + IdSlotMap::const_iterator temp2, temp = +#endif + newEquip.insert( + newEquip.upper_bound(it1->second), + std::make_pair(it1->second, it1->first)); +#ifdef INV_CONST_BOUND_DEBUG + if (temp != + --(temp2 = newEquip.upper_bound(it1->second))) + throw; +#endif + } + for (it2 = poss.equipSlots.begin(), + it2_end = poss.equipSlots.end(); + it2 != it2_end; + ++it2) + oldEquip.insert( + oldEquip.upper_bound(it2->second), + std::make_pair(it2->second, it2->first)); + } + { + IdSlotMap::const_iterator it1 = newEquip.begin(), + it2 = oldEquip.begin(), + it1_end = newEquip.end(), + it2_end = oldEquip.end(), + temp1, temp2; + while (it1 != it1_end || it2 != it2_end) + { + if (it1 == it1_end) + { + if (it2 == it2_end) + break; + equip_sub(0, it1); + } + else if (it2 == it2_end) + equip_sub(newEquip.count(it2->first), it2); + else if (it1->first == it2->first) + { + double invSlot = it1->first; + while ((it1 != it1_end && it1->first == invSlot) || + (it2 != it2_end && it2->first == invSlot)) + { + /* + * Item is still equipped, but need to check + * that the slots didn't change. + */ + if (it1->second == it2->second) + { + // No change. + ++it1; + ++it2; + continue; + } + unsigned int itemId = + mPoss->inventory.at(it1->first).itemId; + changeEquipment(itemId, itemId); + break; + } + } + else if (it1->first > it2->first) + equip_sub(newEquip.count(it2->first), it2); + else // it1->first < it2->first + equip_sub(0, it1); + } + } + } + poss = *mPoss; + delete mPoss; + mPoss = &poss; } + + /* Update server sided states if in delayed mode. If we are not in + delayed mode, the server sided states already reflect the changes + that have just been sent to the client. */ + + if (mEqmMsg.getLength() > 2) + gameHandler->sendTo(mClient, mEqmMsg); + + if (doRestart) + restart(); } -void Inventory::commit() +void Inventory::equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it) { - if (msg.getLength() > 2) + const unsigned int invSlot = it->first; + unsigned int count = 0, eqSlot = it->second; + mEqmMsg.writeShort(invSlot); + mEqmMsg.writeByte(newCount); + do { + if (newCount) + { + if (it->second != eqSlot) + { + mEqmMsg.writeByte(eqSlot); + mEqmMsg.writeByte(count); + count = 1; + eqSlot = it->second; + } + ++count; + } + if (itemManager->isEquipSlotVisible(it->second)) + mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE); + } while ((++it)->first == invSlot); + if (count) { - update(); - gameHandler->sendTo(mClient, msg); - restart(); + mEqmMsg.writeByte(eqSlot); + mEqmMsg.writeByte(count); } + mEqmMsg.writeShort(invSlot); + changeEquipment(newCount ? 0 : mPoss->inventory.at(invSlot).itemId, + newCount ? mPoss->inventory.at(invSlot).itemId : 0); } void Inventory::prepare() { - if (!mDelayed) - { - return; - } - Possessions &poss = mClient->getPossessions(); - if (mPoss == &poss) - { - mPoss = new Possessions(poss); - } + if (!mDelayed) return; + Possessions *poss = &mClient->getPossessions(); + if (mPoss == poss) + mPoss = new Possessions(*poss); } void Inventory::sendFull() const { + /* Sends all the information needed to construct inventory + and equipment to the client */ MessageOut m(GPMSG_INVENTORY_FULL); - for (int i = 0; i < EQUIPMENT_SLOTS; ++i) + m.writeShort(mPoss->inventory.size()); + for (InventoryData::const_iterator l = mPoss->inventory.begin(), + l_end = mPoss->inventory.end(); l != l_end; ++l) { - if (int id = mPoss->equipment[i]) - { - m.writeByte(i); - m.writeShort(id); - } + assert(l->second.itemId); + m.writeShort(l->first); // Slot id + m.writeShort(l->second.itemId); + m.writeShort(l->second.amount); } - int slot = EQUIP_CLIENT_INVENTORY; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + for (EquipData::const_iterator k = mPoss->equipSlots.begin(), + k_end = mPoss->equipSlots.end(); + k != k_end; + ++k) { - if (i->itemId) - { - m.writeByte(slot); - m.writeShort(i->itemId); - m.writeByte(i->amount); - ++slot; - } - else - { - slot += i->amount; - } + m.writeByte(k->first); // equip slot + m.writeShort(k->second); // inventory slot } - m.writeByte(255); - m.writeLong(mPoss->money); - gameHandler->sendTo(mClient, m); } -void Inventory::initialize() +void Inventory::initialise() { assert(!mDelayed); - // First, check the equipment and apply its modifiers. - for (int i = 0; i < EQUIP_PROJECTILE_SLOT; ++i) - { - int itemId = mPoss->equipment[i]; - if (!itemId) continue; - if (ItemClass *ic = ItemManager::getItem(itemId)) - { - ic->getModifiers().applyAttributes(mClient); - } - else - { - mPoss->equipment[i] = 0; - LOG_WARN("Removed unknown item " << itemId << " from equipment " - "of character " << mClient->getDatabaseID() << '.'); - } - } + InventoryData::iterator it1; + EquipData::const_iterator it2, it2_end = mPoss->equipSlots.end(); + /* + * Apply all exists triggers. + * Remove unknown inventory items. + */ - // Second, remove unknown inventory items. - int i = 0; - while (i < (int)mPoss->inventory.size()) - { - int itemId = mPoss->inventory[i].itemId; - if (itemId) - { - ItemClass *ic = ItemManager::getItem(itemId); - if (!ic) - { - LOG_WARN("Removed unknown item " << itemId << " from inventory" - " of character " << mClient->getDatabaseID() << '.'); - freeIndex(i); - continue; - } - } - ++i; - } -} + ItemIdSet itemIds; -int Inventory::getItem(int slot) const -{ - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + /* + * Construct a set of itemIds to keep track of duplicate itemIds. + */ + for (it1 = mPoss->inventory.begin(); it1 != mPoss->inventory.end(); ++it1) { - if (slot == 0) + ItemClass *item = itemManager->getItem(it1->second.itemId); + if (item) { - return i->itemId; + if (itemIds.insert(it1->second.itemId).second) + item->useTrigger(mClient, ITT_IN_INVY); } - - slot -= i->itemId ? 1 : i->amount; - - if (slot < 0) + else { - return 0; + LOG_WARN("Inventory: deleting unknown item type " + << it1->second.itemId << " from the inventory of '" + << mClient->getName() + << "'!"); + removeFromSlot(it1->first, + it1->second.amount); } } - return 0; -} -int Inventory::getIndex(int slot) const -{ - int index = 0; + itemIds.clear(); + + typedef std::set<unsigned int> SlotSet; + SlotSet equipment; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i, ++index) + /* + * Construct a set of slot references from equipment to keep track of + * duplicate slot usage. + */ + for (it2 = mPoss->equipSlots.begin(); it2 != it2_end; ++it2) { - if (slot == 0) + if (equipment.insert(it2->second).second) { - return i->itemId ? index : -1; + /* + * Perform checks for equipped items - check that all needed slots are available. + */ + // TODO - Not needed for testing everything else right now, but + // will be needed for production + /* + * Apply all equip triggers. + */ + itemManager->getItem(mPoss->inventory.at(it2->second).itemId) + ->useTrigger(mClient, ITT_EQUIP); } + } - slot -= i->itemId ? 1 : i->amount; + equipment.clear(); - if (slot < 0) - { - return -1; - } - } - return -1; + checkSize(); } -int Inventory::getSlot(int index) const +void Inventory::checkSize() { - int slot = 0; - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.begin() + index; i != i_end; ++i) - { - slot += i->itemId ? 1 : i->amount; + /* + * Check that the inventory size is greater than or equal to the size + * needed. + * If not, forcibly delete (drop?) items from the end until it is. + * Check that inventory capacity is greater than or equal to zero. + * If not, forcibly delete (drop?) items from the end until it is. + */ + while (mPoss->inventory.size() > INVENTORY_SLOTS + || mClient->getModifiedAttribute(ATTR_INV_CAPACITY) < 0) { + LOG_WARN("Inventory: oversize inventory! Deleting '" + << mPoss->inventory.rbegin()->second.amount + << "' items of type '" + << mPoss->inventory.rbegin()->second.itemId + << "' from slot '" + << mPoss->inventory.rbegin()->first + << "' of character '" + << mClient->getName() + << "'!"); + // FIXME Should probably be dropped rather than deleted. + removeFromSlot(mPoss->inventory.rbegin()->first, + mPoss->inventory.rbegin()->second.amount); } - return slot; } -int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot) +unsigned int Inventory::getItem(unsigned int slot) const { - int slot = 0; - for (int i = 0, i_end = mPoss->inventory.size(); i < i_end; ++i) - { - InventoryItem &it = mPoss->inventory[i]; - if (it.itemId == 0) - { - int nb = std::min(amount, maxPerSlot); - if (it.amount <= 1) - { - it.itemId = itemId; - it.amount = nb; - } - else - { - --it.amount; - InventoryItem iu = { itemId, nb }; - mPoss->inventory.insert(mPoss->inventory.begin() + i, iu); - ++i_end; - } - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(nb); - - amount -= nb; - if (amount == 0) - { - return 0; - } - } - ++slot; - } - - while (slot < INVENTORY_SLOTS - 1 && amount > 0) - { - int nb = std::min(amount, maxPerSlot); - amount -= nb; - InventoryItem it = { itemId, nb }; - mPoss->inventory.push_back(it); - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(nb); - ++slot; - } - - return amount; + InventoryData::iterator item = mPoss->inventory.find(slot); + return item != mPoss->inventory.end() ? item->second.itemId : 0; } -int Inventory::insert(int itemId, int amount) +unsigned int Inventory::insert(unsigned int itemId, unsigned int amount) { - if (itemId == 0 || amount == 0) - { + unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot(); + if (!itemId || !amount) return 0; - } - prepare(); - - int maxPerSlot = ItemManager::getItem(itemId)->getMaxPerSlot(); - if (maxPerSlot == 1) - { - return fillFreeSlot(itemId, amount, maxPerSlot); - } - - int slot = 0; - for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) - { - if (i->itemId == itemId && i->amount < maxPerSlot) + InventoryData::iterator it, it_end = mPoss->inventory.end(); + // Add to slots with existing items of this type first. + for (it = mPoss->inventory.begin(); it != it_end; ++it) + if (it->second.itemId == itemId) { - int nb = std::min(maxPerSlot - i->amount, amount); - i->amount += nb; - amount -= nb; - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - msg.writeShort(itemId); - msg.writeByte(i->amount); - - if (amount == 0) - { + if (it->second.amount >= maxPerSlot) + continue; + unsigned short additions = std::min(amount, maxPerSlot) + - it->second.amount; + amount -= additions; + it->second.amount += additions; + mInvMsg.writeShort(it->first); + mInvMsg.writeShort(itemId); + mInvMsg.writeShort(it->second.amount); + if (!amount) return 0; - } - ++slot; } - else - { - slot += i->itemId ? 1 : i->amount; - } - } - return fillFreeSlot(itemId, amount, maxPerSlot); -} - -int Inventory::count(int itemId) const -{ - int nb = 0; - - for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) + int slot = 0; + // We still have some left, so add to blank slots. + for (it = mPoss->inventory.begin();; ++it) { - if (i->itemId == itemId) + if (!amount) + return 0; + int lim = it == it_end ? INVENTORY_SLOTS : it->first; + while (amount && slot < lim) { - nb += i->amount; + int additions = std::min(amount, maxPerSlot); + mPoss->inventory[slot].itemId = itemId; + mPoss->inventory[slot].amount = additions; + amount -= additions; + mInvMsg.writeShort(slot++); // Last read, so also increment + mInvMsg.writeShort(itemId); + mInvMsg.writeShort(additions); } + ++slot; // Skip the slot that the iterator points to + if (it == it_end) break; } - return nb; -} - -bool Inventory::changeMoney(int amount) -{ - if (amount == 0) - { - return true; - } - - int money = mPoss->money + amount; - if (money < 0) - { - return false; - } - - prepare(); + checkSize(); - mPoss->money = money; - msg.writeByte(255); - msg.writeLong(money); - return true; + return amount; } -void Inventory::freeIndex(int i) +unsigned int Inventory::count(unsigned int itemId) const { - InventoryItem &it = mPoss->inventory[i]; - - // Is it the last slot? - if (i == (int)mPoss->inventory.size() - 1) - { - mPoss->inventory.pop_back(); - if (i > 0 && mPoss->inventory[i - 1].itemId == 0) - { - mPoss->inventory.pop_back(); - } - return; - } - - it.itemId = 0; - - // First concatenate with an empty slot on the right. - if (mPoss->inventory[i + 1].itemId == 0) - { - it.amount = mPoss->inventory[i + 1].amount + 1; - mPoss->inventory.erase(mPoss->inventory.begin() + i + 1); - } - else - { - it.amount = 1; - } - - // Then concatenate with an empty slot on the left. - if (i > 0 && mPoss->inventory[i - 1].itemId == 0) - { - // Note: "it" is no longer a valid reference, hence inventory[i] below. - mPoss->inventory[i - 1].amount += mPoss->inventory[i].amount; - mPoss->inventory.erase(mPoss->inventory.begin() + i); - } + unsigned int nb = 0; + for (InventoryData::iterator it = mPoss->inventory.begin(), + it_end = mPoss->inventory.end(); + it != it_end; ++it) + if (it->second.itemId == itemId) + nb += it->second.amount; + return nb; } -int Inventory::remove(int itemId, int amount) +unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force) { - if (itemId == 0 || amount == 0) - { - return 0; - } - prepare(); - - for (int i = mPoss->inventory.size() - 1; i >= 0; --i) - { - InventoryItem &it = mPoss->inventory[i]; - if (it.itemId == itemId) + bool inv = false, + eq = !itemManager->getItem(itemId)->getItemEquipData().empty(); + for (InventoryData::iterator it = mPoss->inventory.begin(), + it_end = mPoss->inventory.end(); + it != it_end; ++it) + if (it->second.itemId == itemId) { - int nb = std::min((int)it.amount, amount); - it.amount -= nb; - amount -= nb; - - msg.writeByte(getSlot(i) + EQUIP_CLIENT_INVENTORY); - if (it.amount == 0) + if (amount) { - msg.writeShort(0); - freeIndex(i); + if (eq) + { + // If the item is equippable, we have additional checks to make. + bool ch = false; + for (EquipData::iterator it2 = mPoss->equipSlots.begin(), + it2_end = mPoss->equipSlots.end(); + it2 != it2_end; + ++it2) + if (it2->second == it->first) + { + if (force) + unequip(it2); + else + ch = inv = true; + break; + } + if (ch && !force) + continue; + } + unsigned int sub = std::min(amount, it->second.amount); + amount -= sub; + it->second.amount -= sub; + mInvMsg.writeShort(it->first); + if (it->second.amount) + { + mInvMsg.writeShort(it->second.itemId); + mInvMsg.writeShort(it->second.amount); + // Some still exist, and we have none left to remove, so + // no need to run leave invy triggers. + if (!amount) + return 0; + } + else + { + mInvMsg.writeShort(0); + mPoss->inventory.erase(it); + } } else - { - msg.writeShort(itemId); - msg.writeByte(it.amount); - } - - if (amount == 0) - { + // We found an instance of them existing and have none left to + // remove, so no need to run leave invy triggers. return 0; - } } - } - - return amount; + if (force) + itemManager->getItem(itemId)->useTrigger(mClient, ITT_LEAVE_INVY); + // Rather inefficient, but still usable for now assuming small invy size. + // FIXME + return inv && !force ? remove(itemId, amount, true) : amount; } -int Inventory::move(int slot1, int slot2, int amount) +unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned int amount) { - if (amount == 0 || slot1 == slot2 || slot2 >= INVENTORY_SLOTS) - { + if (!amount || slot1 == slot2 || slot2 >= INVENTORY_SLOTS) return amount; - } + prepare(); + InventoryData::iterator it1 = mPoss->inventory.find(slot1), + it2 = mPoss->inventory.find(slot2), + inv_end = mPoss->inventory.end(); - int i1 = getIndex(slot1); - if (i1 < 0) - { + if (it1 == inv_end) return amount; - } - - prepare(); - InventoryItem &it1 = mPoss->inventory[i1]; - int i2 = getIndex(slot2); + EquipData::iterator it, it_end = mPoss->equipSlots.end(); + for (it = mPoss->equipSlots.begin(); + it != it_end; + ++it) + if (it->second == slot1) + // Bad things will happen when you can stack multiple equippable + // items in the same slot anyway. + it->second = slot2; + + unsigned int nb = std::min(amount, it1->second.amount); + if (it2 == inv_end) + { + // Slot2 does not yet exist. + mPoss->inventory[slot2].itemId = it1->second.itemId; + nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot(), + nb); + + mPoss->inventory[slot2].amount = nb; + it1->second.amount -= nb; + amount -= nb; - if (i2 >= 0) - { - InventoryItem &it2 = mPoss->inventory[i2]; - if (it1.itemId == it2.itemId) + mInvMsg.writeShort(slot1); // Slot + if (it1->second.amount) { - // Move between two stacks of the same kind. - int maxPerSlot = ItemManager::getItem(it1.itemId)->getMaxPerSlot(); - int nb = std::min(std::min(amount, (int)it1.amount), maxPerSlot - it2.amount); - if (nb == 0) - { - return amount; - } - - it1.amount -= nb; - it2.amount += nb; - amount -= nb; - - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it2.itemId); - msg.writeByte(it2.amount); - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - if (it1.amount == 0) - { - msg.writeShort(0); - freeIndex(i1); - } - else - { - msg.writeShort(it1.itemId); - msg.writeByte(it1.amount); - } - return amount; + mInvMsg.writeShort(it1->second.itemId); // Item Id + mInvMsg.writeShort(it1->second.amount); // Amount } - - // Swap between two different stacks. - if (it1.amount != amount) + else { - return amount; + mInvMsg.writeShort(0); + mPoss->inventory.erase(it1); } - - std::swap(it1, it2); - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it1.itemId); - msg.writeByte(it1.amount); - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(it2.itemId); - msg.writeByte(it2.amount); - return 0; - } - - // Move some items to an empty slot. - int id = it1.itemId; - int nb = std::min((int)it1.amount, amount); - it1.amount -= nb; - amount -= nb; - - msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY); - if (it1.amount == 0) - { - msg.writeShort(0); - freeIndex(i1); + mInvMsg.writeShort(slot2); // Slot + mInvMsg.writeShort(it1->second.itemId); // Item Id (same as slot 1) + mInvMsg.writeShort(nb); // Amount } else { - msg.writeShort(id); - msg.writeByte(it1.amount); - } - - // Fill second slot. - msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY); - msg.writeShort(id); - msg.writeByte(nb); - - for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(), - i_end = mPoss->inventory.end(); i != i_end; ++i) - { - if (i->itemId) - { - --slot2; - continue; - } - - if (slot2 >= i->amount) - { - slot2 -= i->amount; - continue; - } - - assert(slot2 >= 0 && i + 1 != i_end); + // Slot2 exists. + if (it2->second.itemId != it1->second.itemId) + return amount; // Cannot stack items of a different type. + nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot() + - it2->second.amount, + nb); - if (i->amount == 1) - { - // One single empty slot in the range. - i->itemId = id; - i->amount = nb; - return amount; - } - - InventoryItem it = { id, nb }; - --i->amount; + it1->second.amount -= nb; + it2->second.amount += nb; + amount -= nb; - if (slot2 == 0) + mInvMsg.writeShort(slot1); // Slot + if (it1->second.amount) { - // First slot in an empty range. - mPoss->inventory.insert(i, it); - return amount; + mInvMsg.writeShort(it1->second.itemId); // Item Id + mInvMsg.writeShort(it1->second.amount); // Amount } - - if (slot2 == i->amount) + else { - // Last slot in an empty range. - mPoss->inventory.insert(i + 1, it); - return amount; + mInvMsg.writeShort(0); + mPoss->inventory.erase(it1); } - - InventoryItem it3 = { 0, slot2 }; - i->amount -= slot2; - i = mPoss->inventory.insert(i, it); - mPoss->inventory.insert(i, it3); - return amount; + mInvMsg.writeShort(slot2); // Slot + mInvMsg.writeShort(it2->second.itemId); // Item Id + mInvMsg.writeShort(it2->second.amount); // Amount } - - // The second slot does not yet exist. - assert(slot2 >= 0); - if (slot2 != 0) - { - InventoryItem it = { 0, slot2 }; - mPoss->inventory.insert(mPoss->inventory.end(), it); - } - InventoryItem it = { id, nb }; - mPoss->inventory.insert(mPoss->inventory.end(), it); return amount; } -int Inventory::removeFromSlot(int slot, int amount) +unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount) { - if (amount == 0) - { - return 0; - } - - int i = getIndex(slot); - if (i < 0) - { + prepare(); + InventoryData::iterator it = mPoss->inventory.find(slot); + if (it == mPoss->inventory.end()) return amount; + unequip(slot); + { + bool exists = false; + for (InventoryData::const_iterator it2 = mPoss->inventory.begin(), + it2_end = mPoss->inventory.end(); + it2 != it2_end; + ++it2) + if (it2->second.itemId == it->second.itemId + && it->first != it2->first) + { + exists = true; + break; + } + if (!exists && it->second.itemId) + itemManager->getItem(it->second.itemId) + ->useTrigger(mClient, ITT_LEAVE_INVY); } - - prepare(); - - InventoryItem &it = mPoss->inventory[i]; - int nb = std::min((int)it.amount, amount); - it.amount -= nb; - amount -= nb; - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - if (it.amount == 0) + unsigned int sub = std::min(amount, it->second.amount); + amount -= sub; + it->second.amount -= sub; + mInvMsg.writeShort(it->first); + if (it->second.amount) { - msg.writeShort(0); - freeIndex(i); + mInvMsg.writeShort(it->second.itemId); + mInvMsg.writeShort(it->second.amount); } else { - msg.writeShort(it.itemId); - msg.writeByte(it.amount); + mInvMsg.writeShort(0); + mPoss->inventory.erase(it); } - return amount; } -void Inventory::replaceInSlot(int slot, int itemId, int amount) -{ - int i = getIndex(slot); - assert(i >= 0); - prepare(); - - msg.writeByte(slot + EQUIP_CLIENT_INVENTORY); - if (itemId == 0 || amount == 0) - { - msg.writeShort(0); - freeIndex(i); - } - else - { - InventoryItem &it = mPoss->inventory[i]; - it.itemId = itemId; - it.amount = amount; - msg.writeShort(itemId); - msg.writeByte(amount); - } -} -void Inventory::changeEquipment(int slot, int itemId) +void Inventory::changeEquipment(unsigned int oldId, unsigned int newId) { - // FIXME: Changes are applied now, so it does not work in delayed mode. - assert(!mDelayed); - - int oldId = mPoss->equipment[slot]; - if (oldId == itemId) - { + if (!oldId && !newId) return; - } - - if (oldId) - { - ItemManager::getItem(oldId)->getModifiers().cancelAttributes(mClient); - } - - if (itemId) - { - ItemManager::getItem(itemId)->getModifiers().applyAttributes(mClient); - } - - msg.writeByte(slot); - msg.writeShort(itemId); - mPoss->equipment[slot] = itemId; - mChangedLook = true; - - //mark evade as modified because it depends on equipment weight - mClient->updateDerivedAttributes(BASE_ATTR_EVADE); + changeEquipment(oldId ? itemManager->getItem(oldId) : 0, + newId ? itemManager->getItem(newId) : 0); } -void Inventory::equip(int slot) +void Inventory::changeEquipment(ItemClass *oldI, ItemClass *newI) { - int itemId = getItem(slot); - if (!itemId) - { + // This should only be called when applying changes, either directly + // in non-delayed mode or when the changes are committed in delayed mode. + if (!oldI && !newI) return; - } - - prepare(); - - int availableSlots = 0, firstSlot = 0, secondSlot = 0; + if (oldI && newI) + oldI->useTrigger(mClient, ITT_EQUIPCHG); + else if (oldI) + oldI->useTrigger(mClient, ITT_UNEQUIP); + else if (newI) + newI->useTrigger(mClient, ITT_EQUIP); +} - switch (ItemManager::getItem(itemId)->getType()) - { - case ITEM_EQUIPMENT_TWO_HANDS_WEAPON: +bool Inventory::equip(int slot, bool override) +{ + if (mPoss->equipSlots.count(slot)) + return false; + InventoryData::iterator it; + if ((it = mPoss->inventory.find(slot)) == mPoss->inventory.end()) + return false; + const ItemEquipsInfo &eq = itemManager->getItem(it->second.itemId)->getItemEquipData(); + if (eq.empty()) + return false; + ItemEquipInfo const *ovd = 0; + // Iterate through all possible combinations of slots + for (ItemEquipsInfo::const_iterator it2 = eq.begin(), + it2_end = eq.end(); + it2 != it2_end; + ++it2) + { + // Iterate through this combination of slots. + /* + * 0 = all ok, slots free + * 1 = possible if other items are unequipped first + * 2 = impossible, requires too many slots even with other equipment being removed + */ + int fail = 0; + ItemEquipInfo::const_iterator it3, it3_end; + for (it3 = it2->begin(), + it3_end = it2->end(); + it3 != it3_end; + ++it3) { - // The one-handed weapons are to be placed back in the inventory. - int id1 = mPoss->equipment[EQUIP_FIGHT1_SLOT], - id2 = mPoss->equipment[EQUIP_FIGHT2_SLOT]; - - if (id2) + // it3 -> { slot id, number required } + unsigned int max = itemManager->getMaxSlotsFromId(it3->first), + used = mPoss->equipSlots.count(it3->first); + if (max - used >= it3->second) + continue; + else if (max >= it3->second) { - if (id1 && insert(id1, 1) != 0) - { - return; + fail |= 1; + if (override) + continue; + else + break; + } + else + { + fail |= 2; + break; + } + } + switch (fail) + { + case 0: + /* + * Clean fit. Equip and apply immediately. + */ + if (!mDelayed) { + mEqmMsg.writeShort(slot); // Inventory slot + mEqmMsg.writeByte(it2->size()); // Equip slot type count + } + for (it3 = it2->begin(), + it3_end = it2->end(); + it3 != it3_end; + ++it3) + { + if (!mDelayed) { + mEqmMsg.writeByte(it3->first); // Equip slot + mEqmMsg.writeByte(it3->second); // How many are used } - id1 = id2; + /* + * This bit can be somewhat inefficient, but is far better for + * average case assuming most equip use one slot max for each + * type and infrequently (<1/3) two of each type max. + * If the reader cares, you're more than welcome to add + * compile time options optimising for other usage. + * For now, this is adequate assuming `normal' usage. + */ + for (unsigned int i = 0; i < it3->second; ++i) + mPoss->equipSlots.insert( + std::make_pair(it3->first, slot)); } - - replaceInSlot(slot, id1, 1); - changeEquipment(EQUIP_FIGHT1_SLOT, itemId); - changeEquipment(EQUIP_FIGHT2_SLOT, 0); - return; + if (!mDelayed) + changeEquipment(0, it->second.itemId); + return true; + case 1: + /* + * Definitions earlier in the item file have precedence (even if it + * means requiring unequipping more), so no need to store more + * than the first. + */ + if (override && !ovd) + ovd = &*it2; // Iterator -> object -> pointer. + break; + case 2: + default: + /* + * Since slots are currently static (and I don't see any reason to + * change this right now), something probably went wrong. + * The logic to catch this is here rather than in the item manager + * just in case non-static equip slots do want to be + * implemented later. This would not be a trivial task, + * however. + */ + LOG_WARN("Inventory - item '" << it->second.itemId << + "' cannot be equipped, even by unequipping other items!"); + break; } - - case ITEM_EQUIPMENT_AMMO: - msg.writeByte(EQUIP_PROJECTILE_SLOT); - msg.writeShort(itemId); - mPoss->equipment[EQUIP_PROJECTILE_SLOT] = itemId; - return; - - case ITEM_EQUIPMENT_ONE_HAND_WEAPON: - case ITEM_EQUIPMENT_SHIELD: - availableSlots = 2; - firstSlot = EQUIP_FIGHT1_SLOT; - secondSlot = EQUIP_FIGHT2_SLOT; - break; - case ITEM_EQUIPMENT_RING: - availableSlots = 2; - firstSlot = EQUIP_RING1_SLOT; - secondSlot = EQUIP_RING2_SLOT; - break; - case ITEM_EQUIPMENT_TORSO: - availableSlots = 1; - firstSlot = EQUIP_TORSO_SLOT; - break; - case ITEM_EQUIPMENT_ARMS: - availableSlots = 1; - firstSlot = EQUIP_ARMS_SLOT; - break; - case ITEM_EQUIPMENT_HEAD: - availableSlots = 1; - firstSlot = EQUIP_HEAD_SLOT; - break; - case ITEM_EQUIPMENT_LEGS: - availableSlots = 1; - firstSlot = EQUIP_LEGS_SLOT; - break; - case ITEM_EQUIPMENT_NECKLACE: - availableSlots = 1; - firstSlot = EQUIP_NECKLACE_SLOT; - break; - case ITEM_EQUIPMENT_FEET: - availableSlots = 1; - firstSlot = EQUIP_FEET_SLOT; - break; - - case ITEM_UNUSABLE: - case ITEM_USABLE: - default: - return; } + // We didn't find a clean equip. + if (ovd) + { + /* + * We did find an equip that works if we unequip other items, and we can override. + * Process unequip triggers for all items we have to unequip. + * Process equip triggers for new item. + * Attempt to reequip any equipment we had to remove, but disallowing override. + */ - int id = mPoss->equipment[firstSlot]; + // TODO - this would increase ease of use substatially, add as soon as + // there is time to do so. - if (availableSlots == 2 && id && !mPoss->equipment[secondSlot] && - ItemManager::getItem(id)->getType() != ITEM_EQUIPMENT_TWO_HANDS_WEAPON) - { - // The first equipment slot is full, but the second one is empty. - id = 0; - firstSlot = secondSlot; + return false; // Return true when this section is complete } - - // Put the item in the first equipment slot. - replaceInSlot(slot, id, 1); - changeEquipment(firstSlot, itemId); + /* + * We cannot equip, either because we could not find any valid equip process + * or because we found a dirty equip and weren't allowed to override. + */ + return false; } -void Inventory::unequip(int slot) +bool Inventory::unequip(EquipData::iterator it) { - int itemId = mPoss->equipment[slot]; - if (!itemId) - { - return; - } - // No need to prepare. + return unequip(it->second, &it); +} - if (insert(itemId, 1) == 0) +bool Inventory::unequip(unsigned int slot, EquipData::iterator *itp) +{ + prepare(); + EquipData::iterator it = itp ? *itp : mPoss->equipSlots.begin(), + it_end = mPoss->equipSlots.end(); + bool changed = false; + for (it = mPoss->equipSlots.begin(); + it != it_end; + ++it) + if (it->second == slot) + { + changed = true; + mPoss->equipSlots.erase(it); + } + if (changed && !mDelayed) { - changeEquipment(slot, 0); + changeEquipment(mPoss->inventory.at(it->second).itemId, 0); + mEqmMsg.writeShort(slot); + mEqmMsg.writeByte(0); } + return changed; } diff --git a/src/game-server/inventory.hpp b/src/game-server/inventory.hpp index 0ca49aaa..f2168c2b 100644 --- a/src/game-server/inventory.hpp +++ b/src/game-server/inventory.hpp @@ -24,10 +24,10 @@ #include "game-server/character.hpp" #include "net/messageout.hpp" -enum +/*enum { // Equipment rules: -// 1 Brest equipment +// 1 torso equipment EQUIP_TORSO_SLOT = 0, // 1 arms equipment EQUIP_ARMS_SLOT = 1, @@ -53,9 +53,9 @@ enum EQUIP_PROJECTILE_SLOT = 10, EQUIP_CLIENT_INVENTORY = 32 -}; +};*/ -class GameClient; +class ItemClass; /** * Class used to handle Character possessions and prepare outgoing messages. @@ -66,28 +66,33 @@ class Inventory /** * Creates a view on the possessions of a character. - * @param delayed true if changes have to be cancelable. + * @param delayed If the changes need to be cancelable. */ Inventory(Character *, bool delayed = false); /** - * Commits delayed changes. + * Commits delayed changes if applicable. * Sends the update message to the client. */ ~Inventory(); /** - * Commits delayed changes. + * Commits changes. + * Exclusive to delayed mode. + * @param doRestart Whether to prepare the inventory for more changes + after this. If you are unsure, it is safe (though not + terribly efficient) to leave this as true. */ - void commit(); + void commit(bool doRestart = true); /** - * Cancels delayed changes. + * Cancels changes. + * Exclusive to delayed mode. */ void cancel(); /** - * Sends a complete inventory update to the client. + * Sends complete inventory status to the client. */ void sendFull() const; @@ -95,117 +100,110 @@ class Inventory * Ensures the inventory is sane and apply equipment modifiers. * Should be run only once and the very first time. */ - void initialize(); + void initialise(); /** * Equips item from given inventory slot. + * @param slot The slot in which the target item is in. + * @param override Whether this item can unequip other items to equip + * itself. If true, items that are unequipped will be + * attempted to be reequipped, but with override disabled. + * @returns whether the item could be equipped. */ - void equip(int slot); + bool equip(int slot, bool override = true); /** * Unequips item from given equipment slot. + * @param it Starting iterator. When the only parameter, also extracts + * slot number from it. + * Used so that when we already have an iterator to the first + * occurence from a previous operation we can start from + * there. + * @returns Whether it was unequipped. */ - void unequip(int slot); - - /** - * Gets the ID of projectiles. Removes one of these projectiles from - * inventory. - */ - int fireProjectile(); + bool unequip(EquipData::iterator it); + bool unequip(unsigned int slot, EquipData::iterator *itp = 0); /** * Inserts some items into the inventory. * @return number of items not inserted (to be dropped on floor?). */ - int insert(int itemId, int amount); + unsigned int insert(unsigned int itemId, unsigned int amount); /** * Removes some items from inventory. + * @param force If set to true, also remove any equipment encountered * @return number of items not removed. */ - int remove(int itemId, int amount); + unsigned int remove(unsigned int itemId, unsigned int amount, bool force = false); /** * Moves some items from the first slot to the second one. * @returns number of items not moved. */ - int move(int slot1, int slot2, int amount); + unsigned int move(unsigned int slot1, unsigned int slot2, unsigned int amount); /** * Removes some items from inventory. * @return number of items not removed. */ - int removeFromSlot(int slot, int amount); + unsigned int removeFromSlot(unsigned int slot, unsigned int amount); /** * Counts number of items with given ID. */ - int count(int itemId) const; + unsigned int count(unsigned int itemId) const; /** * Gets the ID of the items in a given slot. */ - int getItem(int slot) const; - - /** - * Changes amount of money. - * @return false if not enough money. - */ - bool changeMoney(int); + unsigned int getItem(unsigned int slot) const; private: /** - * Ensures we are working on a copy in delayed mode. + * Make sure that changes are being done on a copy, not directly. + * No effect when not in delayed mode. */ void prepare(); /** - * Updates the original in delayed mode. - */ - void update(); - - /** * Starts a new notification message. */ void restart(); - /** - * Fills some slots with items. - * @return number of items not inserted. - */ - int fillFreeSlot(int itemId, int amount, int MaxPerSlot); - - /** - * Frees an inventory slot given by its real index. - */ - void freeIndex(int index); /** - * Gets the real index associated to a slot. + * Check the inventory is within the slot limit and capacity. + * Forcibly delete items from the end if it is not. + * @todo Drop items instead? */ - int getIndex(int slot) const; + void checkSize(); /** - * Gets the slot number of an inventory index. + * Helper function for equip() when computing changes to equipment + * When newCount is 0, the item is being unequipped. */ - int getSlot(int index) const; - - /** - * Replaces a whole slot of items from inventory. - */ - void replaceInSlot(int slot, int itemId, int amount); + // inventory slot -> {equip slots} + typedef std::multimap<unsigned int, unsigned short> IdSlotMap; + void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it); /** * Changes equipment and adjusts character attributes. */ - void changeEquipment(int slot, int itemId); + void changeEquipment(unsigned int oldId, unsigned int itemId); + void changeEquipment(ItemClass *oldI, ItemClass *newI); Possessions *mPoss; /**< Pointer to the modified possessions. */ - MessageOut msg; /**< Update message containing all the changes. */ + /** + * Update message containing inventory changes. + * Note that in sendFull(), this is reused to send all full changes + * (for both inventory and equipment) + */ + MessageOut mInvMsg; + MessageOut mEqmMsg; /**< Update message containing equipment changes */ Character *mClient; /**< Character to notify. */ bool mDelayed; /**< Delayed changes. */ - bool mChangedLook; /**< Need to notify of a visible equipment change. */ }; diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp index 91d847d7..93b2bc10 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -25,113 +25,71 @@ #include "game-server/item.hpp" #include "common/configuration.hpp" +#include "game-server/autoattack.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/being.hpp" #include "game-server/state.hpp" #include "scripting/script.hpp" - -ItemType itemTypeFromString (const std::string &name) +bool ItemEffectInfo::apply(Being *itemUser) { - static std::map<const std::string, ItemType> table; - - if (table.empty()) - { - table["generic"] = ITEM_UNUSABLE; - table["usable"] = ITEM_USABLE; - table["equip-1hand"] = ITEM_EQUIPMENT_ONE_HAND_WEAPON; - table["equip-2hand"] = ITEM_EQUIPMENT_TWO_HANDS_WEAPON; - table["equip-torso"] = ITEM_EQUIPMENT_TORSO; - table["equip-arms"] = ITEM_EQUIPMENT_ARMS; - table["equip-head"] = ITEM_EQUIPMENT_HEAD; - table["equip-legs"] = ITEM_EQUIPMENT_LEGS; - table["equip-shield"] = ITEM_EQUIPMENT_SHIELD; - table["equip-ring"] = ITEM_EQUIPMENT_RING; - table["equip-necklace"] = ITEM_EQUIPMENT_NECKLACE; - table["equip-feet"] = ITEM_EQUIPMENT_FEET; - table["equip-ammo"] = ITEM_EQUIPMENT_AMMO; - table["hairsprite"] = ITEM_HAIRSPRITE; - table["racesprite"] = ITEM_RACESPRITE; - } - - std::map<const std::string, ItemType>::iterator val = table.find(name); - - return val == table.end() ? ITEM_UNKNOWN : (*val).second; + LOG_WARN("Virtual defintion used in effect application!"); + return false; } -int ItemModifiers::getValue(int type) const +bool ItemEffectAttrMod::apply(Being *itemUser) { - for (std::vector< ItemModifier >::const_iterator i = mModifiers.begin(), - i_end = mModifiers.end(); i != i_end; ++i) - { - if (i->type == type) return i->value; - } - return 0; + LOG_DEBUG("Applying modifier."); + itemUser->applyModifier(mAttributeId, mMod, mAttributeLayer, + mDuration, mId); + return false; } -int ItemModifiers::getAttributeValue(int attr) const +void ItemEffectAttrMod::dispell(Being *itemUser) { - return getValue(MOD_ATTRIBUTE + attr); + LOG_DEBUG("Dispelling modifier."); + itemUser->removeModifier(mAttributeId, mMod, mAttributeLayer, + mId, mDuration); } -void ItemModifiers::setValue(int type, int value) +bool ItemEffectAutoAttack::apply(Being *itemUser) { - if (value) - { - ItemModifier m; - m.type = type; - m.value = value; - mModifiers.push_back(m); - } + // TODO - STUB + return false; } -void ItemModifiers::setAttributeValue(int attr, int value) +void ItemEffectAutoAttack::dispell(Being *itemUser) { - setValue(MOD_ATTRIBUTE + attr, value); + // TODO } -void ItemModifiers::applyAttributes(Being *b) const +bool ItemEffectScript::apply(Being *itemUser) { - /* Note: if someone puts a "lifetime" property on an equipment, strange - behavior will occur, as its effect will be canceled twice. While this - could be desirable for some "cursed" items, it is probably an error - that should be detected somewhere else. */ - int lifetime = getValue(MOD_LIFETIME); - for (std::vector< ItemModifier >::const_iterator i = mModifiers.begin(), - i_end = mModifiers.end(); i != i_end; ++i) - { - if (i->type < MOD_ATTRIBUTE) continue; - b->applyModifier(i->type - MOD_ATTRIBUTE, i->value, lifetime); - } + // TODO + return false; } -void ItemModifiers::cancelAttributes(Being *b) const +void ItemEffectScript::dispell(Being *itemUser) { - for (std::vector< ItemModifier >::const_iterator i = mModifiers.begin(), - i_end = mModifiers.end(); i != i_end; ++i) - { - if (i->type < MOD_ATTRIBUTE) continue; - b->applyModifier(i->type - MOD_ATTRIBUTE, -i->value); - } + // TODO } -ItemClass::~ItemClass() +bool ItemClass::useTrigger(Being *itemUser, ItemTriggerType trigger) { - if (mScript) delete mScript; -} - -bool ItemClass::use(Being *itemUser) -{ - if (mType != ITEM_USABLE) return false; - if (mScript) - { - mScript->setMap(itemUser->getMap()); - mScript->prepare("use"); - mScript->push(itemUser); - mScript->push(mDatabaseID); // ID of the item - mScript->execute(); - } - mModifiers.applyAttributes(itemUser); - return true; + if (!trigger) return false; + std::pair<std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator, + std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator> + rn = mEffects.equal_range(trigger); + bool ret = false; + while (rn.first != rn.second) + if (rn.first++->second->apply(itemUser)) + ret = true; + + rn = mDispells.equal_range(trigger); + while (rn.first != rn.second) + rn.first++->second->dispell(itemUser); + + return ret; } @@ -147,8 +105,6 @@ void Item::update() { mLifetime--; if (!mLifetime) - { GameState::enqueueRemove(this); - } } } diff --git a/src/game-server/item.hpp b/src/game-server/item.hpp index 98d1fd88..3ccfe2bf 100644 --- a/src/game-server/item.hpp +++ b/src/game-server/item.hpp @@ -26,32 +26,9 @@ #include "game-server/actor.hpp" class Being; -class Script; -/** - * Enumeration of available Item types. - */ -enum ItemType -{ - ITEM_UNUSABLE = 0, - ITEM_USABLE, // 1 - ITEM_EQUIPMENT_ONE_HAND_WEAPON, // 2 - ITEM_EQUIPMENT_TWO_HANDS_WEAPON,// 3 - ITEM_EQUIPMENT_TORSO,// 4 - ITEM_EQUIPMENT_ARMS,// 5 - ITEM_EQUIPMENT_HEAD,// 6 - ITEM_EQUIPMENT_LEGS,// 7 - ITEM_EQUIPMENT_SHIELD,// 8 - ITEM_EQUIPMENT_RING,// 9 - ITEM_EQUIPMENT_NECKLACE,// 10 - ITEM_EQUIPMENT_FEET,// 11 - ITEM_EQUIPMENT_AMMO,// 12 - ITEM_HAIRSPRITE, - ITEM_RACESPRITE, - ITEM_UNKNOWN -}; - -ItemType itemTypeFromString (const std::string &name); +typedef std::list< std::pair< unsigned int, unsigned int> > ItemEquipInfo; +typedef std::list< ItemEquipInfo > ItemEquipsInfo; /** * State effects to beings, and actors. @@ -81,93 +58,101 @@ enum SET_STATE_NOT_FLOATING }; -/** - * Item modifier types. - */ -enum -{ - MOD_WEAPON_TYPE = 0, - MOD_WEAPON_RANGE, - MOD_WEAPON_DAMAGE, - MOD_ELEMENT_TYPE, - MOD_LIFETIME, - MOD_ATTRIBUTE +struct ItemAutoAttackInfo { + unsigned int base; + unsigned int range; + unsigned int baseSpeed; + unsigned int skillId; + /// attribute id -> damage bonus per point + std::map< unsigned int, double > attrBonus; }; -/** - * Characteristic of an item. - */ -struct ItemModifier -{ - unsigned char type; - short value; +enum ItemTriggerType { + ITT_NULL = 0, + ITT_IN_INVY, // Associated effects apply when the item is in the inventory + ITT_ACTIVATE, // Associated effects apply when the item is activated + ITT_EQUIP, // Assosciated effects apply when the item is equipped + ITT_LEAVE_INVY, // Associated effects apply when the item leaves the inventory + ITT_UNEQUIP, // Associated effects apply when the item is unequipped + ITT_EQUIPCHG // When the item is still equipped, but in a different way }; -/** - * Set of item characteristics. - */ -class ItemModifiers +enum ItemEffectType { + // Effects that are removed automatically when the trigger ends + // (ie. item no longer exists in invy, unequipped) + IET_ATTR_MOD = 0, // Modify a given attribute with a given value + IET_AUTOATTACK, // Give the associated being an autoattack + // Effects that do not need any automatic removal + IET_COOLDOWN, // Set a cooldown to this item, preventing activation for n ticks + IET_G_COOLDOWN, // Set a cooldown to all items of this type for this being + IET_SCRIPT // Call an associated lua script with given variables +}; + +class ItemEffectInfo { public: + virtual bool apply(Being *itemUser); + virtual void dispell(Being *itemUser) {} +}; - /** - * Gets the value associated to a modifier type, or zero if none. - */ - int getValue(int type) const; - - /** - * Sets the value associated to a modifier type. - */ - void setValue(int type, int amount); +class ItemEffectAttrMod : public ItemEffectInfo +{ + public: + ItemEffectAttrMod(unsigned int attrId, unsigned int layer, double value, + unsigned int id, unsigned int duration = 0) : + mAttributeId(attrId), mAttributeLayer(layer), + mMod(value), mDuration(duration), mId(id) {} - /** - * Gets the value associated to a MOD_ATTRIBUTE class, or zero if none. - */ - int getAttributeValue(int attr) const; + bool apply(Being *itemUser); + void dispell(Being *itemUser); - /** - * Sets the value associated to a MOD_ATTRIBUTE class. - */ - void setAttributeValue(int attr, int amount); + private: + unsigned int mAttributeId; + unsigned int mAttributeLayer; + double mMod; + unsigned int mDuration; + unsigned int mId; +}; - /** - * Applies all the attribute modifiers to a given Being. - */ - void applyAttributes(Being *) const; +class ItemEffectAutoAttack : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser); + void dispell(Being *itemUser); +}; - /** - * Cancels all the applied modifiers to a given Being. - * Only meant for equipment. - */ - void cancelAttributes(Being *) const; +class ItemEffectConsumes : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser) { return true; } + void dispell(Being *itemUser) {} +}; - private: - std::vector< ItemModifier > mModifiers; +class ItemEffectScript : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser); + void dispell(Being *itemUser); }; + /** * Class for simple reference to item information. */ class ItemClass { public: - ItemClass(int id, ItemType type, Script *s = NULL) - : mScript(NULL), mDatabaseID(id), mType(type), mAttackRange(0) + ItemClass(int id, unsigned int maxperslot) + : mDatabaseID(id), mSpriteID(0), mMaxPerSlot(maxperslot) {} - ~ItemClass(); + ~ItemClass() { resetEffects(); } /** * Applies the modifiers of an item to a given user. - * @return true if the item was sucessfully used and should be removed. + * @return true if item should be removed. */ - bool use(Being *itemUser); - - /** - * Gets item type. - */ - ItemType getType() const - { return mType; } + bool useTrigger(Being *itemUser, ItemTriggerType trigger); /** * Gets item weight. @@ -176,46 +161,19 @@ class ItemClass { return mWeight; } /** - * Sets item weight. - */ - void setWeight(int weight) - { mWeight = weight; } - - /** * Gets unit cost of these items. */ int getCost() const { return mCost; } /** - * Sets unit cost of these items. - */ - void setCost(int cost) - { mCost = cost; } - - /** * Gets max item per slot. */ - int getMaxPerSlot() const + unsigned int getMaxPerSlot() const { return mMaxPerSlot; } - /** - * Sets max item per slot. - */ - void setMaxPerSlot(int perSlot) - { mMaxPerSlot = perSlot; } - - /** - * Gets item modifiers. - */ - const ItemModifiers &getModifiers() const - { return mModifiers; } - - /** - * Sets item modifiers. - */ - void setModifiers(const ItemModifiers &modifiers) - { mModifiers = modifiers; } + bool hasTrigger(ItemTriggerType id) + { return mEffects.count(id); } /** * Gets database ID. @@ -224,49 +182,73 @@ class ItemClass { return mDatabaseID; } /** - * Sets the sprite ID. - */ - void setSpriteID(int spriteID) - { mSpriteID = spriteID; } - - /** * Gets the sprite ID. + * @note At present this is only a stub, and will always return zero. + * When you would want to extend serializeLooks to be more + * efficient, keep track of a sprite id here. */ int getSpriteID() const { return mSpriteID; } /** - * Sets the script that is to be used + * Returns equip requirements. */ - void setScript(Script *s) - { mScript = s; } + const ItemEquipsInfo &getItemEquipData() const { return mEquip; } - /** - * Set attack range (only needed when the item is a weapon) - */ - void setAttackRange(unsigned range) { mAttackRange = range; } + + private: /** - * Gets attack zone of weapon (returns NULL for non-weapon items) + * Add an effect to a trigger + * @param effect The effect to be run when the trigger is hit. + * @param id The trigger type. + * @param dispell The trigger that the effect should be dispelled on. + * @note FIXME: Should be more than one trigger that an effect + * can be dispelled from. */ - const unsigned getAttackRange() const - { return mAttackRange ; } - - - private: - Script *mScript; /**< Script for using items */ + void addEffect(ItemEffectInfo *effect, + ItemTriggerType id, + ItemTriggerType dispell = ITT_NULL) + { + mEffects.insert(std::make_pair(id, effect)); + if (dispell) + mDispells.insert(std::make_pair(dispell, effect)); + } + + void resetEffects() + { + while (mEffects.begin() != mEffects.end()) + { + delete mEffects.begin()->second; + mEffects.erase(mEffects.begin()); + } + while (mDispells.begin() != mDispells.end()) + { + delete mDispells.begin()->second; + mDispells.erase(mDispells.begin()); + } + } unsigned short mDatabaseID; /**< Item reference information */ /** The sprite that should be shown to the character */ unsigned short mSpriteID; - ItemType mType; /**< Type: usable, equipment etc. */ unsigned short mWeight; /**< Weight of the item. */ unsigned short mCost; /**< Unit cost the item. */ /** Max item amount per slot in inventory. */ - unsigned short mMaxPerSlot; + unsigned int mMaxPerSlot; + + std::multimap< ItemTriggerType, ItemEffectInfo * > mEffects; + std::multimap< ItemTriggerType, ItemEffectInfo * > mDispells; + + /** + * List of list of requirements for equipping. Only one inner list + * need be satisfied to sucessfully equip. Checks occur in order + * from outer front to back. + * All conditions in an inner list must be met for success. + */ + ItemEquipsInfo mEquip; - ItemModifiers mModifiers; /**< Item modifiers. */ - unsigned mAttackRange; /**< Attack range when used as a weapon */ + friend class ItemManager; }; /** diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index f2962250..802197d6 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -22,6 +22,7 @@ #include "defines.h" #include "common/resourcemanager.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/item.hpp" #include "game-server/skillmanager.hpp" #include "scripting/script.hpp" @@ -32,27 +33,82 @@ #include <set> #include <sstream> -typedef std::map< int, ItemClass * > ItemClasses; -static ItemClasses itemClasses; /**< Item reference */ -static std::string itemReferenceFile; -static unsigned int itemDatabaseVersion = 0; /**< Version of the loaded items database file.*/ - -void ItemManager::initialize(const std::string &file) +void ItemManager::initialize() { - itemReferenceFile = file; + mVisibleEquipSlotCount = 0; reload(); } void ItemManager::reload() { - std::string absPathFile = ResourceManager::resolve(itemReferenceFile); + std::string absPathFile; + xmlNodePtr rootNode; + + // #################################################################### + // ### Load the equip slots that a character has available to them. ### + // #################################################################### + + absPathFile = ResourceManager::resolve(mEquipCharSlotReferenceFile); if (absPathFile.empty()) { - LOG_ERROR("Item Manager: Could not find " << itemReferenceFile << "!"); + LOG_ERROR("Item Manager: Could not find " << mEquipCharSlotReferenceFile << "!"); + return; + } + + XML::Document doc(absPathFile, int()); + rootNode = doc.rootNode(); + + if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "equip-slots")) + { + LOG_ERROR("Item Manager: Error while parsing equip slots database (" + << absPathFile << ")!"); return; } - XML::Document doc(absPathFile, false); - xmlNodePtr rootNode = doc.rootNode(); + LOG_INFO("Loading equip slots: " << absPathFile); + + { + unsigned int totalCount = 0, slotCount = 0, visibleSlotCount = 0; + for_each_xml_child_node(node, rootNode) + { + if (xmlStrEqual(node->name, BAD_CAST "slot")) + { + std::string name = XML::getProperty(node, "name", ""); + int count = XML::getProperty(node, "count", 0); + if (name.empty() || !count || count < 0) + LOG_WARN("Item Manager: equip slot has no name or zero count"); + else + { + bool visible = XML::getProperty(node, "visible", "false") != "false"; + if (visible) + { + visibleEquipSlots.push_back(equipSlots.size()); + if (++visibleSlotCount > 7) + LOG_WARN("Item Manager: More than 7 visible equip slot!" + "This will not work with current netcode!"); + } + equipSlots.push_back(std::pair<std::string, unsigned int> + (name, count)); + totalCount += count; + ++slotCount; + } + } + } + LOG_INFO("Loaded '" << slotCount << "' slot types with '" + << totalCount << "' slots."); + } + + // #################################### + // ### Load the main item database. ### + // #################################### + + absPathFile = ResourceManager::resolve(mItemReferenceFile); + if (absPathFile.empty()) { + LOG_ERROR("Item Manager: Could not find " << mItemReferenceFile << "!"); + return; + } + + XML::Document doc2(absPathFile, int()); + rootNode = doc2.rootNode(); if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items")) { @@ -62,150 +118,201 @@ void ItemManager::reload() } LOG_INFO("Loading item reference: " << absPathFile); + unsigned nbItems = 0; for_each_xml_child_node(node, rootNode) { - // Try to load the version of the item database. The version is defined - // as subversion tag embedded as XML attribute. So every modification - // to the items.xml file will increase the revision automatically. - if (xmlStrEqual(node->name, BAD_CAST "version")) - { - std::string revision = XML::getProperty(node, "revision", std::string()); - itemDatabaseVersion = atoi(revision.c_str()); - - LOG_INFO("Loading item database version " << itemDatabaseVersion); - continue; - } - if (!xmlStrEqual(node->name, BAD_CAST "item")) - { continue; - } int id = XML::getProperty(node, "id", 0); - if (id == 0) + if (!id) { - LOG_WARN("Item Manager: An (ignored) item has no ID in " - << itemReferenceFile << "!"); + LOG_WARN("Item Manager: An item has no ID in " + << mItemReferenceFile << ", and so has been ignored!"); continue; } + + // Type is mostly unused, but still serves for + // hairsheets and race sheets. std::string sItemType = XML::getProperty(node, "type", ""); - ItemType itemType = itemTypeFromString(sItemType); + if (sItemType == "hairsprite" || sItemType == "racesprite") + continue; - if (itemType == ITEM_UNKNOWN) - { - LOG_WARN(itemReferenceFile << ": Unknown item type \"" << sItemType - << "\" for item #" << id << - " - treating it as \"generic\"."); - itemType = ITEM_UNUSABLE; - } + ItemClass *item; + ItemClasses::iterator i = itemClasses.find(id); - if (itemType == ITEM_HAIRSPRITE || itemType == ITEM_RACESPRITE) + unsigned int maxPerSlot = XML::getProperty(node, "max-per-slot", 0); + if (!maxPerSlot) { - continue; + LOG_WARN("Item Manager: Missing max-per-slot property for " + "item " << id << " in " << mItemReferenceFile << '.'); + maxPerSlot = 1; } - ItemClass *item; - ItemClasses::iterator i = itemClasses.find(id); if (i == itemClasses.end()) { - item = new ItemClass(id, itemType); + item = new ItemClass(id, maxPerSlot); itemClasses[id] = item; } else { + LOG_WARN("Multiple defintions of item '" << id << "'!"); item = i->second; } - int weight = XML::getProperty(node, "weight", 0); int value = XML::getProperty(node, "value", 0); - int maxPerSlot = XML::getProperty(node, "max-per-slot", 0); - int sprite = XML::getProperty(node, "sprite_id", 0); - std::string scriptFile = XML::getProperty(node, "script", ""); - unsigned attackRange = XML::getProperty(node, "attack-range", 0); - - ItemModifiers modifiers; - if (itemType == ITEM_EQUIPMENT_ONE_HAND_WEAPON || - itemType == ITEM_EQUIPMENT_TWO_HANDS_WEAPON) + // Should have multiple value definitions for multiple currencies? + item->mCost = value; + + for_each_xml_child_node(subnode, node) { - int weaponType = 0; - std::string strWeaponType = XML::getProperty(node, "weapon-type", ""); - if (strWeaponType == "") + if (xmlStrEqual(subnode->name, BAD_CAST "equip")) { - LOG_WARN(itemReferenceFile << ": Empty weapon type \"" - << "\" for item #" << id << - " - treating it as generic item."); + ItemEquipInfo req; + for_each_xml_child_node(equipnode, subnode) + if (xmlStrEqual(equipnode->name, BAD_CAST "slot")) + { + std::string slot = XML::getProperty(equipnode, "type", ""); + if (slot.empty()) + { + LOG_WARN("Item Manager: empty equip slot definition!"); + continue; + } + req.push_back(std::make_pair(getEquipIdFromName(slot), + XML::getProperty(equipnode, "required", + 1))); + } + if (req.empty()) + { + LOG_WARN("Item Manager: empty equip requirement " + "definition for item " << id << "!"); + continue; + } + item->mEquip.push_back(req); } - else - weaponType = SkillManager::getIdFromString(strWeaponType); - - modifiers.setValue(MOD_WEAPON_TYPE, weaponType); - modifiers.setValue(MOD_WEAPON_RANGE, XML::getProperty(node, "range", 0)); - modifiers.setValue(MOD_ELEMENT_TYPE, XML::getProperty(node, "element", 0)); - } - modifiers.setValue(MOD_LIFETIME, XML::getProperty(node, "lifetime", 0) * 10); - //TODO: add child nodes for these modifiers (additive and factor) - modifiers.setAttributeValue(BASE_ATTR_PHY_ATK_MIN, XML::getProperty(node, "attack-min", 0)); - modifiers.setAttributeValue(BASE_ATTR_PHY_ATK_DELTA, XML::getProperty(node, "attack-delta", 0)); - modifiers.setAttributeValue(BASE_ATTR_HP, XML::getProperty(node, "hp", 0)); - modifiers.setAttributeValue(BASE_ATTR_PHY_RES, XML::getProperty(node, "defense", 0)); - modifiers.setAttributeValue(CHAR_ATTR_STRENGTH, XML::getProperty(node, "strength", 0)); - modifiers.setAttributeValue(CHAR_ATTR_AGILITY, XML::getProperty(node, "agility", 0)); - modifiers.setAttributeValue(CHAR_ATTR_DEXTERITY, XML::getProperty(node, "dexterity", 0)); - modifiers.setAttributeValue(CHAR_ATTR_VITALITY, XML::getProperty(node, "vitality", 0)); - modifiers.setAttributeValue(CHAR_ATTR_INTELLIGENCE, XML::getProperty(node, "intelligence", 0)); - modifiers.setAttributeValue(CHAR_ATTR_WILLPOWER, XML::getProperty(node, "willpower", 0)); - - if (maxPerSlot == 0) - { - //LOG_WARN("Item Manager: Missing max-per-slot property for " - // "item " << id << " in " << itemReferenceFile << '.'); - maxPerSlot = 1; - } - - if (itemType > ITEM_USABLE && itemType < ITEM_EQUIPMENT_AMMO && - maxPerSlot != 1) - { - LOG_WARN("Item Manager: Setting max-per-slot property to 1 for " - "equipment " << id << " in " << itemReferenceFile << '.'); - maxPerSlot = 1; - } - - if (weight == 0) - { - LOG_WARN("Item Manager: Missing weight for item " - << id << " in " << itemReferenceFile << '.'); - weight = 1; - } - - // TODO: Clean this up some - if (scriptFile != "") - { - std::stringstream filename; - filename << "scripts/items/" << scriptFile; - if (ResourceManager::exists(filename.str())) // file exists! + else if (xmlStrEqual(subnode->name, BAD_CAST "effect")) { - LOG_INFO("Loading item script: " << filename.str()); - Script *s = Script::create("lua"); - s->loadFile(filename.str()); - item->setScript(s); - } else { - LOG_WARN("Could not find script file \"" << filename.str() << "\" for item #"<<id); + std::pair< ItemTriggerType, ItemTriggerType> triggerTypes; + { + std::string triggerName = XML::getProperty(subnode, "trigger", ""), + dispellTrigger = XML::getProperty(subnode, "dispell", ""); + // label -> { trigger (apply), trigger (cancel (default)) } + // The latter can be overridden. + static std::map<const std::string, + std::pair<ItemTriggerType, ItemTriggerType> > + triggerTable; + if (triggerTable.empty()) + { + /* + * The following is a table of all triggers for item + * effects. + * The first element defines the trigger used for this + * trigger, and the second defines the default + * trigger to use for dispelling. + */ + triggerTable["existence"].first = ITT_IN_INVY; + triggerTable["existence"].second = ITT_LEAVE_INVY; + triggerTable["activation"].first = ITT_ACTIVATE; + triggerTable["activation"].second = ITT_NULL; + triggerTable["equip"].first = ITT_EQUIP; + triggerTable["equip"].second = ITT_UNEQUIP; + triggerTable["leave-inventory"].first = ITT_LEAVE_INVY; + triggerTable["leave-inventory"].second = ITT_NULL; + triggerTable["unequip"].first = ITT_UNEQUIP; + triggerTable["unequip"].second = ITT_NULL; + triggerTable["equip-change"].first = ITT_EQUIPCHG; + triggerTable["equip-change"].second = ITT_NULL; + triggerTable["null"].first = ITT_NULL; + triggerTable["null"].second = ITT_NULL; + } + std::map<const std::string, std::pair<ItemTriggerType, + ItemTriggerType> >::iterator + it = triggerTable.find(triggerName); + + if (it == triggerTable.end()) { + LOG_WARN("Item Manager: Unable to find effect trigger type \"" + << triggerName << "\", skipping!"); + continue; + } + triggerTypes = it->second; + if (!dispellTrigger.empty()) + { + if ((it = triggerTable.find(dispellTrigger)) + == triggerTable.end()) + LOG_WARN("Item Manager: Unable to find dispell effect " + "trigger type \"" << dispellTrigger << "\"!"); + else + triggerTypes.second = it->second.first; + } + } + for_each_xml_child_node(effectnode, subnode) + { + if (xmlStrEqual(effectnode->name, BAD_CAST "modifier")) + { + std::string tag = XML::getProperty(effectnode, "attribute", ""); + if (tag.empty()) + { + LOG_WARN("Item Manager: Warning, modifier found " + "but no attribute specified!"); + continue; + } + unsigned int duration = XML::getProperty(effectnode, + "duration", + 0); + std::pair<unsigned int, unsigned int> info = attributeManager->getInfoFromTag(tag); + double value = XML::getFloatProperty(effectnode, "value", 0.0); + item->addEffect(new ItemEffectAttrMod(info.first, + info.second, + value, id, + duration), + triggerTypes.first, triggerTypes.second); + } + else if (xmlStrEqual(effectnode->name, BAD_CAST "autoattack")) + { + // TODO - URGENT + } + // Having a dispell for the next three is nonsensical. + else if (xmlStrEqual(effectnode->name, BAD_CAST "cooldown")) + { + LOG_WARN("Item Manager: Cooldown property not implemented yet!"); + // TODO: Also needs unique items before this action will work + } + else if (xmlStrEqual(effectnode->name, BAD_CAST "g-cooldown")) + { + LOG_WARN("Item Manager: G-Cooldown property not implemented yet!"); + // TODO + } + else if (xmlStrEqual(effectnode->name, BAD_CAST "consumes")) + item->addEffect(new ItemEffectConsumes(), triggerTypes.first); + else if (xmlStrEqual(effectnode->name, BAD_CAST "script")) + { + std::string src = XML::getProperty(effectnode, "src", ""); + if (src.empty()) + { + LOG_WARN("Item Manager: Empty src definition for script effect, skipping!"); + continue; + } + std::string func = XML::getProperty(effectnode, "function", ""); + if (func.empty()) + { + LOG_WARN ("Item Manager: Empty func definition for script effect, skipping!"); + continue; + } + for_each_xml_child_node(scriptnode, effectnode) + { + // TODO: Load variables from variable subnodes + } + std::string dfunc = XML::getProperty(effectnode, "dispell-function", ""); + // STUB + item->addEffect(new ItemEffectScript(), triggerTypes.first, triggerTypes.second); + } + } } + // More properties go here } - - item->setWeight(weight); - item->setCost(value); - item->setMaxPerSlot(maxPerSlot); - item->setModifiers(modifiers); - item->setSpriteID(sprite ? sprite : id); ++nbItems; - item->setAttackRange(attackRange); - - LOG_DEBUG("Item: ID: " << id << ", itemType: " << itemType - << ", weight: " << weight << ", value: " << value << - ", script: " << scriptFile << ", maxPerSlot: " << maxPerSlot << "."); } LOG_INFO("Loaded " << nbItems << " items from " @@ -221,13 +328,55 @@ void ItemManager::deinitialize() itemClasses.clear(); } -ItemClass *ItemManager::getItem(int itemId) +ItemClass *ItemManager::getItem(int itemId) const { ItemClasses::const_iterator i = itemClasses.find(itemId); return i != itemClasses.end() ? i->second : NULL; } -unsigned ItemManager::getDatabaseVersion() +unsigned int ItemManager::getDatabaseVersion() const +{ + return mItemDatabaseVersion; +} + +const std::string &ItemManager::getEquipNameFromId(unsigned int id) const +{ + return equipSlots.at(id).first; +} + +unsigned int ItemManager::getEquipIdFromName(const std::string &name) const +{ + for (unsigned int i = 0; i < equipSlots.size(); ++i) + if (name == equipSlots.at(i).first) + return i; + LOG_WARN("Item Manager: attempt to find equip id from name \"" << + name << "\" not found, defaulting to 0!"); + return 0; +} + +unsigned int ItemManager::getMaxSlotsFromId(unsigned int id) const +{ + return equipSlots.at(id).second; +} + +unsigned int ItemManager::getVisibleSlotCount() const +{ + if (!mVisibleEquipSlotCount) + for (VisibleEquipSlots::const_iterator it = visibleEquipSlots.begin(), + it_end = visibleEquipSlots.end(); + it != it_end; + ++it) + mVisibleEquipSlotCount += equipSlots.at(*it).second; + return mVisibleEquipSlotCount; +} + +bool ItemManager::isEquipSlotVisible(unsigned int id) const { - return itemDatabaseVersion; + for (VisibleEquipSlots::const_iterator it = visibleEquipSlots.begin(), + it_end = visibleEquipSlots.end(); + it != it_end; + ++it) + if (*it == id) + return true; + return false; } diff --git a/src/game-server/itemmanager.hpp b/src/game-server/itemmanager.hpp index 8b9b64bf..ea0641c7 100644 --- a/src/game-server/itemmanager.hpp +++ b/src/game-server/itemmanager.hpp @@ -22,35 +22,71 @@ #define ITEMMANAGER_H #include <string> +#include <map> +#include <vector> class ItemClass; -namespace ItemManager +class ItemManager { - /** - * Loads item reference file. - */ - void initialize(const std::string &); - - /** - * Reloads item reference file. - */ - void reload(); - - /** - * Destroy item classes. - */ - void deinitialize(); - - /** - * Gets the ItemClass having the given ID. - */ - ItemClass *getItem(int itemId); - - /** - * Gets the version of the loaded item database. - */ - unsigned getDatabaseVersion(); -} + public: + ItemManager(const std::string &itemFile, const std::string &equipFile) : + mItemReferenceFile(itemFile), + mEquipCharSlotReferenceFile(equipFile), + mItemDatabaseVersion(0) {} + /** + * Loads item reference file. + */ + void initialize(); + + /** + * Reloads item reference file. + */ + void reload(); + + /** + * Destroy item classes. + */ + void deinitialize(); + + /** + * Gets the ItemClass having the given ID. + */ + ItemClass *getItem(int itemId) const; + + /** + * Gets the version of the loaded item database. + */ + unsigned int getDatabaseVersion() const; + + const std::string &getEquipNameFromId(unsigned int id) const; + + unsigned int getEquipIdFromName(const std::string &name) const; + + unsigned int getMaxSlotsFromId(unsigned int id) const; + + unsigned int getVisibleSlotCount() const; + + bool isEquipSlotVisible(unsigned int id) const; + + private: + typedef std::map< int, ItemClass * > ItemClasses; + // Map a string (name of slot) with (str-id, max-per-equip-slot) + typedef std::vector< std::pair< std::string, unsigned int > > EquipSlots; + // Reference to the vector position of equipSlots + typedef std::vector< unsigned int > VisibleEquipSlots; + + ItemClasses itemClasses; /**< Item reference */ + EquipSlots equipSlots; + VisibleEquipSlots visibleEquipSlots; + + std::string mItemReferenceFile; + std::string mEquipCharSlotReferenceFile; + mutable unsigned int mVisibleEquipSlotCount; // Cache + + unsigned int mItemDatabaseVersion; /**< Version of the loaded items database file.*/ +}; + +extern ItemManager *itemManager; #endif diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp index d7ddefa2..b3eca754 100644 --- a/src/game-server/main-game.cpp +++ b/src/game-server/main-game.cpp @@ -40,6 +40,7 @@ #include "common/permissionmanager.hpp" #include "common/resourcemanager.hpp" #include "game-server/accountconnection.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/gamehandler.hpp" #include "game-server/skillmanager.hpp" #include "game-server/itemmanager.hpp" @@ -64,7 +65,9 @@ using utils::Logger; #define DEFAULT_LOG_FILE "manaserv-game.log" #define DEFAULT_CONFIG_FILE "manaserv.xml" #define DEFAULT_ITEMSDB_FILE "items.xml" +#define DEFAULT_EQUIPDB_FILE "equip.xml" #define DEFAULT_SKILLSDB_FILE "mana-skills.xml" +#define DEFAULT_ATTRIBUTEDB_FILE "attributes.xml" #define DEFAULT_MAPSDB_FILE "maps.xml" #define DEFAULT_MONSTERSDB_FILE "monsters.xml" #define DEFAULT_STATUSDB_FILE "mana-status-effect.xml" @@ -80,6 +83,10 @@ bool running = true; /**< Determines if server keeps running */ utils::StringFilter *stringFilter; /**< Slang's Filter */ +AttributeManager *attributeManager = new AttributeManager(DEFAULT_ATTRIBUTEDB_FILE); +ItemManager *itemManager = new ItemManager(DEFAULT_ITEMSDB_FILE, DEFAULT_EQUIPDB_FILE); +MonsterManager *monsterManager = new MonsterManager(DEFAULT_MONSTERSDB_FILE); + /** Core game message handler */ GameHandler *gameHandler; @@ -152,9 +159,10 @@ static void initializeServer() LOG_FATAL("The Game Server can't find any valid/available maps."); exit(2); } + attributeManager->initialize(); SkillManager::initialize(DEFAULT_SKILLSDB_FILE); - ItemManager::initialize(DEFAULT_ITEMSDB_FILE); - MonsterManager::initialize(DEFAULT_MONSTERSDB_FILE); + itemManager->initialize(); + monsterManager->initialize(); StatusManager::initialize(DEFAULT_STATUSDB_FILE); PermissionManager::initialize(DEFAULT_PERMISSION_FILE); @@ -205,8 +213,8 @@ static void deinitializeServer() // Destroy Managers delete stringFilter; - MonsterManager::deinitialize(); - ItemManager::deinitialize(); + monsterManager->deinitialize(); + itemManager->deinitialize(); MapManager::deinitialize(); StatusManager::deinitialize(); diff --git a/src/game-server/mapreader.cpp b/src/game-server/mapreader.cpp index 16e4660e..ca827dd4 100644 --- a/src/game-server/mapreader.cpp +++ b/src/game-server/mapreader.cpp @@ -266,7 +266,7 @@ Map* MapReader::readMap(xmlNodePtr node, const std::string &path, } } - MonsterClass *monster = MonsterManager::getMonster(monsterId); + MonsterClass *monster = monsterManager->getMonster(monsterId); if (monster) { things.push_back(new SpawnArea(composite, monster, rect, maxBeings, spawnRate)); diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index 14eeee81..85c45336 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -22,6 +22,7 @@ #include "common/configuration.hpp" #include "common/resourcemanager.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/character.hpp" #include "game-server/collisiondetection.hpp" #include "game-server/item.hpp" @@ -29,6 +30,7 @@ #include "game-server/state.hpp" #include "scripting/script.hpp" #include "utils/logger.h" +#include "utils/speedconv.hpp" #include <cmath> @@ -69,19 +71,39 @@ Monster::Monster(MonsterClass *specy): { LOG_DEBUG("Monster spawned!"); - // get basic attributes from monster database + /* + * Initialise the attribute structures. + */ + + const AttributeScopes &mobAttr = attributeManager->getAttributeInfoForType(ATTR_MOB); + + for (AttributeScopes::const_iterator it = mobAttr.begin(), + it_end = mobAttr.end(); + it != it_end; + ++it) + mAttributes.insert(std::pair< unsigned int, Attribute > + (it->first, Attribute(*it->second))); + + /* + * Set the attributes to the values defined by the associated monster + * class with or without mutations as needed. + */ + int mutation = specy->getMutation(); - for (int i = BASE_ATTR_BEGIN; i < BASE_ATTR_END; i++) + + for (AttributeMap::iterator it2 = mAttributes.begin(), + it2_end = mAttributes.end(); + it2 != it2_end; + ++it2) { - float attr = (float)specy->getAttribute(i); - if (mutation) - { - attr *= (100 + (rand()%(mutation * 2)) - mutation) / 100.0f; - } - setAttribute(i, (int)std::ceil(attr)); + double attr = specy->getAttribute(it2->first); + setAttribute(it2->first, + mutation ? + attr * (100 + (rand()%(mutation << 1)) - mutation) / 100.0 : + attr); } - setSpeed(specy->getSpeed()); // Put in tiles per second. + setAttribute(ATTR_MOVE_SPEED_RAW, utils::tpsToSpeed(getAttribute(ATTR_MOVE_SPEED_TPS))); // Put in tiles per second. setSize(specy->getSize()); // Set positions relative to target from which the monster can attack @@ -118,36 +140,41 @@ Monster::~Monster() void Monster::perform() { - if (mAction == ATTACK && mCurrentAttack && mTarget) + if (mAction == ATTACK) { - if (!isTimerRunning(T_B_ATTACK_TIME)) + if (mTarget) { - setTimerHard(T_B_ATTACK_TIME, mCurrentAttack->aftDelay + mCurrentAttack->preDelay); - Damage damage; - damage.base = (int) (getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN) * mCurrentAttack->damageFactor); - damage.delta = (int) (getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA) * mCurrentAttack->damageFactor); - damage.cth = getModifiedAttribute(BASE_ATTR_HIT); - damage.element = mCurrentAttack->element; - damage.type = mCurrentAttack->type; - - int hit = performAttack(mTarget, mCurrentAttack->range, damage); - - if (! mCurrentAttack->scriptFunction.empty() - && mScript - && hit > -1) + if (mCurrentAttack) { - mScript->setMap(getMap()); - mScript->prepare(mCurrentAttack->scriptFunction); - mScript->push(this); - mScript->push(mTarget); - mScript->push(hit); - mScript->execute(); + if (!isTimerRunning(T_M_ATTACK_TIME)) + { + setTimerHard(T_M_ATTACK_TIME, mCurrentAttack->aftDelay + mCurrentAttack->preDelay); + Damage dmg(getModifiedAttribute(MOB_ATTR_PHY_ATK_MIN) * + mCurrentAttack->damageFactor, + getModifiedAttribute(MOB_ATTR_PHY_ATK_DELTA) * + mCurrentAttack->damageFactor, + getModifiedAttribute(ATTR_ACCURACY), + mCurrentAttack->element, + mCurrentAttack->type, + mCurrentAttack->range); + + int hit = performAttack(mTarget, mCurrentAttack->range, dmg); + + if (! mCurrentAttack->scriptFunction.empty() + && mScript + && hit > -1) + { + mScript->setMap(getMap()); + mScript->prepare(mCurrentAttack->scriptFunction); + mScript->push(this); + mScript->push(mTarget); + mScript->push(hit); + mScript->execute(); + } + } } - } - } - if (mAction == ATTACK && !mTarget) - { - setAction(STAND); + } else + setAction(STAND); } } @@ -178,7 +205,7 @@ void Monster::update() } // Cancel the rest when we are currently performing an attack - if (isTimerRunning(T_B_ATTACK_TIME)) return; + if (isTimerRunning(T_M_ATTACK_TIME)) return; // Check potential attack positions Being *bestAttackTarget = mTarget = NULL; diff --git a/src/game-server/monster.hpp b/src/game-server/monster.hpp index e1737da7..0d5d9688 100644 --- a/src/game-server/monster.hpp +++ b/src/game-server/monster.hpp @@ -53,7 +53,7 @@ struct MonsterAttack int priority; float damageFactor; int element; - int type; + DMG_TY type; int preDelay; int aftDelay; int range; @@ -70,7 +70,6 @@ class MonsterClass public: MonsterClass(int id): mID(id), - mAttributes(BASE_ATTR_NB, 0), mSpeed(1), mSize(16), mExp(-1), @@ -99,21 +98,15 @@ class MonsterClass /** * Sets a being base attribute. */ - void setAttribute(size_t attribute, int value) - { mAttributes.at(attribute) = value; } + void setAttribute(int attribute, double value) + { mAttributes[attribute] = value; } /** * Returns a being base attribute. */ - int getAttribute(size_t attribute) const + double getAttribute(int attribute) const { return mAttributes.at(attribute); } - /** Sets movement speed in tiles per second. */ - void setSpeed(float speed) { mSpeed = speed; } - - /** Returns movement speed in tiles per second. */ - float getSpeed() const { return mSpeed; } - /** Sets collision circle radius. */ void setSize(int size) { mSize = size; } @@ -180,14 +173,15 @@ class MonsterClass const std::string &getScript() const { return mScript; } /** - * Randomly selects a monster drop (may return NULL). + * Randomly selects a monster drop + * @returns A class of item to drop, or NULL if none was found. */ ItemClass *getRandomDrop() const; private: unsigned short mID; MonsterDrops mDrops; - std::vector<int> mAttributes; /**< Base attributes of the monster. */ + std::map<int, double> mAttributes; /**< Base attributes of the monster. */ float mSpeed; /**< The monster class speed in tiles per second */ int mSize; int mExp; @@ -200,6 +194,8 @@ class MonsterClass int mOptimalLevel; MonsterAttacks mAttacks; std::string mScript; + + friend class MonsterManager; }; /** @@ -212,7 +208,7 @@ struct AttackPosition x(posX), y(posY), direction(dir) - {}; + {} int x; int y; diff --git a/src/game-server/monstermanager.cpp b/src/game-server/monstermanager.cpp index 3ad033f6..d7540281 100644 --- a/src/game-server/monstermanager.cpp +++ b/src/game-server/monstermanager.cpp @@ -21,17 +21,12 @@ #include "game-server/monstermanager.hpp" #include "common/resourcemanager.hpp" +#include "game-server/attributemanager.hpp" #include "game-server/itemmanager.hpp" #include "game-server/monster.hpp" #include "utils/logger.h" #include "utils/xml.hpp" -#include <map> - -typedef std::map< int, MonsterClass * > MonsterClasses; -static MonsterClasses monsterClasses; /**< Monster reference */ -static std::string monsterReferenceFile; - Element elementFromString (const std::string &name) { static std::map<const std::string, Element> table; @@ -54,17 +49,16 @@ Element elementFromString (const std::string &name) return val == table.end() ? ELEMENT_ILLEGAL : (*val).second; } -void MonsterManager::initialize(const std::string &file) +void MonsterManager::initialize() { - monsterReferenceFile = file; reload(); } void MonsterManager::reload() { - std::string absPathFile = ResourceManager::resolve(monsterReferenceFile); + std::string absPathFile = ResourceManager::resolve(mMonsterReferenceFile); if (absPathFile.empty()) { - LOG_ERROR("Monster Manager: Could not find " << monsterReferenceFile << "!"); + LOG_ERROR("Monster Manager: Could not find " << mMonsterReferenceFile << "!"); return; } @@ -92,16 +86,16 @@ void MonsterManager::reload() { LOG_WARN("Monster Manager: There is a monster (" << name << ") without ID in " - << monsterReferenceFile << "! It has been ignored."); + << mMonsterReferenceFile << "! It has been ignored."); continue; } MonsterClass *monster; - MonsterClasses::iterator i = monsterClasses.find(id); - if (i == monsterClasses.end()) + MonsterClasses::iterator i = mMonsterClasses.find(id); + if (i == mMonsterClasses.end()) { monster = new MonsterClass(id); - monsterClasses[id] = monster; + mMonsterClasses[id] = monster; } else { @@ -117,7 +111,7 @@ void MonsterManager::reload() if (xmlStrEqual(subnode->name, BAD_CAST "drop")) { MonsterDrop drop; - drop.item = ItemManager::getItem(XML::getProperty(subnode, "item", 0)); + drop.item = itemManager->getItem(XML::getProperty(subnode, "item", 0)); drop.probability = XML::getProperty(subnode, "percent", 0) * 100; if (drop.item && drop.probability) { @@ -127,21 +121,21 @@ void MonsterManager::reload() else if (xmlStrEqual(subnode->name, BAD_CAST "attributes")) { attributesSet = true; - monster->setAttribute(BASE_ATTR_HP, + monster->setAttribute(ATTR_MAX_HP, XML::getProperty(subnode, "hp", -1)); - monster->setAttribute(BASE_ATTR_PHY_ATK_MIN, + monster->setAttribute(MOB_ATTR_PHY_ATK_MIN, XML::getProperty(subnode, "attack-min", -1)); - monster->setAttribute(BASE_ATTR_PHY_ATK_DELTA, + monster->setAttribute(MOB_ATTR_PHY_ATK_DELTA, XML::getProperty(subnode, "attack-delta", -1)); - monster->setAttribute(BASE_ATTR_MAG_ATK, + monster->setAttribute(MOB_ATTR_MAG_ATK, XML::getProperty(subnode, "attack-magic", -1)); - monster->setAttribute(BASE_ATTR_EVADE, + monster->setAttribute(ATTR_DODGE, XML::getProperty(subnode, "evade", -1)); - monster->setAttribute(BASE_ATTR_HIT, + monster->setAttribute(ATTR_ACCURACY, XML::getProperty(subnode, "hit", -1)); - monster->setAttribute(BASE_ATTR_PHY_RES, + monster->setAttribute(ATTR_DEFENSE, XML::getProperty(subnode, "physical-defence", -1)); - monster->setAttribute(BASE_ATTR_MAG_RES, + monster->setAttribute(ATTR_MAGIC_DEFENSE, XML::getProperty(subnode, "magical-defence", -1)); monster->setSize(XML::getProperty(subnode, "size", 0)); float speed = (XML::getFloatProperty(subnode, "speed", -1.0f)); @@ -150,21 +144,27 @@ void MonsterManager::reload() //checking attributes for completeness and plausibility if (monster->getMutation() > 99) { - LOG_WARN(monsterReferenceFile + LOG_WARN(mMonsterReferenceFile <<": Mutation of monster #"<<id <<" more than 99% - ignored"); monster->setMutation(0); } bool attributesComplete = true; - for (int i = BASE_ATTR_BEGIN; i < BASE_ATTR_END; i++) + const AttributeScopes &mobAttr = attributeManager->getAttributeInfoForType(ATTR_MOB); + + for (AttributeScopes::const_iterator it = mobAttr.begin(), + it_end = mobAttr.end(); + it != it_end; + ++it) { - if (monster->getAttribute(i) == -1) + if (!monster->mAttributes.count(it->first)) { attributesComplete = false; - monster->setAttribute(i, 0); + monster->setAttribute(it->first, 0); } } + if (monster->getSize() == 0) { monster->setSize(16); @@ -176,12 +176,9 @@ void MonsterManager::reload() attributesComplete = false; } - if (!attributesComplete) LOG_WARN(monsterReferenceFile + if (!attributesComplete) LOG_WARN(mMonsterReferenceFile << ": Attributes incomplete for monster #" << id); - //The speed is set in tiles per second in the monsters.xml - monster->setSpeed(speed); - } else if (xmlStrEqual(subnode->name, BAD_CAST "exp")) { @@ -216,24 +213,27 @@ void MonsterManager::reload() if (sType == "physical") {att->type = DAMAGE_PHYSICAL; } else if (sType == "magical" || sType == "magic") {att->type = DAMAGE_MAGICAL; } else if (sType == "other") {att->type = DAMAGE_OTHER; } - else { att->type = -1; } + else { + LOG_WARN("Monster manager " << mMonsterReferenceFile + << ": unknown damage type '" << sType << "'."); + } if (att->id == 0) { - LOG_WARN(monsterReferenceFile + LOG_WARN(mMonsterReferenceFile << ": Attack without ID for monster #" << id << " (" << name << ") - attack ignored"); } else if (att->element == ELEMENT_ILLEGAL) { - LOG_WARN(monsterReferenceFile + LOG_WARN(mMonsterReferenceFile << ": Attack with unknown element \"" << sElement << "\" for monster #" << id << " (" << name << ") - attack ignored"); } else if (att->type == -1) { - LOG_WARN(monsterReferenceFile + LOG_WARN(mMonsterReferenceFile << ": Attack with unknown type \"" << sType << "\"" << " for monster #" << id << " (" << name << ")"); } @@ -252,15 +252,15 @@ void MonsterManager::reload() } monster->setDrops(drops); - if (!attributesSet) LOG_WARN(monsterReferenceFile + if (!attributesSet) LOG_WARN(mMonsterReferenceFile << ": No attributes defined for monster #" << id << " (" << name << ")"); - if (!behaviorSet) LOG_WARN(monsterReferenceFile + if (!behaviorSet) LOG_WARN(mMonsterReferenceFile << ": No behavior defined for monster #" << id << " (" << name << ")"); if (monster->getExp() == -1) { - LOG_WARN(monsterReferenceFile + LOG_WARN(mMonsterReferenceFile << ": No experience defined for monster #" << id << " (" << name << ")"); monster->setExp(0); @@ -269,21 +269,21 @@ void MonsterManager::reload() } LOG_INFO("Loaded " << nbMonsters << " monsters from " - << monsterReferenceFile << '.'); + << mMonsterReferenceFile << '.'); } void MonsterManager::deinitialize() { - for (MonsterClasses::iterator i = monsterClasses.begin(), - i_end = monsterClasses.end(); i != i_end; ++i) + for (MonsterClasses::iterator i = mMonsterClasses.begin(), + i_end = mMonsterClasses.end(); i != i_end; ++i) { delete i->second; } - monsterClasses.clear(); + mMonsterClasses.clear(); } MonsterClass *MonsterManager::getMonster(int id) { - MonsterClasses::const_iterator i = monsterClasses.find(id); - return i != monsterClasses.end() ? i->second : 0; + MonsterClasses::const_iterator i = mMonsterClasses.find(id); + return i != mMonsterClasses.end() ? i->second : 0; } diff --git a/src/game-server/monstermanager.hpp b/src/game-server/monstermanager.hpp index 18377bc5..6337b816 100644 --- a/src/game-server/monstermanager.hpp +++ b/src/game-server/monstermanager.hpp @@ -22,30 +22,41 @@ #define MONSTERMANAGER_HPP #include <string> - +#include <map> class MonsterClass; - -namespace MonsterManager +class MonsterManager { - /** - * Loads monster reference file. - */ - void initialize(const std::string &); - - /** - * Reloads monster reference file. - */ - void reload(); - - /** - * Destroy monster classes. - */ - void deinitialize(); - - /** - * Gets the MonsterClass having the given ID. - */ - MonsterClass *getMonster(int id); -} + public: + + MonsterManager(const std::string &file) : mMonsterReferenceFile(file) {} + /** + * Loads monster reference file. + */ + void initialize(); + + /** + * Reloads monster reference file. + */ + void reload(); + + /** + * Destroy monster classes. + */ + void deinitialize(); + + /** + * Gets the MonsterClass having the given ID. + */ + MonsterClass *getMonster(int id); + + private: + + typedef std::map< int, MonsterClass * > MonsterClasses; + MonsterClasses mMonsterClasses; /**< Monster reference */ + + std::string mMonsterReferenceFile; +}; + +extern MonsterManager *monsterManager; #endif // MONSTERMANAGER_HPP diff --git a/src/game-server/spawnarea.cpp b/src/game-server/spawnarea.cpp index 69a09c3a..19e665ad 100644 --- a/src/game-server/spawnarea.cpp +++ b/src/game-server/spawnarea.cpp @@ -82,7 +82,7 @@ void SpawnArea::update() Being *being = new Monster(mSpecy); - if (being->getModifiedAttribute(BASE_ATTR_HP) <= 0) + if (being->getModifiedAttribute(ATTR_MAX_HP) <= 0) { //LOG_WARN("Refusing to spawn dead monster " << mSpecy->getType()); delete being; diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index 867cd73d..fdfd8a43 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -39,6 +39,7 @@ #include "net/messageout.hpp" #include "scripting/script.hpp" #include "utils/logger.h" +#include "utils/speedconv.hpp" enum { @@ -102,14 +103,7 @@ static void updateMap(MapComposite *map) static void serializeLooks(Character *ch, MessageOut &msg, bool full) { const Possessions &poss = ch->getPossessions(); - static int const nb_slots = 4; - static int const slots[nb_slots] = - { - EQUIP_FIGHT1_SLOT, - EQUIP_HEAD_SLOT, - EQUIP_TORSO_SLOT, - EQUIP_LEGS_SLOT - }; + unsigned int nb_slots = itemManager->getVisibleSlotCount(); // Bitmask describing the changed entries. int changed = (1 << nb_slots) - 1; @@ -119,21 +113,25 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full) changed = (1 << nb_slots) - 1; } - int items[nb_slots]; + std::vector<unsigned int> items; + items.resize(nb_slots, 0); // Partially build both kinds of packet, to get their sizes. - int mask_full = 0, mask_diff = 0; - int nb_full = 0, nb_diff = 0; - for (int i = 0; i < nb_slots; ++i) + unsigned int mask_full = 0, mask_diff = 0; + unsigned int nb_full = 0, nb_diff = 0; + std::map<unsigned int, unsigned int>::const_iterator it = + poss.equipSlots.begin(); + for (unsigned int i = 0; i < nb_slots; ++i) { - int id = poss.equipment[slots[i]]; - ItemClass *eq; - items[i] = id && (eq = ItemManager::getItem(id)) ? eq->getSpriteID() : 0; if (changed & (1 << i)) { // Skip slots that have not changed, when sending an update. ++nb_diff; mask_diff |= 1 << i; } + if (it == poss.equipSlots.end() || it->first > i) continue; + ItemClass *eq; + items[i] = it->first && (eq = itemManager->getItem(it->first)) ? + eq->getSpriteID() : 0; if (items[i]) { /* If we are sending the whole equipment, only filled slots have to @@ -151,7 +149,7 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full) int mask = full ? mask_full | (1 << 7) : mask_diff; msg.writeByte(mask); - for (int i = 0; i < nb_slots; ++i) + for (unsigned int i = 0; i < nb_slots; ++i) { if (mask & (1 << i)) msg.writeShort(items[i]); } @@ -318,7 +316,7 @@ static void informPlayer(MapComposite *map, Character *p) // We multiply the sent speed (in tiles per second) by ten // to get it within a byte with decimal precision. // For instance, a value of 4.5 will be sent as 45. - moveMsg.writeByte((unsigned short) (o->getSpeed() * 10)); + moveMsg.writeByte((unsigned short) (o->getModifiedAttribute(ATTR_MOVE_SPEED_TPS) * 10)); } } @@ -349,7 +347,8 @@ static void informPlayer(MapComposite *map, Character *p) { MessageOut healthMsg(GPMSG_BEING_HEALTH_CHANGE); healthMsg.writeShort(c->getPublicID()); - healthMsg.writeShort(c->getHealth()); + healthMsg.writeShort(c->getModifiedAttribute(ATTR_HP)); + healthMsg.writeShort(c->getModifiedAttribute(ATTR_MAX_HP)); gameHandler->sendTo(p, healthMsg); } } diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp index c044ece6..e2f1cd0c 100644 --- a/src/game-server/trade.cpp +++ b/src/game-server/trade.cpp @@ -38,7 +38,7 @@ */ Trade::Trade(Character *c1, Character *c2): - mChar1(c1), mChar2(c2), mMoney1(0), mMoney2(0), mState(TRADE_INIT) + mChar1(c1), mChar2(c2), mMoney1(0), mMoney2(0), mState(TRADE_INIT), mCurrencyId(ATTR_GP) { MessageOut msg(GPMSG_TRADE_REQUEST); msg.writeShort(c1->getPublicID()); @@ -131,10 +131,17 @@ void Trade::agree(Character *c) // Check if both player has the objects in their inventories // and enouth money, then swap them. Inventory v1(mChar1, true), v2(mChar2, true); - if (!perform(mItems1, v1, v2) || - !perform(mItems2, v2, v1) || - !v1.changeMoney(mMoney2 - mMoney1) || - !v2.changeMoney(mMoney1 - mMoney2)) + if (mChar1->getAttribute(mCurrencyId) >= mMoney1 - mMoney2 && + mChar2->getAttribute(mCurrencyId) >= mMoney2 - mMoney1 && + perform(mItems1, v1, v2) && + perform(mItems2, v2, v1)) + { + mChar1->setAttribute(mCurrencyId, mChar1->getAttribute(mCurrencyId) + - mMoney1 + mMoney2); + mChar2->setAttribute(mCurrencyId, mChar2->getAttribute(mCurrencyId) + - mMoney2 + mMoney1); + } + else { v1.cancel(); v2.cancel(); diff --git a/src/game-server/trade.hpp b/src/game-server/trade.hpp index b6ac658f..a95e89c1 100644 --- a/src/game-server/trade.hpp +++ b/src/game-server/trade.hpp @@ -101,6 +101,7 @@ class Trade TradedItems mItems1, mItems2; /**< Traded items. */ int mMoney1, mMoney2; /**< Traded money. */ TradeState mState; /**< State of transaction. */ + unsigned int mCurrencyId; /**< The attribute to use as currency. */ }; #endif diff --git a/src/net/messagein.cpp b/src/net/messagein.cpp index 45d1b21c..63fd8778 100644 --- a/src/net/messagein.cpp +++ b/src/net/messagein.cpp @@ -23,8 +23,12 @@ #include <iostream> #include <string> #include <enet/enet.h> +#ifndef USE_NATIVE_DOUBLE +#include <sstream> +#endif #include "net/messagein.hpp" +#include "utils/logger.h" MessageIn::MessageIn(const char *data, int length): mData(data), @@ -42,6 +46,7 @@ int MessageIn::readByte() { value = (unsigned char) mData[mPos]; } + else LOG_DEBUG("Unable to read 1 byte in " << this->getId() << "!"); mPos += 1; return value; } @@ -55,6 +60,7 @@ int MessageIn::readShort() memcpy(&t, mData + mPos, 2); value = (unsigned short) ENET_NET_TO_HOST_16(t); } + else LOG_DEBUG("Unable to read 2 bytes in " << this->getId() << "!"); mPos += 2; return value; } @@ -68,10 +74,26 @@ int MessageIn::readLong() memcpy(&t, mData + mPos, 4); value = ENET_NET_TO_HOST_32(t); } + else LOG_DEBUG("Unable to read 4 bytes in " << this->getId() << "!"); mPos += 4; return value; } +double MessageIn::readDouble() +{ + double value; +#ifdef USE_NATIVE_DOUBLE + if (mPos + sizeof(double) <= mLength) + memcpy(&value, mData + mPos, sizeof(double)); + mPos += sizeof(double); +#else + int length = readByte(); + std::istringstream i (readString(length)); + i >> value; +#endif + return value; +} + std::string MessageIn::readString(int length) { // Get string length diff --git a/src/net/messagein.hpp b/src/net/messagein.hpp index 71884190..c6e49ab5 100644 --- a/src/net/messagein.hpp +++ b/src/net/messagein.hpp @@ -47,6 +47,11 @@ class MessageIn int readByte(); /**< Reads a byte. */ int readShort(); /**< Reads a short. */ int readLong(); /**< Reads a long. */ + /** + * Reads a double. HACKY and should *not* be used for client + * communication! + */ + double readDouble(); /** * Reads a string. If a length is not given (-1), it is assumed diff --git a/src/net/messageout.cpp b/src/net/messageout.cpp index 8e6a4a7a..950da5f3 100644 --- a/src/net/messageout.cpp +++ b/src/net/messageout.cpp @@ -21,6 +21,10 @@ #include <cstring> #include <iomanip> #include <iostream> +#ifndef USE_NATIVE_DOUBLE +#include <limits> +#include <sstream> +#endif #include <string> #include <enet/enet.h> @@ -98,6 +102,25 @@ void MessageOut::writeLong(int value) mPos += 4; } +void MessageOut::writeDouble(double value) +{ +#ifdef USE_NATIVE_DOUBLE + expand(mPos + sizeof(double)); + memcpy(mData + mPos, &value, sizeof(double)); + mPos += sizeof(double); +#else +// Rather inefficient, but I don't have a lot of time. +// If anyone wants to implement a custom double you are more than welcome to. + std::ostringstream o; + // Highest precision for double + o.precision(std::numeric_limits< double >::digits10); + o << value; + std::string str = o.str(); + writeByte(str.size()); + writeString(str, str.size()); +#endif +} + void MessageOut::writeCoordinates(int x, int y) { expand(mPos + 3); diff --git a/src/net/messageout.hpp b/src/net/messageout.hpp index cd7befa7..270b7963 100644 --- a/src/net/messageout.hpp +++ b/src/net/messageout.hpp @@ -56,6 +56,12 @@ class MessageOut void writeLong(int value); /**< Writes an integer on four bytes. */ /** + * Writes a double. HACKY and should *not* be used for client + * communication! + */ + void writeDouble(double value); + + /** * Writes a 3-byte block containing tile-based coordinates. */ void writeCoordinates(int x, int y); diff --git a/src/protocol.h b/src/protocol.h index 9b3e5a1a..9b5c16f3 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -57,7 +57,10 @@ enum { APMSG_CHAR_CREATE_RESPONSE = 0x0021, // B error PAMSG_CHAR_DELETE = 0x0022, // B index APMSG_CHAR_DELETE_RESPONSE = 0x0023, // B error - APMSG_CHAR_INFO = 0x0024, // B index, S name, B gender, B hair style, B hair color, W level, W character points, W correction points, D money, W*6 stats + // B index, S name, B gender, B hair style, B hair color, W level, + // W character points, W correction points, + // {D attr id, D base value (in 1/256ths) D mod value (in 256ths) }* + APMSG_CHAR_INFO = 0x0024, // ^ PAMSG_CHAR_SELECT = 0x0026, // B index APMSG_CHAR_SELECT_RESPONSE = 0x0027, // B error, B*32 token, S game address, W game port, S chat address, W chat port PAMSG_EMAIL_CHANGE = 0x0030, // S email @@ -86,16 +89,17 @@ enum { PGMSG_EQUIP = 0x0112, // B slot PGMSG_UNEQUIP = 0x0113, // B slot PGMSG_MOVE_ITEM = 0x0114, // B slot1, B slot2, B amount - GPMSG_INVENTORY = 0x0120, // { B slot, W item id [, B amount] }* - GPMSG_INVENTORY_FULL = 0x0121, // { B slot, W item id [, B amount] }* - GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, W base value, W modified value }* + GPMSG_INVENTORY = 0x0120, // { W slot, W item id [, W amount] (if item id is nonzero) }* + GPMSG_INVENTORY_FULL = 0x0121, // W inventory slot count { W slot, W itemId, W amount }, { B equip slot, W invy slot}* + GPMSG_EQUIP = 0x0122, // { W Invy slot, B equip slot type count { B equip slot, B number used} }* + GPMSG_PLAYER_ATTRIBUTE_CHANGE = 0x0130, // { W attribute, D base value (in 1/256ths), D modified value (in 1/256ths)}* GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { W skill, D exp got, D exp needed }* GPMSG_LEVELUP = 0x0150, // W new level, W character points, W correction points GPMSG_LEVEL_PROGRESS = 0x0151, // B percent completed to next levelup - PGMSG_RAISE_ATTRIBUTE = 0x0160, // B attribute - GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, B attribute - PGMSG_LOWER_ATTRIBUTE = 0x0170, // B attribute - GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, B attribute + PGMSG_RAISE_ATTRIBUTE = 0x0160, // W attribute + GPMSG_RAISE_ATTRIBUTE_RESPONSE = 0x0161, // B error, W attribute + PGMSG_LOWER_ATTRIBUTE = 0x0170, // W attribute + GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error, W attribute PGMSG_RESPAWN = 0x0180, // - GPMSG_BEING_ENTER = 0x0200, // B type, W being id, B action, W*2 position // character: S name, B hair style, B hair color, B gender, B item bitmask, { W item id }* @@ -109,7 +113,7 @@ enum { GPMSG_BEING_ACTION_CHANGE = 0x0271, // W being id, B action PGMSG_DIRECTION_CHANGE = 0x0272, // B Direction GPMSG_BEING_DIR_CHANGE = 0x0273, // W being id, B direction - GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W health + GPMSG_BEING_HEALTH_CHANGE = 0x0274, // W being id, W hp, W max hp GPMSG_BEINGS_MOVE = 0x0280, // { W being id, B flags [, W*2 position, B speed] }* GPMSG_ITEMS = 0x0281, // { W item id, W*2 position }* PGMSG_ATTACK = 0x0290, // W being id @@ -272,10 +276,11 @@ enum { // used to identify part of sync message enum { - SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints, B attribute id, D attribute value - SYNC_CHARACTER_SKILL = 0x02, // D charId, B skillId, D skill value - SYNC_ONLINE_STATUS = 0x03, // D charId, B 0x00 = offline, 0x01 = online - SYNC_END_OF_BUFFER = 0xFF // shows, that the buffer ends here. + SYNC_CHARACTER_POINTS = 0x01, // D charId, D charPoints, D corrPoints + SYNC_CHARACTER_ATTRIBUTE = 0x02, // D charId, D attrId, DF base, DF mod + SYNC_CHARACTER_SKILL = 0x03, // D charId, B skillId, D skill value + SYNC_ONLINE_STATUS = 0x04, // D charId, B 0x00 = offline, 0x01 = online + SYNC_END_OF_BUFFER = 0xFF // shows, that the buffer ends here. }; // Login specific return values diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp index 945392e1..c186be8b 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -50,6 +50,7 @@ extern "C" { #include "scripting/luautil.hpp" #include "scripting/luascript.hpp" #include "utils/logger.h" +#include "utils/speedconv.hpp" #include <string.h> @@ -342,8 +343,10 @@ static int chr_warp(lua_State *s) * (negative amount) should be passed first, then insertions (positive amount). * If a removal fails, all the previous operations are canceled (except for * items dropped on the floor, hence why removals should be passed first), and - * the function returns false. Otherwise the function will return true. When - * the item identifier is zero, money is modified. + * the function returns false. Otherwise the function will return true. + * Note that previously when the item identifier was zero, money was modified; + * however currency is now handled through attributes. This breaks backwards + * compatibility with old scripts, and so logs a warning. * Note: If an insertion fails, extra items are dropped on the floor. * mana.chr_inv_change(character, (int id, int nb)...): bool success */ @@ -368,14 +371,7 @@ static int chr_inv_change(lua_State *s) int nb = lua_tointeger(s, i * 2 + 3); if (id == 0) - { - if (!inv.changeMoney(nb)) - { - inv.cancel(); - lua_pushboolean(s, 0); - return 1; - } - } + LOG_WARN("chr_inv_change: id 0! Currency is now handled through attributes!"); else if (nb < 0) { nb = inv.remove(id, -nb); @@ -388,7 +384,7 @@ static int chr_inv_change(lua_State *s) } else { - ItemClass *ic = ItemManager::getItem(id); + ItemClass *ic = itemManager->getItem(id); if (!ic) { raiseScriptError(s, "chr_inv_change called with an unknown item."); @@ -410,7 +406,6 @@ static int chr_inv_change(lua_State *s) /** * Callback for counting items in inventory. - * When an item identifier is zero, money is queried. * mana.chr_inv_count(character, int id...): int count... */ static int chr_inv_count(lua_State *s) @@ -426,8 +421,7 @@ static int chr_inv_count(lua_State *s) Inventory inv(q); for (int i = 2; i <= nb_items + 1; ++i) { - const int id = luaL_checkint(s, i); - int nb = id ? inv.count(id) : q->getPossessions().money; + int nb = inv.count(luaL_checkint(s, i)); lua_pushinteger(s, nb); } return nb_items; @@ -632,41 +626,6 @@ static int being_set_status_time(lua_State *s) } /** -* Returns the current speed of the being -* mana.being_get_speed(Being *being) -*/ -static int being_get_speed(lua_State *s) -{ - if (!lua_isuserdata(s, 1)) - { - raiseScriptError(s, "being_get_speed called with incorrect parameters."); - return 0; - } - Being *being = getBeing(s, 1); - lua_pushnumber(s, being->getSpeed()); - return 1; -} - -/** -* Sets the speed of the being -* mana.being_set_speed(Being *being, float speed) -*/ -static int being_set_speed(lua_State *s) -{ - const float speed = luaL_checknumber(s, 2); - - if (!lua_isuserdata(s, 1)) - { - raiseScriptError(s, "being_set_speed called with incorrect parameters."); - return 0; - } - Being *being = getBeing(s, 1); - being->setSpeed(speed); - return 1; -} - - -/** * Returns the Thing type of the given Being * mana.being_type(Being *being) */ @@ -702,7 +661,11 @@ static int being_walk(lua_State *s) being->setDestination(Point(x, y)); if (lua_isnumber(s, 4)) - being->setSpeed((float) lua_tonumber(s, 4)); + { + being->setAttribute(ATTR_MOVE_SPEED_TPS, lua_tonumber(s, 4)); + being->setAttribute(ATTR_MOVE_SPEED_RAW, utils::tpsToSpeed( + being->getModifiedAttribute(ATTR_MOVE_SPEED_TPS))); + } return 0; } @@ -746,14 +709,12 @@ static int being_damage(lua_State *s) if (!being->canFight()) return 0; - Damage damage; - damage.base = lua_tointeger(s, 2); - damage.delta = lua_tointeger(s, 3); - damage.cth = lua_tointeger(s, 4); - damage.type = lua_tointeger(s, 5); - damage.element = lua_tointeger(s, 6); - - being->damage(NULL, damage); + Damage dmg((unsigned short) lua_tointeger(s, 2), /* base */ + (unsigned short) lua_tointeger(s, 3), /* delta */ + (unsigned short) lua_tointeger(s, 4), /* cth */ + (unsigned char) lua_tointeger(s, 6), /* element */ + DAMAGE_PHYSICAL); /* type */ + being->damage(NULL, dmg); return 0; } @@ -967,7 +928,7 @@ static int monster_create(lua_State *s) return 0; } - MonsterClass *spec = MonsterManager::getMonster(monsterId); + MonsterClass *spec = monsterManager->getMonster(monsterId); if (!spec) { raiseScriptError(s, "monster_create called with invalid monster ID: %d", monsterId); @@ -1597,7 +1558,7 @@ static int item_drop(lua_State *s) const int type = luaL_checkint(s, 3); const int number = luaL_optint(s, 4, 1); - ItemClass *ic = ItemManager::getItem(type); + ItemClass *ic = itemManager->getItem(type); if (!ic) { raiseScriptError(s, "item_drop called with unknown item ID"); @@ -1682,8 +1643,6 @@ LuaScript::LuaScript(): { "being_has_status", &being_has_status }, { "being_set_status_time", &being_set_status_time}, { "being_get_status_time", &being_get_status_time}, - { "being_set_speed", &being_set_speed }, - { "being_get_speed", &being_get_speed }, { "being_type", &being_type }, { "being_walk", &being_walk }, { "being_say", &being_say }, diff --git a/src/serialize/characterdata.hpp b/src/serialize/characterdata.hpp index 50acc8ea..6e1facc8 100644 --- a/src/serialize/characterdata.hpp +++ b/src/serialize/characterdata.hpp @@ -41,10 +41,16 @@ void serializeCharacterData(const T &data, MessageOut &msg) msg.writeShort(data.getCharacterPoints()); msg.writeShort(data.getCorrectionPoints()); - // character attributes - for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) + msg.writeShort(data.mAttributes.size()); + AttributeMap::const_iterator attr_it, attr_it_end; + for (attr_it = data.mAttributes.begin(), + attr_it_end = data.mAttributes.end(); + attr_it != attr_it_end; + ++attr_it) { - msg.writeByte(data.getAttribute(i)); + msg.writeShort(attr_it->first); + msg.writeDouble(data.getAttrBase(attr_it)); + msg.writeDouble(data.getAttrMod(attr_it)); } // character skills @@ -91,18 +97,22 @@ void serializeCharacterData(const T &data, MessageOut &msg) // inventory - must be last because size isn't transmitted const Possessions &poss = data.getPossessions(); - msg.writeLong(poss.money); - for (int j = 0; j < EQUIPMENT_SLOTS; ++j) + msg.writeShort(poss.equipSlots.size()); // number of equipment + for (EquipData::const_iterator k = poss.equipSlots.begin(), + k_end = poss.equipSlots.end(); + k != k_end; + ++k) { - msg.writeShort(poss.equipment[j]); + msg.writeByte(k->first); // Equip slot type + msg.writeShort(k->second); // Inventory slot } - for (std::vector< InventoryItem >::const_iterator j = poss.inventory.begin(), + for (InventoryData::const_iterator j = poss.inventory.begin(), j_end = poss.inventory.end(); j != j_end; ++j) { - msg.writeShort(j->itemId); - msg.writeByte(j->amount); + msg.writeShort(j->first); // slot id + msg.writeShort(j->second.itemId); // item type + msg.writeShort(j->second.amount); // amount } - } template< class T > @@ -118,9 +128,14 @@ void deserializeCharacterData(T &data, MessageIn &msg) data.setCorrectionPoints(msg.readShort()); // character attributes - for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i) + unsigned int attrSize = msg.readShort(); + for (unsigned int i = 0; i < attrSize; ++i) { - data.setAttribute(i, msg.readByte()); + unsigned int id = msg.readShort(); + double base = msg.readDouble(), + mod = msg.readDouble(); + data.setAttribute(id, base); + data.setModAttribute(id, mod); } // character skills @@ -168,20 +183,31 @@ void deserializeCharacterData(T &data, MessageIn &msg) data.giveSpecial(msg.readLong()); } - // inventory - must be last because size isn't transmitted + Possessions &poss = data.getPossessions(); - poss.money = msg.readLong(); - for (int j = 0; j < EQUIPMENT_SLOTS; ++j) + poss.equipSlots.clear(); + int equipSlotsSize = msg.readShort(); + unsigned int eqSlot, invSlot; + for (int j = 0; j < equipSlotsSize; ++j) { - poss.equipment[j] = msg.readShort(); + int equipmentInSlotType = msg.readByte(); + for (int k = 0; k < equipmentInSlotType; ++k) + { + eqSlot = msg.readByte(); + invSlot = msg.readShort(); + poss.equipSlots.insert(poss.equipSlots.end(), + std::make_pair(eqSlot, invSlot)); + } } poss.inventory.clear(); + // inventory - must be last because size isn't transmitted while (msg.getUnreadLength()) { InventoryItem i; - i.itemId = msg.readShort(); - i.amount = msg.readByte(); - poss.inventory.push_back(i); + int slotId = msg.readShort(); + i.itemId = msg.readShort(); + i.amount = msg.readShort(); + poss.inventory.insert(poss.inventory.end(), std::make_pair(slotId, i)); } } diff --git a/src/sql/mysql/createTables.sql b/src/sql/mysql/createTables.sql index 2887f1c8..a76cc337 100644 --- a/src/sql/mysql/createTables.sql +++ b/src/sql/mysql/createTables.sql @@ -36,18 +36,10 @@ CREATE TABLE IF NOT EXISTS `mana_characters` ( `level` tinyint(3) unsigned NOT NULL, `char_pts` smallint(5) unsigned NOT NULL, `correct_pts` smallint(5) unsigned NOT NULL, - `money` int(10) unsigned NOT NULL, -- location on the map `x` smallint(5) unsigned NOT NULL, `y` smallint(5) unsigned NOT NULL, `map_id` tinyint(3) unsigned NOT NULL, - -- attributes - `str` smallint(5) unsigned NOT NULL, - `agi` smallint(5) unsigned NOT NULL, - `dex` smallint(5) unsigned NOT NULL, - `vit` smallint(5) unsigned NOT NULL, - `int` smallint(5) unsigned NOT NULL, - `will` smallint(5) unsigned NOT NULL, -- PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), @@ -60,6 +52,23 @@ DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; -- +-- Create table: `mana_char_attr` +-- + +CREATE TABLE IF NOT EXISTS `mana_char_attr` ( + `char_id` int(10) unsigned NOT NULL, + `attr_id` int(10) unsigned NOT NULL, + `attr_base` double unsigned NOT NULL, + `attr_mod` double unsigned NOT NULL, + -- + PRIMARY KEY (`char_id`, `attr_id`), + FOREIGN KEY (`char_id`) + REFERENCES `mana_characters` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB +DEFAULT CHARSET=utf8; + +-- -- table: `mana_char_skills` -- CREATE TABLE IF NOT EXISTS `mana_char_skills` ( @@ -169,6 +178,21 @@ DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; -- +-- table: `mana_char_equips` +-- +CREATE TABLE IF NOT EXISTS `mana_char_equips` ( + id int(10) unsigned NOT NULL auto_increment, + owner_id int(10) unsigned NOT NULL, + slot_type tinyint(3) unsigned NOT NULL, + inventory_slot tinyint(3) unsigned NOT NULL, + -- + PRIMARY KEY (`id`), + UNIQUE KEY `owner_id` (`owner_id`, ) + FOREIGN KEY (owner_id) REFERENCES mana_characters(id) +) ENGINE=InnoDB +DEFAULT CHARSET=utf8; + +-- -- table: `mana_inventories` -- todo: remove class_id and amount and reference on mana_item_instances -- @@ -396,7 +420,7 @@ AUTO_INCREMENT=0 ; INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, NOW()); INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, NOW()); -INSERT INTO mana_world_states VALUES('database_version', NULL,'10', NOW()); +INSERT INTO mana_world_states VALUES('database_version', NULL,'11', NOW()); -- all known transaction codes diff --git a/src/sql/mysql/updates/update_10_to_11.sql b/src/sql/mysql/updates/update_10_to_11.sql new file mode 100644 index 00000000..66922f14 --- /dev/null +++ b/src/sql/mysql/updates/update_10_to_11.sql @@ -0,0 +1,49 @@ +-- +-- Modify the table `mana_characters` to remove the no longer used columns. +-- Note that this is not an intelligent update script at the moment - the +-- values that were stored here are not currently being transferred +-- into their replacement structures. +-- + +ALTER TABLE `mana_char_attr` DROP `money`; +ALTER TABLE `mana_char_attr` DROP `str`; +ALTER TABLE `mana_char_attr` DROP `agi`; +ALTER TABLE `mana_char_attr` DROP `vit`; +ALTER TABLE `mana_char_attr` DROP `int`; +ALTER TABLE `mana_char_attr` DROP `dex`; +ALTER TABLE `mana_char_attr` DROP `will`; + + +-- +-- Create table: `mana_char_attr` +-- + +CREATE TABLE IF NOT EXISTS `mana_char_attr` ( + `char_id` int(10) unsigned NOT NULL, + `attr_id` int(10) unsigned NOT NULL, + `attr_base` double unsigned NOT NULL, + `attr_mod` double unsigned NOT NULL, + -- + PRIMARY KEY (`char_id`, `attr_id`), + FOREIGN KEY (`char_id`) + REFERENCES `mana_characters` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB +DEFAULT CHARSET=utf8; + +-- +-- table: `mana_char_equips` +-- +CREATE TABLE IF NOT EXISTS `mana_char_equips` ( + id int(10) unsigned NOT NULL auto_increment, + owner_id int(10) unsigned NOT NULL, + slot_type tinyint(3) unsigned NOT NULL, + inventory_slot tinyint(3) unsigned NOT NULL, + -- + PRIMARY KEY (`id`), + UNIQUE KEY `owner_id` (`owner_id`, ) + FOREIGN KEY (owner_id) REFERENCES mana_characters(id) +) ENGINE=InnoDB +DEFAULT CHARSET=utf8; + +UPDATE mana_world_states SET value = '11', moddate = UNIX_TIMESTAMP() WHERE state_name = 'database_version'; diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index 41478603..0345b9eb 100644 --- a/src/sql/sqlite/createTables.sql +++ b/src/sql/sqlite/createTables.sql @@ -52,16 +52,9 @@ CREATE TABLE mana_characters level INTEGER NOT NULL, char_pts INTEGER NOT NULL, correct_pts INTEGER NOT NULL, - money INTEGER NOT NULL, x INTEGER NOT NULL, y INTEGER NOT NULL, map_id INTEGER NOT NULL, - str INTEGER NOT NULL, - agi INTEGER NOT NULL, - dex INTEGER NOT NULL, - vit INTEGER NOT NULL, - int INTEGER NOT NULL, - will INTEGER NOT NULL, -- FOREIGN KEY (user_id) REFERENCES mana_accounts(id) ); @@ -71,6 +64,20 @@ CREATE UNIQUE INDEX mana_characters_name ON mana_characters ( name ); ----------------------------------------------------------------------------- +CREATE TABLE mana_char_attr +( + char_id INTEGER NOT NULL, + attr_id INTEGER NOT NULL, + attr_base FLOAT NOT NULL, + attr_mod FLOAT NOT NULL, + -- + FOREIGN KEY (char_id) REFERENCES mana_characters(id) +); + +CREATE INDEX mana_char_attr_char ON mana_char_attr ( char_id ); + +----------------------------------------------------------------------------- + CREATE TABLE mana_char_skills ( char_id INTEGER NOT NULL, @@ -165,6 +172,18 @@ CREATE INDEX mana_item_attributes_item ON mana_item_attributes ( item_id ); ----------------------------------------------------------------------------- +CREATE TABLE mana_char_equips +( + id INTEGER PRIMARY KEY, + owner_id INTEGER NOT NULL, + slot_type INTEGER NOT NULL, + inventory_slot INTEGER NOT NULL, + -- + FOREIGN KEY (owner_id) REFERENCES mana_characters(id) +); + +----------------------------------------------------------------------------- + -- todo: remove class_id and amount and reference on mana_item_instances CREATE TABLE mana_inventories ( @@ -386,7 +405,7 @@ AS INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, strftime('%s','now')); INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, strftime('%s','now')); -INSERT INTO mana_world_states VALUES('database_version', NULL,'10', strftime('%s','now')); +INSERT INTO mana_world_states VALUES('database_version', NULL,'11', strftime('%s','now')); -- all known transaction codes diff --git a/src/utils/logger.cpp b/src/utils/logger.cpp index a8d72415..71f92a88 100644 --- a/src/utils/logger.cpp +++ b/src/utils/logger.cpp @@ -107,9 +107,15 @@ void Logger::output(const std::string &msg, Level atVerbosity) { static const char *prefixes[] = { +#ifdef T_COL_LOG + "[\033[45mFTL\033[0m]", + "[\033[41mERR\033[0m]", + "[\033[43mWRN\033[0m]", +#else "[FTL]", "[ERR]", "[WRN]", +#endif "[INF]", "[DBG]" }; diff --git a/src/utils/speedconv.cpp b/src/utils/speedconv.cpp new file mode 100644 index 00000000..14d328fd --- /dev/null +++ b/src/utils/speedconv.cpp @@ -0,0 +1,31 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "utils/speedconv.hpp" + +double utils::tpsToSpeed(double tps) +{ + return (32000 / (tps * DEFAULT_TILE_LENGTH)); +} + +double utils::speedToTps(double speed) +{ + return (32000 / (speed * DEFAULT_TILE_LENGTH)); +} diff --git a/src/utils/speedconv.hpp b/src/utils/speedconv.hpp new file mode 100644 index 00000000..6a29d0f3 --- /dev/null +++ b/src/utils/speedconv.hpp @@ -0,0 +1,44 @@ +/* + * The Mana Server + * Copyright (C) 2004-2010 The Mana World Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEEDCONV_HPP +#define SPEEDCONV_HPP + +// Simple helper functions for converting between tiles per +// second and the internal speed representation + +#include "defines.h" + +namespace utils { + /** + * tpsToSpeed() + * @param tps The speed value in tiles per second + * @returns The speed value in the internal representation + */ + double tpsToSpeed(double); + /** + * speedToTps() + * @param speed The speed value in the internal representation + * @returns The speed value in tiles per second + */ + double speedToTps(double); +} + +#endif // SPEEDCONV_HPP |