diff options
Diffstat (limited to 'src/game-server')
-rw-r--r-- | src/game-server/accountconnection.cpp | 38 | ||||
-rw-r--r-- | src/game-server/being.hpp | 3 | ||||
-rw-r--r-- | src/game-server/character.cpp | 186 | ||||
-rw-r--r-- | src/game-server/character.hpp | 91 | ||||
-rw-r--r-- | src/game-server/gamehandler.cpp | 31 | ||||
-rw-r--r-- | src/game-server/item.hpp | 23 | ||||
-rw-r--r-- | src/game-server/itemmanager.cpp | 5 | ||||
-rw-r--r-- | src/game-server/monster.cpp | 99 | ||||
-rw-r--r-- | src/game-server/monster.hpp | 10 |
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 */ |