summaryrefslogtreecommitdiff
path: root/src/game-server
diff options
context:
space:
mode:
Diffstat (limited to 'src/game-server')
-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
9 files changed, 407 insertions, 79 deletions
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 */