summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFreeyorp <Freeyorp101@hotmail.com>2010-05-17 20:55:06 +1200
committerFreeyorp <Freeyorp101@hotmail.com>2010-07-10 21:51:07 +1200
commit98cdcb1de4f422255aa5ef924042ae7d00a5b968 (patch)
tree1746776580502fb007581f171fa89638ab6bc64f
parent26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2 (diff)
downloadmanaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.gz
manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.xz
manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.zip
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 <consumes /> 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.
-rw-r--r--gameserver.cbp8
-rw-r--r--src/Makefile.am8
-rw-r--r--src/account-server/accounthandler.cpp81
-rw-r--r--src/account-server/character.cpp4
-rw-r--r--src/account-server/character.hpp34
-rw-r--r--src/account-server/serverhandler.cpp15
-rw-r--r--src/account-server/storage.cpp382
-rw-r--r--src/account-server/storage.hpp6
-rw-r--r--src/common/inventorydata.hpp18
-rw-r--r--src/defines.h103
-rw-r--r--src/game-server/accountconnection.cpp17
-rw-r--r--src/game-server/accountconnection.hpp3
-rw-r--r--src/game-server/attribute.cpp333
-rw-r--r--src/game-server/attribute.hpp178
-rw-r--r--src/game-server/attributemanager.cpp232
-rw-r--r--src/game-server/attributemanager.hpp78
-rw-r--r--src/game-server/autoattack.cpp39
-rw-r--r--src/game-server/autoattack.hpp100
-rw-r--r--src/game-server/being.cpp241
-rw-r--r--src/game-server/being.hpp100
-rw-r--r--src/game-server/buysell.cpp25
-rw-r--r--src/game-server/buysell.hpp3
-rw-r--r--src/game-server/character.cpp311
-rw-r--r--src/game-server/character.hpp44
-rw-r--r--src/game-server/command.cpp13
-rw-r--r--src/game-server/commandhandler.cpp19
-rw-r--r--src/game-server/gamehandler.cpp31
-rw-r--r--src/game-server/inventory.cpp1169
-rw-r--r--src/game-server/inventory.hpp116
-rw-r--r--src/game-server/item.cpp123
-rw-r--r--src/game-server/item.hpp256
-rw-r--r--src/game-server/itemmanager.cpp374
-rw-r--r--src/game-server/itemmanager.hpp88
-rw-r--r--src/game-server/main-game.cpp16
-rw-r--r--src/game-server/mapreader.cpp2
-rw-r--r--src/game-server/monster.cpp99
-rw-r--r--src/game-server/monster.hpp20
-rw-r--r--src/game-server/monstermanager.cpp88
-rw-r--r--src/game-server/monstermanager.hpp57
-rw-r--r--src/game-server/spawnarea.cpp2
-rw-r--r--src/game-server/state.cpp35
-rw-r--r--src/game-server/trade.cpp17
-rw-r--r--src/game-server/trade.hpp1
-rw-r--r--src/net/messagein.cpp22
-rw-r--r--src/net/messagein.hpp5
-rw-r--r--src/net/messageout.cpp23
-rw-r--r--src/net/messageout.hpp6
-rw-r--r--src/protocol.h31
-rw-r--r--src/scripting/lua.cpp79
-rw-r--r--src/serialize/characterdata.hpp64
-rw-r--r--src/sql/mysql/createTables.sql42
-rw-r--r--src/sql/mysql/updates/update_9_to_10.sql49
-rw-r--r--src/sql/sqlite/createTables.sql35
-rw-r--r--src/utils/logger.cpp6
-rw-r--r--src/utils/speedconv.cpp31
-rw-r--r--src/utils/speedconv.hpp44
56 files changed, 3397 insertions, 1929 deletions
diff --git a/gameserver.cbp b/gameserver.cbp
index 6b5d41d..afb8b73 100644
--- a/gameserver.cbp
+++ b/gameserver.cbp
@@ -73,6 +73,12 @@
<Unit filename="src\game-server\accountconnection.hpp" />
<Unit filename="src\game-server\actor.cpp" />
<Unit filename="src\game-server\actor.hpp" />
+ <Unit filename="src\game-server\attribute.cpp" />
+ <Unit filename="src\game-server\attribute.hpp" />
+ <Unit filename="src\game-server\attributemanager.cpp" />
+ <Unit filename="src\game-server\attributemanager.hpp" />
+ <Unit filename="src\game-server\autoattack.cpp" />
+ <Unit filename="src\game-server\autoattack.hpp" />
<Unit filename="src\game-server\being.cpp" />
<Unit filename="src\game-server\being.hpp" />
<Unit filename="src\game-server\buysell.cpp" />
@@ -162,6 +168,8 @@
<Unit filename="src\utils\string.hpp" />
<Unit filename="src\utils\stringfilter.cpp" />
<Unit filename="src\utils\stringfilter.h" />
+ <Unit filename="src\utils\speedconv.cpp" />
+ <Unit filename="src\utils\speedconv.hpp">
<Unit filename="src\utils\timer.cpp" />
<Unit filename="src\utils\timer.h" />
<Unit filename="src\utils\tokencollector.cpp" />
diff --git a/src/Makefile.am b/src/Makefile.am
index ff53714..19390cb 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 002803e..0acd3f5 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<AccountHandler, AccountClient *, int> 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<unsigned int>(initAttr.size(), 0);
+ for (unsigned int i = 0; i < initAttr.size(); ++i)
attributes[i] = msg.readShort();
unsigned int totalAttributes = 0;
bool validNonZeroAttributes = true;
- for (int i = 0; i < CHAR_ATTR_NB; ++i)
+ for (unsigned int i = 0; i < initAttr.size(); ++i)
{
// For good total attributes check.
- totalAttributes += attributes[i];
+ totalAttributes += attributes.at(i);
// For checking if all stats are at least > 0
if (attributes[i] <= 0) validNonZeroAttributes = false;
@@ -641,8 +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 e2a18f3..b691819 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 d96413d..f9114c7 100644
--- a/src/account-server/character.hpp
+++ b/src/account-server/character.hpp
@@ -31,6 +31,9 @@
class Account;
class MessageIn;
+class MessageOut;
+
+typedef std::map< unsigned int, std::pair<double, double> > AttributeMap;
/** placeholder type needed for include compatibility with game server*/
typedef void Special;
@@ -101,13 +104,12 @@ class Character
int getLevel() const { return mLevel; }
void setLevel(int level) { mLevel = level; }
- /** Gets the value of a base attribute of the character. */
- int getAttribute(int n) const
- { return mAttributes[n - CHAR_ATTR_BEGIN]; }
-
/** Sets the value of a base attribute of the character. */
- void setAttribute(int n, int value)
- { mAttributes[n - CHAR_ATTR_BEGIN] = value; }
+ void setAttribute(unsigned int id, double value)
+ { mAttributes[id].first = value; }
+
+ void setModAttribute(unsigned int id, double value)
+ { mAttributes[id].second = value; }
int getSkillSize() const
{ return mExperience.size(); }
@@ -220,16 +222,29 @@ class Character
private:
+
Character(const Character &);
Character &operator=(const Character &);
+ double getAttrBase(AttributeMap::const_iterator &it) const
+ { return it->second.first; }
+ double getAttrMod(AttributeMap::const_iterator &it) const
+ { return it->second.second; }
+
Possessions mPossessions; //!< All the possesions of the character.
std::string mName; //!< Name of the character.
int mDatabaseID; //!< Character database ID.
int mAccountID; //!< Account ID of the owner.
Account *mAccount; //!< Account owning the character.
Point mPos; //!< Position the being is at.
- unsigned short mAttributes[CHAR_ATTR_NB]; //!< Attributes.
+ /**
+ * Stores attributes.
+ * The key is an unsigned int which is the id of the attribute.
+ * The value stores the base value of the attribute in the first part,
+ * and the modified value in the second. The modified value is only
+ * used when transmitting to the client.
+ */
+ AttributeMap mAttributes; //!< Attributes.
std::map<int, int> mExperience; //!< Skill Experience.
std::map<int, int> mStatusEffects; //!< Status Effects
std::map<int, int> mKillCount; //!< Kill Count
@@ -245,6 +260,11 @@ class Character
std::vector<std::string> mGuilds; //!< All the guilds the player
//!< belongs to.
+ friend class AccountHandler;
+ friend class Storage;
+ // Set as a friend, but still a lot of redundant accessors. FIXME.
+ template< class T >
+ friend void serializeCharacterData(const T &data, MessageOut &msg);
};
/**
diff --git a/src/account-server/serverhandler.cpp b/src/account-server/serverhandler.cpp
index a6cf21f..106f582 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 c104b4f..f427a2a 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<unsigned int, unsigned int>(
+ toUint(equipInfo(k, 3)),
+ toUint(equipInfo(k, 2))));
+ }
+ catch (const dal::DbSqlQueryExecFailure &e)
+ {
+ LOG_ERROR("(DALStorage::getCharacter #1) SQL query failure: " << e.what());
+ return 0;
}
try
@@ -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());
@@ -702,13 +704,34 @@ bool Storage::updateCharacter(Character *character,
}
/**
+ * Character attributes.
+ */
+ try
+ {
+ std::ostringstream sqlAttr;
+ for (AttributeMap::const_iterator
+ it = character->mAttributes.begin(),
+ it_end = character->mAttributes.end();
+ it != it_end;
+ ++it)
+ updateAttribute(character->getDatabaseID(), it->first,
+ it->second.first, it->second.second);
+ }
+ catch (const dal::DbSqlQueryExecFailure &e)
+ {
+ if (startTransaction)
+ mDb->rollbackTransaction();
+ LOG_ERROR("(DALStorage::updateCharacter #2) SQL query failure: " << e.what());
+ }
+
+ /**
* Character's skills
*/
try
{
std::map<int, int>::const_iterator skill_it;
- for (skill_it = character->getSkillBegin();
- skill_it != character->getSkillEnd(); skill_it++)
+ for (skill_it = character->mExperience.begin();
+ skill_it != character->mExperience.end(); skill_it++)
{
updateExperience(character->getDatabaseID(), skill_it->first, skill_it->second);
}
@@ -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<unsigned int, std::pair<double, double> >::const_iterator
+ attr_it, attr_end;
+ for (attr_it = (*it)->mAttributes.begin(),
+ attr_end = (*it)->mAttributes.end();
+ attr_it != attr_end;
+ ++attr_it)
+ updateAttribute((*it)->getDatabaseID(), attr_it->first,
+ attr_it->second.first,
+ attr_it->second.second);
+
// Update the character ID.
(*it)->setDatabaseID(mDb->getLastId());
// update the characters skill
std::map<int, int>::const_iterator skill_it;
- for (skill_it = (*it)->getSkillBegin();
- skill_it != (*it)->getSkillEnd(); skill_it++)
- {
+ for (skill_it = (*it)->mExperience.begin();
+ skill_it != (*it)->mExperience.end();
+ skill_it++)
updateExperience((*it)->getDatabaseID(), skill_it->first, skill_it->second);
- }
}
} //
@@ -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());
}
/**
@@ -1215,6 +1233,50 @@ void Storage::updateExperience(int charId, int skillId, int skillValue)
}
/**
+ * Write a modification message about character attributes to the database.
+ * @param charId The Id of the character
+ * @param attrId The Id of the attribute
+ * @param base The base value of the attribute for this character
+ * @param mod The cached modified value for this character.
+ */
+
+void Storage::updateAttribute(int charId, unsigned int attrId,
+ double base, double mod)
+{
+ try {
+ std::ostringstream sql;
+ sql << "UPDATE " << CHAR_ATTR_TBL_NAME << " "
+ << "SET "
+ << "attr_base = '" << base << "', "
+ << "attr_mod = '" << mod << "' "
+ << "WHERE "
+ << "char_id = '" << charId << "' "
+ << "AND "
+ << "attr_id = '" << attrId << "';";
+ mDb->execSql(sql.str());
+ // If this has modified a row, we're done, it updated sucessfully.
+ if (mDb->getModifiedRows() > 0)
+ return;
+ // If it did not change anything, then the record didn't previously exist.
+ // Create it.
+ sql.clear();
+ sql.str("");
+ sql << "INSERT INTO " << CHAR_ATTR_TBL_NAME << " "
+ << "(char_id, attr_id, attr_base, attr_mod) VALUES ( "
+ << charId << ", "
+ << attrId << ", "
+ << base << ", "
+ << mod << ")";
+ mDb->execSql(sql.str());
+ }
+ catch (const dal::DbSqlQueryExecFailure &e)
+ {
+ LOG_ERROR("DALStorage::updateAttribute: " << e.what());
+ throw;
+ }
+}
+
+/**
* Write a modification message about character skills to the database.
* @param CharId ID of the character
* @param monsterId ID of the monster type
@@ -1445,7 +1507,7 @@ std::list<Guild*> Storage::getGuildList()
i != members.end();
++i)
{
- Character *character = getCharacter((*i).first, NULL);
+ Character *character = getCharacter((*i).first, 0);
if (character)
{
character->addGuild((*itr)->getName());
@@ -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 204e6c9..1ae8726 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 f177240..cf7ba1b 100644
--- a/src/common/inventorydata.hpp
+++ b/src/common/inventorydata.hpp
@@ -22,6 +22,7 @@
#define COMMON_INVENTORYDATA_HPP
#include <vector>
+#include <map>
/**
* Numbers of inventory slots
@@ -29,7 +30,6 @@
enum
{
- EQUIPMENT_SLOTS = 11,
INVENTORY_SLOTS = 50
};
@@ -40,20 +40,22 @@ enum
struct InventoryItem
{
- unsigned short itemId;
- unsigned char amount;
+ unsigned int itemId;
+ unsigned int amount;
};
+// slot id -> { item }
+typedef std::map< unsigned short, InventoryItem > InventoryData;
+// equip slot type -> { slot ids }
+// Equipment taking up multiple slots will be referenced multiple times
+typedef std::multimap< unsigned int, unsigned int > EquipData;
/**
* Structure storing the equipment and inventory of a Player.
*/
struct Possessions
{
- std::vector< InventoryItem > inventory;
- int money;
- unsigned short equipment[EQUIPMENT_SLOTS];
- Possessions(): money(0)
- { for (int i = 0; i < EQUIPMENT_SLOTS; ++i) equipment[i] = 0; }
+ InventoryData inventory;
+ EquipData equipSlots;
};
#endif
diff --git a/src/defines.h b/src/defines.h
index bc32d05..4f80afa 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 95f53c9..1a35587 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 0aed67e..e2b793b 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 0000000..37ac07b
--- /dev/null
+++ b/src/game-server/attribute.cpp
@@ -0,0 +1,333 @@
+/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "attribute.hpp"
+#include "game-server/being.hpp"
+#include "utils/logger.h"
+#include <cassert>
+
+AttributeModifiersEffect::AttributeModifiersEffect(AT_TY sType, AME_TY eType)
+ : mMod(eType == AME_MULT ? 1 : 0), mSType(sType), mEType(eType)
+{
+ assert(eType == AME_MULT || eType == AME_ADD);
+ assert(sType == TY_ST || sType == TY_NST || sType == TY_NSTB);
+ LOG_DEBUG("Layer created with eType " << eType << " and sType " << sType << ".");
+}
+
+AttributeModifiersEffect::~AttributeModifiersEffect()
+{
+ // ?
+ /*mStates.clear();*/
+ LOG_WARN("DELETION of attribute effect!");
+}
+
+bool AttributeModifiersEffect::add(unsigned short duration, double value, double prevLayerValue, int level)
+{
+ LOG_DEBUG("Adding modifier with value " << value <<
+ " with a previous layer value of " << prevLayerValue << ". "
+ "Current mod at this layer: " << mMod << ".");
+ bool ret = false;
+ mStates.push_back(new AttributeModifierState(duration, value, level));
+ switch (mSType) {
+ case TY_ST:
+ switch (mEType) {
+ case AME_ADD:
+ if (value)
+ {
+ ret = true;
+ mMod += value;
+ mCacheVal = prevLayerValue + mMod;
+ }
+ break;
+ case AME_MULT:
+ if (value != 1)
+ {
+ ret = true;
+ mMod *= value;
+ mCacheVal = prevLayerValue * mMod;
+ }
+ break;
+ default:
+ LOG_FATAL("Attribute modifiers effect: unhandled type '"
+ << mEType << "' as a stackable!");
+ assert(0);
+ break;
+ }
+ break;
+ case TY_NST:
+ switch (mEType) {
+ case AME_ADD:
+ if (value > mMod)
+ {
+ ret = true;
+ mMod = value;
+ if (mMod > prevLayerValue)
+ mCacheVal = mMod;
+ }
+ break;
+ default:
+ LOG_FATAL("Attribute modifiers effect: unhandled type '"
+ << mEType << "' as a non-stackable!");
+ assert(0);
+ }
+ // A multiplicative type would also be nonsensical
+ break;
+ case TY_NSTB:
+ switch (mEType) {
+ case AME_ADD:
+ case AME_MULT:
+ if (value > mMod)
+ {
+ ret = true;
+ mMod = value;
+ mCacheVal = mEType == AME_ADD ? prevLayerValue + mMod
+ : prevLayerValue * mMod;
+ }
+ break;
+ default:
+ LOG_FATAL("Attribute modifiers effect: unhandled type '"
+ << mEType << "' as a non-stackable bonus!");
+ assert(0);
+ }
+ break;
+ default:
+ LOG_FATAL("Attribute modifiers effect: unknown modifier type '"
+ << mSType << "'!");
+ assert(0);
+ }
+ return ret;
+}
+
+bool durationCompare(const AttributeModifierState *lhs,
+ const AttributeModifierState *rhs)
+{ return lhs->mDuration < rhs->mDuration; }
+
+bool AttributeModifiersEffect::remove(double value, unsigned int id, bool fullCheck) {
+ /* We need to find and check this entry exists, and erase the entry
+ from the list too. */
+ if (!fullCheck)
+ mStates.sort(durationCompare); /* Search only through those with a duration of 0. */
+ bool ret = false;
+ for (std::list< AttributeModifierState * >::iterator it = mStates.begin();
+ it != mStates.end() && (fullCheck || !(*it)->mDuration);
+ ++it)
+ {
+ /* Check for a match */
+ if ((*it)->mValue != value || (*it)->mId != id)
+ continue;
+ if (mSType == TY_ST)
+ updateMod();
+ delete *it;
+ mStates.erase(it);
+ if (!id)
+ return true;
+ else ret = true;
+ }
+ if (ret && mSType != TY_ST)
+ updateMod();
+ return ret;
+}
+
+void AttributeModifiersEffect::updateMod(double value)
+{
+ if (mSType == TY_ST)
+ {
+ if (mEType == AME_ADD)
+ mMod -= value;
+ else if (mEType == AME_MULT)
+ mMod /= value;
+ else LOG_ERROR("Attribute modifiers effect: unhandled type '"
+ << mEType << "' as a stackable in cache update!");
+ }
+ else if (mSType == TY_NST || mSType == TY_NSTB)
+ {
+ if (mMod == value)
+ {
+ mMod = 0;
+ for (std::list< AttributeModifierState * >::const_iterator
+ it2 = mStates.begin(),
+ it2_end = mStates.end();
+ it2 != it2_end;
+ ++it2)
+ if ((*it2)->mValue > mMod)
+ mMod = (*it2)->mValue;
+ }
+ else LOG_ERROR("Attribute modifiers effect: unhandled type '"
+ << mEType << "' as a non-stackable in cache update!");
+ }
+ else LOG_ERROR("Attribute modifiers effect: unknown modifier type '"
+ << mSType << "' in cache update!");
+}
+
+bool AttributeModifiersEffect::recalculateModifiedValue(double newPrevLayerValue)
+ {
+ double oldValue = mCacheVal;
+ switch (mEType)
+ case AME_ADD: {
+ switch (mSType) {
+ case TY_ST:
+ case TY_NSTB:
+ mCacheVal = newPrevLayerValue + mMod;
+ break;
+ case TY_NST:
+ mCacheVal = newPrevLayerValue < mMod ? mMod : newPrevLayerValue;
+ break;
+ default:
+ LOG_FATAL("Unknown stack type '" << mEType << "'!");
+ assert(0);
+ } break;
+ case AME_MULT:
+ mCacheVal = mSType == TY_ST ? newPrevLayerValue * mMod : newPrevLayerValue * mMod;
+ break;
+ default:
+ LOG_FATAL("Unknown effect type '" << mEType << "'!");
+ assert(0);
+ }
+ return oldValue != mCacheVal;
+}
+
+bool Attribute::add(unsigned short duration, double value, unsigned int layer, int level)
+{
+ assert(mMods.size() > layer);
+ LOG_DEBUG("Adding modifier to attribute with duration " << duration <<
+ ", value " << value << ", at layer " << layer << " with id "
+ << level);
+ if (mMods.at(layer)->add(duration, value,
+ (layer ? mMods.at(layer - 1)->getCachedModifiedValue()
+ : mBase)
+ , level))
+ {
+ while (++layer < mMods.size())
+ {
+ if (!mMods.at(layer)->recalculateModifiedValue(
+ mMods.at(layer - 1)->getCachedModifiedValue()))
+ {
+ LOG_DEBUG("Modifier added, but modified value not changed.");
+ return false;
+ }
+ }
+ LOG_DEBUG("Modifier added. Base value: " << mBase << ", new modified "
+ "value: " << getModifiedAttribute() << ".");
+ return true;
+ }
+ LOG_DEBUG("Failed to add modifier!");
+ return false;
+}
+
+bool Attribute::remove(double value, unsigned int layer, int lvl, bool fullcheck)
+{
+ assert(mMods.size() > layer);
+ if (mMods.at(layer)->remove(value, lvl, fullcheck))
+ {
+ while (++layer < mMods.size())
+ if (!mMods.at(layer)->recalculateModifiedValue(
+ mMods.at(layer - 1)->getCachedModifiedValue()))
+ return false;
+ return true;
+ }
+ return false;
+}
+
+bool AttributeModifiersEffect::tick()
+{
+ bool ret = false;
+ std::list<AttributeModifierState *>::iterator it = mStates.begin();
+ while (it != mStates.end())
+ {
+ if ((*it)->tick())
+ {
+ double value = (*it)->mValue;
+ LOG_DEBUG("Modifier of value " << value << " expiring!");
+ delete *it;
+ mStates.erase(it++);
+ updateMod(value);
+ ret = true;
+ }
+ ++it;
+ }
+ return ret;
+}
+
+Attribute::Attribute(const std::vector<struct AttributeInfoType> &type)
+{
+ LOG_DEBUG("Construction of new attribute with '" << type.size() << "' layers.");
+ for (unsigned int i = 0; i < type.size(); ++i)
+ {
+ LOG_DEBUG("Adding layer with stack type " << type[i].sType << " and effect type " << type[i].eType << ".");
+ mMods.push_back(new AttributeModifiersEffect(type[i].sType,
+ type[i].eType));
+ LOG_DEBUG("Layer added.");
+ }
+}
+
+Attribute::~Attribute()
+{
+ for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(),
+ it_end = mMods.end(); it != it_end; ++it)
+ {
+ // ?
+ //delete *it;
+ }
+}
+
+bool Attribute::tick()
+{
+ bool ret = false;
+ double prev = mBase;
+ for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(),
+ it_end = mMods.end(); it != it_end; ++it)
+ {
+ if ((*it)->tick())
+ {
+ LOG_DEBUG("Attribute layer " << mMods.begin() - it
+ << " has expiring modifiers.");
+ ret = true;
+ }
+ if (ret)
+ if (!(*it)->recalculateModifiedValue(prev)) ret = false;
+ prev = (*it)->getCachedModifiedValue();
+ }
+ return ret;
+}
+
+void Attribute::clearMods()
+{
+ for (std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin(),
+ it_end = mMods.end(); it != it_end; ++it)
+ (*it)->clearMods(mBase);
+}
+
+void Attribute::setBase(double base) {
+ LOG_DEBUG("Setting base attribute from " << mBase << " to " << base << ".");
+ double prev = mBase = base;
+ std::vector<AttributeModifiersEffect *>::iterator it = mMods.begin();
+ while (it != mMods.end())
+ if ((*it)->recalculateModifiedValue(prev))
+ prev = (*it++)->getCachedModifiedValue();
+ else
+ break;
+}
+
+void AttributeModifiersEffect::clearMods(double baseValue)
+{
+ mStates.clear();
+ mCacheVal = baseValue;
+ mMod = mEType == AME_ADD ? 0 : 1;
+}
diff --git a/src/game-server/attribute.hpp b/src/game-server/attribute.hpp
new file mode 100644
index 0000000..c5f833f
--- /dev/null
+++ b/src/game-server/attribute.hpp
@@ -0,0 +1,178 @@
+/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ATTRIBUTE_HPP
+#define ATTRIBUTE_HPP
+
+#include "defines.h"
+#include <vector>
+#include <list>
+
+class AttributeModifierState
+{
+ public:
+ AttributeModifierState(unsigned short duration, double value,
+ unsigned int id)
+ : mDuration(duration), mValue(value), mId(id) {}
+ ~AttributeModifierState() {}
+ bool tick() { return mDuration ? !--mDuration : false; }
+ private:
+ /** Number of ticks (0 means permanent, e.g. equipment). */
+ unsigned short mDuration;
+ const double mValue; /**< Positive or negative amount. */
+ /**
+ * Special purpose variable used to identify this effect to
+ * dispells or similar. Exact usage depends on the effect,
+ * origin, etc.
+ */
+ const unsigned int mId;
+ friend bool durationCompare(const AttributeModifierState*,
+ const AttributeModifierState*);
+ friend class AttributeModifiersEffect;
+};
+
+class AttributeModifiersEffect {
+ public:
+ AttributeModifiersEffect(AT_TY sType, AME_TY eType);
+ ~AttributeModifiersEffect();
+ /**
+ * Recalculates the value for this level.
+ * @returns True if the value changed, false if it did not change.
+ * Note that this will not change values at a higher level, nor the
+ * overall modified value for this attribute.
+ * If this returns true, the cached values for *all* modifiers of a
+ * higher level must be recalculated, as well as the final
+ */
+ bool add(unsigned short duration, double value,
+ double prevLayerValue, int level);
+
+ /**
+ * remove() - as with Attribute::remove().
+ */
+
+ bool remove(double value, unsigned int id, bool fullCheck);
+
+ /**
+ * Performs the necessary modifications to mMod when the states change.
+ */
+
+ void updateMod(double value = 0);
+
+ /**
+ * Performs the necessary modifications to mCacheVal when the states
+ * change.
+ */
+
+ bool recalculateModifiedValue(double newPrevLayerValue);
+
+ double getCachedModifiedValue() const { return mCacheVal; }
+
+ bool tick();
+
+ /**
+ * clearMods() - removes all modifications present in this layer.
+ * This only really makes sense when all other layers are being reset too.
+ * @param baseValue the value to reset to - typically an Attribute's mBase
+ */
+
+ void clearMods(double baseValue);
+
+ private:
+ /** List of all modifications present at this level */
+ std::list< AttributeModifierState * > mStates;
+ /**
+ * Stores the value that results from mStates. This takes into
+ * account all previous layers.
+ */
+ double mCacheVal;
+ /**
+ * Stores the effective modifying value from mStates. This defaults to
+ * 0 for additive modifiers and 1 for multiplicative modifiers.
+ */
+ double mMod;
+ const AT_TY mSType;
+ const AME_TY mEType;
+};
+
+class Attribute
+{
+ public:
+ Attribute() {throw;} // DEBUG; Find improper constructions
+
+ Attribute(const std::vector<struct AttributeInfoType> &type);
+
+ ~Attribute();
+
+ void setBase(double base);
+ double getBase() const { return mBase; }
+ double getModifiedAttribute() const
+ { return mMods.empty() ? mBase :
+ (*mMods.rbegin())->getCachedModifiedValue(); }
+
+ /**
+ * add() and remove() are the standard functions used to add and
+ * remove modifiers while keeping track of the modifier state.
+ */
+
+ /**
+ * @param duration The amount of time before the modifier expires
+ * naturally.
+ * When set to 0, the effect does not expire.
+ * @param value The value to be applied as the modifier.
+ * @param layer The id of the layer with which this modifier is to be
+ * applied to.
+ * @param id Used to identify this effect.
+ * @return Whether the modified attribute value was changed.
+ */
+
+ bool add(unsigned short duration, double value, unsigned int layer, int id = 0);
+
+ /**
+ * @param value The value of the modifier to be removed.
+ * - When 0, id is used exclusively to identify modifiers.
+ * @param layer The id of the layer which contains the modifier to be removed.
+ * @param id Used to identify this effect.
+ * - When 0, only the first match will be removed.
+ * - When non-0, all modifiers matching this id and other
+ * parameters will be removed.
+ * @param fullcheck Whether to perform a check for all modifiers,
+ * or only those that are otherwise permanent (ie. duration of 0)
+ * @returns Whether the modified attribute value was changed.
+ */
+ bool remove(double value, unsigned int layer, int id, bool fullcheck);
+
+ /**
+ * clearMods() removes *all* modifications present in this Attribute (!)
+ */
+
+ void clearMods();
+
+ /**
+ * tick() processes all timers associated with modifiers for this attribute.
+ */
+
+ bool tick();
+
+ private:
+ double mBase;
+ std::vector< AttributeModifiersEffect * > mMods;
+};
+
+#endif // ATTRIBUTE_HPP
diff --git a/src/game-server/attributemanager.cpp b/src/game-server/attributemanager.cpp
new file mode 100644
index 0000000..f62abbc
--- /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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "game-server/attributemanager.hpp"
+
+#include "common/resourcemanager.hpp"
+#include "utils/string.hpp"
+#include "utils/logger.h"
+#include "utils/xml.hpp"
+#include "defines.h"
+
+void AttributeManager::initialize()
+{
+ reload();
+}
+
+void AttributeManager::reload()
+{
+ mTagMap.clear();
+ mAttributeMap.clear();
+ for (unsigned int i = 0; i < ATTR_MAX; ++i)
+ mAttributeScopes[i].clear();
+
+ std::string absPathFile;
+ xmlNodePtr node;
+
+ absPathFile = ResourceManager::resolve(mAttributeReferenceFile);
+ if (absPathFile.empty()) {
+ LOG_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<struct AttributeInfoType> >(false , std::vector<struct AttributeInfoType>());
+ unsigned int layerCount = 0;
+ for_each_xml_child_node(subnode, attributenode)
+ {
+ if (xmlStrEqual(subnode->name, BAD_CAST "modifier"))
+ {
+ std::string sType = utils::toupper(XML::getProperty(subnode, "stacktype", ""));
+ std::string eType = utils::toupper(XML::getProperty(subnode, "modtype", ""));
+ std::string tag = utils::toupper(XML::getProperty(subnode, "tag", ""));
+ AT_TY pSType;
+ AME_TY pEType;
+ if (!sType.empty())
+ {
+ if (!eType.empty())
+ {
+ bool fail = false;
+ if (sType == "STACKABLE")
+ pSType = TY_ST;
+ else if (sType == "NON STACKABLE")
+ pSType = TY_NST;
+ else if (sType == "NON STACKABLE BONUS")
+ pSType = TY_NSTB;
+ else
+ {
+ LOG_WARN("Attribute manager: attribute '" << id
+ << "' has unknown stack type '" << sType
+ << "', skipping modifier!");
+ fail = true;
+ }
+ if (!fail)
+ {
+ if (eType == "ADDITIVE")
+ pEType = AME_ADD;
+ else if (eType == "MULTIPLICATIVE")
+ pEType = AME_MULT;
+ else
+ {
+ LOG_WARN("Attribute manager: attribute '" << id
+ << "' has unknown modification type '" << sType
+ << "', skipping modifier!");
+ fail = true;
+ }
+ if (!fail)
+ {
+ mAttributeMap[id].second.push_back(AttributeInfoType(pSType, pEType));
+ std::string tag = XML::getProperty(subnode, "tag", "");
+
+ if (!tag.empty())
+ mTagMap.insert(std::make_pair(tag,
+ std::make_pair(id, layerCount)));
+ ++layerCount;
+ }
+ }
+ }
+ else
+ LOG_WARN("Attribute manager: attribute '" << id <<
+ "' has undefined modification type, skipping modifier!");
+ }
+ else
+ {
+ LOG_WARN("Attribute manager: attribute '" << id <<
+ "' has undefined stack type, skipping modifier!");
+ }
+ }
+ }
+ std::string scope = utils::toupper(XML::getProperty(attributenode, "scope", std::string()));
+ if (scope.empty())
+ {
+ // Give a warning unless scope has been explicitly set to "NONE"
+ LOG_WARN("Attribute manager: attribute '" << id
+ << "' has no default scope.");
+ }
+ else if (scope == "CHARACTER")
+ {
+ mAttributeScopes[ATTR_CHAR][id] = &mAttributeMap.at(id).second;
+ LOG_DEBUG("Attribute manager: attribute '" << id
+ << "' added to default character scope.");
+ }
+ else if (scope == "MONSTER")
+ {
+ mAttributeScopes[ATTR_MOB][id] = &mAttributeMap.at(id).second;
+ LOG_DEBUG("Attribute manager: attribute '" << id
+ << "' added to default monster scope.");
+ }
+ else if (scope == "BEING")
+ {
+ mAttributeScopes[ATTR_BEING][id] = &mAttributeMap.at(id).second;
+ LOG_DEBUG("Attribute manager: attribute '" << id
+ << "' added to default being scope.");
+ }
+ else if (scope == "NONE")
+ {
+ LOG_DEBUG("Attribute manager: attribute '" << id
+ << "' set to have no default scope.");
+ }
+ }
+ }
+
+ LOG_DEBUG("attribute map:");
+ LOG_DEBUG("TY_ST is " << TY_ST << ", TY_ NST is " << TY_NST << ", TY_NSTB is " << TY_NSTB << ".");
+ LOG_DEBUG("AME_ADD is " << AME_ADD << ", AME_MULT is " << AME_MULT << ".");
+ const std::string *tag;
+ unsigned int count = 0;
+ for (AttributeMap::const_iterator i = mAttributeMap.begin(); i != mAttributeMap.end(); ++i)
+ {
+ unsigned int lCount = 0;
+ LOG_DEBUG(" "<<i->first<<" : ");
+ for (std::vector<struct AttributeInfoType>::const_iterator j = i->second.second.begin();
+ j != i->second.second.end();
+ ++j)
+ {
+ tag = getTagFromInfo(i->first, lCount);
+ std::string end = tag ? "tag of '" + (*tag) + "'." : "no tag.";
+ LOG_DEBUG(" sType: " << j->sType << ", eType: " << j->eType << ", "
+ "and " << end);
+ ++lCount;
+ ++count;
+ }
+ }
+ LOG_INFO("Loaded '" << mAttributeMap.size() << "' attributes with '"
+ << count << "' modifier layers.");
+
+ for(TagMap::const_iterator i = mTagMap.begin(), i_end = mTagMap.end();
+ i != i_end; ++i)
+ {
+ LOG_DEBUG("Tag '" << i->first << "': '" << i->second.first << "', '"
+ << i->second.second << "'.");
+ }
+
+ LOG_INFO("Loaded '" << mTagMap.size() << "' modifier tags.");
+}
+
+const std::vector<struct AttributeInfoType> *AttributeManager::getAttributeInfo(unsigned int id) const
+{
+ AttributeMap::const_iterator ret = mAttributeMap.find(id);
+ if (ret == mAttributeMap.end()) return 0;
+ return &ret->second.second;
+}
+
+const AttributeScopes &AttributeManager::getAttributeInfoForType(SCOPE_TYPES type) const
+{
+ return mAttributeScopes[type];
+}
+
+bool AttributeManager::isAttributeDirectlyModifiable(unsigned int id) const
+{
+ AttributeMap::const_iterator ret = mAttributeMap.find(id);
+ if (ret == mAttributeMap.end()) return false;
+ return ret->second.first;
+}
+
+// { attribute id, layer }
+std::pair<unsigned int,unsigned int> AttributeManager::getInfoFromTag(const std::string &tag) const
+{
+ return mTagMap.at(tag);
+}
+
+const std::string *AttributeManager::getTagFromInfo(unsigned int attribute, unsigned int layer) const
+{
+ for (TagMap::const_iterator it = mTagMap.begin(),
+ it_end = mTagMap.end(); it != it_end; ++it)
+ if (it->second.first == attribute && it->second.second == layer)
+ return &it->first;
+ return 0;
+}
diff --git a/src/game-server/attributemanager.hpp b/src/game-server/attributemanager.hpp
new file mode 100644
index 0000000..0afac02
--- /dev/null
+++ b/src/game-server/attributemanager.hpp
@@ -0,0 +1,78 @@
+/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ATTRIBUTEMANAGER_HPP
+#define ATTRIBUTEMANAGER_HPP
+
+#include <map>
+#include <vector>
+#include <string>
+
+typedef struct AttributeInfoType AttributeInfoType_t;
+
+enum SCOPE_TYPES {
+ ATTR_BEING = 0,
+ ATTR_CHAR,
+ ATTR_MOB,
+ // Add new types here as needed
+ ATTR_MAX
+};
+
+typedef std::map< int, std::vector<struct AttributeInfoType> * > AttributeScopes;
+
+class AttributeManager
+{
+ public:
+ AttributeManager(const std::string &file) : mAttributeReferenceFile(file) {}
+ /**
+ * Loads attribute reference file.
+ */
+ void initialize();
+
+ /**
+ * Reloads attribute reference file.
+ */
+ void reload();
+ const std::vector<struct AttributeInfoType> *getAttributeInfo(unsigned int) const;
+
+ const AttributeScopes &getAttributeInfoForType(SCOPE_TYPES) const;
+
+ bool isAttributeDirectlyModifiable(unsigned int) const;
+
+ std::pair<unsigned int,unsigned int> getInfoFromTag(const std::string &) const;
+
+ const std::string *getTagFromInfo(unsigned int, unsigned int) const;
+ private:
+ // attribute id -> { modifiable, { stackable type, effect type }[] }
+ typedef std::map< int, std::pair< bool, std::vector<struct AttributeInfoType> > > AttributeMap;
+ // tag name -> { attribute id, layer }
+ typedef std::map< std::string, std::pair<unsigned int, unsigned int> > TagMap;
+
+ // being type id -> (*{ stackable type, effect type })[]
+ AttributeScopes mAttributeScopes[ATTR_MAX];
+ AttributeMap mAttributeMap;
+ TagMap mTagMap;
+
+ const std::string mAttributeReferenceFile;
+};
+
+extern AttributeManager *attributeManager;
+
+#endif // ATTRIBUTEMANAGER_HPP
diff --git a/src/game-server/autoattack.cpp b/src/game-server/autoattack.cpp
new file mode 100644
index 0000000..dae6b0f
--- /dev/null
+++ b/src/game-server/autoattack.cpp
@@ -0,0 +1,39 @@
+#include "autoattack.hpp"
+
+void AutoAttacks::add(AutoAttack n)
+{
+ mAutoAttacks.push_back(n);
+ // Slow, but safe.
+ mAutoAttacks.sort();
+}
+
+void AutoAttacks::clear()
+{
+ mAutoAttacks.clear();
+}
+
+void AutoAttacks::stop()
+{
+ for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it)
+ it->halt();
+ mActive = false;
+}
+
+void AutoAttacks::start()
+{
+ for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it)
+ it->softReset();
+ mActive = true;
+}
+
+void AutoAttacks::tick(std::list<AutoAttack> *ret)
+{
+ for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); it != mAutoAttacks.end(); ++it)
+ if (it->tick()) {
+ if (mActive)
+ it->reset();
+ else
+ it->halt();
+ } else if (ret && it->isReady())
+ ret->push_back(*it);
+}
diff --git a/src/game-server/autoattack.hpp b/src/game-server/autoattack.hpp
new file mode 100644
index 0000000..a931d92
--- /dev/null
+++ b/src/game-server/autoattack.hpp
@@ -0,0 +1,100 @@
+#ifndef AUTOATTACK_HPP
+#define AUTOATTACK_HPP
+
+#include <list>
+#include <limits>
+
+/**
+ * Methods of damage calculation
+ */
+enum DMG_TY
+{
+ DAMAGE_PHYSICAL = 0,
+ DAMAGE_MAGICAL,
+ DAMAGE_DIRECT,
+ DAMAGE_OTHER = -1
+};
+
+/**
+ * Structure that describes the severity and nature of an attack a being can
+ * be hit by.
+ */
+struct Damage
+{
+ unsigned short base; /**< Base amount of damage. */
+ unsigned short delta; /**< Additional damage when lucky. */
+ unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */
+ unsigned char element; /**< Elemental damage. */
+ DMG_TY type; /**< Damage type: Physical or magical? */
+ unsigned trueStrike : 1; /**< Override dodge calculation */
+ std::list<size_t> usedSkills; /**< Skills used by source (needed for exp calculation) */
+ unsigned short range; /**< Maximum distance that this attack can be used from */
+ Damage(unsigned short base, unsigned short delta, unsigned short cth,
+ unsigned char element, DMG_TY type = DAMAGE_OTHER,
+ unsigned short range = std::numeric_limits<unsigned short>::max())
+ : base(base), delta(delta), cth(cth), element(element), type(type),
+ trueStrike(false), range(range) {}
+};
+
+/**
+ * Class that stores information about an auto-attack
+ */
+
+class AutoAttack
+{
+ public:
+ AutoAttack(Damage &damage, unsigned int delay, unsigned int warmup)
+ : mDamage(damage), mTimer(0), mAspd(delay),
+ mWarmup(warmup && warmup < delay ? warmup : delay >> 2) {}
+ unsigned short getTimer() const { return mTimer; }
+ bool tick() { return mTimer ? !--mTimer : false; }
+ void reset() { mTimer = mAspd; }
+ bool operator<(const AutoAttack &rhs) const
+ { return mTimer < rhs.getTimer(); }
+ bool isReady() const { return !(mTimer - mWarmup); }
+ void halt() { if (mTimer >= mWarmup) mTimer = 0; }
+ void softReset() { if (mTimer >= mWarmup) mTimer = mAspd; }
+ const Damage &getDamage() const { return mDamage; }
+ private:
+ Damage mDamage;
+ /**
+ * Internal timer that is modified each tick.
+ * When > warmup, the attack is warming up before a strike
+ * When = warmup, the attack triggers, dealing damage to the target *if* the target is still in range.
+ * (The attack is canceled when the target moves out of range before the attack can hit, there should be a trigger for scripts here too)
+ * (Should the character automatically persue when the target is still visible in this case?)
+ * When < warmup, the attack is cooling down after a strike. When in cooldown, the timer should not be soft-reset.
+ * When 0, the attack is inactive (the character is doing something other than attacking and the attack is not in cooldown)
+ */
+ unsigned short mTimer;
+ unsigned short mAspd; // Value to reset the timer to (warmup + cooldown)
+ /**
+ * Pre-attack delay tick.
+ * This MUST be smaller than or equal to the aspd!
+ * So the attack triggers where timer == warmup, having gone through aspd - warmup ticks.
+ */
+ unsigned short mWarmup;
+};
+
+/**
+ * Helper class for storing multiple auto-attacks.
+ */
+class AutoAttacks
+{
+ public:
+
+ /**
+ * Whether the being has at least one auto attack that is ready.
+ */
+ void add(AutoAttack n);
+ void clear(); // Wipe the list completely (used in place of remove for now; FIXME)
+ void start();
+ void stop(); // If the character does some action other than attacking, reset all warmups (NOT cooldowns!)
+ void tick(std::list<AutoAttack> *ret = 0);
+ private:
+ bool mActive; /**< Marks whether or not to keep auto-attacking. Cooldowns still need to be processed when false. */
+ std::list < AutoAttack > mAutoAttacks;
+
+};
+
+#endif // AUTOATTACK_HPP
diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp
index 0e885de..65b2ac2 100644
--- a/src/game-server/being.cpp
+++ b/src/game-server/being.cpp
@@ -24,6 +24,8 @@
#include "defines.h"
#include "common/configuration.hpp"
+#include "game-server/attributemanager.hpp"
+#include "game-server/character.hpp"
#include "game-server/collisiondetection.hpp"
#include "game-server/eventlistener.hpp"
#include "game-server/mapcomposite.hpp"
@@ -36,64 +38,92 @@ Being::Being(ThingType type):
Actor(type),
mAction(STAND),
mTarget(NULL),
- mSpeed(0),
mDirection(0)
{
- Attribute attr = { 0, 0 };
- mAttributes.resize(NB_BEING_ATTRIBUTES + CHAR_ATTR_NB, attr);
+ const AttributeScopes &attr = attributeManager->getAttributeInfoForType(ATTR_BEING);
+ LOG_DEBUG("Being creation: initialisation of " << attr.size() << " attributes.");
+ for (AttributeScopes::const_iterator it1 = attr.begin(),
+ it1_end = attr.end();
+ it1 != it1_end;
+ ++it1)
+ {
+ if (mAttributes.count(it1->first))
+ LOG_WARN("Redefinition of attribute '" << it1->first << "'!");
+ LOG_DEBUG("Attempting to create attribute '" << it1->first << "'.");
+ mAttributes.insert(std::make_pair(it1->first,
+ Attribute(*it1->second)));
+
+ }
+ // TODO: Way to define default base values?
+ // Should this be handled by the virtual modifiedAttribute?
+ // URGENT either way
+#if 0
// Initialize element resistance to 100 (normal damage).
- for (int i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i)
+ for (i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i)
{
- mAttributes[i].base = 100;
+ mAttributes[i] = Attribute(TY_ST);
+ mAttributes[i].setBase(100);
}
+#endif
}
-int Being::damage(Actor *, const Damage &damage)
+int Being::damage(Actor *source, const Damage &damage)
{
if (mAction == DEAD)
return 0;
int HPloss = damage.base;
if (damage.delta)
- {
- HPloss += rand() / (RAND_MAX / (damage.delta + 1));
- }
+ HPloss += rand() * (damage.delta + 1) / RAND_MAX;
- int hitThrow = rand()%(damage.cth + 1);
- int evadeThrow = rand()%(getModifiedAttribute(BASE_ATTR_EVADE) + 1);
- if (evadeThrow > hitThrow)
- {
- HPloss = 0;
- }
-
- /* Elemental modifier at 100 means normal damage. At 0, it means immune.
- And at 200, it means vulnerable (double damage). */
- int mod1 = getModifiedAttribute(BASE_ELEM_BEGIN + damage.element);
- HPloss = HPloss * (mod1 / 100);
- /* Defence is an absolute value which is subtracted from the damage total. */
- int mod2 = 0;
+ // TODO magical attacks and associated elemental modifiers
switch (damage.type)
{
case DAMAGE_PHYSICAL:
- mod2 = getModifiedAttribute(BASE_ATTR_PHY_RES);
- HPloss = HPloss - mod2;
+ if (!damage.trueStrike &&
+ rand()%((int) getModifiedAttribute(ATTR_DODGE) + 1) >
+ rand()%(damage.cth + 1))
+ {
+ HPloss = 0;
+ // TODO Process triggers for a dodged physical attack here.
+ // If there is an attacker included, also process triggers for the attacker (failed physical strike)
+ }
+ else
+ {
+ HPloss = HPloss * (1.0 - (0.0159375f *
+ getModifiedAttribute(ATTR_DEFENSE)) /
+ (1.0 + 0.017 *
+ getModifiedAttribute(ATTR_DEFENSE))) +
+ (rand()%((HPloss >> 4) + 1));
+ // TODO Process triggers for receiving damage here.
+ // If there is an attacker included, also process triggers for the attacker (successful physical strike)
+ }
break;
case DAMAGE_MAGICAL:
- mod2 = getModifiedAttribute(BASE_ATTR_MAG_RES);
- HPloss = HPloss / (mod2 + 1);
+#if 0
+ getModifiedAttribute(BASE_ELEM_BEGIN + damage.element);
+#else
+ LOG_WARN("Attempt to use magical type damage! This has not been"
+ "implemented yet and should not be used!");
+ HPloss = 0;
+#endif
+ case DAMAGE_DIRECT:
break;
default:
+ LOG_WARN("Unknown damage type '" << damage.type << "'!");
break;
}
if (HPloss > 0)
{
mHitsTaken.push_back(HPloss);
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: "<<HP.base + HP.mod<<"/"<<HP.base);
- HP.mod -= HPloss;
- modifiedAttribute(BASE_ATTR_HP);
- setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit
+ Attribute &HP = mAttributes.at(ATTR_HP);
+ LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: "
+ << HP.getModifiedAttribute() << "/"
+ << mAttributes.at(ATTR_MAX_HP).getModifiedAttribute());
+ HP.setBase(HP.getBase() - HPloss);
+ modifiedAttribute(ATTR_HP);
+ setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit if this is set.
} else {
HPloss = 0;
}
@@ -103,17 +133,23 @@ int Being::damage(Actor *, const Damage &damage)
void Being::heal()
{
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- HP.mod = HP.base;
- 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.clearMods(); // Reset all modifications present in hp
+ hp.setBase(maxHp.getModifiedAttribute());
+ modifiedAttribute(ATTR_HP);
}
-void Being::heal(int hp)
+void Being::heal(int gain)
{
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- HP.mod += hp;
- if (HP.mod > HP.base) HP.mod = HP.base;
- 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 f8a65c9..96151a3 100644
--- a/src/game-server/being.hpp
+++ b/src/game-server/being.hpp
@@ -28,11 +28,15 @@
#include "limits.h"
#include "game-server/actor.hpp"
+#include "game-server/attribute.hpp"
+#include "game-server/autoattack.hpp"
class Being;
class MapComposite;
class StatusEffect;
+typedef std::map< unsigned int, Attribute > AttributeMap;
+
/**
* Beings and actors directions
* Needs to match client
@@ -50,60 +54,10 @@ enum TimerID
T_M_STROLL, // time until monster strolls to new location
T_M_KILLSTEAL_PROTECTED, // killsteal protection time
T_M_DECAY, // time until dead monster is removed
- T_B_ATTACK_TIME, // time until being can attack again
+ T_M_ATTACK_TIME, // time until monster can attack again
T_B_HP_REGEN // time until hp is regenerated again
};
-/**
- * Methods of damage calculation
- */
-enum
-{
- DAMAGE_PHYSICAL = 0,
- DAMAGE_MAGICAL,
- DAMAGE_OTHER
-};
-
-/**
- * Structure that describes the severity and nature of an attack a being can
- * be hit by.
- */
-struct Damage
-{
- unsigned short base; /**< Base amount of damage. */
- unsigned short delta; /**< Additional damage when lucky. */
- unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */
- unsigned char element; /**< Elemental damage. */
- unsigned char type; /**< Damage type: Physical or magical? */
- std::list<size_t> usedSkills; /**< Skills used by source (needed for exp calculation) */
-};
-
-
-/**
- * Holds the base value of an attribute and the sum of all its modifiers.
- * While base + mod may be negative, the modified attribute is not.
- */
-struct Attribute
-{
- unsigned short base;
- short mod;
-};
-
-struct AttributeModifier
-{
- /**< Number of ticks (0 means permanent, e.g. equipment). */
- unsigned short duration;
- short value; /**< Positive or negative amount. */
- unsigned char attr; /**< Attribute to modify. */
- /**
- * Strength of the modification.
- * - Zero means permanent, e.g. equipment.
- * - Non-zero means spell. Can only be removed by a wizard with a
- * dispell level higher than this value.
- */
- unsigned char level;
-};
-
struct Status
{
StatusEffect *status;
@@ -111,7 +65,6 @@ struct Status
};
typedef std::map< int, Status > StatusEffects;
-typedef std::vector< AttributeModifier > AttributeModifiers;
/**
* Type definition for a list of hits
@@ -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<TimerID, int> Timers;
Timers mTimers;
diff --git a/src/game-server/buysell.cpp b/src/game-server/buysell.cpp
index 2ae6461..cf5b8fe 100644
--- a/src/game-server/buysell.cpp
+++ b/src/game-server/buysell.cpp
@@ -27,10 +27,12 @@
#include "game-server/item.hpp"
#include "net/messageout.hpp"
+#include "defines.h"
+
#include <algorithm>
BuySell::BuySell(Character *c, bool sell):
- mChar(c), mSell(sell)
+ mCurrencyId(ATTR_GP), mChar(c), mSell(sell)
{
c->setBuySell(this);
}
@@ -61,8 +63,18 @@ bool BuySell::registerItem(int id, int amount, int cost)
return true;
}
+
int BuySell::registerPlayerItems()
{
+ return 0; // FIXME: STUB
+ /*
+ * Replaced with a no-op stub after the equipment slots become softcoded.
+ * I think this function is meant to fill the sell dialog with player
+ * items, but it's iterating through the inventory.
+ * The no-op here is to stop compilation errors while I work on other
+ * areas. FIXME
+ */
+ /*
int nbItemsToSell = 0;
if (mSell)
{
@@ -85,6 +97,7 @@ int BuySell::registerPlayerItems()
}
}
return nbItemsToSell;
+ */
}
bool BuySell::start(Actor *actor)
@@ -119,13 +132,17 @@ void BuySell::perform(int id, int amount)
if (mSell)
{
amount -= inv.remove(id, amount);
- inv.changeMoney(amount * i->cost);
+ mChar->setAttribute(mCurrencyId,
+ mChar->getAttribute(mCurrencyId) +
+ amount * i->cost);
}
else
{
- amount = std::min(amount, mChar->getPossessions().money / i->cost);
+ amount = std::min(amount, ((int) mChar->getAttribute(mCurrencyId)) / i->cost);
amount -= inv.insert(id, amount);
- inv.changeMoney(-amount * i->cost);
+ mChar->setAttribute(mCurrencyId,
+ mChar->getAttribute(mCurrencyId) -
+ amount * i->cost);
}
if (i->amount)
{
diff --git a/src/game-server/buysell.hpp b/src/game-server/buysell.hpp
index 514200a..a9903bc 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 46ffa05..e24871c 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<Special*>::iterator i = rechargeNeeded.begin(); i != rechargeNeeded.end(); i++)
{
(*i)->currentMana += mRechargePerSpecial;
@@ -145,36 +151,11 @@ void Character::perform()
return;
}
- // TODO: Check slot 2 too.
- int itemId = mPossessions.equipment[EQUIP_FIGHT1_SLOT];
- ItemClass *ic = ItemManager::getItem(itemId);
- int type = ic ? ic->getModifiers().getValue(MOD_WEAPON_TYPE) : 100;
-
- Damage damage;
- damage.base = getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN);
- damage.delta = getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA) +
- getModifiedAttribute(type);
- damage.type = DAMAGE_PHYSICAL;
- damage.cth = getModifiedAttribute(BASE_ATTR_HIT) +
- getModifiedAttribute(type);
- damage.usedSkills.push_back(type);
-
- if (ic)
- {
- // weapon fighting
- const ItemModifiers &mods = ic->getModifiers();
- damage.element = mods.getValue(MOD_ELEMENT_TYPE);
- // todo: get attack range of weapon
- // (weapon equipping has to be fixed first)
- performAttack(mTarget, 64, damage);
- }
- else
- {
- // No-weapon fighting.
- damage.element = ELEMENT_NEUTRAL;
- performAttack(mTarget, 32, damage);
- }
-
+ std::list<AutoAttack> attacks;
+ mAutoAttacks.tick(&attacks);
+ if (attacks.empty()) return; // Install default attack?
+ else for (std::list<AutoAttack>::iterator it = attacks.begin(); it != attacks.end(); ++it)
+ performAttack(mTarget, it->getDamage());
}
void Character::died()
@@ -201,8 +182,9 @@ void Character::respawn()
{
// script-controlled respawning didn't work - fall back to
// hardcoded logic
- mAttributes[BASE_ATTR_HP].mod = -mAttributes[BASE_ATTR_HP].base + 1;
- 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 fdee364..b3e2482 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.
@@ -359,12 +359,6 @@ class Character : public Being
int getKillCount(int monsterType) const;
/**
- * Shortcut to get being's health
- */
- int getHealth() const
- { return getModifiedAttribute(CHAR_ATTR_VITALITY); }
-
- /**
* Returns the exp needed to reach a specific skill level
*/
static int expForLevel(int level);
@@ -404,7 +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<int, int> 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 1a7b4f7..b421c67 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 0f3bc85..cf5673b 100644
--- a/src/game-server/commandhandler.cpp
+++ b/src/game-server/commandhandler.cpp
@@ -55,7 +55,7 @@ static void handleRecall(Character*, std::string&);
static void handleBan(Character*, std::string&);
static void handleItem(Character*, std::string&);
static void handleDrop(Character*, std::string&);
-static void handleMoney(Character*, std::string&);
+//static void handleMoney(Character*, std::string&);
static void handleSpawn(Character*, std::string&);
static void handleAttribute(Character*, std::string&);
static void handleReload(Character*, std::string&);
@@ -86,8 +86,8 @@ static CmdRef const cmdRef[] =
"Creates a number of items in the inventory of a character", &handleItem},
{"drop", "<item id> <amount>",
"Drops a stack of items on the ground at your current location", &handleDrop},
- {"money", "<character> <amount>",
- "Changes the money a character possesses", &handleMoney},
+/* {"money", "<character> <amount>",
+ "Changes the money a character possesses", &handleMoney},*/
{"spawn", "<monster id> <number>",
"Creates a number of monsters near your location", &handleSpawn},
{"attribute", "<character> <attribute> <value>",
@@ -362,7 +362,7 @@ static void handleItem(Character *player, std::string &args)
id = utils::stringToInt(itemclass);
// check for valid item class
- ic = ItemManager::getItem(id);
+ ic = itemManager->getItem(id);
if (!ic)
{
@@ -421,7 +421,7 @@ static void handleDrop(Character *player, std::string &args)
id = utils::stringToInt(itemclass);
// check for valid item
- ic = ItemManager::getItem(id);
+ ic = itemManager->getItem(id);
if (!ic)
{
say("Invalid item", player);
@@ -448,7 +448,7 @@ static void handleDrop(Character *player, std::string &args)
str << "User created item " << ic->getDatabaseID();
accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_DROP, str.str());
}
-
+/*
static void handleMoney(Character *player, std::string &args)
{
Character *other;
@@ -499,6 +499,7 @@ static void handleMoney(Character *player, std::string &args)
std::string msg = "User created " + valuestr + " money";
accountHandler->sendTransaction(player->getDatabaseID(), TRANS_CMD_MONEY, msg);
}
+*/
static void handleSpawn(Character *player, std::string &args)
{
@@ -530,7 +531,7 @@ static void handleSpawn(Character *player, std::string &args)
id = utils::stringToInt(monsterclass);
// check for valid monster
- mc = MonsterManager::getMonster(id);
+ mc = monsterManager->getMonster(id);
if (!mc)
{
say("Invalid monster", player);
@@ -626,8 +627,8 @@ static void handleRecall(Character *player, std::string &args)
static void handleReload(Character *player, std::string &args)
{
// reload the items and monsters
- ItemManager::reload();
- MonsterManager::reload();
+ itemManager->reload();
+ monsterManager->reload();
}
static void handleBan(Character *player, std::string &args)
diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp
index 3081658..5b60da0 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<int, int>::const_iterator skill_it;
for (skill_it = character->getSkillBegin(); skill_it != character->getSkillEnd(); skill_it++)
{
diff --git a/src/game-server/inventory.cpp b/src/game-server/inventory.cpp
index b90354b..f560ce5 100644
--- a/src/game-server/inventory.cpp
+++ b/src/game-server/inventory.cpp
@@ -28,26 +28,27 @@
#include "net/messageout.hpp"
#include "utils/logger.h"
+// TODO:
+// - Inventory::initialise() Usable but could use a few more things
+// - Inventory::equip() Usable but last part would be nice
+
+typedef std::set<unsigned int> ItemIdSet;
+
Inventory::Inventory(Character *p, bool d):
- mPoss(&p->getPossessions()), msg(GPMSG_INVENTORY), mClient(p),
- mDelayed(d), mChangedLook(false)
+ mPoss(&p->getPossessions()), mInvMsg(GPMSG_INVENTORY),
+ mEqmMsg(GPMSG_EQUIP), mClient(p), mDelayed(d)
{
}
Inventory::~Inventory()
{
- if (msg.getLength() > 2)
- {
- update();
- gameHandler->sendTo(mClient, msg);
- }
+ commit(false);
}
void Inventory::restart()
{
- msg.clear();
- msg.writeShort(GPMSG_INVENTORY);
- mChangedLook = false;
+ mInvMsg.clear();
+ mInvMsg.writeShort(GPMSG_INVENTORY);
}
void Inventory::cancel()
@@ -62,736 +63,702 @@ void Inventory::cancel()
restart();
}
-void Inventory::update()
+void Inventory::commit(bool doRestart)
{
- if (mDelayed)
+ Possessions &poss = mClient->getPossessions();
+ /* Sends changes, whether delayed or not. */
+ if (mInvMsg.getLength() > 2)
{
- Possessions &poss = mClient->getPossessions();
- if (mPoss != &poss)
- {
- poss = *mPoss;
- delete mPoss;
- mPoss = &poss;
- }
+ /* Send the message to the client directly. Perhaps this should be
+ done through an update flag, too? */
+ gameHandler->sendTo(mClient, mInvMsg);
}
- if (mChangedLook)
+ if (mPoss != &poss)
{
- mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE);
+ if (mDelayed)
+ {
+ /*
+ * Search for any and all changes to equipment.
+ * Search through equipment for changes between old and new equipment.
+ * Send changes directly when there is a change.
+ * Even when equipment references to invy slots are the same, it still
+ * needs to be searched for changes to the internal equiment slot
+ * usage.
+ * This is probably the worst part of doing this in delayed mode.
+ */
+ IdSlotMap oldEquip, newEquip;
+ {
+ EquipData::const_iterator it1, it2, it1_end, it2_end;
+ for (it1 = mPoss->equipSlots.begin(),
+ it1_end = mPoss->equipSlots.end();
+ it1 != it1_end;
+ ++it1)
+ {
+#ifdef INV_CONST_BOUND_DEBUG
+ IdSlotMap::const_iterator temp2, temp =
+#endif
+ newEquip.insert(
+ newEquip.upper_bound(it1->second),
+ std::make_pair(it1->second, it1->first));
+#ifdef INV_CONST_BOUND_DEBUG
+ if (temp !=
+ --(temp2 = newEquip.upper_bound(it1->second)))
+ throw;
+#endif
+ }
+ for (it2 = poss.equipSlots.begin(),
+ it2_end = poss.equipSlots.end();
+ it2 != it2_end;
+ ++it2)
+ oldEquip.insert(
+ oldEquip.upper_bound(it2->second),
+ std::make_pair(it2->second, it2->first));
+ }
+ {
+ IdSlotMap::const_iterator it1 = newEquip.begin(),
+ it2 = oldEquip.begin(),
+ it1_end = newEquip.end(),
+ it2_end = oldEquip.end(),
+ temp1, temp2;
+ while (it1 != it1_end || it2 != it2_end)
+ {
+ if (it1 == it1_end)
+ {
+ if (it2 == it2_end)
+ break;
+ equip_sub(0, it1);
+ }
+ else if (it2 == it2_end)
+ equip_sub(newEquip.count(it2->first), it2);
+ else if (it1->first == it2->first)
+ {
+ double invSlot = it1->first;
+ while ((it1 != it1_end && it1->first == invSlot) ||
+ (it2 != it2_end && it2->first == invSlot))
+ {
+ /*
+ * Item is still equipped, but need to check
+ * that the slots didn't change.
+ */
+ if (it1->second == it2->second)
+ {
+ // No change.
+ ++it1;
+ ++it2;
+ continue;
+ }
+ unsigned int itemId =
+ mPoss->inventory.at(it1->first).itemId;
+ changeEquipment(itemId, itemId);
+ break;
+ }
+ }
+ else if (it1->first > it2->first)
+ equip_sub(newEquip.count(it2->first), it2);
+ else // it1->first < it2->first
+ equip_sub(0, it1);
+ }
+ }
+ }
+ poss = *mPoss;
+ delete mPoss;
+ mPoss = &poss;
}
+
+ /* Update server sided states if in delayed mode. If we are not in
+ delayed mode, the server sided states already reflect the changes
+ that have just been sent to the client. */
+
+ if (mEqmMsg.getLength() > 2)
+ gameHandler->sendTo(mClient, mEqmMsg);
+
+ if (doRestart)
+ restart();
}
-void Inventory::commit()
+void Inventory::equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it)
{
- if (msg.getLength() > 2)
+ const unsigned int invSlot = it->first;
+ unsigned int count = 0, eqSlot = it->second;
+ mEqmMsg.writeShort(invSlot);
+ mEqmMsg.writeByte(newCount);
+ do {
+ if (newCount)
+ {
+ if (it->second != eqSlot)
+ {
+ mEqmMsg.writeByte(eqSlot);
+ mEqmMsg.writeByte(count);
+ count = 1;
+ eqSlot = it->second;
+ }
+ ++count;
+ }
+ if (itemManager->isEquipSlotVisible(it->second))
+ mClient->raiseUpdateFlags(UPDATEFLAG_LOOKSCHANGE);
+ } while ((++it)->first == invSlot);
+ if (count)
{
- update();
- gameHandler->sendTo(mClient, msg);
- restart();
+ mEqmMsg.writeByte(eqSlot);
+ mEqmMsg.writeByte(count);
}
+ mEqmMsg.writeShort(invSlot);
+ changeEquipment(newCount ? 0 : mPoss->inventory.at(invSlot).itemId,
+ newCount ? mPoss->inventory.at(invSlot).itemId : 0);
}
void Inventory::prepare()
{
- if (!mDelayed)
- {
- return;
- }
- Possessions &poss = mClient->getPossessions();
- if (mPoss == &poss)
- {
- mPoss = new Possessions(poss);
- }
+ if (!mDelayed) return;
+ Possessions *poss = &mClient->getPossessions();
+ if (mPoss == poss)
+ mPoss = new Possessions(*poss);
}
void Inventory::sendFull() const
{
+ /* Sends all the information needed to construct inventory
+ and equipment to the client */
MessageOut m(GPMSG_INVENTORY_FULL);
- for (int i = 0; i < EQUIPMENT_SLOTS; ++i)
+ m.writeShort(mPoss->inventory.size());
+ for (InventoryData::const_iterator l = mPoss->inventory.begin(),
+ l_end = mPoss->inventory.end(); l != l_end; ++l)
{
- if (int id = mPoss->equipment[i])
- {
- m.writeByte(i);
- m.writeShort(id);
- }
+ assert(l->second.itemId);
+ m.writeShort(l->first); // Slot id
+ m.writeShort(l->second.itemId);
+ m.writeShort(l->second.amount);
}
- int slot = EQUIP_CLIENT_INVENTORY;
- for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i)
+ for (EquipData::const_iterator k = mPoss->equipSlots.begin(),
+ k_end = mPoss->equipSlots.end();
+ k != k_end;
+ ++k)
{
- if (i->itemId)
- {
- m.writeByte(slot);
- m.writeShort(i->itemId);
- m.writeByte(i->amount);
- ++slot;
- }
- else
- {
- slot += i->amount;
- }
+ m.writeByte(k->first); // equip slot
+ m.writeShort(k->second); // inventory slot
}
- m.writeByte(255);
- m.writeLong(mPoss->money);
-
gameHandler->sendTo(mClient, m);
}
-void Inventory::initialize()
+void Inventory::initialise()
{
assert(!mDelayed);
- // First, check the equipment and apply its modifiers.
- for (int i = 0; i < EQUIP_PROJECTILE_SLOT; ++i)
- {
- int itemId = mPoss->equipment[i];
- if (!itemId) continue;
- if (ItemClass *ic = ItemManager::getItem(itemId))
- {
- ic->getModifiers().applyAttributes(mClient);
- }
- else
- {
- mPoss->equipment[i] = 0;
- LOG_WARN("Removed unknown item " << itemId << " from equipment "
- "of character " << mClient->getDatabaseID() << '.');
- }
- }
+ InventoryData::iterator it1;
+ EquipData::const_iterator it2, it2_end = mPoss->equipSlots.end();
+ /*
+ * Apply all exists triggers.
+ * Remove unknown inventory items.
+ */
- // Second, remove unknown inventory items.
- int i = 0;
- while (i < (int)mPoss->inventory.size())
- {
- int itemId = mPoss->inventory[i].itemId;
- if (itemId)
- {
- ItemClass *ic = ItemManager::getItem(itemId);
- if (!ic)
- {
- LOG_WARN("Removed unknown item " << itemId << " from inventory"
- " of character " << mClient->getDatabaseID() << '.');
- freeIndex(i);
- continue;
- }
- }
- ++i;
- }
-}
+ ItemIdSet itemIds;
-int Inventory::getItem(int slot) const
-{
- for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i)
+ /*
+ * Construct a set of itemIds to keep track of duplicate itemIds.
+ */
+ for (it1 = mPoss->inventory.begin(); it1 != mPoss->inventory.end(); ++it1)
{
- if (slot == 0)
+ ItemClass *item = itemManager->getItem(it1->second.itemId);
+ if (item)
{
- return i->itemId;
+ if (itemIds.insert(it1->second.itemId).second)
+ item->useTrigger(mClient, ITT_IN_INVY);
}
-
- slot -= i->itemId ? 1 : i->amount;
-
- if (slot < 0)
+ else
{
- return 0;
+ LOG_WARN("Inventory: deleting unknown item type "
+ << it1->second.itemId << " from the inventory of '"
+ << mClient->getName()
+ << "'!");
+ removeFromSlot(it1->first,
+ it1->second.amount);
}
}
- return 0;
-}
-int Inventory::getIndex(int slot) const
-{
- int index = 0;
+ itemIds.clear();
+
+ typedef std::set<unsigned int> SlotSet;
+ SlotSet equipment;
- for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i, ++index)
+ /*
+ * Construct a set of slot references from equipment to keep track of
+ * duplicate slot usage.
+ */
+ for (it2 = mPoss->equipSlots.begin(); it2 != it2_end; ++it2)
{
- if (slot == 0)
+ if (equipment.insert(it2->second).second)
{
- return i->itemId ? index : -1;
+ /*
+ * Perform checks for equipped items - check that all needed slots are available.
+ */
+ // TODO - Not needed for testing everything else right now, but
+ // will be needed for production
+ /*
+ * Apply all equip triggers.
+ */
+ itemManager->getItem(mPoss->inventory.at(it2->second).itemId)
+ ->useTrigger(mClient, ITT_EQUIP);
}
+ }
- slot -= i->itemId ? 1 : i->amount;
+ equipment.clear();
- if (slot < 0)
- {
- return -1;
- }
- }
- return -1;
+ checkSize();
}
-int Inventory::getSlot(int index) const
+void Inventory::checkSize()
{
- int slot = 0;
- for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.begin() + index; i != i_end; ++i)
- {
- slot += i->itemId ? 1 : i->amount;
+ /*
+ * Check that the inventory size is greater than or equal to the size
+ * needed.
+ * If not, forcibly delete (drop?) items from the end until it is.
+ * Check that inventory capacity is greater than or equal to zero.
+ * If not, forcibly delete (drop?) items from the end until it is.
+ */
+ while (mPoss->inventory.size() > INVENTORY_SLOTS
+ || mClient->getModifiedAttribute(ATTR_INV_CAPACITY) < 0) {
+ LOG_WARN("Inventory: oversize inventory! Deleting '"
+ << mPoss->inventory.rbegin()->second.amount
+ << "' items of type '"
+ << mPoss->inventory.rbegin()->second.itemId
+ << "' from slot '"
+ << mPoss->inventory.rbegin()->first
+ << "' of character '"
+ << mClient->getName()
+ << "'!");
+ // FIXME Should probably be dropped rather than deleted.
+ removeFromSlot(mPoss->inventory.rbegin()->first,
+ mPoss->inventory.rbegin()->second.amount);
}
- return slot;
}
-int Inventory::fillFreeSlot(int itemId, int amount, int maxPerSlot)
+unsigned int Inventory::getItem(unsigned int slot) const
{
- int slot = 0;
- for (int i = 0, i_end = mPoss->inventory.size(); i < i_end; ++i)
- {
- InventoryItem &it = mPoss->inventory[i];
- if (it.itemId == 0)
- {
- int nb = std::min(amount, maxPerSlot);
- if (it.amount <= 1)
- {
- it.itemId = itemId;
- it.amount = nb;
- }
- else
- {
- --it.amount;
- InventoryItem iu = { itemId, nb };
- mPoss->inventory.insert(mPoss->inventory.begin() + i, iu);
- ++i_end;
- }
-
- msg.writeByte(slot + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(itemId);
- msg.writeByte(nb);
-
- amount -= nb;
- if (amount == 0)
- {
- return 0;
- }
- }
- ++slot;
- }
-
- while (slot < INVENTORY_SLOTS - 1 && amount > 0)
- {
- int nb = std::min(amount, maxPerSlot);
- amount -= nb;
- InventoryItem it = { itemId, nb };
- mPoss->inventory.push_back(it);
-
- msg.writeByte(slot + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(itemId);
- msg.writeByte(nb);
- ++slot;
- }
-
- return amount;
+ InventoryData::iterator item = mPoss->inventory.find(slot);
+ return item != mPoss->inventory.end() ? item->second.itemId : 0;
}
-int Inventory::insert(int itemId, int amount)
+unsigned int Inventory::insert(unsigned int itemId, unsigned int amount)
{
- if (itemId == 0 || amount == 0)
- {
+ unsigned int maxPerSlot = itemManager->getItem(itemId)->getMaxPerSlot();
+ if (!itemId || !amount)
return 0;
- }
-
prepare();
-
- int maxPerSlot = ItemManager::getItem(itemId)->getMaxPerSlot();
- if (maxPerSlot == 1)
- {
- return fillFreeSlot(itemId, amount, maxPerSlot);
- }
-
- int slot = 0;
- for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i)
- {
- if (i->itemId == itemId && i->amount < maxPerSlot)
+ InventoryData::iterator it, it_end = mPoss->inventory.end();
+ // Add to slots with existing items of this type first.
+ for (it = mPoss->inventory.begin(); it != it_end; ++it)
+ if (it->second.itemId == itemId)
{
- int nb = std::min(maxPerSlot - i->amount, amount);
- i->amount += nb;
- amount -= nb;
-
- msg.writeByte(slot + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(itemId);
- msg.writeByte(i->amount);
-
- if (amount == 0)
- {
+ if (it->second.amount >= maxPerSlot)
+ continue;
+ unsigned short additions = std::min(amount, maxPerSlot)
+ - it->second.amount;
+ amount -= additions;
+ it->second.amount += additions;
+ mInvMsg.writeShort(it->first);
+ mInvMsg.writeShort(itemId);
+ mInvMsg.writeShort(it->second.amount);
+ if (!amount)
return 0;
- }
- ++slot;
}
- else
- {
- slot += i->itemId ? 1 : i->amount;
- }
- }
- return fillFreeSlot(itemId, amount, maxPerSlot);
-}
-
-int Inventory::count(int itemId) const
-{
- int nb = 0;
-
- for (std::vector< InventoryItem >::const_iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i)
+ int slot = 0;
+ // We still have some left, so add to blank slots.
+ for (it = mPoss->inventory.begin();; ++it)
{
- if (i->itemId == itemId)
+ if (!amount)
+ return 0;
+ int lim = it == it_end ? INVENTORY_SLOTS : it->first;
+ while (amount && slot < lim)
{
- nb += i->amount;
+ int additions = std::min(amount, maxPerSlot);
+ mPoss->inventory[slot].itemId = itemId;
+ mPoss->inventory[slot].amount = additions;
+ amount -= additions;
+ mInvMsg.writeShort(slot++); // Last read, so also increment
+ mInvMsg.writeShort(itemId);
+ mInvMsg.writeShort(additions);
}
+ ++slot; // Skip the slot that the iterator points to
+ if (it == it_end) break;
}
- return nb;
-}
-
-bool Inventory::changeMoney(int amount)
-{
- if (amount == 0)
- {
- return true;
- }
-
- int money = mPoss->money + amount;
- if (money < 0)
- {
- return false;
- }
-
- prepare();
+ checkSize();
- mPoss->money = money;
- msg.writeByte(255);
- msg.writeLong(money);
- return true;
+ return amount;
}
-void Inventory::freeIndex(int i)
+unsigned int Inventory::count(unsigned int itemId) const
{
- InventoryItem &it = mPoss->inventory[i];
-
- // Is it the last slot?
- if (i == (int)mPoss->inventory.size() - 1)
- {
- mPoss->inventory.pop_back();
- if (i > 0 && mPoss->inventory[i - 1].itemId == 0)
- {
- mPoss->inventory.pop_back();
- }
- return;
- }
-
- it.itemId = 0;
-
- // First concatenate with an empty slot on the right.
- if (mPoss->inventory[i + 1].itemId == 0)
- {
- it.amount = mPoss->inventory[i + 1].amount + 1;
- mPoss->inventory.erase(mPoss->inventory.begin() + i + 1);
- }
- else
- {
- it.amount = 1;
- }
-
- // Then concatenate with an empty slot on the left.
- if (i > 0 && mPoss->inventory[i - 1].itemId == 0)
- {
- // Note: "it" is no longer a valid reference, hence inventory[i] below.
- mPoss->inventory[i - 1].amount += mPoss->inventory[i].amount;
- mPoss->inventory.erase(mPoss->inventory.begin() + i);
- }
+ unsigned int nb = 0;
+ for (InventoryData::iterator it = mPoss->inventory.begin(),
+ it_end = mPoss->inventory.end();
+ it != it_end; ++it)
+ if (it->second.itemId == itemId)
+ nb += it->second.amount;
+ return nb;
}
-int Inventory::remove(int itemId, int amount)
+unsigned int Inventory::remove(unsigned int itemId, unsigned int amount, bool force)
{
- if (itemId == 0 || amount == 0)
- {
- return 0;
- }
-
prepare();
-
- for (int i = mPoss->inventory.size() - 1; i >= 0; --i)
- {
- InventoryItem &it = mPoss->inventory[i];
- if (it.itemId == itemId)
+ bool inv = false,
+ eq = !itemManager->getItem(itemId)->getItemEquipData().empty();
+ for (InventoryData::iterator it = mPoss->inventory.begin(),
+ it_end = mPoss->inventory.end();
+ it != it_end; ++it)
+ if (it->second.itemId == itemId)
{
- int nb = std::min((int)it.amount, amount);
- it.amount -= nb;
- amount -= nb;
-
- msg.writeByte(getSlot(i) + EQUIP_CLIENT_INVENTORY);
- if (it.amount == 0)
+ if (amount)
{
- msg.writeShort(0);
- freeIndex(i);
+ if (eq)
+ {
+ // If the item is equippable, we have additional checks to make.
+ bool ch = false;
+ for (EquipData::iterator it2 = mPoss->equipSlots.begin(),
+ it2_end = mPoss->equipSlots.end();
+ it2 != it2_end;
+ ++it2)
+ if (it2->second == it->first)
+ {
+ if (force)
+ unequip(it2);
+ else
+ ch = inv = true;
+ break;
+ }
+ if (ch && !force)
+ continue;
+ }
+ unsigned int sub = std::min(amount, it->second.amount);
+ amount -= sub;
+ it->second.amount -= sub;
+ mInvMsg.writeShort(it->first);
+ if (it->second.amount)
+ {
+ mInvMsg.writeShort(it->second.itemId);
+ mInvMsg.writeShort(it->second.amount);
+ // Some still exist, and we have none left to remove, so
+ // no need to run leave invy triggers.
+ if (!amount)
+ return 0;
+ }
+ else
+ {
+ mInvMsg.writeShort(0);
+ mPoss->inventory.erase(it);
+ }
}
else
- {
- msg.writeShort(itemId);
- msg.writeByte(it.amount);
- }
-
- if (amount == 0)
- {
+ // We found an instance of them existing and have none left to
+ // remove, so no need to run leave invy triggers.
return 0;
- }
}
- }
-
- return amount;
+ if (force)
+ itemManager->getItem(itemId)->useTrigger(mClient, ITT_LEAVE_INVY);
+ // Rather inefficient, but still usable for now assuming small invy size.
+ // FIXME
+ return inv && !force ? remove(itemId, amount, true) : amount;
}
-int Inventory::move(int slot1, int slot2, int amount)
+unsigned int Inventory::move(unsigned int slot1, unsigned int slot2, unsigned int amount)
{
- if (amount == 0 || slot1 == slot2 || slot2 >= INVENTORY_SLOTS)
- {
+ if (!amount || slot1 == slot2 || slot2 >= INVENTORY_SLOTS)
return amount;
- }
+ prepare();
+ InventoryData::iterator it1 = mPoss->inventory.find(slot1),
+ it2 = mPoss->inventory.find(slot2),
+ inv_end = mPoss->inventory.end();
- int i1 = getIndex(slot1);
- if (i1 < 0)
- {
+ if (it1 == inv_end)
return amount;
- }
-
- prepare();
- InventoryItem &it1 = mPoss->inventory[i1];
- int i2 = getIndex(slot2);
+ EquipData::iterator it, it_end = mPoss->equipSlots.end();
+ for (it = mPoss->equipSlots.begin();
+ it != it_end;
+ ++it)
+ if (it->second == slot1)
+ // Bad things will happen when you can stack multiple equippable
+ // items in the same slot anyway.
+ it->second = slot2;
+
+ unsigned int nb = std::min(amount, it1->second.amount);
+ if (it2 == inv_end)
+ {
+ // Slot2 does not yet exist.
+ mPoss->inventory[slot2].itemId = it1->second.itemId;
+ nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot(),
+ nb);
+
+ mPoss->inventory[slot2].amount = nb;
+ it1->second.amount -= nb;
+ amount -= nb;
- if (i2 >= 0)
- {
- InventoryItem &it2 = mPoss->inventory[i2];
- if (it1.itemId == it2.itemId)
+ mInvMsg.writeShort(slot1); // Slot
+ if (it1->second.amount)
{
- // Move between two stacks of the same kind.
- int maxPerSlot = ItemManager::getItem(it1.itemId)->getMaxPerSlot();
- int nb = std::min(std::min(amount, (int)it1.amount), maxPerSlot - it2.amount);
- if (nb == 0)
- {
- return amount;
- }
-
- it1.amount -= nb;
- it2.amount += nb;
- amount -= nb;
-
- msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(it2.itemId);
- msg.writeByte(it2.amount);
-
- msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY);
- if (it1.amount == 0)
- {
- msg.writeShort(0);
- freeIndex(i1);
- }
- else
- {
- msg.writeShort(it1.itemId);
- msg.writeByte(it1.amount);
- }
- return amount;
+ mInvMsg.writeShort(it1->second.itemId); // Item Id
+ mInvMsg.writeShort(it1->second.amount); // Amount
}
-
- // Swap between two different stacks.
- if (it1.amount != amount)
+ else
{
- return amount;
+ mInvMsg.writeShort(0);
+ mPoss->inventory.erase(it1);
}
-
- std::swap(it1, it2);
-
- msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(it1.itemId);
- msg.writeByte(it1.amount);
- msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(it2.itemId);
- msg.writeByte(it2.amount);
- return 0;
- }
-
- // Move some items to an empty slot.
- int id = it1.itemId;
- int nb = std::min((int)it1.amount, amount);
- it1.amount -= nb;
- amount -= nb;
-
- msg.writeByte(slot1 + EQUIP_CLIENT_INVENTORY);
- if (it1.amount == 0)
- {
- msg.writeShort(0);
- freeIndex(i1);
+ mInvMsg.writeShort(slot2); // Slot
+ mInvMsg.writeShort(it1->second.itemId); // Item Id (same as slot 1)
+ mInvMsg.writeShort(nb); // Amount
}
else
{
- msg.writeShort(id);
- msg.writeByte(it1.amount);
- }
-
- // Fill second slot.
- msg.writeByte(slot2 + EQUIP_CLIENT_INVENTORY);
- msg.writeShort(id);
- msg.writeByte(nb);
-
- for (std::vector< InventoryItem >::iterator i = mPoss->inventory.begin(),
- i_end = mPoss->inventory.end(); i != i_end; ++i)
- {
- if (i->itemId)
- {
- --slot2;
- continue;
- }
-
- if (slot2 >= i->amount)
- {
- slot2 -= i->amount;
- continue;
- }
-
- assert(slot2 >= 0 && i + 1 != i_end);
+ // Slot2 exists.
+ if (it2->second.itemId != it1->second.itemId)
+ return amount; // Cannot stack items of a different type.
+ nb = std::min(itemManager->getItem(it1->second.itemId)->getMaxPerSlot()
+ - it2->second.amount,
+ nb);
- if (i->amount == 1)
- {
- // One single empty slot in the range.
- i->itemId = id;
- i->amount = nb;
- return amount;
- }
-
- InventoryItem it = { id, nb };
- --i->amount;
+ it1->second.amount -= nb;
+ it2->second.amount += nb;
+ amount -= nb;
- if (slot2 == 0)
+ mInvMsg.writeShort(slot1); // Slot
+ if (it1->second.amount)
{
- // First slot in an empty range.
- mPoss->inventory.insert(i, it);
- return amount;
+ mInvMsg.writeShort(it1->second.itemId); // Item Id
+ mInvMsg.writeShort(it1->second.amount); // Amount
}
-
- if (slot2 == i->amount)
+ else
{
- // Last slot in an empty range.
- mPoss->inventory.insert(i + 1, it);
- return amount;
+ mInvMsg.writeShort(0);
+ mPoss->inventory.erase(it1);
}
-
- InventoryItem it3 = { 0, slot2 };
- i->amount -= slot2;
- i = mPoss->inventory.insert(i, it);
- mPoss->inventory.insert(i, it3);
- return amount;
+ mInvMsg.writeShort(slot2); // Slot
+ mInvMsg.writeShort(it2->second.itemId); // Item Id
+ mInvMsg.writeShort(it2->second.amount); // Amount
}
-
- // The second slot does not yet exist.
- assert(slot2 >= 0);
- if (slot2 != 0)
- {
- InventoryItem it = { 0, slot2 };
- mPoss->inventory.insert(mPoss->inventory.end(), it);
- }
- InventoryItem it = { id, nb };
- mPoss->inventory.insert(mPoss->inventory.end(), it);
return amount;
}
-int Inventory::removeFromSlot(int slot, int amount)
+unsigned int Inventory::removeFromSlot(unsigned int slot, unsigned int amount)
{
- if (amount == 0)
- {
- return 0;
- }
-
- int i = getIndex(slot);
- if (i < 0)
- {
+ prepare();
+ InventoryData::iterator it = mPoss->inventory.find(slot);
+ if (it == mPoss->inventory.end())
return amount;
+ unequip(slot);
+ {
+ bool exists = false;
+ for (InventoryData::const_iterator it2 = mPoss->inventory.begin(),
+ it2_end = mPoss->inventory.end();
+ it2 != it2_end;
+ ++it2)
+ if (it2->second.itemId == it->second.itemId
+ && it->first != it2->first)
+ {
+ exists = true;
+ break;
+ }
+ if (!exists && it->second.itemId)
+ itemManager->getItem(it->second.itemId)
+ ->useTrigger(mClient, ITT_LEAVE_INVY);
}
-
- prepare();
-
- InventoryItem &it = mPoss->inventory[i];
- int nb = std::min((int)it.amount, amount);
- it.amount -= nb;
- amount -= nb;
-
- msg.writeByte(slot + EQUIP_CLIENT_INVENTORY);
- if (it.amount == 0)
+ unsigned int sub = std::min(amount, it->second.amount);
+ amount -= sub;
+ it->second.amount -= sub;
+ mInvMsg.writeShort(it->first);
+ if (it->second.amount)
{
- msg.writeShort(0);
- freeIndex(i);
+ mInvMsg.writeShort(it->second.itemId);
+ mInvMsg.writeShort(it->second.amount);
}
else
{
- msg.writeShort(it.itemId);
- msg.writeByte(it.amount);
+ mInvMsg.writeShort(0);
+ mPoss->inventory.erase(it);
}
-
return amount;
}
-void Inventory::replaceInSlot(int slot, int itemId, int amount)
-{
- int i = getIndex(slot);
- assert(i >= 0);
- prepare();
-
- msg.writeByte(slot + EQUIP_CLIENT_INVENTORY);
- if (itemId == 0 || amount == 0)
- {
- msg.writeShort(0);
- freeIndex(i);
- }
- else
- {
- InventoryItem &it = mPoss->inventory[i];
- it.itemId = itemId;
- it.amount = amount;
- msg.writeShort(itemId);
- msg.writeByte(amount);
- }
-}
-void Inventory::changeEquipment(int slot, int itemId)
+void Inventory::changeEquipment(unsigned int oldId, unsigned int newId)
{
- // FIXME: Changes are applied now, so it does not work in delayed mode.
- assert(!mDelayed);
-
- int oldId = mPoss->equipment[slot];
- if (oldId == itemId)
- {
+ if (!oldId && !newId)
return;
- }
-
- if (oldId)
- {
- ItemManager::getItem(oldId)->getModifiers().cancelAttributes(mClient);
- }
-
- if (itemId)
- {
- ItemManager::getItem(itemId)->getModifiers().applyAttributes(mClient);
- }
-
- msg.writeByte(slot);
- msg.writeShort(itemId);
- mPoss->equipment[slot] = itemId;
- mChangedLook = true;
-
- //mark evade as modified because it depends on equipment weight
- mClient->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 0ca49aa..f2168c2 100644
--- a/src/game-server/inventory.hpp
+++ b/src/game-server/inventory.hpp
@@ -24,10 +24,10 @@
#include "game-server/character.hpp"
#include "net/messageout.hpp"
-enum
+/*enum
{
// Equipment rules:
-// 1 Brest equipment
+// 1 torso equipment
EQUIP_TORSO_SLOT = 0,
// 1 arms equipment
EQUIP_ARMS_SLOT = 1,
@@ -53,9 +53,9 @@ enum
EQUIP_PROJECTILE_SLOT = 10,
EQUIP_CLIENT_INVENTORY = 32
-};
+};*/
-class GameClient;
+class ItemClass;
/**
* Class used to handle Character possessions and prepare outgoing messages.
@@ -66,28 +66,33 @@ class Inventory
/**
* Creates a view on the possessions of a character.
- * @param delayed true if changes have to be cancelable.
+ * @param delayed If the changes need to be cancelable.
*/
Inventory(Character *, bool delayed = false);
/**
- * Commits delayed changes.
+ * Commits delayed changes if applicable.
* Sends the update message to the client.
*/
~Inventory();
/**
- * Commits delayed changes.
+ * Commits changes.
+ * Exclusive to delayed mode.
+ * @param doRestart Whether to prepare the inventory for more changes
+ after this. If you are unsure, it is safe (though not
+ terribly efficient) to leave this as true.
*/
- void commit();
+ void commit(bool doRestart = true);
/**
- * Cancels delayed changes.
+ * Cancels changes.
+ * Exclusive to delayed mode.
*/
void cancel();
/**
- * Sends a complete inventory update to the client.
+ * Sends complete inventory status to the client.
*/
void sendFull() const;
@@ -95,117 +100,110 @@ class Inventory
* Ensures the inventory is sane and apply equipment modifiers.
* Should be run only once and the very first time.
*/
- void initialize();
+ void initialise();
/**
* Equips item from given inventory slot.
+ * @param slot The slot in which the target item is in.
+ * @param override Whether this item can unequip other items to equip
+ * itself. If true, items that are unequipped will be
+ * attempted to be reequipped, but with override disabled.
+ * @returns whether the item could be equipped.
*/
- void equip(int slot);
+ bool equip(int slot, bool override = true);
/**
* Unequips item from given equipment slot.
+ * @param it Starting iterator. When the only parameter, also extracts
+ * slot number from it.
+ * Used so that when we already have an iterator to the first
+ * occurence from a previous operation we can start from
+ * there.
+ * @returns Whether it was unequipped.
*/
- void unequip(int slot);
-
- /**
- * Gets the ID of projectiles. Removes one of these projectiles from
- * inventory.
- */
- int fireProjectile();
+ bool unequip(EquipData::iterator it);
+ bool unequip(unsigned int slot, EquipData::iterator *itp = 0);
/**
* Inserts some items into the inventory.
* @return number of items not inserted (to be dropped on floor?).
*/
- int insert(int itemId, int amount);
+ unsigned int insert(unsigned int itemId, unsigned int amount);
/**
* Removes some items from inventory.
+ * @param force If set to true, also remove any equipment encountered
* @return number of items not removed.
*/
- int remove(int itemId, int amount);
+ unsigned int remove(unsigned int itemId, unsigned int amount, bool force = false);
/**
* Moves some items from the first slot to the second one.
* @returns number of items not moved.
*/
- int move(int slot1, int slot2, int amount);
+ unsigned int move(unsigned int slot1, unsigned int slot2, unsigned int amount);
/**
* Removes some items from inventory.
* @return number of items not removed.
*/
- int removeFromSlot(int slot, int amount);
+ unsigned int removeFromSlot(unsigned int slot, unsigned int amount);
/**
* Counts number of items with given ID.
*/
- int count(int itemId) const;
+ unsigned int count(unsigned int itemId) const;
/**
* Gets the ID of the items in a given slot.
*/
- int getItem(int slot) const;
-
- /**
- * Changes amount of money.
- * @return false if not enough money.
- */
- bool changeMoney(int);
+ unsigned int getItem(unsigned int slot) const;
private:
/**
- * Ensures we are working on a copy in delayed mode.
+ * Make sure that changes are being done on a copy, not directly.
+ * No effect when not in delayed mode.
*/
void prepare();
/**
- * Updates the original in delayed mode.
- */
- void update();
-
- /**
* Starts a new notification message.
*/
void restart();
- /**
- * Fills some slots with items.
- * @return number of items not inserted.
- */
- int fillFreeSlot(int itemId, int amount, int MaxPerSlot);
-
- /**
- * Frees an inventory slot given by its real index.
- */
- void freeIndex(int index);
/**
- * Gets the real index associated to a slot.
+ * Check the inventory is within the slot limit and capacity.
+ * Forcibly delete items from the end if it is not.
+ * @todo Drop items instead?
*/
- int getIndex(int slot) const;
+ void checkSize();
/**
- * Gets the slot number of an inventory index.
+ * Helper function for equip() when computing changes to equipment
+ * When newCount is 0, the item is being unequipped.
*/
- int getSlot(int index) const;
-
- /**
- * Replaces a whole slot of items from inventory.
- */
- void replaceInSlot(int slot, int itemId, int amount);
+ // inventory slot -> {equip slots}
+ typedef std::multimap<unsigned int, unsigned short> IdSlotMap;
+ void equip_sub(unsigned int newCount, IdSlotMap::const_iterator &it);
/**
* Changes equipment and adjusts character attributes.
*/
- void changeEquipment(int slot, int itemId);
+ void changeEquipment(unsigned int oldId, unsigned int itemId);
+ void changeEquipment(ItemClass *oldI, ItemClass *newI);
Possessions *mPoss; /**< Pointer to the modified possessions. */
- MessageOut msg; /**< Update message containing all the changes. */
+ /**
+ * Update message containing inventory changes.
+ * Note that in sendFull(), this is reused to send all full changes
+ * (for both inventory and equipment)
+ */
+ MessageOut mInvMsg;
+ MessageOut mEqmMsg; /**< Update message containing equipment changes */
Character *mClient; /**< Character to notify. */
bool mDelayed; /**< Delayed changes. */
- bool mChangedLook; /**< Need to notify of a visible equipment change. */
};
diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp
index 91d847d..95ee973 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<const std::string, ItemType> table;
-
- if (table.empty())
- {
- table["generic"] = ITEM_UNUSABLE;
- table["usable"] = ITEM_USABLE;
- table["equip-1hand"] = ITEM_EQUIPMENT_ONE_HAND_WEAPON;
- table["equip-2hand"] = ITEM_EQUIPMENT_TWO_HANDS_WEAPON;
- table["equip-torso"] = ITEM_EQUIPMENT_TORSO;
- table["equip-arms"] = ITEM_EQUIPMENT_ARMS;
- table["equip-head"] = ITEM_EQUIPMENT_HEAD;
- table["equip-legs"] = ITEM_EQUIPMENT_LEGS;
- table["equip-shield"] = ITEM_EQUIPMENT_SHIELD;
- table["equip-ring"] = ITEM_EQUIPMENT_RING;
- table["equip-necklace"] = ITEM_EQUIPMENT_NECKLACE;
- table["equip-feet"] = ITEM_EQUIPMENT_FEET;
- table["equip-ammo"] = ITEM_EQUIPMENT_AMMO;
- table["hairsprite"] = ITEM_HAIRSPRITE;
- table["racesprite"] = ITEM_RACESPRITE;
- }
-
- std::map<const std::string, ItemType>::iterator val = table.find(name);
-
- return val == table.end() ? ITEM_UNKNOWN : (*val).second;
+ LOG_WARN("Virtual defintion used in effect application!");
+ return false;
}
-int ItemModifiers::getValue(int type) const
+bool ItemEffectAttrMod::apply(Being *itemUser)
{
- for (std::vector< ItemModifier >::const_iterator i = mModifiers.begin(),
- i_end = mModifiers.end(); i != i_end; ++i)
- {
- if (i->type == type) return i->value;
- }
- return 0;
+ LOG_DEBUG("Applying modifier.");
+ itemUser->applyModifier(mAttributeId, mMod, mAttributeLayer,
+ mDuration, mId);
+ return false;
}
-int ItemModifiers::getAttributeValue(int attr) const
+void ItemEffectAttrMod::dispell(Being *itemUser)
{
- return getValue(MOD_ATTRIBUTE + attr);
+ LOG_DEBUG("Dispelling modifier.");
+ itemUser->removeModifier(mAttributeId, mMod, mAttributeLayer,
+ mId, mDuration);
}
-void ItemModifiers::setValue(int type, int value)
+bool ItemEffectAutoAttack::apply(Being *itemUser)
{
- if (value)
- {
- ItemModifier m;
- m.type = type;
- m.value = value;
- mModifiers.push_back(m);
- }
+ // TODO - STUB
+ return false;
}
-void ItemModifiers::setAttributeValue(int attr, int value)
+void ItemEffectAutoAttack::dispell(Being *itemUser)
{
- setValue(MOD_ATTRIBUTE + attr, value);
+ // TODO
}
-void ItemModifiers::applyAttributes(Being *b) const
+bool 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<std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator,
+ std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator>
+ rn = mEffects.equal_range(trigger);
+ bool ret = false;
+ while (rn.first != rn.second)
+ if (rn.first++->second->apply(itemUser))
+ ret = true;
+
+ rn = mDispells.equal_range(trigger);
+ while (rn.first != rn.second)
+ rn.first++->second->dispell(itemUser);
+
+ return ret;
}
@@ -147,8 +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 98d1fd8..6fb7c38 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.
@@ -176,46 +153,19 @@ class ItemClass
{ return mWeight; }
/**
- * Sets item weight.
- */
- void setWeight(int weight)
- { mWeight = weight; }
-
- /**
* Gets unit cost of these items.
*/
int getCost() const
{ return mCost; }
/**
- * Sets unit cost of these items.
- */
- void setCost(int cost)
- { mCost = cost; }
-
- /**
* Gets max item per slot.
*/
- int getMaxPerSlot() const
+ unsigned int getMaxPerSlot() const
{ return mMaxPerSlot; }
- /**
- * Sets max item per slot.
- */
- void setMaxPerSlot(int perSlot)
- { mMaxPerSlot = perSlot; }
-
- /**
- * Gets item modifiers.
- */
- const ItemModifiers &getModifiers() const
- { return mModifiers; }
-
- /**
- * Sets item modifiers.
- */
- void setModifiers(const ItemModifiers &modifiers)
- { mModifiers = modifiers; }
+ bool hasTrigger(ItemTriggerType id)
+ { return mEffects.count(id); }
/**
* Gets database ID.
@@ -224,49 +174,73 @@ class ItemClass
{ return mDatabaseID; }
/**
- * Sets the sprite ID.
- */
- void setSpriteID(int spriteID)
- { mSpriteID = spriteID; }
-
- /**
* Gets the sprite ID.
+ * @note At present this is only a stub, and will always return zero.
+ * When you would want to extend serializeLooks to be more
+ * efficient, keep track of a sprite id here.
*/
int getSpriteID() const
{ return mSpriteID; }
/**
- * Sets the script that is to be used
+ * Returns equip requirements.
*/
- void setScript(Script *s)
- { mScript = s; }
+ const ItemEquipsInfo &getItemEquipData() const { return mEquip; }
- /**
- * Set attack range (only needed when the item is a weapon)
- */
- void setAttackRange(unsigned range) { mAttackRange = range; }
+
+ private:
/**
- * Gets attack zone of weapon (returns NULL for non-weapon items)
+ * Add an effect to a trigger
+ * @param effect The effect to be run when the trigger is hit.
+ * @param id The trigger type.
+ * @param dispell The trigger that the effect should be dispelled on.
+ * @note FIXME: Should be more than one trigger that an effect
+ * can be dispelled from.
*/
- const unsigned getAttackRange() const
- { return mAttackRange ; }
-
-
- private:
- Script *mScript; /**< Script for using items */
+ void addEffect(ItemEffectInfo *effect,
+ ItemTriggerType id,
+ ItemTriggerType dispell = ITT_NULL)
+ {
+ mEffects.insert(std::make_pair(id, effect));
+ if (dispell)
+ mDispells.insert(std::make_pair(dispell, effect));
+ }
+
+ void resetEffects()
+ {
+ while (mEffects.begin() != mEffects.end())
+ {
+ delete mEffects.begin()->second;
+ mEffects.erase(mEffects.begin());
+ }
+ while (mDispells.begin() != mDispells.end())
+ {
+ delete mDispells.begin()->second;
+ mDispells.erase(mDispells.begin());
+ }
+ }
unsigned short mDatabaseID; /**< Item reference information */
/** The sprite that should be shown to the character */
unsigned short mSpriteID;
- ItemType mType; /**< Type: usable, equipment etc. */
unsigned short mWeight; /**< Weight of the item. */
unsigned short mCost; /**< Unit cost the item. */
/** Max item amount per slot in inventory. */
- unsigned short mMaxPerSlot;
+ unsigned int mMaxPerSlot;
+
+ std::multimap< ItemTriggerType, ItemEffectInfo * > mEffects;
+ std::multimap< ItemTriggerType, ItemEffectInfo * > mDispells;
+
+ /**
+ * List of list of requirements for equipping. Only one inner list
+ * need be satisfied to sucessfully equip. Checks occur in order
+ * from outer front to back.
+ * All conditions in an inner list must be met for success.
+ */
+ ItemEquipsInfo mEquip;
- ItemModifiers mModifiers; /**< Item modifiers. */
- unsigned mAttackRange; /**< Attack range when used as a weapon */
+ friend class ItemManager;
};
/**
diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp
index c175044..363ada4 100644
--- a/src/game-server/itemmanager.cpp
+++ b/src/game-server/itemmanager.cpp
@@ -22,6 +22,7 @@
#include "defines.h"
#include "common/resourcemanager.hpp"
+#include "game-server/attributemanager.hpp"
#include "game-server/item.hpp"
#include "game-server/skillmanager.hpp"
#include "scripting/script.hpp"
@@ -32,27 +33,82 @@
#include <set>
#include <sstream>
-typedef std::map< int, ItemClass * > ItemClasses;
-static ItemClasses itemClasses; /**< Item reference */
-static std::string itemReferenceFile;
-static unsigned int itemDatabaseVersion = 0; /**< Version of the loaded items database file.*/
-
-void ItemManager::initialize(const std::string &file)
+void ItemManager::initialize()
{
- itemReferenceFile = file;
+ mVisibleEquipSlotCount = 0;
reload();
}
void ItemManager::reload()
{
- std::string absPathFile = ResourceManager::resolve(itemReferenceFile);
+ std::string absPathFile;
+ xmlNodePtr rootNode;
+
+ // ####################################################################
+ // ### Load the equip slots that a character has available to them. ###
+ // ####################################################################
+
+ absPathFile = ResourceManager::resolve(mEquipCharSlotReferenceFile);
+ if (absPathFile.empty()) {
+ LOG_ERROR("Item Manager: Could not find " << 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<std::string, unsigned int>
+ (name, count));
+ totalCount += count;
+ ++slotCount;
+ }
+ }
+ }
+ LOG_INFO("Loaded '" << slotCount << "' slot types with '"
+ << totalCount << "' slots.");
+ }
+
+ // ####################################
+ // ### Load the main item database. ###
+ // ####################################
+
+ absPathFile = ResourceManager::resolve(mItemReferenceFile);
if (absPathFile.empty()) {
- LOG_ERROR("Item Manager: Could not find " << 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 #"<<id);
+ std::pair< ItemTriggerType, ItemTriggerType> triggerTypes;
+ {
+ std::string triggerName = XML::getProperty(subnode, "trigger", ""),
+ dispellTrigger = XML::getProperty(subnode, "dispell", "");
+ // label -> { trigger (apply), trigger (cancel (default)) }
+ // The latter can be overridden.
+ static std::map<const std::string,
+ std::pair<ItemTriggerType, ItemTriggerType> >
+ triggerTable;
+ if (triggerTable.empty())
+ {
+ /*
+ * The following is a table of all triggers for item
+ * effects.
+ * The first element defines the trigger used for this
+ * trigger, and the second defines the default
+ * trigger to use for dispelling.
+ */
+ triggerTable["existence"].first = ITT_IN_INVY;
+ triggerTable["existence"].second = ITT_LEAVE_INVY;
+ triggerTable["activation"].first = ITT_ACTIVATE;
+ triggerTable["activation"].second = ITT_NULL;
+ triggerTable["equip"].first = ITT_EQUIP;
+ triggerTable["equip"].second = ITT_UNEQUIP;
+ triggerTable["leave-inventory"].first = ITT_LEAVE_INVY;
+ triggerTable["leave-inventory"].second = ITT_NULL;
+ triggerTable["unequip"].first = ITT_UNEQUIP;
+ triggerTable["unequip"].second = ITT_NULL;
+ triggerTable["equip-change"].first = ITT_EQUIPCHG;
+ triggerTable["equip-change"].second = ITT_NULL;
+ triggerTable["null"].first = ITT_NULL;
+ triggerTable["null"].second = ITT_NULL;
+ }
+ std::map<const std::string, std::pair<ItemTriggerType,
+ ItemTriggerType> >::iterator
+ it = triggerTable.find(triggerName);
+
+ if (it == triggerTable.end()) {
+ LOG_WARN("Item Manager: Unable to find effect trigger type \""
+ << triggerName << "\", skipping!");
+ continue;
+ }
+ triggerTypes = it->second;
+ if (!dispellTrigger.empty())
+ {
+ if ((it = triggerTable.find(dispellTrigger))
+ == triggerTable.end())
+ LOG_WARN("Item Manager: Unable to find dispell effect "
+ "trigger type \"" << dispellTrigger << "\"!");
+ else
+ triggerTypes.second = it->second.first;
+ }
+ }
+ for_each_xml_child_node(effectnode, subnode)
+ {
+ if (xmlStrEqual(effectnode->name, BAD_CAST "modifier"))
+ {
+ std::string tag = XML::getProperty(effectnode, "attribute", "");
+ if (tag.empty())
+ {
+ LOG_WARN("Item Manager: Warning, modifier found "
+ "but no attribute specified!");
+ continue;
+ }
+ unsigned int duration = XML::getProperty(effectnode,
+ "duration",
+ 0);
+ std::pair<unsigned int, unsigned int> info = attributeManager->getInfoFromTag(tag);
+ double value = XML::getFloatProperty(effectnode, "value", 0.0);
+ item->addEffect(new ItemEffectAttrMod(info.first,
+ info.second,
+ value, id,
+ duration),
+ triggerTypes.first, triggerTypes.second);
+ }
+ else if (xmlStrEqual(effectnode->name, BAD_CAST "autoattack"))
+ {
+ // TODO - URGENT
+ }
+ // Having a dispell for the next three is nonsensical.
+ else if (xmlStrEqual(effectnode->name, BAD_CAST "cooldown"))
+ {
+ LOG_WARN("Item Manager: Cooldown property not implemented yet!");
+ // TODO: Also needs unique items before this action will work
+ }
+ else if (xmlStrEqual(effectnode->name, BAD_CAST "g-cooldown"))
+ {
+ LOG_WARN("Item Manager: G-Cooldown property not implemented yet!");
+ // TODO
+ }
+ else if (xmlStrEqual(effectnode->name, BAD_CAST "consumes"))
+ item->addEffect(new ItemEffectConsumes(), triggerTypes.first);
+ }
}
+ // 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 8b9b64b..ea0641c 100644
--- a/src/game-server/itemmanager.hpp
+++ b/src/game-server/itemmanager.hpp
@@ -22,35 +22,71 @@
#define ITEMMANAGER_H
#include <string>
+#include <map>
+#include <vector>
class ItemClass;
-namespace ItemManager
+class ItemManager
{
- /**
- * Loads item reference file.
- */
- void initialize(const std::string &);
-
- /**
- * Reloads item reference file.
- */
- void reload();
-
- /**
- * Destroy item classes.
- */
- void deinitialize();
-
- /**
- * Gets the ItemClass having the given ID.
- */
- ItemClass *getItem(int itemId);
-
- /**
- * Gets the version of the loaded item database.
- */
- unsigned getDatabaseVersion();
-}
+ public:
+ ItemManager(const std::string &itemFile, const std::string &equipFile) :
+ mItemReferenceFile(itemFile),
+ mEquipCharSlotReferenceFile(equipFile),
+ mItemDatabaseVersion(0) {}
+ /**
+ * Loads item reference file.
+ */
+ void initialize();
+
+ /**
+ * Reloads item reference file.
+ */
+ void reload();
+
+ /**
+ * Destroy item classes.
+ */
+ void deinitialize();
+
+ /**
+ * Gets the ItemClass having the given ID.
+ */
+ ItemClass *getItem(int itemId) const;
+
+ /**
+ * Gets the version of the loaded item database.
+ */
+ unsigned int getDatabaseVersion() const;
+
+ const std::string &getEquipNameFromId(unsigned int id) const;
+
+ unsigned int getEquipIdFromName(const std::string &name) const;
+
+ unsigned int getMaxSlotsFromId(unsigned int id) const;
+
+ unsigned int getVisibleSlotCount() const;
+
+ bool isEquipSlotVisible(unsigned int id) const;
+
+ private:
+ typedef std::map< int, ItemClass * > ItemClasses;
+ // Map a string (name of slot) with (str-id, max-per-equip-slot)
+ typedef std::vector< std::pair< std::string, unsigned int > > EquipSlots;
+ // Reference to the vector position of equipSlots
+ typedef std::vector< unsigned int > VisibleEquipSlots;
+
+ ItemClasses itemClasses; /**< Item reference */
+ EquipSlots equipSlots;
+ VisibleEquipSlots visibleEquipSlots;
+
+ std::string mItemReferenceFile;
+ std::string mEquipCharSlotReferenceFile;
+ mutable unsigned int mVisibleEquipSlotCount; // Cache
+
+ unsigned int mItemDatabaseVersion; /**< Version of the loaded items database file.*/
+};
+
+extern ItemManager *itemManager;
#endif
diff --git a/src/game-server/main-game.cpp b/src/game-server/main-game.cpp
index 0901ce1..405ec68 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 80a0646..83a7306 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 ffb178d..456c4c8 100644
--- a/src/game-server/monster.cpp
+++ b/src/game-server/monster.cpp
@@ -22,6 +22,7 @@
#include "common/configuration.hpp"
#include "common/resourcemanager.hpp"
+#include "game-server/attributemanager.hpp"
#include "game-server/character.hpp"
#include "game-server/collisiondetection.hpp"
#include "game-server/item.hpp"
@@ -29,6 +30,7 @@
#include "game-server/state.hpp"
#include "scripting/script.hpp"
#include "utils/logger.h"
+#include "utils/speedconv.hpp"
#include <cmath>
@@ -69,19 +71,39 @@ Monster::Monster(MonsterClass *specy):
{
LOG_DEBUG("Monster spawned!");
- // get basic attributes from monster database
+ /*
+ * Initialise the attribute structures.
+ */
+
+ const AttributeScopes &mobAttr = attributeManager->getAttributeInfoForType(ATTR_MOB);
+
+ for (AttributeScopes::const_iterator it = mobAttr.begin(),
+ it_end = mobAttr.end();
+ it != it_end;
+ ++it)
+ mAttributes.insert(std::pair< unsigned int, Attribute >
+ (it->first, Attribute(*it->second)));
+
+ /*
+ * Set the attributes to the values defined by the associated monster
+ * class with or without mutations as needed.
+ */
+
int mutation = specy->getMutation();
- for (int i = BASE_ATTR_BEGIN; i < BASE_ATTR_END; i++)
+
+ for (AttributeMap::iterator it2 = mAttributes.begin(),
+ it2_end = mAttributes.end();
+ it2 != it2_end;
+ ++it2)
{
- float attr = (float)specy->getAttribute(i);
- if (mutation)
- {
- attr *= (100 + (rand()%(mutation * 2)) - mutation) / 100.0f;
- }
- setAttribute(i, (int)std::ceil(attr));
+ double attr = specy->getAttribute(it2->first);
+ setAttribute(it2->first,
+ mutation ?
+ attr * (100 + (rand()%(mutation << 1)) - mutation) / 100.0 :
+ attr);
}
- setSpeed(specy->getSpeed()); // Put in tiles per second.
+ setAttribute(ATTR_MOVE_SPEED_RAW, utils::tpsToSpeed(getAttribute(ATTR_MOVE_SPEED_TPS))); // Put in tiles per second.
setSize(specy->getSize());
// Set positions relative to target from which the monster can attack
@@ -118,36 +140,41 @@ Monster::~Monster()
void Monster::perform()
{
- if (mAction == ATTACK && mCurrentAttack && mTarget)
+ if (mAction == ATTACK)
{
- if (!isTimerRunning(T_B_ATTACK_TIME))
+ if (mTarget)
{
- setTimerHard(T_B_ATTACK_TIME, mCurrentAttack->aftDelay + mCurrentAttack->preDelay);
- Damage damage;
- damage.base = (int) (getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN) * mCurrentAttack->damageFactor);
- damage.delta = (int) (getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA) * mCurrentAttack->damageFactor);
- damage.cth = getModifiedAttribute(BASE_ATTR_HIT);
- damage.element = mCurrentAttack->element;
- damage.type = mCurrentAttack->type;
-
- int hit = performAttack(mTarget, mCurrentAttack->range, damage);
-
- if (! mCurrentAttack->scriptFunction.empty()
- && mScript
- && hit > -1)
+ if (mCurrentAttack)
{
- mScript->setMap(getMap());
- mScript->prepare(mCurrentAttack->scriptFunction);
- mScript->push(this);
- mScript->push(mTarget);
- mScript->push(hit);
- mScript->execute();
+ if (!isTimerRunning(T_M_ATTACK_TIME))
+ {
+ setTimerHard(T_M_ATTACK_TIME, mCurrentAttack->aftDelay + mCurrentAttack->preDelay);
+ Damage dmg(getModifiedAttribute(MOB_ATTR_PHY_ATK_MIN) *
+ mCurrentAttack->damageFactor,
+ getModifiedAttribute(MOB_ATTR_PHY_ATK_DELTA) *
+ mCurrentAttack->damageFactor,
+ getModifiedAttribute(ATTR_ACCURACY),
+ mCurrentAttack->element,
+ mCurrentAttack->type,
+ mCurrentAttack->range);
+
+ int hit = performAttack(mTarget, mCurrentAttack->range, dmg);
+
+ if (! mCurrentAttack->scriptFunction.empty()
+ && mScript
+ && hit > -1)
+ {
+ mScript->setMap(getMap());
+ mScript->prepare(mCurrentAttack->scriptFunction);
+ mScript->push(this);
+ mScript->push(mTarget);
+ mScript->push(hit);
+ mScript->execute();
+ }
+ }
}
- }
- }
- if (mAction == ATTACK && !mTarget)
- {
- setAction(STAND);
+ } else
+ setAction(STAND);
}
}
@@ -178,7 +205,7 @@ void Monster::update()
}
// Cancel the rest when we are currently performing an attack
- if (isTimerRunning(T_B_ATTACK_TIME)) return;
+ if (isTimerRunning(T_M_ATTACK_TIME)) return;
// Check potential attack positions
Being *bestAttackTarget = mTarget = NULL;
diff --git a/src/game-server/monster.hpp b/src/game-server/monster.hpp
index e1737da..86a69ac 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<int> mAttributes; /**< Base attributes of the monster. */
+ std::map<unsigned int, double> mAttributes; /**< Base attributes of the monster. */
float mSpeed; /**< The monster class speed in tiles per second */
int mSize;
int mExp;
@@ -200,6 +194,8 @@ class MonsterClass
int mOptimalLevel;
MonsterAttacks mAttacks;
std::string mScript;
+
+ friend class MonsterManager;
};
/**
@@ -212,7 +208,7 @@ struct AttackPosition
x(posX),
y(posY),
direction(dir)
- {};
+ {}
int x;
int y;
diff --git a/src/game-server/monstermanager.cpp b/src/game-server/monstermanager.cpp
index 3ad033f..d754028 100644
--- a/src/game-server/monstermanager.cpp
+++ b/src/game-server/monstermanager.cpp
@@ -21,17 +21,12 @@
#include "game-server/monstermanager.hpp"
#include "common/resourcemanager.hpp"
+#include "game-server/attributemanager.hpp"
#include "game-server/itemmanager.hpp"
#include "game-server/monster.hpp"
#include "utils/logger.h"
#include "utils/xml.hpp"
-#include <map>
-
-typedef std::map< int, MonsterClass * > MonsterClasses;
-static MonsterClasses monsterClasses; /**< Monster reference */
-static std::string monsterReferenceFile;
-
Element elementFromString (const std::string &name)
{
static std::map<const std::string, Element> table;
@@ -54,17 +49,16 @@ Element elementFromString (const std::string &name)
return val == table.end() ? ELEMENT_ILLEGAL : (*val).second;
}
-void MonsterManager::initialize(const std::string &file)
+void MonsterManager::initialize()
{
- monsterReferenceFile = file;
reload();
}
void MonsterManager::reload()
{
- std::string absPathFile = ResourceManager::resolve(monsterReferenceFile);
+ std::string absPathFile = ResourceManager::resolve(mMonsterReferenceFile);
if (absPathFile.empty()) {
- LOG_ERROR("Monster Manager: Could not find " << monsterReferenceFile << "!");
+ LOG_ERROR("Monster Manager: Could not find " << mMonsterReferenceFile << "!");
return;
}
@@ -92,16 +86,16 @@ void MonsterManager::reload()
{
LOG_WARN("Monster Manager: There is a monster ("
<< name << ") without ID in "
- << monsterReferenceFile << "! It has been ignored.");
+ << mMonsterReferenceFile << "! It has been ignored.");
continue;
}
MonsterClass *monster;
- MonsterClasses::iterator i = monsterClasses.find(id);
- if (i == monsterClasses.end())
+ MonsterClasses::iterator i = mMonsterClasses.find(id);
+ if (i == mMonsterClasses.end())
{
monster = new MonsterClass(id);
- monsterClasses[id] = monster;
+ mMonsterClasses[id] = monster;
}
else
{
@@ -117,7 +111,7 @@ void MonsterManager::reload()
if (xmlStrEqual(subnode->name, BAD_CAST "drop"))
{
MonsterDrop drop;
- drop.item = ItemManager::getItem(XML::getProperty(subnode, "item", 0));
+ drop.item = itemManager->getItem(XML::getProperty(subnode, "item", 0));
drop.probability = XML::getProperty(subnode, "percent", 0) * 100;
if (drop.item && drop.probability)
{
@@ -127,21 +121,21 @@ void MonsterManager::reload()
else if (xmlStrEqual(subnode->name, BAD_CAST "attributes"))
{
attributesSet = true;
- monster->setAttribute(BASE_ATTR_HP,
+ monster->setAttribute(ATTR_MAX_HP,
XML::getProperty(subnode, "hp", -1));
- monster->setAttribute(BASE_ATTR_PHY_ATK_MIN,
+ monster->setAttribute(MOB_ATTR_PHY_ATK_MIN,
XML::getProperty(subnode, "attack-min", -1));
- monster->setAttribute(BASE_ATTR_PHY_ATK_DELTA,
+ monster->setAttribute(MOB_ATTR_PHY_ATK_DELTA,
XML::getProperty(subnode, "attack-delta", -1));
- monster->setAttribute(BASE_ATTR_MAG_ATK,
+ monster->setAttribute(MOB_ATTR_MAG_ATK,
XML::getProperty(subnode, "attack-magic", -1));
- monster->setAttribute(BASE_ATTR_EVADE,
+ monster->setAttribute(ATTR_DODGE,
XML::getProperty(subnode, "evade", -1));
- monster->setAttribute(BASE_ATTR_HIT,
+ monster->setAttribute(ATTR_ACCURACY,
XML::getProperty(subnode, "hit", -1));
- monster->setAttribute(BASE_ATTR_PHY_RES,
+ monster->setAttribute(ATTR_DEFENSE,
XML::getProperty(subnode, "physical-defence", -1));
- monster->setAttribute(BASE_ATTR_MAG_RES,
+ monster->setAttribute(ATTR_MAGIC_DEFENSE,
XML::getProperty(subnode, "magical-defence", -1));
monster->setSize(XML::getProperty(subnode, "size", 0));
float speed = (XML::getFloatProperty(subnode, "speed", -1.0f));
@@ -150,21 +144,27 @@ void MonsterManager::reload()
//checking attributes for completeness and plausibility
if (monster->getMutation() > 99)
{
- LOG_WARN(monsterReferenceFile
+ LOG_WARN(mMonsterReferenceFile
<<": Mutation of monster #"<<id
<<" more than 99% - ignored");
monster->setMutation(0);
}
bool attributesComplete = true;
- for (int i = BASE_ATTR_BEGIN; i < BASE_ATTR_END; i++)
+ const AttributeScopes &mobAttr = attributeManager->getAttributeInfoForType(ATTR_MOB);
+
+ for (AttributeScopes::const_iterator it = mobAttr.begin(),
+ it_end = mobAttr.end();
+ it != it_end;
+ ++it)
{
- if (monster->getAttribute(i) == -1)
+ if (!monster->mAttributes.count(it->first))
{
attributesComplete = false;
- monster->setAttribute(i, 0);
+ monster->setAttribute(it->first, 0);
}
}
+
if (monster->getSize() == 0)
{
monster->setSize(16);
@@ -176,12 +176,9 @@ void MonsterManager::reload()
attributesComplete = false;
}
- if (!attributesComplete) LOG_WARN(monsterReferenceFile
+ if (!attributesComplete) LOG_WARN(mMonsterReferenceFile
<< ": Attributes incomplete for monster #" << id);
- //The speed is set in tiles per second in the monsters.xml
- monster->setSpeed(speed);
-
}
else if (xmlStrEqual(subnode->name, BAD_CAST "exp"))
{
@@ -216,24 +213,27 @@ void MonsterManager::reload()
if (sType == "physical") {att->type = DAMAGE_PHYSICAL; }
else if (sType == "magical" || sType == "magic") {att->type = DAMAGE_MAGICAL; }
else if (sType == "other") {att->type = DAMAGE_OTHER; }
- else { att->type = -1; }
+ else {
+ LOG_WARN("Monster manager " << mMonsterReferenceFile
+ << ": unknown damage type '" << sType << "'.");
+ }
if (att->id == 0)
{
- LOG_WARN(monsterReferenceFile
+ LOG_WARN(mMonsterReferenceFile
<< ": Attack without ID for monster #"
<< id << " (" << name << ") - attack ignored");
}
else if (att->element == ELEMENT_ILLEGAL)
{
- LOG_WARN(monsterReferenceFile
+ LOG_WARN(mMonsterReferenceFile
<< ": Attack with unknown element \""
<< sElement << "\" for monster #" << id
<< " (" << name << ") - attack ignored");
}
else if (att->type == -1)
{
- LOG_WARN(monsterReferenceFile
+ LOG_WARN(mMonsterReferenceFile
<< ": Attack with unknown type \"" << sType << "\""
<< " for monster #" << id << " (" << name << ")");
}
@@ -252,15 +252,15 @@ void MonsterManager::reload()
}
monster->setDrops(drops);
- if (!attributesSet) LOG_WARN(monsterReferenceFile
+ if (!attributesSet) LOG_WARN(mMonsterReferenceFile
<< ": No attributes defined for monster #"
<< id << " (" << name << ")");
- if (!behaviorSet) LOG_WARN(monsterReferenceFile
+ if (!behaviorSet) LOG_WARN(mMonsterReferenceFile
<< ": No behavior defined for monster #"
<< id << " (" << name << ")");
if (monster->getExp() == -1)
{
- LOG_WARN(monsterReferenceFile
+ LOG_WARN(mMonsterReferenceFile
<< ": No experience defined for monster #"
<< id << " (" << name << ")");
monster->setExp(0);
@@ -269,21 +269,21 @@ void MonsterManager::reload()
}
LOG_INFO("Loaded " << nbMonsters << " monsters from "
- << monsterReferenceFile << '.');
+ << mMonsterReferenceFile << '.');
}
void MonsterManager::deinitialize()
{
- for (MonsterClasses::iterator i = monsterClasses.begin(),
- i_end = monsterClasses.end(); i != i_end; ++i)
+ for (MonsterClasses::iterator i = mMonsterClasses.begin(),
+ i_end = mMonsterClasses.end(); i != i_end; ++i)
{
delete i->second;
}
- monsterClasses.clear();
+ mMonsterClasses.clear();
}
MonsterClass *MonsterManager::getMonster(int id)
{
- MonsterClasses::const_iterator i = monsterClasses.find(id);
- return i != monsterClasses.end() ? i->second : 0;
+ MonsterClasses::const_iterator i = mMonsterClasses.find(id);
+ return i != mMonsterClasses.end() ? i->second : 0;
}
diff --git a/src/game-server/monstermanager.hpp b/src/game-server/monstermanager.hpp
index 18377bc..6337b81 100644
--- a/src/game-server/monstermanager.hpp
+++ b/src/game-server/monstermanager.hpp
@@ -22,30 +22,41 @@
#define MONSTERMANAGER_HPP
#include <string>
-
+#include <map>
class MonsterClass;
-
-namespace MonsterManager
+class MonsterManager
{
- /**
- * Loads monster reference file.
- */
- void initialize(const std::string &);
-
- /**
- * Reloads monster reference file.
- */
- void reload();
-
- /**
- * Destroy monster classes.
- */
- void deinitialize();
-
- /**
- * Gets the MonsterClass having the given ID.
- */
- MonsterClass *getMonster(int id);
-}
+ public:
+
+ MonsterManager(const std::string &file) : mMonsterReferenceFile(file) {}
+ /**
+ * Loads monster reference file.
+ */
+ void initialize();
+
+ /**
+ * Reloads monster reference file.
+ */
+ void reload();
+
+ /**
+ * Destroy monster classes.
+ */
+ void deinitialize();
+
+ /**
+ * Gets the MonsterClass having the given ID.
+ */
+ MonsterClass *getMonster(int id);
+
+ private:
+
+ typedef std::map< int, MonsterClass * > MonsterClasses;
+ MonsterClasses mMonsterClasses; /**< Monster reference */
+
+ std::string mMonsterReferenceFile;
+};
+
+extern MonsterManager *monsterManager;
#endif // MONSTERMANAGER_HPP
diff --git a/src/game-server/spawnarea.cpp b/src/game-server/spawnarea.cpp
index 69a09c3..19e665a 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 867cd73..fdfd8a4 100644
--- a/src/game-server/state.cpp
+++ b/src/game-server/state.cpp
@@ -39,6 +39,7 @@
#include "net/messageout.hpp"
#include "scripting/script.hpp"
#include "utils/logger.h"
+#include "utils/speedconv.hpp"
enum
{
@@ -102,14 +103,7 @@ static void updateMap(MapComposite *map)
static void serializeLooks(Character *ch, MessageOut &msg, bool full)
{
const Possessions &poss = ch->getPossessions();
- static int const nb_slots = 4;
- static int const slots[nb_slots] =
- {
- EQUIP_FIGHT1_SLOT,
- EQUIP_HEAD_SLOT,
- EQUIP_TORSO_SLOT,
- EQUIP_LEGS_SLOT
- };
+ unsigned int nb_slots = itemManager->getVisibleSlotCount();
// Bitmask describing the changed entries.
int changed = (1 << nb_slots) - 1;
@@ -119,21 +113,25 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full)
changed = (1 << nb_slots) - 1;
}
- int items[nb_slots];
+ std::vector<unsigned int> items;
+ items.resize(nb_slots, 0);
// Partially build both kinds of packet, to get their sizes.
- int mask_full = 0, mask_diff = 0;
- int nb_full = 0, nb_diff = 0;
- for (int i = 0; i < nb_slots; ++i)
+ unsigned int mask_full = 0, mask_diff = 0;
+ unsigned int nb_full = 0, nb_diff = 0;
+ std::map<unsigned int, unsigned int>::const_iterator it =
+ poss.equipSlots.begin();
+ for (unsigned int i = 0; i < nb_slots; ++i)
{
- int id = poss.equipment[slots[i]];
- ItemClass *eq;
- items[i] = id && (eq = ItemManager::getItem(id)) ? eq->getSpriteID() : 0;
if (changed & (1 << i))
{
// Skip slots that have not changed, when sending an update.
++nb_diff;
mask_diff |= 1 << i;
}
+ if (it == poss.equipSlots.end() || it->first > i) continue;
+ ItemClass *eq;
+ items[i] = it->first && (eq = itemManager->getItem(it->first)) ?
+ eq->getSpriteID() : 0;
if (items[i])
{
/* If we are sending the whole equipment, only filled slots have to
@@ -151,7 +149,7 @@ static void serializeLooks(Character *ch, MessageOut &msg, bool full)
int mask = full ? mask_full | (1 << 7) : mask_diff;
msg.writeByte(mask);
- for (int i = 0; i < nb_slots; ++i)
+ for (unsigned int i = 0; i < nb_slots; ++i)
{
if (mask & (1 << i)) msg.writeShort(items[i]);
}
@@ -318,7 +316,7 @@ static void informPlayer(MapComposite *map, Character *p)
// We multiply the sent speed (in tiles per second) by ten
// to get it within a byte with decimal precision.
// For instance, a value of 4.5 will be sent as 45.
- moveMsg.writeByte((unsigned short) (o->getSpeed() * 10));
+ moveMsg.writeByte((unsigned short) (o->getModifiedAttribute(ATTR_MOVE_SPEED_TPS) * 10));
}
}
@@ -349,7 +347,8 @@ static void informPlayer(MapComposite *map, Character *p)
{
MessageOut healthMsg(GPMSG_BEING_HEALTH_CHANGE);
healthMsg.writeShort(c->getPublicID());
- healthMsg.writeShort(c->getHealth());
+ healthMsg.writeShort(c->getModifiedAttribute(ATTR_HP));
+ healthMsg.writeShort(c->getModifiedAttribute(ATTR_MAX_HP));
gameHandler->sendTo(p, healthMsg);
}
}
diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp
index c044ece..e2f1cd0 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 b6ac658..a95e89c 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 45d1b21..63fd877 100644
--- a/src/net/messagein.cpp
+++ b/src/net/messagein.cpp
@@ -23,8 +23,12 @@
#include <iostream>
#include <string>
#include <enet/enet.h>
+#ifndef USE_NATIVE_DOUBLE
+#include <sstream>
+#endif
#include "net/messagein.hpp"
+#include "utils/logger.h"
MessageIn::MessageIn(const char *data, int length):
mData(data),
@@ -42,6 +46,7 @@ int MessageIn::readByte()
{
value = (unsigned char) mData[mPos];
}
+ else LOG_DEBUG("Unable to read 1 byte in " << this->getId() << "!");
mPos += 1;
return value;
}
@@ -55,6 +60,7 @@ int MessageIn::readShort()
memcpy(&t, mData + mPos, 2);
value = (unsigned short) ENET_NET_TO_HOST_16(t);
}
+ else LOG_DEBUG("Unable to read 2 bytes in " << this->getId() << "!");
mPos += 2;
return value;
}
@@ -68,10 +74,26 @@ int MessageIn::readLong()
memcpy(&t, mData + mPos, 4);
value = ENET_NET_TO_HOST_32(t);
}
+ else LOG_DEBUG("Unable to read 4 bytes in " << this->getId() << "!");
mPos += 4;
return value;
}
+double MessageIn::readDouble()
+{
+ double value;
+#ifdef USE_NATIVE_DOUBLE
+ if (mPos + sizeof(double) <= mLength)
+ memcpy(&value, mData + mPos, sizeof(double));
+ mPos += sizeof(double);
+#else
+ int length = readByte();
+ std::istringstream i (readString(length));
+ i >> value;
+#endif
+ return value;
+}
+
std::string MessageIn::readString(int length)
{
// Get string length
diff --git a/src/net/messagein.hpp b/src/net/messagein.hpp
index 7188419..c6e49ab 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 8e6a4a7..950da5f 100644
--- a/src/net/messageout.cpp
+++ b/src/net/messageout.cpp
@@ -21,6 +21,10 @@
#include <cstring>
#include <iomanip>
#include <iostream>
+#ifndef USE_NATIVE_DOUBLE
+#include <limits>
+#include <sstream>
+#endif
#include <string>
#include <enet/enet.h>
@@ -98,6 +102,25 @@ void MessageOut::writeLong(int value)
mPos += 4;
}
+void MessageOut::writeDouble(double value)
+{
+#ifdef USE_NATIVE_DOUBLE
+ expand(mPos + sizeof(double));
+ memcpy(mData + mPos, &value, sizeof(double));
+ mPos += sizeof(double);
+#else
+// Rather inefficient, but I don't have a lot of time.
+// If anyone wants to implement a custom double you are more than welcome to.
+ std::ostringstream o;
+ // Highest precision for double
+ o.precision(std::numeric_limits< double >::digits10);
+ o << value;
+ std::string str = o.str();
+ writeByte(str.size());
+ writeString(str, str.size());
+#endif
+}
+
void MessageOut::writeCoordinates(int x, int y)
{
expand(mPos + 3);
diff --git a/src/net/messageout.hpp b/src/net/messageout.hpp
index cd7befa..270b796 100644
--- a/src/net/messageout.hpp
+++ b/src/net/messageout.hpp
@@ -56,6 +56,12 @@ class MessageOut
void writeLong(int value); /**< Writes an integer on four bytes. */
/**
+ * Writes a double. HACKY and should *not* be used for client
+ * communication!
+ */
+ void writeDouble(double value);
+
+ /**
* Writes a 3-byte block containing tile-based coordinates.
*/
void writeCoordinates(int x, int y);
diff --git a/src/protocol.h b/src/protocol.h
index 9b3e5a1..9b5c16f 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 2d9562e..7245678 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 <string.h>
@@ -342,8 +343,10 @@ static int chr_warp(lua_State *s)
* (negative amount) should be passed first, then insertions (positive amount).
* If a removal fails, all the previous operations are canceled (except for
* items dropped on the floor, hence why removals should be passed first), and
- * the function returns false. Otherwise the function will return true. When
- * the item identifier is zero, money is modified.
+ * the function returns false. Otherwise the function will return true.
+ * Note that previously when the item identifier was zero, money was modified;
+ * however currency is now handled through attributes. This breaks backwards
+ * compatibility with old scripts, and so logs a warning.
* Note: If an insertion fails, extra items are dropped on the floor.
* mana.chr_inv_change(character, (int id, int nb)...): bool success
*/
@@ -368,14 +371,7 @@ static int chr_inv_change(lua_State *s)
int nb = lua_tointeger(s, i * 2 + 3);
if (id == 0)
- {
- if (!inv.changeMoney(nb))
- {
- inv.cancel();
- lua_pushboolean(s, 0);
- return 1;
- }
- }
+ LOG_WARN("chr_inv_change: id 0! Currency is now handled through attributes!");
else if (nb < 0)
{
nb = inv.remove(id, -nb);
@@ -388,7 +384,7 @@ static int chr_inv_change(lua_State *s)
}
else
{
- ItemClass *ic = ItemManager::getItem(id);
+ ItemClass *ic = itemManager->getItem(id);
if (!ic)
{
raiseScriptError(s, "chr_inv_change called with an unknown item.");
@@ -410,7 +406,6 @@ static int chr_inv_change(lua_State *s)
/**
* Callback for counting items in inventory.
- * When an item identifier is zero, money is queried.
* mana.chr_inv_count(character, int id...): int count...
*/
static int chr_inv_count(lua_State *s)
@@ -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 50acc8e..6e1facc 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 a3b37ce..0312981 100644
--- a/src/sql/mysql/createTables.sql
+++ b/src/sql/mysql/createTables.sql
@@ -36,18 +36,10 @@ CREATE TABLE IF NOT EXISTS `mana_characters` (
`level` tinyint(3) unsigned NOT NULL,
`char_pts` smallint(5) unsigned NOT NULL,
`correct_pts` smallint(5) unsigned NOT NULL,
- `money` int(10) unsigned NOT NULL,
-- location on the map
`x` smallint(5) unsigned NOT NULL,
`y` smallint(5) unsigned NOT NULL,
`map_id` tinyint(3) unsigned NOT NULL,
- -- attributes
- `str` smallint(5) unsigned NOT NULL,
- `agi` smallint(5) unsigned NOT NULL,
- `dex` smallint(5) unsigned NOT NULL,
- `vit` smallint(5) unsigned NOT NULL,
- `int` smallint(5) unsigned NOT NULL,
- `will` smallint(5) unsigned NOT NULL,
--
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`),
@@ -60,6 +52,23 @@ DEFAULT CHARSET=utf8
AUTO_INCREMENT=1 ;
--
+-- Create table: `mana_char_attr`
+--
+
+CREATE TABLE IF NOT EXISTS `mana_char_attr` (
+ `char_id` int(10) unsigned NOT NULL,
+ `attr_id` int(10) unsigned NOT NULL,
+ `attr_base` double unsigned NOT NULL,
+ `attr_mod` double unsigned NOT NULL,
+ --
+ PRIMARY KEY (`char_id`, `attr_id`),
+ FOREIGN KEY (`char_id`)
+ REFERENCES `mana_characters` (`id`)
+ ON DELETE CASCADE
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8;
+
+--
-- table: `mana_char_skills`
--
CREATE TABLE IF NOT EXISTS `mana_char_skills` (
@@ -169,6 +178,21 @@ DEFAULT CHARSET=utf8
AUTO_INCREMENT=1 ;
--
+-- table: `mana_char_equips`
+--
+CREATE TABLE IF NOT EXISTS `mana_char_equips` (
+ id int(10) unsigned NOT NULL auto_increment,
+ owner_id int(10) unsigned NOT NULL,
+ slot_type tinyint(3) unsigned NOT NULL,
+ inventory_slot tinyint(3) unsigned NOT NULL,
+ --
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `owner_id` (`owner_id`, )
+ FOREIGN KEY (owner_id) REFERENCES mana_characters(id)
+) ENGINE=InnoDB
+DEFAULT CHARSET=utf8;
+
+--
-- table: `mana_inventories`
-- todo: remove class_id and amount and reference on mana_item_instances
--
@@ -396,7 +420,7 @@ AUTO_INCREMENT=0 ;
INSERT INTO mana_world_states VALUES('accountserver_startup',NULL,NULL, NOW());
INSERT INTO mana_world_states VALUES('accountserver_version',NULL,NULL, NOW());
-INSERT INTO mana_world_states VALUES('database_version', NULL,'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 0000000..5bb722a
--- /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 bc5eefb..94aabda 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 a8d7241..71f92a8 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 0000000..14d328f
--- /dev/null
+++ b/src/utils/speedconv.cpp
@@ -0,0 +1,31 @@
+/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "utils/speedconv.hpp"
+
+double utils::tpsToSpeed(double tps)
+{
+ return (32000 / (tps * DEFAULT_TILE_LENGTH));
+}
+
+double utils::speedToTps(double speed)
+{
+ return (32000 / (speed * DEFAULT_TILE_LENGTH));
+}
diff --git a/src/utils/speedconv.hpp b/src/utils/speedconv.hpp
new file mode 100644
index 0000000..6a29d0f
--- /dev/null
+++ b/src/utils/speedconv.hpp
@@ -0,0 +1,44 @@
+/*
+ * The Mana Server
+ * Copyright (C) 2004-2010 The Mana World Development Team
+ *
+ * This file is part of The Mana Server.
+ *
+ * The Mana Server is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * The Mana Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SPEEDCONV_HPP
+#define SPEEDCONV_HPP
+
+// Simple helper functions for converting between tiles per
+// second and the internal speed representation
+
+#include "defines.h"
+
+namespace utils {
+ /**
+ * tpsToSpeed()
+ * @param tps The speed value in tiles per second
+ * @returns The speed value in the internal representation
+ */
+ double tpsToSpeed(double);
+ /**
+ * speedToTps()
+ * @param speed The speed value in the internal representation
+ * @returns The speed value in tiles per second
+ */
+ double speedToTps(double);
+}
+
+#endif // SPEEDCONV_HPP