summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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