From 98cdcb1de4f422255aa5ef924042ae7d00a5b968 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Mon, 17 May 2010 20:55:06 +1200 Subject: New attribute system and major changes to many low-level areas. Attribute system: Structure is no longer completely hardcoded. Attributes and structure is defined by new xml file (defaulting to stats.xml) Structure defines non-base modifications to an attribute, to be used by modifiers from items, effects, etc. Calculating the base value for core attributes is still done in C++ (and for such fundamental elements the only reason I can think of to do it any other way is perhaps being able to quickly change scripts without a compile could be useful for testing, but such things are a low priority anyway) Item structure: Modifiers are now through triggers rather than single events. This also removes hardcoded types - an item could be both able to be equipped and be able to be activated. Item activation no longer consumes by default, this must be specified by the property inside the trigger. Currently only attribute modifications, autoattacks, and consumes are defined as effects, but stubs for others do exist. Autoattacks are currently non-functional, and this should be rectified with some urgency. Auto Attacks: AutoAttacks are now separate entities, though not fully complete, nor fully integrated with all beings yet. Integration with the Character class is urgent, integration with other Being children less so. When fully integrated this will allow for multiple autoattacks, through equipping multiple items with this as an equip effect or even through other means if needed. Equipment structure: As ItemClass types are no longer hardcoded, so too are equip types. An item have multiple ways to be equipped across multiple equipment slots with any number in each slot. Character maximums are global but configurable. Miscellaneous: Speed, money, and weight are now attributes. Some managers have been changed into classes such that their associated classes can have them as friends, to avoid (ab)use of public accessors. The serialise procedure should also be set as a friend of Character (both in the account- and game- server) as well; having public accessors returning iterators is simply ridiculous. Some start for such cleanups have been made, but this is not the primary focus here. Significant work will need to be done before this is resolved completely, but the start is there. BuySell::registerPlayerItems() has been completely disabled temporarily. The previous function iterated through equipment, yet in the context I think it is intended to fill items? I have been unable to update this function to fit the modifications made to the Inventory/Equipment/Possessions, as I am unsure what exactly what it should be doing. ItemClass::mSpriteId was previously unused, so had been removed, but I notice that it was used when transmitting equipment to nearby clients. Experimentation showed that this value was never set to anything other than 0, and so has been left out of the ItemManager rewrite. I am not entirely sure what is happening here, but it should be worth looking into at a later time, as I am not sure how equipment appearences would be sent otherwise. --- gameserver.cbp | 8 + src/Makefile.am | 8 + src/account-server/accounthandler.cpp | 81 ++- src/account-server/character.cpp | 4 - src/account-server/character.hpp | 34 +- src/account-server/serverhandler.cpp | 15 +- src/account-server/storage.cpp | 382 ++++++---- src/account-server/storage.hpp | 6 +- src/common/inventorydata.hpp | 18 +- src/defines.h | 103 +-- src/game-server/accountconnection.cpp | 17 +- src/game-server/accountconnection.hpp | 3 + src/game-server/attribute.cpp | 333 +++++++++ src/game-server/attribute.hpp | 178 +++++ src/game-server/attributemanager.cpp | 232 ++++++ src/game-server/attributemanager.hpp | 78 ++ src/game-server/autoattack.cpp | 39 + src/game-server/autoattack.hpp | 100 +++ src/game-server/being.cpp | 241 +++--- src/game-server/being.hpp | 100 +-- src/game-server/buysell.cpp | 25 +- src/game-server/buysell.hpp | 3 + src/game-server/character.cpp | 311 ++++---- src/game-server/character.hpp | 44 +- src/game-server/command.cpp | 13 +- src/game-server/commandhandler.cpp | 19 +- src/game-server/gamehandler.cpp | 31 +- src/game-server/inventory.cpp | 1169 +++++++++++++++--------------- src/game-server/inventory.hpp | 116 ++- src/game-server/item.cpp | 123 +--- src/game-server/item.hpp | 256 +++---- src/game-server/itemmanager.cpp | 374 ++++++---- src/game-server/itemmanager.hpp | 88 ++- src/game-server/main-game.cpp | 16 +- src/game-server/mapreader.cpp | 2 +- src/game-server/monster.cpp | 99 ++- src/game-server/monster.hpp | 20 +- src/game-server/monstermanager.cpp | 88 +-- src/game-server/monstermanager.hpp | 57 +- src/game-server/spawnarea.cpp | 2 +- src/game-server/state.cpp | 35 +- src/game-server/trade.cpp | 17 +- src/game-server/trade.hpp | 1 + src/net/messagein.cpp | 22 + src/net/messagein.hpp | 5 + src/net/messageout.cpp | 23 + src/net/messageout.hpp | 6 + src/protocol.h | 31 +- src/scripting/lua.cpp | 79 +- src/serialize/characterdata.hpp | 64 +- src/sql/mysql/createTables.sql | 42 +- src/sql/mysql/updates/update_9_to_10.sql | 49 ++ src/sql/sqlite/createTables.sql | 35 +- src/utils/logger.cpp | 6 + src/utils/speedconv.cpp | 31 + src/utils/speedconv.hpp | 44 ++ 56 files changed, 3397 insertions(+), 1929 deletions(-) create mode 100644 src/game-server/attribute.cpp create mode 100644 src/game-server/attribute.hpp create mode 100644 src/game-server/attributemanager.cpp create mode 100644 src/game-server/attributemanager.hpp create mode 100644 src/game-server/autoattack.cpp create mode 100644 src/game-server/autoattack.hpp create mode 100644 src/sql/mysql/updates/update_9_to_10.sql create mode 100644 src/utils/speedconv.cpp create mode 100644 src/utils/speedconv.hpp diff --git a/gameserver.cbp b/gameserver.cbp index 6b5d41dd..afb8b73b 100644 --- a/gameserver.cbp +++ b/gameserver.cbp @@ -73,6 +73,12 @@ + + + + + + @@ -162,6 +168,8 @@ + + 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..0acd3f52 100644 --- a/src/account-server/accounthandler.cpp +++ b/src/account-server/accounthandler.cpp @@ -29,6 +29,7 @@ #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" @@ -39,6 +40,10 @@ #include "utils/tokencollector.hpp" #include "utils/tokendispenser.hpp" #include "utils/sha256.h" +#include "utils/string.hpp" +#include "utils/xml.hpp" + +#define DEFAULT_ATTRIBUTEDB_FILE "stats.xml" static void addUpdateHost(MessageOut *msg) { @@ -46,13 +51,18 @@ 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; + 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 +87,9 @@ public: */ TokenCollector mTokenCollector; + static void sendCharacterData(AccountClient &client, int slot, + const Character &ch); + protected: /** * Processes account related messages. @@ -106,14 +119,40 @@ 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_ERROR("Account handler: Could not find " << attrFile << "!"); + return; + } + + XML::Document doc(absPathFile, int()); + node = doc.rootNode(); + + if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) + { + LOG_ERROR("Account handler: " << attrFile + << " is not a valid database file!"); + return; + } + for_each_xml_child_node(attributenode, node) + if (xmlStrEqual(attributenode->name, BAD_CAST "stat")) + if (utils::toupper(XML::getProperty(attributenode, "modifiable", "false")) == "TRUE") + initAttr.push_back(XML::getProperty(attributenode, "id", 0)); // id + } } bool AccountClientHandler::initialize(int port, const std::string &host) { - accountHandler = new AccountHandler; + accountHandler = new AccountHandler(DEFAULT_ATTRIBUTEDB_FILE); LOG_INFO("Account handler started:"); return accountHandler->startListen(port, host); @@ -151,7 +190,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 +202,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 +596,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 +655,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(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 +685,11 @@ 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->setAccount(acc); newCharacter->setLevel(1); newCharacter->setCharacterPoints(0); 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 > 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 mExperience; //!< Skill Experience. std::map mStatusEffects; //!< Status Effects std::map mKillCount; //!< Kill Count @@ -245,6 +260,11 @@ class Character std::vector 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/serverhandler.cpp b/src/account-server/serverhandler.cpp index a6cf21f0..106f582e 100644 --- a/src/account-server/serverhandler.cpp +++ b/src/account-server/serverhandler.cpp @@ -562,10 +562,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 c104b4f7..f427a2a7 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -40,7 +40,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 = "9"; +static const char *SUPPORTED_DB_VERSION = "10"; /* * MySQL specificities: @@ -67,24 +67,26 @@ static const char *SUPPORTED_DB_VERSION = "9"; * 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. @@ -217,7 +219,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 @@ -237,7 +239,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. @@ -285,7 +287,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 } } @@ -345,13 +347,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))); @@ -360,16 +361,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); @@ -398,12 +393,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()) { @@ -465,7 +483,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( + 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 @@ -476,44 +515,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; @@ -553,7 +567,7 @@ Character *Storage::getCharacter(const std::string &name) { mDb->bindValue(1, name); } - return getCharacterBySQL(NULL); + return getCharacterBySQL(0); } /** @@ -671,21 +685,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) << "', " -#if defined(MYSQL_SUPPORT) || defined(POSTGRESQL_SUPPORT) - << "`int` = '" -#else - << "int = '" -#endif - << character->getAttribute(CHAR_ATTR_INTELLIGENCE) << "', " - << "will = '" << character->getAttribute(CHAR_ATTR_WILLPOWER) << "' " + << "map_id = '" << character->getMapId() << "' " << "where id = '" << character->getDatabaseID() << "';"; mDb->execSql(sqlUpdateCharacterInfo.str()); @@ -701,14 +703,35 @@ bool Storage::updateCharacter(Character *character, return false; } + /** + * 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::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); } @@ -720,7 +743,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; } @@ -743,7 +766,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; } /** @@ -777,7 +800,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; } @@ -810,36 +833,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; } } @@ -850,7 +876,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; } @@ -874,7 +900,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 @@ -893,7 +919,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) @@ -1005,14 +1031,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, " -#if defined(MYSQL_SUPPORT) || defined(POSTGRESQL_SUPPORT) - << "`int`, " -#else - << "int, " -#endif - << "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() << ", " @@ -1021,30 +1041,33 @@ 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()); + // Update all attributes. + std::map >::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 character ID. (*it)->setDatabaseID(mDb->getLastId()); // update the characters skill std::map::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); - } } } // @@ -1135,31 +1158,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()); } /** @@ -1214,6 +1232,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 @@ -1445,7 +1507,7 @@ std::list Storage::getGuildList() i != members.end(); ++i) { - Character *character = getCharacter((*i).first, NULL); + Character *character = getCharacter((*i).first, 0); if (character) { character->addGuild((*itr)->getName()); @@ -1567,7 +1629,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) @@ -1594,10 +1656,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) @@ -1657,7 +1719,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()); } @@ -1766,7 +1828,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) @@ -1835,7 +1897,7 @@ void Storage::storeLetter(Letter *letter) << letter->getSender()->getDatabaseID() << ", " << letter->getReceiver()->getDatabaseID() << ", " << letter->getExpiry() << ", " - << time(NULL) << ", " + << time(0) << ", " << "?)"; if (mDb->prepareSql(sql.str())) { @@ -1857,7 +1919,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() << "'"; @@ -1905,8 +1967,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); @@ -2089,7 +2151,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 @@ -2119,7 +2181,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 204e6c99..1ae87266 100644 --- a/src/account-server/storage.hpp +++ b/src/account-server/storage.hpp @@ -63,11 +63,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 +#include /** * 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 . + */ + +#include "attribute.hpp" +#include "game-server/being.hpp" +#include "utils/logger.h" +#include + +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::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 &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::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::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::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::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 . + */ + +#ifndef ATTRIBUTE_HPP +#define ATTRIBUTE_HPP + +#include "defines.h" +#include +#include + +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 &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..f62abbc1 --- /dev/null +++ b/src/game-server/attributemanager.cpp @@ -0,0 +1,232 @@ +/* + * 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 . + */ + +#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_ERROR("Attribute Manager: Could not find " << mAttributeReferenceFile << "!"); + return; + } + + XML::Document doc(absPathFile, int()); + node = doc.rootNode(); + + if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) + { + LOG_ERROR("Attribute Manager: " << mAttributeReferenceFile + << " is not a valid database file!"); + 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 >(false , std::vector()); + 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(" "<first<<" : "); + for (std::vector::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 *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 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 . + */ + +#ifndef ATTRIBUTEMANAGER_HPP +#define ATTRIBUTEMANAGER_HPP + +#include +#include +#include + +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 * > 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 *getAttributeInfo(unsigned int) const; + + const AttributeScopes &getAttributeInfoForType(SCOPE_TYPES) const; + + bool isAttributeDirectlyModifiable(unsigned int) const; + + std::pair 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 > > AttributeMap; + // tag name -> { attribute id, layer } + typedef std::map< std::string, std::pair > 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::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it) + it->halt(); + mActive = false; +} + +void AutoAttacks::start() +{ + for (std::list::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it) + it->softReset(); + mActive = true; +} + +void AutoAttacks::tick(std::list *ret) +{ + for (std::list::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 +#include + +/** + * 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 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::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 *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 0e885de3..65b2ac2c 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 "< HP.base) HP.mod = HP.base; - modifiedAttribute(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()); + modifiedAttribute(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); + modifiedAttribute(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); modifiedAttribute(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; - modifiedAttribute(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) + modifiedAttribute(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; - modifiedAttribute(i->attr); - i = mModifiers.erase(i); - continue; - } - ++i; - } + for (AttributeMap::iterator it = mAttributes.begin(); + it != mAttributes.end(); + ++it) + if (it->second.tick()) + modifiedAttribute(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 f8a65c97..96151a39 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 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 @@ -209,8 +162,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. @@ -218,7 +169,6 @@ class Being : public Actor * This function automatically transform it * into millsecond per tile. */ - void setSpeed(float s); /** * Gets the damage list. @@ -236,6 +186,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); /** @@ -268,19 +219,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. @@ -289,17 +253,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 modifiedAttribute(int) {} + virtual void modifiedAttribute(unsigned int) {} /** * Sets a statuseffect on this being @@ -355,7 +318,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. */ @@ -384,12 +348,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 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 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 46ffa05e..e24871c2 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) - { - modifiedAttribute(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::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 attacks; + mAutoAttacks.tick(&attacks); + if (attacks.empty()) return; // Install default attack? + else for (std::list::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; - modifiedAttribute(BASE_ATTR_HP); //warp back to spawn point + mAttributes[ATTR_HP].setBase(mAttributes[ATTR_MAX_HP].getModifiedAttribute()); + modifiedAttribute(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,97 @@ 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::modifiedAttribute(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() +{ + for (AttributeMap::iterator it = mAttributes.begin(), + it_end = mAttributes.end(); + it != it_end; ++it) + modifiedAttribute(it->first); +} + +void Character::modifiedAttribute(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. + if (!mAttributes.count(attr)) return; + double newBase = getAttribute(attr); + + switch (attr) { + case ATTR_STR: + modifiedAttribute(ATTR_INV_CAPACITY); + break; + case ATTR_AGI: + modifiedAttribute(ATTR_DODGE); + break; + case ATTR_VIT: + modifiedAttribute(ATTR_MAX_HP); + modifiedAttribute(ATTR_HP_REGEN); + modifiedAttribute(ATTR_DEFENSE); + break; + case ATTR_INT: + break; + case ATTR_DEX: + modifiedAttribute(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_MAX_HP: + newBase = ((getModifiedAttribute(ATTR_VIT) + 3) * (getModifiedAttribute(ATTR_VIT) + 20)) * 0.125; + break; + case ATTR_MOVE_SPEED_TPS: + newBase = 3.0 + getModifiedAttribute(ATTR_AGI) * 0.08; // Provisional. + modifiedAttribute(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); flagAttribute(attr); } 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 +451,44 @@ 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)) - { - modifiedAttribute(skill); - } - - mRecalculateLevel = true; + // check for skill levelup + if (Character::levelForExp(newExp) >= Character::levelForExp(oldExp)) + { + modifiedAttribute(skill); } + + mRecalculateLevel = true; } void Character::incrementKillCount(int monsterType) @@ -543,7 +524,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 +558,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 +587,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); modifiedAttribute(attribute); return ATTRIBMOD_OK; @@ -618,13 +599,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); modifiedAttribute(attribute); return ATTRIBMOD_OK; diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp index fdee3645..b3e24823 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,19 @@ 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; + void modifiedAllAttribute(); /** - * Over loads Being::getModifiedAttribute - * Charcter skills are treated as extend attributes + * Updates base Being attributes. */ - int getModifiedAttribute(int) const; + void modifiedAttribute(unsigned int); /** - * Updates base Being attributes. + * Generate an autoattack from the given itemID to the AutoAttack instance */ - void modifiedAttribute(int); + void generateAutoAttack(int itemID, AutoAttack *ret); /** * Calls all the "disconnected" listener. @@ -358,12 +358,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 */ @@ -404,7 +398,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 +490,9 @@ class Character : public Being TransactionType mTransaction; /**< Trade/buy/sell action the character is involved in. */ std::map mKillCount; /**< how many monsters the character has slayn 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", " ", "Drops a stack of items on the ground at your current location", &handleDrop}, - {"money", " ", - "Changes the money a character possesses", &handleMoney}, +/* {"money", " ", + "Changes the money a character possesses", &handleMoney},*/ {"spawn", " ", "Creates a number of monsters near your location", &handleSpawn}, {"attribute", " ", @@ -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 30816586..5b60da0e 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->modifiedAttribute(i); - } + character->modifiedAllAttribute(); std::map::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 b90354b7..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 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 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->modifiedAttribute(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 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..95ee973d 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -25,113 +25,60 @@ #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 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::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 ItemClass::useTrigger(Being *itemUser, ItemTriggerType trigger) { - /* 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); - } -} - -void ItemModifiers::cancelAttributes(Being *b) const -{ - 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); - } -} - -ItemClass::~ItemClass() -{ - 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::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 +94,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..6fb7c380 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,68 +58,74 @@ 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); - - /** - * Gets the value associated to a MOD_ATTRIBUTE class, or zero if none. - */ - int getAttributeValue(int attr) const; +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) {} - /** - * Sets the value associated to a MOD_ATTRIBUTE class. - */ - void setAttributeValue(int attr, int amount); + bool apply(Being *itemUser); + void dispell(Being *itemUser); - /** - * Applies all the attribute modifiers to a given Being. - */ - void applyAttributes(Being *) const; + private: + unsigned int mAttributeId; + unsigned int mAttributeLayer; + double mMod; + unsigned int mDuration; + unsigned int mId; +}; - /** - * Cancels all the applied modifiers to a given Being. - * Only meant for equipment. - */ - void cancelAttributes(Being *) const; +class ItemEffectAutoAttack : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser); + void dispell(Being *itemUser); +}; - private: - std::vector< ItemModifier > mModifiers; +class ItemEffectConsumes : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser) { return true; } + void dispell(Being *itemUser) {} }; /** @@ -151,23 +134,17 @@ class ItemModifiers 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. @@ -175,47 +152,20 @@ class ItemClass int getWeight() const { 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. @@ -223,50 +173,74 @@ class ItemClass int getDatabaseID() const { 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 c1750443..363ada4b 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 #include -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 " << 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; + } + + 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 + (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 " << itemReferenceFile << "!"); + LOG_ERROR("Item Manager: Could not find " << mItemReferenceFile << "!"); return; } - XML::Document doc(absPathFile, false); - xmlNodePtr rootNode = doc.rootNode(); + XML::Document doc2(absPathFile, int()); + rootNode = doc2.rootNode(); if (!rootNode || !xmlStrEqual(rootNode->name, BAD_CAST "items")) { @@ -62,149 +118,179 @@ 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 << ": Unknown weapon type \"" - << "\" for item #" << id << - " - treating it as generic item"); - } else { - weaponType = SkillManager::getIdFromString(strWeaponType); + 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); } - 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 #"< 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 > + 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 >::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 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); + } } + // 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 " @@ -220,13 +306,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 +#include +#include 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 0901ce16..405ec68f 100644 --- a/src/game-server/main-game.cpp +++ b/src/game-server/main-game.cpp @@ -39,6 +39,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" @@ -63,7 +64,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 "stats.xml" #define DEFAULT_MAPSDB_FILE "maps.xml" #define DEFAULT_MONSTERSDB_FILE "monsters.xml" #define DEFAULT_STATUSDB_FILE "mana-status-effect.xml" @@ -79,6 +82,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; @@ -180,9 +187,10 @@ void initialize() 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); // Initialize global event script @@ -237,8 +245,8 @@ void deinitialize() // 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 80a0646d..83a73065 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 ffb178d2..456c4c8b 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 @@ -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..86a69ac8 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), @@ -105,15 +104,9 @@ class MonsterClass /** * Returns a being base attribute. */ - int getAttribute(size_t attribute) const + int getAttribute(unsigned 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 mAttributes; /**< Base attributes of the monster. */ + std::map 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 - -typedef std::map< int, MonsterClass * > MonsterClasses; -static MonsterClasses monsterClasses; /**< Monster reference */ -static std::string monsterReferenceFile; - Element elementFromString (const std::string &name) { static std::map 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 #"<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 - +#include 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 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::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 #include #include +#ifndef USE_NATIVE_DOUBLE +#include +#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 #include #include +#ifndef USE_NATIVE_DOUBLE +#include +#include +#endif #include #include @@ -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 @@ -55,6 +55,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. */ 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 2d9562ed..72456789 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -49,6 +49,7 @@ extern "C" { #include "scripting/luautil.hpp" #include "scripting/luascript.hpp" #include "utils/logger.h" +#include "utils/speedconv.hpp" #include @@ -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) @@ -432,7 +427,7 @@ static int chr_inv_count(lua_State *s) return 0; } int id = lua_tointeger(s, i); - int nb = id ? inv.count(id) : q->getPossessions().money; + int nb = id ? inv.count(id) : 0; lua_pushinteger(s, nb); } return nb_items; @@ -626,38 +621,6 @@ static int being_set_status_time(lua_State *s) return 1; } -/** -* 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) -{ - if (!lua_isuserdata(s, 1) || !lua_isnumber(s, 2)) - { - raiseScriptError(s, "being_set_speed called with incorrect parameters."); - return 0; - } - Being *being = getBeing(s, 1); - being->setSpeed(lua_tonumber(s, 2)); - return 1; -} - /** * Returns the Thing type of the given Being @@ -698,7 +661,11 @@ static int being_walk(lua_State *s) being->setDestination(destination); 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; } @@ -741,14 +708,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; } @@ -965,7 +930,7 @@ static int monster_create(lua_State *s) } int monsterId = lua_tointeger(s, 1); - MonsterClass *spec = MonsterManager::getMonster(monsterId); + MonsterClass *spec = monsterManager->getMonster(monsterId); if (!spec) { raiseScriptError(s, "monster_create called with invalid monster ID: %d", monsterId); @@ -1649,7 +1614,7 @@ static int item_drop(lua_State *s) number = lua_tointeger(s, 4); } - ItemClass *ic = ItemManager::getItem(type); + ItemClass *ic = itemManager->getItem(type); if (!ic) { raiseScriptError(s, "item_drop called with unknown item ID"); @@ -1734,8 +1699,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 a3b37ce0..03129815 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`), @@ -59,6 +51,23 @@ CREATE TABLE IF NOT EXISTS `mana_characters` ( 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` -- @@ -168,6 +177,21 @@ CREATE TABLE IF NOT EXISTS `mana_item_attributes` ( 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,'9', NOW()); +INSERT INTO mana_world_states VALUES('database_version', NULL,'10', NOW()); -- all known transaction codes diff --git a/src/sql/mysql/updates/update_9_to_10.sql b/src/sql/mysql/updates/update_9_to_10.sql new file mode 100644 index 00000000..5bb722a8 --- /dev/null +++ b/src/sql/mysql/updates/update_9_to_10.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 = '10', moddate = UNIX_TIMESTAMP() WHERE state_name = 'database_version'; diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index bc5eefba..94aabdad 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,'9', strftime('%s','now')); +INSERT INTO mana_world_states VALUES('database_version', NULL,'10', 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 . + */ + +#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 . + */ + +#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 -- cgit v1.2.3-70-g09d2 From 151f6acdee17556d249e1b61f264ed2e95b84354 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Sat, 10 Jul 2010 21:45:38 +1200 Subject: Add in stubs for scripted item effects --- src/game-server/item.cpp | 11 +++++++++++ src/game-server/item.hpp | 8 ++++++++ src/game-server/itemmanager.cpp | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp index 95ee973d..93b2bc10 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -63,6 +63,17 @@ void ItemEffectAutoAttack::dispell(Being *itemUser) // TODO } +bool ItemEffectScript::apply(Being *itemUser) +{ + // TODO + return false; +} + +void ItemEffectScript::dispell(Being *itemUser) +{ + // TODO +} + bool ItemClass::useTrigger(Being *itemUser, ItemTriggerType trigger) { if (!trigger) return false; diff --git a/src/game-server/item.hpp b/src/game-server/item.hpp index 6fb7c380..3ccfe2bf 100644 --- a/src/game-server/item.hpp +++ b/src/game-server/item.hpp @@ -128,6 +128,14 @@ class ItemEffectConsumes : public ItemEffectInfo void dispell(Being *itemUser) {} }; +class ItemEffectScript : public ItemEffectInfo +{ + public: + bool apply(Being *itemUser); + void dispell(Being *itemUser); +}; + + /** * Class for simple reference to item information. */ diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index 363ada4b..802197d6 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -286,6 +286,28 @@ void ItemManager::reload() } 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 -- cgit v1.2.3-70-g09d2 From 499352c94a5926587c6974d96cbe12b0f3420e83 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Fri, 30 Jul 2010 13:18:57 +0200 Subject: Made the game-server compile again with CMake. --- src/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b7c06412..5c95d901 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -189,6 +189,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 @@ -250,6 +256,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 -- cgit v1.2.3-70-g09d2 From 9b30cad049d8f3ac9c63290331e73515d51c0260 Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Fri, 30 Jul 2010 13:33:13 +0200 Subject: Fixed a little mistake done in Monster::setAttribute. It was using at() for setting. Also I changed the unsigned int to int as the default returned was -1 in the MonsterManager::reload() function. --- src/game-server/monster.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game-server/monster.hpp b/src/game-server/monster.hpp index 86a69ac8..0d5d9688 100644 --- a/src/game-server/monster.hpp +++ b/src/game-server/monster.hpp @@ -98,13 +98,13 @@ 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(unsigned int attribute) const + double getAttribute(int attribute) const { return mAttributes.at(attribute); } /** Sets collision circle radius. */ @@ -181,7 +181,7 @@ class MonsterClass private: unsigned short mID; MonsterDrops mDrops; - std::map mAttributes; /**< Base attributes of the monster. */ + std::map mAttributes; /**< Base attributes of the monster. */ float mSpeed; /**< The monster class speed in tiles per second */ int mSize; int mExp; -- cgit v1.2.3-70-g09d2 From 5f4936e70b92a625f54d791debadf65d8fa3a02c Mon Sep 17 00:00:00 2001 From: Yohann Ferreira Date: Mon, 2 Aug 2010 22:59:34 +0200 Subject: Changed the stat file name to attributes.xml. I also made it required to start properly since it's now the case. Reviewed-by: Jaxad0127. --- src/account-server/accounthandler.cpp | 13 +++++++------ src/account-server/accounthandler.hpp | 3 ++- src/account-server/main-account.cpp | 10 ++++++---- src/game-server/attributemanager.cpp | 6 ++++-- src/game-server/main-game.cpp | 2 +- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp index 0acd3f52..af04119a 100644 --- a/src/account-server/accounthandler.cpp +++ b/src/account-server/accounthandler.cpp @@ -43,8 +43,6 @@ #include "utils/string.hpp" #include "utils/xml.hpp" -#define DEFAULT_ATTRIBUTEDB_FILE "stats.xml" - static void addUpdateHost(MessageOut *msg) { std::string updateHost = Configuration::getValue("defaultUpdateHost", ""); @@ -130,7 +128,8 @@ AccountHandler::AccountHandler(const std::string &attrFile): absPathFile = ResourceManager::resolve(attrFile); if (absPathFile.empty()) { - LOG_ERROR("Account handler: Could not find " << attrFile << "!"); + LOG_FATAL("Account handler: Could not find " << attrFile << "!"); + exit(3); return; } @@ -139,8 +138,9 @@ AccountHandler::AccountHandler(const std::string &attrFile): if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) { - LOG_ERROR("Account handler: " << attrFile + LOG_FATAL("Account handler: " << attrFile << " is not a valid database file!"); + exit(3); return; } for_each_xml_child_node(attributenode, node) @@ -150,9 +150,10 @@ AccountHandler::AccountHandler(const std::string &attrFile): } } -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(DEFAULT_ATTRIBUTEDB_FILE); + accountHandler = new AccountHandler(configFile); LOG_INFO("Account handler started:"); return accountHandler->startListen(port, host); 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/main-account.cpp b/src/account-server/main-account.cpp index e43f1a65..a059c38e 100644 --- a/src/account-server/main-account.cpp +++ b/src/account-server/main-account.cpp @@ -50,9 +50,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 */ @@ -339,7 +340,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/game-server/attributemanager.cpp b/src/game-server/attributemanager.cpp index f62abbc1..817eb6d6 100644 --- a/src/game-server/attributemanager.cpp +++ b/src/game-server/attributemanager.cpp @@ -43,7 +43,8 @@ void AttributeManager::reload() absPathFile = ResourceManager::resolve(mAttributeReferenceFile); if (absPathFile.empty()) { - LOG_ERROR("Attribute Manager: Could not find " << mAttributeReferenceFile << "!"); + LOG_FATAL("Attribute Manager: Could not find " << mAttributeReferenceFile << "!"); + exit(3); return; } @@ -52,8 +53,9 @@ void AttributeManager::reload() if (!node || !xmlStrEqual(node->name, BAD_CAST "stats")) { - LOG_ERROR("Attribute Manager: " << mAttributeReferenceFile + LOG_FATAL("Attribute Manager: " << mAttributeReferenceFile << " is not a valid database file!"); + exit(3); return; } diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp index 405ec68f..5c146dea 100644 --- a/src/game-server/main-game.cpp +++ b/src/game-server/main-game.cpp @@ -66,7 +66,7 @@ using utils::Logger; #define DEFAULT_ITEMSDB_FILE "items.xml" #define DEFAULT_EQUIPDB_FILE "equip.xml" #define DEFAULT_SKILLSDB_FILE "mana-skills.xml" -#define DEFAULT_ATTRIBUTEDB_FILE "stats.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" -- cgit v1.2.3-70-g09d2 From 6e1d5a811339a75f80ebb66a697c16ef0e9a7e67 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Tue, 13 Jul 2010 00:09:17 +1200 Subject: Allow default values for attributes at character creation time. TODO: The game-server also needs to keep track of this for when new attributes or attributes not in the default scope need to be created. Also hopefully fix attribute calculation order for derived attributes. Still hardcoded for now. Reviewed-by: Bertram. --- src/account-server/accounthandler.cpp | 24 +++++++++++++++++++++++- src/account-server/serverhandler.cpp | 1 + src/game-server/character.cpp | 24 +++++++++++++++++------- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp index af04119a..99faad62 100644 --- a/src/account-server/accounthandler.cpp +++ b/src/account-server/accounthandler.cpp @@ -35,6 +35,7 @@ #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" @@ -54,6 +55,15 @@ static void addUpdateHost(MessageOut *msg) 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: @@ -145,8 +155,19 @@ AccountHandler::AccountHandler(const std::string &attrFile): } 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(XML::getProperty(attributenode, "id", 0)); // id + 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()(defStr); + defAttr.insert(std::make_pair(id, std::make_pair(val, val))); + } + } } } @@ -691,6 +712,7 @@ void AccountHandler::handleCharacterCreateMessage(AccountClient &client, Message (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/serverhandler.cpp b/src/account-server/serverhandler.cpp index 106f582e..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; } diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index e24871c2..ae1ba37e 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -345,6 +345,7 @@ void Character::sendStatus() 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) @@ -356,25 +357,29 @@ void Character::modifiedAttribute(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: - modifiedAttribute(ATTR_INV_CAPACITY); + deps.insert(ATTR_INV_CAPACITY); break; case ATTR_AGI: - modifiedAttribute(ATTR_DODGE); + deps.insert(ATTR_DODGE); break; case ATTR_VIT: - modifiedAttribute(ATTR_MAX_HP); - modifiedAttribute(ATTR_HP_REGEN); - modifiedAttribute(ATTR_DEFENSE); + deps.insert(ATTR_MAX_HP); + deps.insert(ATTR_HP_REGEN); + deps.insert(ATTR_DEFENSE); break; case ATTR_INT: break; case ATTR_DEX: - modifiedAttribute(ATTR_ACCURACY); + deps.insert(ATTR_ACCURACY); break; case ATTR_WIL: break; @@ -410,7 +415,7 @@ void Character::modifiedAttribute(unsigned int attr) break; case ATTR_MOVE_SPEED_TPS: newBase = 3.0 + getModifiedAttribute(ATTR_AGI) * 0.08; // Provisional. - modifiedAttribute(ATTR_MOVE_SPEED_RAW); + deps.insert(ATTR_MOVE_SPEED_RAW); break; case ATTR_MOVE_SPEED_RAW: newBase = utils::tpsToSpeed(getModifiedAttribute(ATTR_MOVE_SPEED_TPS)); @@ -423,7 +428,12 @@ void Character::modifiedAttribute(unsigned int attr) if (newBase != getAttribute(attr)) Being::setAttribute(attr, newBase, false); + else + LOG_DEBUG("No changes to sync."); flagAttribute(attr); + for (std::set::const_iterator it = deps.begin(), + it_end = deps.end(); it != it_end; ++it) + modifiedAttribute(*it); } void Character::flagAttribute(int attr) -- cgit v1.2.3-70-g09d2 From 3f0b8b1ba23854aa7e5b23ab6f9d9c4768dafe88 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Tue, 13 Jul 2010 00:17:43 +1200 Subject: Add sanity checks for hp when hp or max hp change. Reviewed-by: Bertram. --- src/game-server/character.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index ae1ba37e..c7561945 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -410,8 +410,14 @@ void Character::modifiedAttribute(unsigned int attr) 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. -- cgit v1.2.3-70-g09d2 From a822f8f257aaec37174a11add3543ef70a29e206 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Sat, 14 Aug 2010 13:25:09 +1200 Subject: Set the database ID of a new character immediately after creating it. (Ooops.) --- src/account-server/storage.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index f427a2a7..e740b103 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -1048,6 +1048,9 @@ void Storage::flush(Account *account) mDb->execSql(sqlInsertCharactersTable.str()); + // Update the character ID. + (*it)->setDatabaseID(mDb->getLastId()); + // Update all attributes. std::map >::const_iterator attr_it, attr_end; @@ -1059,9 +1062,6 @@ void Storage::flush(Account *account) attr_it->second.first, attr_it->second.second); - // Update the character ID. - (*it)->setDatabaseID(mDb->getLastId()); - // update the characters skill std::map::const_iterator skill_it; for (skill_it = (*it)->mExperience.begin(); -- cgit v1.2.3-70-g09d2 From 7db9f6fe36b737d2eec7c6070497035b0834def2 Mon Sep 17 00:00:00 2001 From: Freeyorp Date: Wed, 18 Aug 2010 23:23:12 +1200 Subject: Change references from database version 10 to version 11 to reflect changes made to mainline --- src/account-server/storage.cpp | 2 +- src/sql/mysql/createTables.sql | 2 +- src/sql/mysql/updates/update_10_to_11.sql | 49 +++++++++++++++++++++++++++++++ src/sql/mysql/updates/update_9_to_10.sql | 49 ------------------------------- src/sql/sqlite/createTables.sql | 2 +- 5 files changed, 52 insertions(+), 52 deletions(-) create mode 100644 src/sql/mysql/updates/update_10_to_11.sql delete mode 100644 src/sql/mysql/updates/update_9_to_10.sql diff --git a/src/account-server/storage.cpp b/src/account-server/storage.cpp index e740b103..71a78359 100644 --- a/src/account-server/storage.cpp +++ b/src/account-server/storage.cpp @@ -40,7 +40,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: diff --git a/src/sql/mysql/createTables.sql b/src/sql/mysql/createTables.sql index 03129815..a76cc337 100644 --- a/src/sql/mysql/createTables.sql +++ b/src/sql/mysql/createTables.sql @@ -420,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/mysql/updates/update_9_to_10.sql b/src/sql/mysql/updates/update_9_to_10.sql deleted file mode 100644 index 5bb722a8..00000000 --- a/src/sql/mysql/updates/update_9_to_10.sql +++ /dev/null @@ -1,49 +0,0 @@ --- --- 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 = '10', moddate = UNIX_TIMESTAMP() WHERE state_name = 'database_version'; diff --git a/src/sql/sqlite/createTables.sql b/src/sql/sqlite/createTables.sql index 94aabdad..8c57e45d 100644 --- a/src/sql/sqlite/createTables.sql +++ b/src/sql/sqlite/createTables.sql @@ -405,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 -- cgit v1.2.3-70-g09d2