summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilipp Sehmisch <tmw@crushnet.org>2008-01-28 07:51:40 +0000
committerPhilipp Sehmisch <tmw@crushnet.org>2008-01-28 07:51:40 +0000
commitaa603c3ec05f6143b1c9085b56e3becf45be4bf5 (patch)
tree75a2a2b767f5a9380716986a82d98af2df7a589b
parent2dd95f1b69e9b024d8877dd400a141ccdf1c153f (diff)
downloadmanaserv-aa603c3ec05f6143b1c9085b56e3becf45be4bf5.tar.gz
manaserv-aa603c3ec05f6143b1c9085b56e3becf45be4bf5.tar.bz2
manaserv-aa603c3ec05f6143b1c9085b56e3becf45be4bf5.tar.xz
manaserv-aa603c3ec05f6143b1c9085b56e3becf45be4bf5.zip
Added weapon skill system and leveling system.
-rw-r--r--ChangeLog20
-rw-r--r--src/account-server/accounthandler.cpp6
-rw-r--r--src/account-server/character.cpp7
-rw-r--r--src/account-server/character.hpp27
-rw-r--r--src/account-server/dalstorage.cpp52
-rw-r--r--src/account-server/dalstoragesql.hpp53
-rw-r--r--src/dal/sqlitedataprovider.cpp4
-rw-r--r--src/defines.h52
-rw-r--r--src/game-server/accountconnection.cpp38
-rw-r--r--src/game-server/being.hpp3
-rw-r--r--src/game-server/character.cpp186
-rw-r--r--src/game-server/character.hpp91
-rw-r--r--src/game-server/gamehandler.cpp31
-rw-r--r--src/game-server/item.hpp23
-rw-r--r--src/game-server/itemmanager.cpp5
-rw-r--r--src/game-server/monster.cpp99
-rw-r--r--src/game-server/monster.hpp10
-rw-r--r--src/serialize/characterdata.hpp19
18 files changed, 613 insertions, 113 deletions
diff --git a/ChangeLog b/ChangeLog
index 1f847d5d..d68b4159 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2008-01-28 Philipp Sehmisch <tmw@crushnet.org>
+
+ * src/account-server/accounthandler.cpp, src/account-server/character.cpp,
+ src/account-server/character.hpp, src/account-server/dalstorage.cpp,
+ src/account-server/dalstoragesql.hpp, src/dal/sqlitedataprovider.cpp,
+ src/defines.h, src/game-server/accountconnection.cpp,
+ src/game-server/being.hpp, src/game-server/character.cpp,
+ src/game-server/character.hpp, src/game-server/gamehandler.cpp,
+ src/game-server/item.cpp, src/game-server/itemmanager.cpp,
+ src/game-server/monster.cpp, src/game-server/monster.hpp,
+ src/serialize/characterdata.hpp: Implemented skill system, level gain
+ and attribute raising. Using 16 bit instead of 8 bit for representing
+ the character level. Updated weapon skill selection to latest design
+ decisions.
+ * src/game-server/monster.cpp: Monster attack animation is now
+ started in the moment the monster decides to attack and not the
+ moment damage is calculated. This makes it easier for the player
+ to react on the monsters attacks and makes the combat behavior of
+ monsters look more natural.
+
2008-01-24 Philipp Sehmisch <tmw@crushnet.org>
* src/game-server/state.cpp: The direction of attacking beings is
diff --git a/src/account-server/accounthandler.cpp b/src/account-server/accounthandler.cpp
index 3d2161e4..4e203535 100644
--- a/src/account-server/accounthandler.cpp
+++ b/src/account-server/accounthandler.cpp
@@ -141,7 +141,9 @@ static void sendCharacterData(AccountClient &computer, int slot, Character const
charInfo.writeByte(ch.getGender());
charInfo.writeByte(ch.getHairStyle());
charInfo.writeByte(ch.getHairColor());
- charInfo.writeByte(ch.getLevel());
+ 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)
@@ -558,6 +560,8 @@ static void handleCharacterCreateMessage(AccountClient &computer, MessageIn &msg
newCharacter->setAttribute(i, attributes[i - CHAR_ATTR_BEGIN]);
newCharacter->setAccount(acc);
newCharacter->setLevel(1);
+ newCharacter->setCharacterPoints(0);
+ newCharacter->setCorrectionPoints(0);
newCharacter->setGender(gender);
newCharacter->setHairStyle(hairStyle);
newCharacter->setHairColor(hairColor);
diff --git a/src/account-server/character.cpp b/src/account-server/character.cpp
index e9a40987..5c55b044 100644
--- a/src/account-server/character.cpp
+++ b/src/account-server/character.cpp
@@ -26,12 +26,17 @@
Character::Character(std::string const &name, int id):
mName(name), mDatabaseID(id), mAccountID(-1), mAccount(NULL), mPos(0,0), mMapId(0),
- mGender(0), mHairStyle(0), mHairColor(0), mLevel(0), mAccountLevel(0)
+ mGender(0), mHairStyle(0), mHairColor(0), mLevel(0), mCharacterPoints(0),
+ mCorrectionPoints(0), mAccountLevel(0)
{
for (int i = 0; i < CHAR_ATTR_NB; ++i)
{
mAttributes[i] = 0;
}
+ for (int i = 0; i < CHAR_SKILL_NB; ++i)
+ {
+ mExperience[i] = 0;
+ }
}
void Character::setAccount(Account *acc)
diff --git a/src/account-server/character.hpp b/src/account-server/character.hpp
index 52233b92..cbb60d68 100644
--- a/src/account-server/character.hpp
+++ b/src/account-server/character.hpp
@@ -125,6 +125,15 @@ class Character
void setAttribute(int n, int value)
{ mAttributes[n - CHAR_ATTR_BEGIN] = value; }
+ int getExperience(int skill) const
+ { return mExperience[skill]; }
+
+ void setExperience(int skill, int value)
+ { mExperience[skill] = value; }
+
+ void receiveExperience(int skill, int value)
+ { mExperience[skill] += value; }
+
/** Gets the Id of the map that the character is on. */
int
getMapId() const { return mMapId; }
@@ -160,6 +169,19 @@ class Character
Possessions &getPossessions()
{ return mPossessions; }
+ void setCharacterPoints(int points)
+ { mCharacterPoints = points; }
+
+ int getCharacterPoints() const
+ { return mCharacterPoints; }
+
+ void setCorrectionPoints(int points)
+ { mCorrectionPoints = points; }
+
+ int getCorrectionPoints() const
+ { return mCorrectionPoints; }
+
+
private:
Character(Character const &);
Character &operator=(Character const &);
@@ -171,11 +193,14 @@ class Character
Account *mAccount; //!< Account owning the character.
Point mPos; //!< Position the being is at.
unsigned short mAttributes[CHAR_ATTR_NB]; //!< Attributes.
+ int mExperience[CHAR_SKILL_NB]; //!< Skill Experience.
unsigned short mMapId; //!< Map the being is on.
unsigned char mGender; //!< Gender of the being.
unsigned char mHairStyle; //!< Hair style of the being.
unsigned char mHairColor; //!< Hair color of the being.
- unsigned char mLevel; //!< Level of the being.
+ short mLevel; //!< Level of the being.
+ short mCharacterPoints; //!< Unused character points.
+ short mCorrectionPoints; //!< Unused correction points.
unsigned char mAccountLevel; //!< Level of the associated account.
std::vector<std::string> mGuilds; //!< All the guilds the player
diff --git a/src/account-server/dalstorage.cpp b/src/account-server/dalstorage.cpp
index 77dac0f0..de6c7452 100644
--- a/src/account-server/dalstorage.cpp
+++ b/src/account-server/dalstorage.cpp
@@ -279,16 +279,23 @@ Character *DALStorage::getCharacterBySQL(std::string const &query, Account *owne
character->setHairStyle(toUshort(charInfo(0, 4)));
character->setHairColor(toUshort(charInfo(0, 5)));
character->setLevel(toUshort(charInfo(0, 6)));
- character->getPossessions().money = toUint(charInfo(0, 7));
- Point pos(toUshort(charInfo(0, 8)), toUshort(charInfo(0, 9)));
+ 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)));
character->setPosition(pos);
for (int i = 0; i < CHAR_ATTR_NB; ++i)
{
character->setAttribute(CHAR_ATTR_BEGIN + i,
- toUshort(charInfo(0, 11 + i)));
+ toUshort(charInfo(0, 13 + i)));
+ }
+ for (int i = 0; i < CHAR_SKILL_WEAPON_NB; ++i)
+ {
+ int exp = toUint(charInfo(0, 13 + CHAR_ATTR_NB + i));
+ character->setExperience(i, exp);
}
- int mapId = toUint(charInfo(0, 10));
+ int mapId = toUint(charInfo(0, 12));
if (mapId > 0)
{
character->setMapId(mapId);
@@ -522,6 +529,8 @@ bool DALStorage::updateCharacter(Character *character)
<< "hair_style = '" << character->getHairStyle() << "', "
<< "hair_color = '" << character->getHairColor() << "', "
<< "level = '" << character->getLevel() << "', "
+ << "char_pts = '" << character->getCharacterPoints() << "', "
+ << "correct_pts = '"<< character->getCorrectionPoints() << "', "
<< "money = '" << character->getPossessions().money << "', "
<< "x = '" << character->getPosition().x << "', "
<< "y = '" << character->getPosition().y << "', "
@@ -536,7 +545,19 @@ bool DALStorage::updateCharacter(Character *character)
<< "int = '"
#endif
<< character->getAttribute(CHAR_ATTR_INTELLIGENCE) << "', "
- << "will = '" << character->getAttribute(CHAR_ATTR_WILLPOWER) << "' "
+ << "will = '" << character->getAttribute(CHAR_ATTR_WILLPOWER) << "', "
+ << "unarmed_exp = '"<< character->getExperience(CHAR_SKILL_WEAPON_NONE - CHAR_SKILL_BEGIN) << "', "
+ << "knife_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_KNIFE - CHAR_SKILL_BEGIN) << "', "
+ << "sword_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_SWORD - CHAR_SKILL_BEGIN) << "', "
+ << "polearm_exp = '"<< character->getExperience(CHAR_SKILL_WEAPON_POLEARM - CHAR_SKILL_BEGIN) << "', "
+ << "staff_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_STAFF - CHAR_SKILL_BEGIN) << "', "
+ << "whip_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_WHIP - CHAR_SKILL_BEGIN) << "', "
+ << "bow_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_BOW - CHAR_SKILL_BEGIN) << "', "
+ << "shoot_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_SHOOTING - CHAR_SKILL_BEGIN) << "', "
+ << "mace_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_MACE - CHAR_SKILL_BEGIN) << "', "
+ << "axe_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_AXE - CHAR_SKILL_BEGIN) << "', "
+ << "thrown_exp = '" << character->getExperience(CHAR_SKILL_WEAPON_THROWN - CHAR_SKILL_BEGIN) << "' "
+
<< "where id = '" << character->getDatabaseID() << "';";
mDb->execSql(sqlUpdateCharacterInfo.str());
@@ -823,14 +844,17 @@ void DALStorage::flush(Account *account)
// uniqueness
sqlInsertCharactersTable
<< "insert into " << CHARACTERS_TBL_NAME
- << " (user_id, name, gender, hair_style, hair_color, level, money,"
- << " x, y, map_id, str, agi, dex, vit, int, will) values ("
+ << " (user_id, name, gender, hair_style, hair_color, level, char_pts, correct_pts, money,"
+ << " x, y, map_id, str, agi, dex, vit, int, will, unarmed_exp, knife_exp, sword_exp, polearm_exp,"
+ << " staff_exp, whip_exp, bow_exp, shoot_exp, mace_exp, axe_exp, thrown_exp) values ("
<< account->getID() << ", \""
<< (*it)->getName() << "\", "
<< (*it)->getGender() << ", "
<< (int)(*it)->getHairStyle() << ", "
<< (int)(*it)->getHairColor() << ", "
<< (int)(*it)->getLevel() << ", "
+ << (int)(*it)->getCharacterPoints() << ", "
+ << (int)(*it)->getCorrectionPoints() << ", "
<< (*it)->getPossessions().money << ", "
<< (*it)->getPosition().x << ", "
<< (*it)->getPosition().y << ", "
@@ -840,7 +864,19 @@ void DALStorage::flush(Account *account)
<< (*it)->getAttribute(CHAR_ATTR_DEXTERITY) << ", "
<< (*it)->getAttribute(CHAR_ATTR_VITALITY) << ", "
<< (*it)->getAttribute(CHAR_ATTR_INTELLIGENCE) << ", "
- << (*it)->getAttribute(CHAR_ATTR_WILLPOWER) << ");";
+ << (*it)->getAttribute(CHAR_ATTR_WILLPOWER) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_NONE - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_KNIFE - CHAR_SKILL_BEGIN) << ","
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_SWORD - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_POLEARM - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_STAFF - CHAR_SKILL_BEGIN) << ","
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_WHIP - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_BOW - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_SHOOTING - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_MACE - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_AXE - CHAR_SKILL_BEGIN) << ", "
+ << (*it)->getExperience(CHAR_SKILL_WEAPON_THROWN - CHAR_SKILL_BEGIN)
+ << ");";
mDb->execSql(sqlInsertCharactersTable.str());
diff --git a/src/account-server/dalstoragesql.hpp b/src/account-server/dalstoragesql.hpp
index 51215e1b..16d0067e 100644
--- a/src/account-server/dalstoragesql.hpp
+++ b/src/account-server/dalstoragesql.hpp
@@ -97,11 +97,6 @@ static char const *SQL_ACCOUNTS_TABLE =
/**
* TABLE: tmw_characters.
- *
- * Notes:
- * - the stats will need to be thought over, as we'll be implementing a
- * much more elaborate skill based system; we should probably have a
- * separate table for storing the skill levels.
* - gender is 0 for male, 1 for female.
*/
static char const *CHARACTERS_TBL_NAME = "tmw_characters";
@@ -115,7 +110,9 @@ static char const *SQL_CHARACTERS_TABLE =
"gender TINYINT UNSIGNED NOT NULL,"
"hair_style TINYINT UNSIGNED NOT NULL,"
"hair_color TINYINT UNSIGNED NOT NULL,"
- "level TINYINT UNSIGNED NOT NULL,"
+ "level INTEGER UNSIGNED NOT NULL,"
+ "char_pts INTEGER UNSIGNED NOT NULL,"
+ "correct_pts INTEGER UNSIGNED NOT NULL,"
"money INTEGER UNSIGNED NOT NULL,"
// location on the map
"x SMALLINT UNSIGNED NOT NULL,"
@@ -129,6 +126,19 @@ static char const *SQL_CHARACTERS_TABLE =
// note: int must be backquoted as it's a MySQL keyword
"`int` SMALLINT UNSIGNED NOT NULL,"
"will SMALLINT UNSIGNED NOT NULL,"
+ //skill experience
+ "unarmedExp INTEGER UNSIGNED NOT NULL,"
+ "knife_exp INTEGER UNSIGNED NOT NULL,"
+ "sword_exp INTEGER UNSIGNED NOT NULL,"
+ "polearm_exp INTEGER UNSIGNED NOT NULL,"
+ "staff_exp INTEGER UNSIGNED NOT NULL,"
+ "whip_exp INTEGER UNSIGNED NOT NULL,"
+ "bow_exp INTEGER UNSIGNED NOT NULL,"
+ "shoot_exp INTEGER UNSIGNED NOT NULL,"
+ "mace_exp INTEGER UNSIGNED NOT NULL,"
+ "axe_exp INTEGER UNSIGNED NOT NULL,"
+ "thrown_exp INTEGER UNSIGNED NOT NULL,"
+
"FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
"FOREIGN KEY (map_id) REFERENCES tmw_maps(id),"
"INDEX (id)"
@@ -140,7 +150,9 @@ static char const *SQL_CHARACTERS_TABLE =
"gender INTEGER NOT NULL,"
"hair_style INTEGER NOT NULL,"
"hair_color INTEGER NOT NULL,"
- "level INTEGER NOT NULL,"
+ "level INTEGER NOT NULL,"
+ "char_pts INTEGER NOT NULL,"
+ "correct_pts INTEGER NOT NULL,"
"money INTEGER NOT NULL,"
// location on the map
"x INTEGER NOT NULL,"
@@ -153,6 +165,18 @@ static char const *SQL_CHARACTERS_TABLE =
"vit INTEGER NOT NULL,"
"int INTEGER NOT NULL,"
"will INTEGER NOT NULL,"
+ //skill experience
+ "unarmed_exp INTEGER NOT NULL,"
+ "knife_exp INTEGER NOT NULL,"
+ "sword_exp INTEGER NOT NULL,"
+ "polearm_exp INTEGER NOT NULL,"
+ "staff_exp INTEGER NOT NULL,"
+ "whip_exp INTEGER NOT NULL,"
+ "bow_exp INTEGER NOT NULL,"
+ "shoot_exp INTEGER NOT NULL,"
+ "mace_exp INTEGER NOT NULL,"
+ "axe_exp INTEGER NOT NULL,"
+ "thrown_exp INTEGER NOT NULL,"
"FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
"FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
#elif defined (POSTGRESQL_SUPPORT)
@@ -164,6 +188,9 @@ static char const *SQL_CHARACTERS_TABLE =
"hair_style INTEGER NOT NULL,"
"hair_color INTEGER NOT NULL,"
"level INTEGER NOT NULL,"
+
+ "char_pts INTEGER NOT NULL,"
+ "correct_pts INTEGER NOT NULL,"
"money INTEGER NOT NULL,"
// location on the map
"x INTEGER NOT NULL,"
@@ -176,6 +203,18 @@ static char const *SQL_CHARACTERS_TABLE =
"vit INTEGER NOT NULL,"
"int INTEGER NOT NULL,"
"will INTEGER NOT NULL,"
+ //skill experience
+ "unarmed_exp INTEGER NOT NULL,"
+ "knife_exp INTEGER NOT NULL,"
+ "sword_exp INTEGER NOT NULL,"
+ "polearm_exp INTEGER NOT NULL,"
+ "staff_exp INTEGER NOT NULL,"
+ "whip_exp INTEGER NOT NULL,"
+ "bow_exp INTEGER NOT NULL,"
+ "shoot_exp INTEGER NOT NULL,"
+ "mace_exp INTEGER NOT NULL,"
+ "axe_exp INTEGER NOT NULL,"
+ "thrown_exp INTEGER NOT NULL,"
"FOREIGN KEY (user_id) REFERENCES tmw_accounts(id),"
"FOREIGN KEY (map_id) REFERENCES tmw_maps(id)"
#endif
diff --git a/src/dal/sqlitedataprovider.cpp b/src/dal/sqlitedataprovider.cpp
index f9c3ed51..b126c19a 100644
--- a/src/dal/sqlitedataprovider.cpp
+++ b/src/dal/sqlitedataprovider.cpp
@@ -26,6 +26,8 @@
#include "dalexcept.h"
+#include "../utils/logger.h"
+
namespace dal
{
@@ -116,6 +118,8 @@ SqLiteDataProvider::execSql(const std::string& sql,
throw std::runtime_error("not connected to database");
}
+ LOG_DEBUG("Performing SQL querry: "<<sql);
+
// do something only if the query is different from the previous
// or if the cache must be refreshed
// otherwise just return the recordset from cache.
diff --git a/src/defines.h b/src/defines.h
index a22a739d..61c18e24 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -105,7 +105,7 @@ 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, B level, W money, W*6 stats
+ 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
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
@@ -139,6 +139,13 @@ enum {
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, // { B attribute, W base value, W modified value }*
+ GPMSG_PLAYER_EXP_CHANGE = 0x0140, // { B 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
+ PGMSG_LOWER_ATTRIBUTE = 0x0170, // B attribute
+ GPMSG_LOWER_ATTRIBUTE_RESPONSE = 0x0171, // B error
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 }*
// monster: W type id
@@ -292,6 +299,14 @@ enum {
CREATE_TOO_MUCH_CHARACTERS
};
+// Character attribute modification specific return value
+enum AttribmodResponseCode {
+ ATTRIBMOD_OK = ERRMSG_OK,
+ ATTRIBMOD_INVALID_ATTRIBUTE = 0x40,
+ ATTRIBMOD_NO_POINTS_LEFT,
+ ATTRIBMOD_DENIED
+};
+
// Email change specific return values
enum {
EMAILCHG_EXISTS_EMAIL = 0x40
@@ -399,30 +414,39 @@ enum
CHAR_ATTR_END,
CHAR_ATTR_NB = CHAR_ATTR_END - CHAR_ATTR_BEGIN,
- CHAR_SKILL_WEAPON_BEGIN = CHAR_ATTR_END,
+ CHAR_SKILL_BEGIN = CHAR_ATTR_END,
+
+ CHAR_SKILL_WEAPON_BEGIN = CHAR_SKILL_BEGIN,
CHAR_SKILL_WEAPON_NONE = CHAR_SKILL_WEAPON_BEGIN,
CHAR_SKILL_WEAPON_KNIFE,
CHAR_SKILL_WEAPON_SWORD,
- CHAR_SKILL_WEAPON_SPEAR,
- CHAR_SKILL_WEAPON_JAVELIN,
- CHAR_SKILL_WEAPON_ROD,
+ CHAR_SKILL_WEAPON_POLEARM,
CHAR_SKILL_WEAPON_STAFF,
CHAR_SKILL_WEAPON_WHIP,
- CHAR_SKILL_WEAPON_PROJECTILE,
- CHAR_SKILL_WEAPON_BOOMERANG,
CHAR_SKILL_WEAPON_BOW,
- CHAR_SKILL_WEAPON_SICKLE,
- CHAR_SKILL_WEAPON_CROSSBOW,
- CHAR_SKILL_WEAPON_STICK,
- CHAR_SKILL_WEAPON_HAMMER,
+ CHAR_SKILL_WEAPON_SHOOTING,
+ CHAR_SKILL_WEAPON_MACE,
CHAR_SKILL_WEAPON_AXE,
- CHAR_SKILL_WEAPON_HAND_PROJECTILE,
+ CHAR_SKILL_WEAPON_THROWN,
CHAR_SKILL_WEAPON_END,
CHAR_SKILL_WEAPON_NB = CHAR_SKILL_WEAPON_END - CHAR_SKILL_WEAPON_BEGIN,
- // Magic skills should follow.
+ CHAR_SKILL_MAGIC_BEGIN = CHAR_SKILL_WEAPON_END,
+ CHAR_SKILL_MAGIC_IAMJUSTAPLACEHOLDER = CHAR_SKILL_MAGIC_BEGIN,
+ // add magic skills here
+ CHAR_SKILL_MAGIC_END,
+ CHAR_SKILL_MAGIC_NB = CHAR_SKILL_MAGIC_END - CHAR_SKILL_MAGIC_BEGIN,
+
+ CHAR_SKILL_CRAFT_BEGIN = CHAR_SKILL_MAGIC_END,
+ CHAR_SKILL_CRAFT_IAMJUSTAPLACEHOLDER = CHAR_SKILL_CRAFT_BEGIN,
+ // add crafting skills here
+ CHAR_SKILL_CRAFT_END,
+ CHAR_SKILL_CRAFT_NB = CHAR_SKILL_CRAFT_END - CHAR_SKILL_CRAFT_BEGIN,
+
+ CHAR_SKILL_END = CHAR_SKILL_CRAFT_END,
+ CHAR_SKILL_NB = CHAR_SKILL_END - CHAR_SKILL_BEGIN,
- NB_CHARACTER_ATTRIBUTES = CHAR_SKILL_WEAPON_END
+ NB_CHARACTER_ATTRIBUTES = CHAR_SKILL_END
};
#endif // _TMWSERV_DEFINES_H_
diff --git a/src/game-server/accountconnection.cpp b/src/game-server/accountconnection.cpp
index a07230eb..60da1ee1 100644
--- a/src/game-server/accountconnection.cpp
+++ b/src/game-server/accountconnection.cpp
@@ -77,8 +77,6 @@ void AccountConnection::processMessage(MessageIn &msg)
std::string token = msg.readString(MAGIC_TOKEN_LENGTH);
Character *ptr = new Character(msg);
ptr->setSpeed(250); // TODO
- // FIXME: for testing purpose.
- ptr->setAttribute(CHAR_SKILL_WEAPON_NONE, 10);
gameHandler->addPendingCharacter(token, ptr);
} break;
@@ -112,10 +110,10 @@ void AccountConnection::processMessage(MessageIn &msg)
if(msg.readByte() == ERRMSG_OK)
{
int playerId = msg.readLong();
-
+
MessageOut result(GPMSG_GUILD_CREATE_RESPONSE);
result.writeByte(ERRMSG_OK);
-
+
/* Create a message that the player has joined the guild
* Output the guild ID and guild name
* Send a 1 if the player has rights
@@ -125,7 +123,7 @@ void AccountConnection::processMessage(MessageIn &msg)
out.writeShort(msg.readShort());
out.writeString(msg.readString());
out.writeShort(msg.readShort());
-
+
Character *player = gameHandler->messageMap[playerId];
if(player)
{
@@ -134,33 +132,33 @@ void AccountConnection::processMessage(MessageIn &msg)
}
}
} break;
-
+
case AGMSG_GUILD_INVITE_RESPONSE:
{
if(msg.readByte() == ERRMSG_OK)
{
int playerId = msg.readLong();
-
+
MessageOut result(GPMSG_GUILD_INVITE_RESPONSE);
result.writeByte(ERRMSG_OK);
-
+
Character *player = gameHandler->messageMap[playerId];
if(player)
{
gameHandler->sendTo(player, result);
- }
+ }
}
} break;
-
+
case AGMSG_GUILD_ACCEPT_RESPONSE:
{
if(msg.readByte() == ERRMSG_OK)
{
int playerId = msg.readLong();
-
+
MessageOut result(GPMSG_GUILD_ACCEPT_RESPONSE);
result.writeByte(ERRMSG_OK);
-
+
/* Create a message that the player has joined the guild
* Output the guild ID and guild name
* Send a 0 for invite rights, since player has been invited
@@ -170,23 +168,23 @@ void AccountConnection::processMessage(MessageIn &msg)
out.writeShort(msg.readShort());
out.writeString(msg.readString());
out.writeShort(0);
-
+
Character *player = gameHandler->messageMap[playerId];
if(player)
{
gameHandler->sendTo(player, result);
gameHandler->sendTo(player, out);
- }
+ }
}
} break;
-
+
case AGMSG_GUILD_GET_MEMBERS_RESPONSE:
{
if(msg.readByte() != ERRMSG_OK)
break;
int playerId = msg.readLong();
short guildId = msg.readShort();
-
+
MessageOut result(GPMSG_GUILD_GET_MEMBERS_RESPONSE);
result.writeByte(ERRMSG_OK);
result.writeShort(guildId);
@@ -194,25 +192,25 @@ void AccountConnection::processMessage(MessageIn &msg)
{
result.writeString(msg.readString());
}
-
+
Character *player = gameHandler->messageMap[playerId];
if(player)
{
gameHandler->sendTo(player, result);
}
} break;
-
+
case AGMSG_GUILD_QUIT_RESPONSE:
{
if(msg.readByte() != ERRMSG_OK)
break;
int playerId = msg.readLong();
short guildId = msg.readShort();
-
+
MessageOut result(GPMSG_GUILD_QUIT_RESPONSE);
result.writeByte(ERRMSG_OK);
result.writeShort(guildId);
-
+
Character *player = gameHandler->messageMap[playerId];
if(player)
{
diff --git a/src/game-server/being.hpp b/src/game-server/being.hpp
index 55aa603e..44857dc1 100644
--- a/src/game-server/being.hpp
+++ b/src/game-server/being.hpp
@@ -64,6 +64,7 @@ struct Damage
unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */
unsigned char element; /**< Elemental damage. */
unsigned char type; /**< Damage type: Physical or magical? */
+ size_t usedSkill; /**< Skill used by source (needed for exp calculation) */
};
/**
@@ -128,7 +129,7 @@ class Being : public MovingObject
/**
* Cleans obsolete attribute modifiers.
*/
- void update();
+ virtual void update();
/**
* Takes a damage structure, computes the real damage based on the
diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp
index c87f310f..02094edd 100644
--- a/src/game-server/character.cpp
+++ b/src/game-server/character.cpp
@@ -22,6 +22,7 @@
#include <algorithm>
#include <cassert>
+#include <cmath>
#include "game-server/character.hpp"
@@ -39,14 +40,17 @@
#include "net/messageout.hpp"
#include "serialize/characterdata.hpp"
+#include "utils/logger.h"
+
Character::Character(MessageIn &msg):
Being(OBJECT_CHARACTER, 65535),
mClient(NULL), mTransactionHandler(NULL), mDatabaseID(-1),
- mGender(0), mHairStyle(0), mHairColor(0), mLevel(1),
- mTransaction(TRANS_NONE)
+ mGender(0), mHairStyle(0), mHairColor(0), mLevel(1), mLevelProgress(0),
+ mUpdateLevelProgress(false), mRecalculateLevel(true), mTransaction(TRANS_NONE)
{
Attribute attr = { 0, 0 };
mAttributes.resize(NB_CHARACTER_ATTRIBUTES, attr);
+ mExperience.resize(CHAR_SKILL_NB, 0);
// Get character data.
mDatabaseID = msg.readLong();
mName = msg.readString();
@@ -59,6 +63,16 @@ Character::Character(MessageIn &msg):
Inventory(this).initialize();
}
+void Character::update()
+{
+ if (mRecalculateLevel)
+ {
+ mRecalculateLevel = false;
+ recalculateLevel();
+ }
+ Being::update();
+}
+
void Character::perform()
{
if (mAction != ATTACK || mActionTime > 0) return;
@@ -78,6 +92,7 @@ void Character::perform()
damage.type = DAMAGE_PHYSICAL;
damage.cth = getModifiedAttribute(BASE_ATTR_HIT)
+ getModifiedAttribute(CHAR_SKILL_WEAPON_BEGIN + type);
+ damage.usedSkill = CHAR_SKILL_WEAPON_BEGIN + type;
if (type)
{
ItemModifiers const &mods = ic->getModifiers();
@@ -166,20 +181,37 @@ void Character::setBuySell(BuySell *t)
void Character::sendStatus()
{
- if (mModifiedAttributes.empty()) return;
-
- MessageOut msg(GPMSG_PLAYER_ATTRIBUTE_CHANGE);
- for (std::vector< unsigned char >::const_iterator i = mModifiedAttributes.begin(),
+ MessageOut attribMsg(GPMSG_PLAYER_ATTRIBUTE_CHANGE);
+ for (std::set<size_t>::const_iterator i = mModifiedAttributes.begin(),
i_end = mModifiedAttributes.end(); i != i_end; ++i)
{
int attr = *i;
- msg.writeByte(attr);
- msg.writeShort(getAttribute(attr));
- msg.writeShort(getModifiedAttribute(attr));
+ attribMsg.writeByte(attr);
+ attribMsg.writeShort(getAttribute(attr));
+ attribMsg.writeShort(getModifiedAttribute(attr));
}
- gameHandler->sendTo(this, msg);
-
+ if (attribMsg.getLength() > 2) gameHandler->sendTo(this, attribMsg);
mModifiedAttributes.clear();
+
+ MessageOut expMsg(GPMSG_PLAYER_EXP_CHANGE);
+ for (std::set<size_t>::const_iterator i = mModifiedExperience.begin(),
+ i_end = mModifiedExperience.end(); i != i_end; ++i)
+ {
+ int skill = *i;
+ expMsg.writeByte(skill);
+ expMsg.writeLong(getExpGot(skill));
+ expMsg.writeLong(getExpNeeded(skill));
+ }
+ if (expMsg.getLength() > 2) gameHandler->sendTo(this, expMsg);
+ mModifiedExperience.clear();
+
+ if (mUpdateLevelProgress)
+ {
+ mUpdateLevelProgress = false;
+ MessageOut progressMessage(GPMSG_LEVEL_PROGRESS);
+ progressMessage.writeByte(mLevelProgress);
+ gameHandler->sendTo(this, progressMessage);
+ }
}
void Character::modifiedAttribute(int attr)
@@ -211,8 +243,12 @@ void Character::modifiedAttribute(int attr)
/* weapon attack is applied through equip modifiers */
}
else if (i == BASE_ATTR_PHY_ATK_DELTA) {
- newValue = 0 /* + skill in class of currently equipped weapon */;
- /* weapon attack is applied through equip modifiers */
+ 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);
@@ -234,10 +270,126 @@ void Character::modifiedAttribute(int attr)
void Character::flagAttribute(int attr)
{
// Warn the player of this attribute modification.
- std::vector< unsigned char >::iterator
- i_end = mModifiedAttributes.end(),
- i = std::find(mModifiedAttributes.begin(), i_end, (unsigned char)attr);
- if (i == i_end) mModifiedAttributes.push_back(attr);
+ mModifiedAttributes.insert(attr);
+}
+
+int Character::expForLevel(int level)
+{
+ return int(pow(level, EXPCURVE_EXPONENT) * EXPCURVE_FACTOR);
+}
+
+void Character::receiveExperience(size_t skill, int experience)
+{
+ if (skill >= CHAR_SKILL_BEGIN && skill < CHAR_SKILL_END)
+ {
+ // add exp
+ long int newExp = mExperience.at(skill - CHAR_SKILL_BEGIN) + experience;
+ if (newExp > INT_MAX) newExp = INT_MAX; // avoid integer overflow.
+ mExperience.at(skill - CHAR_SKILL_BEGIN) = newExp;
+ mModifiedExperience.insert(skill - CHAR_SKILL_BEGIN);
+
+ // check for skill levelup
+ while (newExp >= Character::expForLevel(getAttribute(skill) + 1))
+ {
+ setAttribute(skill, getAttribute(skill) + 1);
+ modifiedAttribute(skill);
+ }
+
+ mRecalculateLevel = true;
+ }
+}
+
+void Character::recalculateLevel()
+{
+ std::list<float> levels;
+ for (int a = CHAR_SKILL_BEGIN; a < CHAR_SKILL_END; a++)
+ {
+ float expGot = getExpGot(a - CHAR_SKILL_BEGIN);
+ float expNeed = getExpNeeded(a - CHAR_SKILL_BEGIN);
+ levels.push_back(getAttribute(a) + expGot / expNeed);
+ }
+ levels.sort();
+
+ std::list<float>::iterator i = levels.end();
+ float level = 0.0f;
+ float factor = 1.0f;
+ float factorSum = 0.0f;
+ while (i != levels.begin()) //maybe it wouldn't be a bad idea to unroll this loop
+ {
+ i--;
+ level += *i * factor;
+ factorSum += factor;
+ factor *= LEVEL_SKILL_PRECEDENCE_FACTOR;
+ }
+ level /= factorSum;
+ level += 1.0f; // + 1.0f because the lowest level is 1 and not 0
+
+ while (mLevel < level)
+ {
+ levelup();
+ }
+
+ int levelProgress = int((level - floor(level)) * 100);
+ if (levelProgress != mLevelProgress)
+ {
+ mLevelProgress = levelProgress;
+ mUpdateLevelProgress = true;
+ }
+}
+
+int Character::getExpNeeded(size_t skill)
+{
+ int level = getAttribute(skill + CHAR_SKILL_BEGIN);
+ return Character::expForLevel(level + 1) - expForLevel(level);
+}
+
+int Character::getExpGot(size_t skill)
+{
+ int level = getAttribute(skill + CHAR_SKILL_BEGIN);
+ return mExperience.at(skill) - Character::expForLevel(level);
+}
+
+void Character::levelup()
+{
+ mLevel++;
+
+ mCharacterPoints += CHARPOINTS_PER_LEVELUP;
+ mCorrectionPoints += CORRECTIONPOINTS_PER_LEVELUP;
+ if (mCorrectionPoints > CORRECTIONPOINTS_MAX)
+ mCorrectionPoints = CORRECTIONPOINTS_MAX;
+
+ MessageOut levelupMsg(GPMSG_LEVELUP);
+ levelupMsg.writeShort(mLevel);
+ levelupMsg.writeShort(mCharacterPoints);
+ levelupMsg.writeShort(mCorrectionPoints);
+ gameHandler->sendTo(this, levelupMsg);
+ LOG_INFO(mName<<" reached level "<<mLevel);
+}
+
+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 (!mCharacterPoints) return ATTRIBMOD_NO_POINTS_LEFT;
+
+ mCharacterPoints--;
+ setAttribute(attribute, getAttribute(attribute) + 1);
+ modifiedAttribute(attribute);
+ return ATTRIBMOD_OK;
+}
+
+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 (!mCorrectionPoints) return ATTRIBMOD_NO_POINTS_LEFT;
+ if (getAttribute(attribute) <= 1) return ATTRIBMOD_DENIED;
+
+ mCorrectionPoints--;
+ mCharacterPoints++;
+ setAttribute(attribute, getAttribute(attribute) - 1);
+ modifiedAttribute(attribute);
+ return ATTRIBMOD_OK;
}
void Character::disconnected()
diff --git a/src/game-server/character.hpp b/src/game-server/character.hpp
index fd4e0054..316c4c79 100644
--- a/src/game-server/character.hpp
+++ b/src/game-server/character.hpp
@@ -51,6 +51,11 @@ class Character : public Being
Character(MessageIn &msg);
/**
+ * recalculates the level when necessary and calls Being::update
+ */
+ void update();
+
+ /**
* Perform actions.
*/
void perform();
@@ -219,15 +224,89 @@ class Character : public Being
*/
std::map< std::string, std::string > questCache;
+ /**
+ * Gives a skill a specific amount of exp and checks if a levelup
+ * occured.
+ */
+ void receiveExperience(size_t skill, int experience);
+
+ /**
+ * Gets total accumulated exp for skill
+ */
+ int getExperience(int skill) const
+ { return mExperience[skill]; }
+
+ /**
+ * Sets total accumulated exp for skill
+ */
+ void setExperience(int skill, int value)
+ { mExperience[skill] = 0; receiveExperience(skill + CHAR_SKILL_BEGIN , value) ; }
+
+ /**
+ * Tries to use a character point to increase a
+ * basic attribute
+ */
+ AttribmodResponseCode useCharacterPoint(size_t attribute);
+
+ /**
+ * Tries to use a correction point to reduce a
+ * basic attribute and regain a character point
+ */
+ AttribmodResponseCode useCorrectionPoint(size_t attribute);
+
+ void setCharacterPoints(int points)
+ { mCharacterPoints = points; }
+
+ int getCharacterPoints() const
+ { return mCharacterPoints; }
+
+ void setCorrectionPoints(int points)
+ { mCorrectionPoints = points; }
+
+ int getCorrectionPoints() const
+ { return mCorrectionPoints; }
+
private:
Character(Character const &);
Character &operator=(Character const &);
+ static const float EXPCURVE_EXPONENT = 3.0f; // should maybe be obtained
+ static const float EXPCURVE_FACTOR = 10.0f; // from the config file
+ static const float LEVEL_SKILL_PRECEDENCE_FACTOR = 0.75f; // I am taking suggestions for a better name
+ static const int CHARPOINTS_PER_LEVELUP = 5;
+ static const int CORRECTIONPOINTS_PER_LEVELUP = 2;
+ static const int CORRECTIONPOINTS_MAX = 10;
+
+ /**
+ * Advances the character by one level;
+ */
+ void levelup();
+
/**
* Marks attribute as recently modified.
*/
void flagAttribute(int);
+ /**
+ * Returns the exp needed to reach a specific skill level
+ */
+ static int expForLevel(int level);
+
+ /**
+ * Returns the exp needed for next skill levelup
+ */
+ int getExpNeeded(size_t skill);
+
+ /**
+ * Returns the exp collected on this skill level
+ */
+ int getExpGot(size_t skill);
+
+ /**
+ * Recalculates the character level
+ */
+ void recalculateLevel();
+
enum TransactionType
{ TRANS_NONE, TRANS_TRADE, TRANS_BUYSELL };
@@ -238,14 +317,22 @@ class Character : public Being
Possessions mPossessions; /**< Possesssions of the character. */
/** Attributes modified since last update. */
- std::vector< unsigned char > mModifiedAttributes;
+ std::set<size_t> mModifiedAttributes;
+ std::set<size_t> mModifiedExperience;
+
+ std::vector<unsigned int> mExperience; /**< experience collected for each skill.*/
std::string mName; /**< Name of the character. */
int mDatabaseID; /**< Character's database ID. */
unsigned char mGender; /**< Gender of the character. */
unsigned char mHairStyle; /**< Hair Style of the character. */
unsigned char mHairColor; /**< Hair Color of the character. */
- unsigned char mLevel; /**< Level of the character. */
+ int mLevel; /**< Level of the character. */
+ int mLevelProgress; /**< progress to next level in percent */
+ int mCharacterPoints; /**< unused attribute points that can be distributed */
+ int mCorrectionPoints; /**< unused attribute correction points */
+ bool mUpdateLevelProgress; /**< flag raised when percent to next level changed */
+ bool mRecalculateLevel; /**< flag raised when the character level might have increased */
unsigned char mAccountLevel; /**< Account level of the user. */
TransactionType mTransaction; /**< Trade/buy/sell action the character is involved in. */
};
diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp
index 8901a49e..6edbce12 100644
--- a/src/game-server/gamehandler.cpp
+++ b/src/game-server/gamehandler.cpp
@@ -406,6 +406,27 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
t->perform(id, amount);
} break;
+ case PGMSG_RAISE_ATTRIBUTE:
+ {
+ int attribute = message.readByte();
+ AttribmodResponseCode retCode;
+ retCode = computer.character->useCharacterPoint(attribute);
+ result.writeShort(GPMSG_RAISE_ATTRIBUTE_RESPONSE);
+ result.writeByte(retCode);
+ result.writeByte(attribute);
+ } break;
+
+ case PGMSG_LOWER_ATTRIBUTE:
+ {
+ int attribute = message.readByte();
+ AttribmodResponseCode retCode;
+ retCode = computer.character->useCorrectionPoint(attribute);
+ result.writeShort(GPMSG_LOWER_ATTRIBUTE_RESPONSE);
+ result.writeByte(retCode);
+ result.writeByte(attribute);
+ } break;
+
+
// The following messages should be handled by the chat server, not the game server.
#if 0
@@ -416,7 +437,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
messageMap[characterId] = computer.character;
accountHandler->playerCreateGuild(characterId, name);
} break;
-
+
case PGMSG_GUILD_INVITE:
{
short guildId = message.readShort();
@@ -425,7 +446,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
messageMap[characterId] = computer.character;
accountHandler->playerInviteToGuild(characterId, guildId, member);
} break;
-
+
case PGMSG_GUILD_ACCEPT:
{
std::string guildName = message.readString();
@@ -433,7 +454,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
messageMap[characterId] = computer.character;
accountHandler->playerAcceptInvite(characterId, guildName);
} break;
-
+
case PGMSG_GUILD_GET_MEMBERS:
{
short guildId = message.readShort();
@@ -441,7 +462,7 @@ void GameHandler::processMessage(NetComputer *comp, MessageIn &message)
messageMap[characterId] = computer.character;
accountHandler->getGuildMembers(characterId, guildId);
} break;
-
+
case PGMSG_GUILD_QUIT:
{
short guildId = message.readShort();
@@ -502,7 +523,7 @@ void GameHandler::addPendingCharacter(std::string const &token, Character *ch)
}
}
- // Mark the character as pending a connection.
+ // Mark the character as pending a connection.
mTokenCollector.addPendingConnect(token, ch);
}
diff --git a/src/game-server/item.hpp b/src/game-server/item.hpp
index dcf1b731..4f1186be 100644
--- a/src/game-server/item.hpp
+++ b/src/game-server/item.hpp
@@ -56,19 +56,16 @@ enum ItemType
enum WeaponType
{
WPNTYPE_NONE = 0,
- WPNTYPE_KNIFE,// 1
- WPNTYPE_SWORD,// 2
- WPNTYPE_POLEARM,// 3
- WPNTYPE_JAVELIN,// 4
- WPNTYPE_STAFF,// 5
- WPNTYPE_WHIP,// 6
- WPNTYPE_BOOMERANG,// 7
- WPNTYPE_BOW,// 8
- WPNTYPE_SICKLE,// 9
- WPNTYPE_CROSSBOW,// 10
- WPNTYPE_MACE,// 11
- WPNTYPE_AXE,// 12
- WPNTYPE_THROWN// 13
+ WPNTYPE_KNIFE,
+ WPNTYPE_SWORD,
+ WPNTYPE_POLEARM,
+ WPNTYPE_STAFF,
+ WPNTYPE_WHIP,
+ WPNTYPE_BOW,
+ WPNTYPE_SHOOTING,
+ WPNTYPE_MACE,
+ WPNTYPE_AXE,
+ WPNTYPE_THROWN
};
/**
diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp
index 8113a109..ef3963d4 100644
--- a/src/game-server/itemmanager.cpp
+++ b/src/game-server/itemmanager.cpp
@@ -72,13 +72,10 @@ WeaponType weaponTypeFromString (std::string name, int id = 0)
if (name=="knife") return WPNTYPE_KNIFE;
else if (name=="sword") return WPNTYPE_SWORD;
else if (name=="polearm") return WPNTYPE_POLEARM;
- else if (name=="javelin") return WPNTYPE_JAVELIN;
else if (name=="staff") return WPNTYPE_STAFF;
else if (name=="whip") return WPNTYPE_WHIP;
- else if (name=="boomerang") return WPNTYPE_BOOMERANG;
else if (name=="bow") return WPNTYPE_BOW;
- else if (name=="sickle") return WPNTYPE_SICKLE;
- else if (name=="crossbow") return WPNTYPE_CROSSBOW;
+ else if (name=="shooting") return WPNTYPE_SHOOTING;
else if (name=="mace") return WPNTYPE_MACE;
else if (name=="axe") return WPNTYPE_AXE;
else if (name=="thrown") return WPNTYPE_THROWN;
diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp
index 188c8781..018a379f 100644
--- a/src/game-server/monster.cpp
+++ b/src/game-server/monster.cpp
@@ -22,6 +22,7 @@
#include "game-server/monster.hpp"
+#include "game-server/character.hpp"
#include "game-server/item.hpp"
#include "game-server/mapcomposite.hpp"
#include "game-server/state.hpp"
@@ -59,6 +60,8 @@ Monster::Monster(MonsterClass *specy):
mSpecy(specy),
mCountDown(0),
mTargetListener(&monsterTargetEventDispatch),
+ mOwner(NULL),
+ mOwnerTimer(0),
mAttackTime(0)
{
LOG_DEBUG("Monster spawned!");
@@ -78,6 +81,7 @@ Monster::Monster(MonsterClass *specy):
setAttribute(BASE_ATTR_PHY_ATK_DELTA, 2);
setAttribute(BASE_ATTR_HIT, 10);
setAttribute(BASE_ATTR_EVADE, 10);
+ mExpReward = 100;
// Set positions relative to target from which the monster can attack
mAttackPositions.push_back(AttackPosition(+32, 0, DIRECTION_LEFT));
@@ -98,25 +102,39 @@ Monster::~Monster()
void Monster::perform()
{
- if (mAttackTime != mAttackAftDelay) return;
-
- mAction = ATTACK;
- raiseUpdateFlags(UPDATEFLAG_ATTACK);
-
- // Hard-coded values for now.
- Damage damage;
- damage.base = getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN);
- damage.delta = getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA);
- damage.cth = getModifiedAttribute(BASE_ATTR_HIT);
- damage.element = ELEMENT_NEUTRAL;
- damage.type = DAMAGE_PHYSICAL;
- performAttack(damage, mAttackRange, mAttackAngle);
+
+ if (mAction == ATTACK)
+ {
+ if (mAttackTime == mAttackAftDelay)
+ {
+ // Hard-coded values for now.
+ Damage damage;
+ damage.base = getModifiedAttribute(BASE_ATTR_PHY_ATK_MIN);
+ damage.delta = getModifiedAttribute(BASE_ATTR_PHY_ATK_DELTA);
+ damage.cth = getModifiedAttribute(BASE_ATTR_HIT);
+ damage.element = ELEMENT_NEUTRAL;
+ damage.type = DAMAGE_PHYSICAL;
+ damage.usedSkill = 0;
+ performAttack(damage, mAttackRange, mAttackAngle);
+ }
+ if (!mAttackTime)
+ {
+ setAction(STAND);
+ }
+ }
}
void Monster::update()
{
Being::update();
+ if (mOwner && mOwnerTimer)
+ {
+ mOwnerTimer--;
+ } else {
+ mOwner = NULL;
+ }
+
// If dead do nothing but rot
if (mAction == DEAD)
{
@@ -128,8 +146,7 @@ void Monster::update()
return;
}
- // If currently attacking finish attack;
- if (mAttackTime)
+ if (mAction == ATTACK)
{
mAttackTime--;
return;
@@ -196,8 +213,10 @@ void Monster::update()
// Check if we are there
if (bestAttackPosition == getPosition())
{
- // We are there - let's get ready to beat the crap out of the target
+ // We are there - let's beat the crap out of the target
setDirection(bestAttackDirection);
+ setAction(ATTACK);
+ raiseUpdateFlags(UPDATEFLAG_ATTACK);
mAttackTime = mAttackPreDelay + mAttackAftDelay;
}
else
@@ -251,6 +270,13 @@ void Monster::forgetTarget(Thing *t)
Being *b = static_cast< Being * >(t);
mAnger.erase(b);
b->removeListener(&mTargetListener);
+
+ if (b->getType() == OBJECT_CHARACTER)
+ {
+ Character *c = static_cast< Character * >(b);
+ mExpReceivers.erase(c);
+ mLegalExpReceivers.erase(c);
+ }
}
int Monster::damage(Object *source, Damage const &damage)
@@ -258,7 +284,7 @@ int Monster::damage(Object *source, Damage const &damage)
int HPLoss = Being::damage(source, damage);
if (HPLoss && source && source->getType() == OBJECT_CHARACTER)
{
- Being *s = static_cast< Being * >(source);
+ Character *s = static_cast< Character * >(source);
std::pair< std::map< Being *, int >::iterator, bool > ib =
mAnger.insert(std::make_pair(s, HPLoss));
@@ -270,6 +296,17 @@ int Monster::damage(Object *source, Damage const &damage)
{
ib.first->second += HPLoss;
}
+
+ if (damage.usedSkill)
+ {
+ mExpReceivers[s].insert(damage.usedSkill);
+ if (!mOwnerTimer || mOwner == s /*TODO: || mOwner->getParty() == s->getParty() */)
+ {
+ mOwner = s;
+ mLegalExpReceivers.insert(s);
+ mOwnerTimer = KILLSTEAL_PROTECTION_TIME;
+ }
+ }
}
return HPLoss;
}
@@ -278,6 +315,8 @@ void Monster::died()
{
Being::died();
mCountDown = 50; // Sets remove time to 5 seconds
+
+ //drop item
if (ItemClass *drop = mSpecy->getRandomDrop())
{
Item *item = new Item(drop, 1);
@@ -285,5 +324,31 @@ void Monster::died()
item->setPosition(getPosition());
GameState::enqueueInsert(item);
}
+
+ //distribute exp reward
+ if (mExpReceivers.size() > 0)
+ {
+ std::map<Character *, std::set <size_t> > ::iterator iChar;
+ std::set<size_t>::iterator iSkill;
+
+ float expPerChar = mExpReward / mExpReceivers.size();
+
+ for (iChar = mExpReceivers.begin(); iChar != mExpReceivers.end(); iChar++)
+ {
+ Character *character = (*iChar).first;
+ std::set<size_t> *skillSet = &(*iChar).second;
+
+ if (mLegalExpReceivers.find(character) == mLegalExpReceivers.end()
+ || skillSet->size() < 1)
+ {
+ continue;
+ }
+ int expPerSkill = int(expPerChar / skillSet->size());
+ for (iSkill = skillSet->begin(); iSkill != skillSet->end(); iSkill++)
+ {
+ character->receiveExperience(*iSkill, expPerSkill);
+ }
+ }
+ }
}
diff --git a/src/game-server/monster.hpp b/src/game-server/monster.hpp
index af1d241e..7167e469 100644
--- a/src/game-server/monster.hpp
+++ b/src/game-server/monster.hpp
@@ -97,6 +97,9 @@ struct AttackPosition
class Monster : public Being
{
public:
+
+ static const int KILLSTEAL_PROTECTION_TIME = 100; /**< Time in game ticks until ownership of a monster can change */
+
/**
* Constructor.
*/
@@ -145,10 +148,17 @@ class Monster : public Being
int mCountDown; /**< Count down till next random movement (temporary). */
std::map<Being *, int> mAnger; /**< Aggression towards other beings */
EventListener mTargetListener; /**< Listener for updating the anger list. */
+
+ Character* mOwner; /**< Character who currently owns this monster (killsteal protection) */
+ int mOwnerTimer; /**< Time until someone else can claim this monster (killsteal protection) */
+ std::map<Character *, std::set <size_t> > mExpReceivers; /**< List of characters and their skills that attacked this monster*/
+ std::set<Character *> mLegalExpReceivers; /**< List of characters who are entitled to receive exp (killsteal protection)*/
+
int mAttackTime; /**< Delay until monster can attack */
// TODO: the following vars should all be the same for all monsters of
// the same type. So they should be put into some central data structure
// to save memory.
+ int mExpReward; /**< Exp reward for defeating the monster */
int mAttackPreDelay; /**< time between decision to make an attack and performing the attack */
int mAttackAftDelay; /**< time it takes to perform an attack */
int mAttackRange; /**< range of the monsters attacks in pixel */
diff --git a/src/serialize/characterdata.hpp b/src/serialize/characterdata.hpp
index 7d3a7fc1..b1ef19e7 100644
--- a/src/serialize/characterdata.hpp
+++ b/src/serialize/characterdata.hpp
@@ -36,13 +36,21 @@ void serializeCharacterData(T const &data, MessageOut &msg)
msg.writeByte(data.getGender());
msg.writeByte(data.getHairStyle());
msg.writeByte(data.getHairColor());
- msg.writeByte(data.getLevel());
+ msg.writeShort(data.getLevel());
+ msg.writeShort(data.getCharacterPoints());
+ msg.writeShort(data.getCorrectionPoints());
for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i)
{
msg.writeByte(data.getAttribute(i));
}
+ for (int i = 0; i < CHAR_SKILL_NB; ++i)
+ {
+ msg.writeLong(data.getExperience(i));
+ }
+
+
msg.writeShort(data.getMapId());
Point const &pos = data.getPosition();
msg.writeShort(pos.x);
@@ -69,13 +77,20 @@ void deserializeCharacterData(T &data, MessageIn &msg)
data.setGender(msg.readByte());
data.setHairStyle(msg.readByte());
data.setHairColor(msg.readByte());
- data.setLevel(msg.readByte());
+ data.setLevel(msg.readShort());
+ data.setCharacterPoints(msg.readShort());
+ data.setCorrectionPoints(msg.readShort());
for (int i = CHAR_ATTR_BEGIN; i < CHAR_ATTR_END; ++i)
{
data.setAttribute(i, msg.readByte());
}
+ for (int i = 0; i < CHAR_SKILL_NB; ++i)
+ {
+ data.setExperience(i, msg.readLong());
+ }
+
data.setMapId(msg.readShort());
Point temporaryPoint;