From 6f287f239e9d94707735b183d6c6b89eea9fef20 Mon Sep 17 00:00:00 2001 From: Erik Schilling Date: Sun, 10 Jun 2012 15:11:01 +0200 Subject: Work on (Auto)Attack system. During the implementation bjorn and I agreed to limit the number of attacks that can be used in the same tick to one. This makes a lot of stuff easier and the client cannot display two frames at the same time Things done: - Implemented setting of attacks when equipping/unequipping items - Single place where the xml attack node is parsed - Finished attack logic - Unified the attack handling of monsters and characters - Added a global cooldown after attack use (not only for next use of same attack) - Removed the temponary attributes for the monster attack values - Priorities for all attacks - Rewrote the attack core: - Attacks now have this attributes: - warmup -> time a attack needs after starting it to actually deal the damage - cooldown -> time a attack needs after dealing damage before another attack can be used - reuse -> time before the same attack can be used again - If no attack is performed at the moment the following is done: - make a list with all ready attacks - check for attack that has the necessarily range and highest priority - start this attack (inform client about it) - when warmup is finished -> trigger damage - when cooldown is finished -> allow to use other (or the same if reusetimer allows) attacks TODO: - sync client with this to allow better timed animations --- src/common/defines.h | 49 +++++++++-- src/game-server/attack.cpp | 124 ++++++++++++++++++++------- src/game-server/attack.h | 171 ++++++++++++++++++++++++------------- src/game-server/being.cpp | 86 +++++++++++++++---- src/game-server/being.h | 42 +++++++-- src/game-server/character.cpp | 94 +++++++++----------- src/game-server/character.h | 6 ++ src/game-server/item.cpp | 42 ++++++--- src/game-server/item.h | 53 +++++++----- src/game-server/itemmanager.cpp | 118 ++++++++++++------------- src/game-server/monster.cpp | 170 +++++++++++++++--------------------- src/game-server/monster.h | 42 +++------ src/game-server/monstermanager.cpp | 78 ++--------------- src/game-server/skillmanager.cpp | 12 ++- src/game-server/skillmanager.h | 2 + src/game-server/timeout.h | 2 +- 16 files changed, 617 insertions(+), 474 deletions(-) (limited to 'src') diff --git a/src/common/defines.h b/src/common/defines.h index dab5b03..d572df1 100644 --- a/src/common/defines.h +++ b/src/common/defines.h @@ -127,6 +127,28 @@ enum Element ELEMENT_ILLEGAL }; +static inline Element elementFromString(const std::string &name) +{ + static std::map table; + + if (table.empty()) + { + table["neutral"] = ELEMENT_NEUTRAL; + table["fire"] = ELEMENT_FIRE; + table["water"] = ELEMENT_WATER; + table["earth"] = ELEMENT_EARTH; + table["air"] = ELEMENT_AIR; + table["lightning"] = ELEMENT_LIGHTNING; + table["metal"] = ELEMENT_METAL; + table["wood"] = ELEMENT_WOOD; + table["ice"] = ELEMENT_ICE; + } + + std::map::iterator val = table.find(name); + + return val == table.end() ? ELEMENT_ILLEGAL : (*val).second; +} + /** * Damage type, used to know how to compute them. */ @@ -138,6 +160,23 @@ enum DamageType DAMAGE_OTHER = -1 }; +static inline DamageType damageTypeFromString(const std::string &name) +{ + static std::map table; + + if (table.empty()) + { + table["physical"] = DAMAGE_PHYSICAL; + table["magical"] = DAMAGE_MAGICAL; + table["direct"] = DAMAGE_DIRECT; + table["other"] = DAMAGE_OTHER; + } + + std::map::iterator val = table.find(name); + + return val == table.end() ? DAMAGE_OTHER : (*val).second; +} + /** * A series of hardcoded attributes that must be defined. * FIXME: Much of these serve only to indicate derivatives, and so would not be @@ -174,15 +213,7 @@ enum // Money and inventory size attributes. ATTR_GP = 18, - ATTR_INV_CAPACITY = 19, - - /** - * Temporary attributes. - * @todo Use Attacks instead. - */ - MOB_ATTR_PHY_ATK_MIN = 20, - MOB_ATTR_PHY_ATK_DELTA = 21, - MOB_ATTR_MAG_ATK = 22 + ATTR_INV_CAPACITY = 19 }; #endif // DEFINES_H diff --git a/src/game-server/attack.cpp b/src/game-server/attack.cpp index 4f04255..1825b86 100644 --- a/src/game-server/attack.cpp +++ b/src/game-server/attack.cpp @@ -20,58 +20,120 @@ #include "attack.h" -void Attacks::add(const Attack &attack) +#include + +#include "common/defines.h" + +#include "game-server/character.h" +#include "game-server/skillmanager.h" + +AttackInfo *AttackInfo::readAttackNode(xmlNodePtr node) { - mAttacks.push_back(attack); - // Slow, but safe. - mAttacks.sort(); + std::string skill = XML::getProperty(node, "skill", std::string()); + + unsigned skillId; + if (utils::isNumeric(skill)) + skillId = utils::stringToInt(skill); + else + skillId = skillManager->getId(skill); + + if (!skill.empty() && !skillManager->exists(skillId)) + { + LOG_WARN("Error parsing Attack node: Invalid skill " << skill + << " taking default skill"); + skillId = skillManager->getDefaultSkillId(); + } + + unsigned id = XML::getProperty(node, "id", 0); + unsigned priority = XML::getProperty(node, "priority", 0); + unsigned warmupTime = XML::getProperty(node, "warmuptime", 0); + unsigned cooldownTime = XML::getProperty(node, "cooldowntime", 0); + unsigned reuseTime = XML::getProperty(node, "reusetime", 0); + unsigned short baseDamange = XML::getProperty(node, "basedamage", 0); + unsigned short deltaDamage = XML::getProperty(node, "deltadamage", 0); + unsigned short chanceToHit = XML::getProperty(node, "chancetohit", 0); + unsigned short range = XML::getProperty(node, "range", 0); + Element element = elementFromString( + XML::getProperty(node, "element", "neutral")); + DamageType type = damageTypeFromString( + XML::getProperty(node, "type", "other")); + + Damage dmg; + dmg.id = id; + dmg.skill = skillId; + dmg.base = baseDamange; + dmg.delta = deltaDamage; + dmg.cth = chanceToHit; + dmg.range = range; + dmg.element = element; + dmg.type = type; + AttackInfo *attack = new AttackInfo(priority, dmg, warmupTime, cooldownTime, + reuseTime); + return attack; } -void Attacks::clear() +void Attacks::add(AttackInfo *attackInfo) { - mAttacks.clear(); + mAttacks.push_back(Attack(attackInfo)); } -void Attacks::stop() +void Attacks::remove(AttackInfo *attackInfo) { - for (std::list::iterator it = mAttacks.begin(); - it != mAttacks.end(); ++it) + for (std::vector::iterator it = mAttacks.begin(), + it_end = mAttacks.end(); it != it_end; ++it) { - it->halt(); + if ((*it).getAttackInfo() == attackInfo) + { + if (mCurrentAttack && mCurrentAttack->getAttackInfo() == attackInfo) + mCurrentAttack = 0; + mAttacks.erase(it); + return; + } } - mActive = false; } -void Attacks::start() +void Attacks::markAttackAsTriggered() { - for (std::list::iterator it = mAttacks.begin(); - it != mAttacks.end(); ++it) + mCurrentAttack->markAsTriggered(); + mCurrentAttack = 0; +} + +Attack *Attacks::getTriggerableAttack() +{ + if (!mCurrentAttack) + return 0; + + int cooldownTime = mCurrentAttack->getAttackInfo()->getCooldownTime(); + if (mAttackTimer.remaining() <= cooldownTime) { - // If the attack is inactive, we hard reset it. - if (!it->getTimer()) - it->reset(); - else - it->softReset(); + return mCurrentAttack; } - mActive = true; + + return 0; } -void Attacks::tick(std::list *ret) +void Attacks::startAttack(Attack *attack) { - for (std::list::iterator it = mAttacks.begin(); + mCurrentAttack = attack; + mAttackTimer.set(attack->getAttackInfo()->getWarmupTime() + + attack->getAttackInfo()->getCooldownTime()); +} + +void Attacks::getUsuableAttacks(std::vector *ret) +{ + assert(ret != 0); + + // we have a current Attack + if ((!mAttackTimer.expired() && mCurrentAttack)) + return; + for (std::vector::iterator it = mAttacks.begin(); it != mAttacks.end(); ++it) { - if (it->tick()) - { - if (mActive) - it->reset(); - else - it->halt(); - } + Attack &attack = *it; - if (ret && it->isReady()) + if (attack.isUsuable()) { - ret->push_back(*it); + ret->push_back(&attack); } } } diff --git a/src/game-server/attack.h b/src/game-server/attack.h index f9ddf6a..0bcc361 100644 --- a/src/game-server/attack.h +++ b/src/game-server/attack.h @@ -26,22 +26,30 @@ #include "common/defines.h" +#include "scripting/script.h" + +#include "utils/xml.h" + +#include "game-server/timeout.h" + /** * Structure that describes the severity and nature of an attack a being can * be hit by. */ struct Damage { - unsigned int skill; /**< Skill used by source (needed for exp calculation) */ - unsigned short base; /**< Base amount of damage. */ - unsigned short delta; /**< Additional damage when lucky. */ - unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */ - unsigned char element; /**< Elemental damage. */ - DamageType type; /**< Damage type: Physical or magical? */ - bool trueStrike; /**< Override dodge calculation */ - unsigned short range; /**< Maximum distance that this attack can be used from, in pixels */ + unsigned id; /**< Id of the attack (needed for displaying animation clientside */ + unsigned skill; /**< Skill used by source (needed for exp calculation) */ + unsigned short base; /**< Base amount of damage. */ + unsigned short delta; /**< Additional damage when lucky. */ + unsigned short cth; /**< Chance to hit. Opposes the evade attribute. */ + Element element; /**< Elemental damage. */ + DamageType type; /**< Damage type: Physical or magical? */ + bool trueStrike; /**< Override dodge calculation */ + unsigned short range; /**< Maximum distance that this attack can be used from, in pixels */ Damage(): + id(0), skill(0), base(0), delta(0), @@ -57,53 +65,51 @@ struct Damage * Class that stores information about an auto-attack */ -class Attack +class Character; + +struct AttackInfo { public: - Attack(Damage &damage, unsigned int warmup, unsigned int cooldown): + AttackInfo(unsigned priority, const Damage &damage, + unsigned short warmupTime, unsigned short cooldownTime, + unsigned short reuseTime): mDamage(damage), - mTimer(0), - mAspd(cooldown), - mWarmup(warmup && warmup < cooldown ? warmup : cooldown >> 2) + mCooldownTime(cooldownTime), + mWarmupTime(warmupTime), + mReuseTime(reuseTime), + mPriority(priority) {} - unsigned short getTimer() const { return mTimer; } - bool tick() { return mTimer ? !--mTimer : false; } - void reset() { mTimer = mAspd; } - void softReset() { if (mTimer >= mWarmup) mTimer = mAspd; } - void halt() { if (mTimer >= mWarmup) mTimer = 0; } - bool isReady() const { return !(mTimer - mWarmup); } + unsigned short getWarmupTime() const + { return mWarmupTime; } + + unsigned short getCooldownTime() const + { return mCooldownTime; } - bool operator<(const Attack &rhs) const - { return mTimer < rhs.mTimer; } + unsigned short getReuseTime() const + { return mReuseTime; } - const Damage &getDamage() const { return mDamage; } + static AttackInfo *readAttackNode(xmlNodePtr node); + + Damage &getDamage() + { return mDamage; } + + const Script::Ref &getScriptCallback() const + { return mCallback; } + + void setCallback(Script *script) + { script->assignCallback(mCallback); } + + unsigned getPriority() const + { return mPriority; } private: Damage mDamage; - /** - * Internal timer that is modified each tick. - * - * When > warmup, the attack is warming up before a strike - * When = warmup, the attack triggers, dealing damage to the target - * *if* the target is still in range. - * (The attack is canceled when the target moves out of range before - * the attack can hit, there should be a trigger for scripts here - * too) - * (Should the character automatically persue when the target is still - * visible in this case?) - * When < warmup, the attack is cooling down after a strike. When in - * cooldown, the timer should not be soft-reset. - * When 0, the attack is inactive (the character is doing something - * other than attacking and the attack is not in cooldown) - */ - unsigned short mTimer; - /** * Value to reset the timer to (warmup + cooldown) */ - unsigned short mAspd; + unsigned short mCooldownTime; /** * Pre-attack delay tick. @@ -111,7 +117,52 @@ class Attack * So the attack triggers where timer == warmup, having gone through * aspd - warmup ticks. */ - unsigned short mWarmup; + unsigned short mWarmupTime; + + /** + * The global cooldown that needs to be finished before the being can + * use the next attack. + */ + unsigned short mReuseTime; + + /** + * Name of the script callback + */ + Script::Ref mCallback; + + /** + * Priority of the attack + */ + unsigned mPriority; +}; + +class Attack +{ + public: + Attack(AttackInfo *info): + mInfo(info) + {} + + AttackInfo *getAttackInfo() + { return mInfo; } + + void markAsTriggered() + { mReuseTimer.set(mInfo->getCooldownTime() + mInfo->getReuseTime()); } + + bool isUsuable() const + { return mReuseTimer.expired(); } + + + private: + /** + * Contains infos about cooldown/damage/etc + */ + AttackInfo *mInfo; + + /** + * Internal timer that checks time for reuse + */ + Timeout mReuseTimer; }; /** @@ -120,14 +171,16 @@ class Attack class Attacks { public: - /** - * Whether the being has at least one auto attack that is ready. - */ - void add(const Attack &); - void clear(); // Wipe the list completely (used in place of remove for now; FIXME) - void start(); - void stop(); // If the character does some action other than attacking, reset all warmups (NOT cooldowns!) - void tick(std::list *ret = 0); + Attacks(): + mCurrentAttack(0) + {} + + void add(AttackInfo *); + void remove(AttackInfo *); + void markAttackAsTriggered(); + Attack *getTriggerableAttack(); + void startAttack(Attack *attack); + void getUsuableAttacks(std::vector *ret); /** * Tells the number of attacks available @@ -135,19 +188,17 @@ class Attacks unsigned getNumber() { return mAttacks.size(); } - /** - * Tells whether the attacks are active. - */ - bool areActive() - { return mActive; } - private: + std::vector mAttacks; + + Attack *mCurrentAttack; + /** - * Marks whether or not to keep auto-attacking. Cooldowns still need - * to be processed when false. + * when greater than cooldown -> warming up + * when equals cooldown -> trigger attack + * when smaller -> cooling down */ - bool mActive; - std::list mAttacks; + Timeout mAttackTimer; }; #endif // ATTACK_H diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp index 7ae01a0..2064cfc 100644 --- a/src/game-server/being.cpp +++ b/src/game-server/being.cpp @@ -30,6 +30,7 @@ #include "game-server/eventlistener.h" #include "game-server/mapcomposite.h" #include "game-server/effect.h" +#include "game-server/skillmanager.h" #include "game-server/statuseffect.h" #include "game-server/statusmanager.h" #include "utils/logger.h" @@ -40,6 +41,7 @@ Being::Being(EntityType type): mAction(STAND), mTarget(NULL), mGender(GENDER_UNSPECIFIED), + mCurrentAttack(0), mDirection(DOWN) { const AttributeManager::AttributeScope &attr = attributeManager->getAttributeScope(BeingScope); @@ -185,6 +187,65 @@ void Being::died() } } +void Being::processAttacks() +{ + if (mAction != ATTACK || !mTarget) + return; + + // Ticks attacks even when not attacking to permit cooldowns and warmups. + std::vector attacksReady; + mAttacks.getUsuableAttacks(&attacksReady); + + if (Attack *triggerableAttack = mAttacks.getTriggerableAttack()) + { + processAttack(*triggerableAttack); + mAttacks.markAttackAsTriggered(); + } + + // Deal with the ATTACK action. + if (attacksReady.empty()) + return; + + Attack *highestPriorityAttack = 0; + // Performs all ready attacks. + for (std::vector::const_iterator it = attacksReady.begin(), + it_end = attacksReady.end(); it != it_end; ++it) + { + // check if target is in range using the pythagorean theorem + int distx = this->getPosition().x - mTarget->getPosition().x; + int disty = this->getPosition().y - mTarget->getPosition().y; + int distSquare = (distx * distx + disty * disty); + AttackInfo *info = (*it)->getAttackInfo(); + int maxDist = info->getDamage().range + getSize(); + + if (distSquare <= maxDist * maxDist && + (!highestPriorityAttack || + highestPriorityAttack->getAttackInfo()->getPriority() + < info->getPriority())) + { + highestPriorityAttack = *it; + } + } + if (highestPriorityAttack) + { + mAttacks.startAttack(highestPriorityAttack); + mCurrentAttack = highestPriorityAttack; + setDestination(getPosition()); + // TODO: Turn into direction of enemy + raiseUpdateFlags(UPDATEFLAG_ATTACK); + } +} + +void Being::addAttack(AttackInfo *attackInfo) +{ + mAttacks.add(attackInfo); +} + +void Being::removeAttack(AttackInfo *attackInfo) +{ + mAttacks.remove(attackInfo); +} + void Being::setDestination(const Point &dst) { mDst = dst; @@ -409,7 +470,7 @@ int Being::directionToAngle(int direction) } } -int Being::performAttack(Being *target, const Damage &damage) +int Being::performAttack(Being *target, const Damage &dmg) { // check target legality if (!target @@ -423,25 +484,11 @@ int Being::performAttack(Being *target, const Damage &damage) && getType() == OBJECT_CHARACTER) return -1; - // check if target is in range using the pythagorean theorem - int distx = this->getPosition().x - target->getPosition().x; - int disty = this->getPosition().y - target->getPosition().y; - int distSquare = (distx * distx + disty * disty); - int maxDist = damage.range + target->getSize(); - if (maxDist * maxDist < distSquare) - return -1; - - // Note: The auto-attack system will handle the delay between two attacks. - - return target->damage(this, damage); + return target->damage(this, dmg); } void Being::setAction(BeingAction action) { - // Stops the auto-attacks when changing action - if (mAction == ATTACK && action != ATTACK) - mAttacks.stop(); - mAction = action; if (action != ATTACK && // The players are informed about these actions action != WALK) // by other messages @@ -692,6 +739,8 @@ void Being::update() // Check if being died if (getModifiedAttribute(ATTR_HP) <= 0 && mAction != DEAD) died(); + + processAttacks(); } void Being::inserted() @@ -707,3 +756,8 @@ void Being::setGender(BeingGender gender) { mGender = gender; } + +void Being::processAttack(Attack &attack) +{ + performAttack(mTarget, attack.getAttackInfo()->getDamage()); +} diff --git a/src/game-server/being.h b/src/game-server/being.h index be1e570..eee8147 100644 --- a/src/game-server/being.h +++ b/src/game-server/being.h @@ -86,6 +86,21 @@ class Being : public Actor */ virtual void died(); + /** + * Process all available attacks + */ + void processAttacks(); + + /** + * Adds an attack to the available attacks + */ + void addAttack(AttackInfo *attack); + + /** + * Removes an attack from the available attacks + */ + void removeAttack(AttackInfo *attackInfo); + /** * Gets the destination coordinates of the being. */ @@ -135,7 +150,7 @@ class Being : public Actor * Performs an attack. * Return Value: damage inflicted or -1 when illegal target */ - int performAttack(Being *target, const Damage &damage); + int performAttack(Being *target, const Damage &dmg); /** * Sets the current action. @@ -153,7 +168,9 @@ class Being : public Actor * For being, this is defaulted to the first one (1). */ virtual int getAttackId() const - { return 1; } + { return mCurrentAttack ? + mCurrentAttack->getAttackInfo()->getDamage().id : 0; + } /** * Moves the being toward its destination. @@ -286,6 +303,18 @@ class Being : public Actor virtual void inserted(); protected: + /** + * Performs an attack + */ + virtual void processAttack(Attack &attack); + + /** + * Update the being direction when moving so avoid directions desyncs + * with other clients. + */ + void updateDirection(const Point ¤tPos, + const Point &destPos); + static const int TICKS_PER_HP_REGENERATION = 100; BeingAction mAction; @@ -296,18 +325,13 @@ class Being : public Actor Point mOld; /**< Old coordinates. */ Point mDst; /**< Target coordinates. */ BeingGender mGender; /**< Gender of the being. */ + Attack *mCurrentAttack; /**< Last used attack. */ + private: Being(const Being &rhs); Being &operator=(const Being &rhs); - /** - * Update the being direction when moving so avoid directions desyncs - * with other clients. - */ - void updateDirection(const Point ¤tPos, - const Point &destPos); - Path mPath; BeingDirection mDirection; /**< Facing direction. */ diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index d2b694c..5d96cab 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -87,7 +87,8 @@ Character::Character(MessageIn &msg): mParty(0), mTransaction(TRANS_NONE), mTalkNpcId(0), - mNpcThread(0) + mNpcThread(0), + mKnuckleAttackInfo(0) { const AttributeManager::AttributeScope &attr = attributeManager->getAttributeScope(CharacterScope); @@ -107,11 +108,27 @@ Character::Character(MessageIn &msg): Inventory(this).initialize(); modifiedAllAttribute(); setSize(16); + + // Default knuckle attack + int damageBase = this->getModifiedAttribute(ATTR_STR); + int damageDelta = damageBase / 2; + Damage knuckleDamage; + knuckleDamage.skill = skillManager->getDefaultSkillId(); + knuckleDamage.base = damageBase; + knuckleDamage.delta = damageDelta; + knuckleDamage.cth = 2; + knuckleDamage.element = ELEMENT_NEUTRAL; + knuckleDamage.type = DAMAGE_PHYSICAL; + knuckleDamage.range = DEFAULT_TILE_LENGTH; + + mKnuckleAttackInfo = new AttackInfo(0, knuckleDamage, 7, 3, 0); + addAttack(mKnuckleAttackInfo); } Character::~Character() { delete mNpcThread; + delete mKnuckleAttackInfo; } void Character::update() @@ -163,58 +180,6 @@ void Character::update() mStatusEffects[it->first] = it->second.time; it++; } - - processAttacks(); -} - -void Character::processAttacks() -{ - // Ticks attacks even when not attacking to permit cooldowns and warmups. - std::list attacksReady; - mAttacks.tick(&attacksReady); - - if (mAction != ATTACK || !mTarget) - { - mAttacks.stop(); - return; - } - - // Deal with the ATTACK action. - - // Install default bare knuckle attack if no attacks were added from config. - // TODO: Get this from configuration. - if (!mAttacks.getNumber()) - { - int damageBase = getModifiedAttribute(ATTR_STR); - int damageDelta = damageBase / 2; - Damage knuckleDamage; - knuckleDamage.skill = skillManager->getDefaultSkillId(); - knuckleDamage.base = damageBase; - knuckleDamage.delta = damageDelta; - knuckleDamage.cth = 2; - knuckleDamage.element = ELEMENT_NEUTRAL; - knuckleDamage.type = DAMAGE_PHYSICAL; - knuckleDamage.range = (getSize() < DEFAULT_TILE_LENGTH) ? - DEFAULT_TILE_LENGTH : getSize(); - - Attack knuckleAttack(knuckleDamage, 7, 3); - mAttacks.add(knuckleAttack); - } - - if (attacksReady.empty()) - { - if (!mAttacks.areActive()) - mAttacks.start(); - } - else - { - // Performs all ready attacks. - for (std::list::iterator it = attacksReady.begin(); - it != attacksReady.end(); ++it) - { - performAttack(mTarget, it->getDamage()); - } - } } void Character::died() @@ -537,6 +502,14 @@ bool Character::recalculateBaseAttribute(unsigned int attr) newBase = 0.0; // TODO break; + case ATTR_STR: + if (mKnuckleAttackInfo) + { + Damage &knuckleDamage = mKnuckleAttackInfo->getDamage(); + knuckleDamage.base = getModifiedAttribute(ATTR_STR); + knuckleDamage.delta = knuckleDamage.base / 2; + } + break; default: return Being::recalculateBaseAttribute(attr); } @@ -794,6 +767,21 @@ void Character::resumeNpcThread() } } +void Character::addAttack(AttackInfo *attackInfo) +{ + // Remove knuckle attack + Being::addAttack(attackInfo); + Being::removeAttack(mKnuckleAttackInfo); +} + +void Character::removeAttack(AttackInfo *attackInfo) +{ + Being::removeAttack(attackInfo); + // Add knuckle attack + if (mAttacks.getNumber() == 0) + Being::addAttack(mKnuckleAttackInfo); +} + void Character::disconnected() { mConnected = false; diff --git a/src/game-server/character.h b/src/game-server/character.h index b5ef578..1176229 100644 --- a/src/game-server/character.h +++ b/src/game-server/character.h @@ -424,6 +424,10 @@ class Character : public Being void triggerLoginCallback(); + virtual void addAttack(AttackInfo *attackInfo); + + virtual void removeAttack(AttackInfo *attackInfo); + protected: /** * Gets the way the actor blocks pathfinding for other objects @@ -527,6 +531,8 @@ class Character : public Being Timeout mMuteTimeout; /**< Time until the character is no longer muted */ + AttackInfo *mKnuckleAttackInfo; + static Script::Ref mDeathCallback; static Script::Ref mDeathAcceptedCallback; static Script::Ref mLoginCallback; diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp index 15041ad..1ca6461 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -47,15 +47,15 @@ void ItemEffectAttrMod::dispell(Being *itemUser) mId, !mDuration); } -bool ItemEffectAttack::apply(Being * /* itemUser */) +bool ItemEffectAttack::apply(Being *itemUser) { - // TODO - STUB + itemUser->addAttack(mAttackInfo); return false; } -void ItemEffectAttack::dispell(Being * /* itemUser */) +void ItemEffectAttack::dispell(Being *itemUser) { - // TODO + itemUser->removeAttack(mAttackInfo); } ItemEffectScript::~ItemEffectScript() @@ -105,12 +105,18 @@ ItemClass::~ItemClass() delete mEffects.begin()->second; mEffects.erase(mEffects.begin()); } + + for (std::vector::iterator it = mAttackInfos.begin(), + it_end = mAttackInfos.end(); + it != it_end; ++it) + { + delete *it; + } } void ItemClass::addEffect(ItemEffectInfo *effect, ItemTriggerType id, ItemTriggerType dispell) -{ mEffects.insert(std::make_pair(id, effect)); if (dispell) mDispells.insert(std::make_pair(dispell, effect)); @@ -121,21 +127,29 @@ bool ItemClass::useTrigger(Being *itemUser, ItemTriggerType trigger) if (!trigger) return false; - std::pair::iterator, - std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator> - rn = mEffects.equal_range(trigger); + std::multimap::iterator it, it_end; + bool ret = false; - while (rn.first != rn.second) - if (rn.first++->second->apply(itemUser)) - ret = true; + for (it = mEffects.begin(), it_end = mEffects.end(); it != it_end; ++it) + if (it->first == trigger) + if (it->second->apply(itemUser)) + ret = true; - rn = mDispells.equal_range(trigger); - while (rn.first != rn.second) - rn.first++->second->dispell(itemUser); + for (it = mDispells.begin(), it_end = mDispells.end(); it != it_end; ++it) + if (it->first == trigger) + it->second->dispell(itemUser); return ret; } +void ItemClass::addAttack(AttackInfo *attackInfo, + ItemTriggerType applyTrigger, + ItemTriggerType dispellTrigger) +{ + mAttackInfos.push_back(attackInfo); + addEffect(new ItemEffectAttack(attackInfo), applyTrigger, dispellTrigger); +} + Item::Item(ItemClass *type, int amount) : Actor(OBJECT_ITEM), mType(type), mAmount(amount) diff --git a/src/game-server/item.h b/src/game-server/item.h index 872e52c..7004a2d 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -24,6 +24,7 @@ #include #include "game-server/actor.h" +#include "game-server/attack.h" #include "scripting/script.h" class Being; @@ -67,25 +68,15 @@ enum SET_STATE_NOT_FLOATING }; -struct ItemAttackInfo -{ - unsigned int base; - unsigned int range; - unsigned int baseSpeed; - unsigned int skillId; - /// attribute id -> damage bonus per point - std::map< unsigned int, double > attrBonus; -}; - enum ItemTriggerType { ITT_NULL = 0, - ITT_IN_INVY, // Associated effects apply when the item is in the inventory - ITT_ACTIVATE, // Associated effects apply when the item is activated - ITT_EQUIP, // Assosciated effects apply when the item is equipped + ITT_IN_INVY, // Associated effects apply when the item is in the inventory + ITT_ACTIVATE, // Associated effects apply when the item is activated + ITT_EQUIP, // Assosciated effects apply when the item is equipped ITT_LEAVE_INVY, // Associated effects apply when the item leaves the inventory - ITT_UNEQUIP, // Associated effects apply when the item is unequipped - ITT_EQUIPCHG // When the item is still equipped, but in a different way + ITT_UNEQUIP, // Associated effects apply when the item is unequipped + ITT_EQUIPCHG // When the item is still equipped, but in a different way }; enum ItemEffectType @@ -93,13 +84,20 @@ enum ItemEffectType // Effects that are removed automatically when the trigger ends // (ie. item no longer exists in invy, unequipped) IET_ATTR_MOD = 0, // Modify a given attribute with a given value - IET_ATTACK, // Give the associated being an attack + IET_ATTACK, // Give the associated being an attack // Effects that do not need any automatic removal - IET_COOLDOWN, // Set a cooldown to this item, preventing activation for n ticks - IET_G_COOLDOWN, // Set a cooldown to all items of this type for this being - IET_SCRIPT // Call an associated lua script with given variables + IET_COOLDOWN, // Set a cooldown to this item, preventing activation for n ticks + IET_G_COOLDOWN, // Set a cooldown to all items of this type for this being + IET_SCRIPT // Call an associated lua script with given variables }; +struct ItemTrigger +{ + ItemTriggerType apply; + ItemTriggerType dispell; +}; + + class ItemEffectInfo { public: @@ -115,7 +113,8 @@ class ItemEffectAttrMod : public ItemEffectInfo ItemEffectAttrMod(unsigned int attrId, unsigned int layer, double value, unsigned int id, unsigned int duration = 0) : mAttributeId(attrId), mAttributeLayer(layer), - mMod(value), mDuration(duration), mId(id) {} + mMod(value), mDuration(duration), mId(id) + {} bool apply(Being *itemUser); void dispell(Being *itemUser); @@ -131,8 +130,14 @@ class ItemEffectAttrMod : public ItemEffectInfo class ItemEffectAttack : public ItemEffectInfo { public: + ItemEffectAttack(AttackInfo *attackInfo) : + mAttackInfo(attackInfo) + {} + bool apply(Being *itemUser); void dispell(Being *itemUser); + private: + AttackInfo *mAttackInfo; }; class ItemEffectConsumes : public ItemEffectInfo @@ -243,6 +248,12 @@ class ItemClass Script::Ref getEventCallback(const std::string &event) const { return mEventCallbacks.value(event); } + void addAttack(AttackInfo *attackInfo, ItemTriggerType applyTrigger, + ItemTriggerType dispellTrigger); + + std::vector &getAttackInfos() + { return mAttackInfos; } + private: /** * Add an effect to a trigger @@ -277,6 +288,8 @@ class ItemClass */ utils::NameMap mEventCallbacks; + std::vector mAttackInfos; + friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index 2b6056b..946815a 100644 --- a/src/game-server/itemmanager.cpp +++ b/src/game-server/itemmanager.cpp @@ -307,66 +307,66 @@ void ItemManager::readEquipNode(xmlNodePtr equipNode, ItemClass *item) void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) { - std::pair triggerTypes; + const std::string triggerName = XML::getProperty( + effectNode, "trigger", std::string()); + const std::string dispellTrigger = XML::getProperty( + effectNode, "dispell", std::string()); + // label -> { trigger (apply), trigger (cancel (default)) } + // The latter can be overridden. + ItemTrigger triggerType; + + static std::map triggerTable; + if (triggerTable.empty()) { - const std::string triggerName = XML::getProperty( - effectNode, "trigger", std::string()); - const std::string dispellTrigger = XML::getProperty( - effectNode, "dispell", std::string()); - // label -> { trigger (apply), trigger (cancel (default)) } - // The latter can be overridden. - static std::map > - triggerTable; - if (triggerTable.empty()) - { - /* - * The following is a table of all triggers for item - * effects. - * The first element defines the trigger used for this - * trigger, and the second defines the default - * trigger to use for dispelling. - */ - triggerTable["in-inventory"].first = ITT_IN_INVY; - triggerTable["in-inventory"].second = ITT_LEAVE_INVY; - triggerTable["activation"].first = ITT_ACTIVATE; - triggerTable["activation"].second = ITT_NULL; - triggerTable["equip"].first = ITT_EQUIP; - triggerTable["equip"].second = ITT_UNEQUIP; - triggerTable["leave-inventory"].first = ITT_LEAVE_INVY; - triggerTable["leave-inventory"].second = ITT_NULL; - triggerTable["unequip"].first = ITT_UNEQUIP; - triggerTable["unequip"].second = ITT_NULL; - triggerTable["equip-change"].first = ITT_EQUIPCHG; - triggerTable["equip-change"].second = ITT_NULL; - triggerTable["null"].first = ITT_NULL; - triggerTable["null"].second = ITT_NULL; - } - std::map >::iterator - it = triggerTable.find(triggerName); - - if (it == triggerTable.end()) { - LOG_WARN("Item Manager: Unable to find effect trigger type \"" - << triggerName << "\", skipping!"); - return; - } - triggerTypes = it->second; - if (!dispellTrigger.empty()) - { - if ((it = triggerTable.find(dispellTrigger)) == triggerTable.end()) - LOG_WARN("Item Manager: Unable to find dispell effect " - "trigger type \"" << dispellTrigger << "\"!"); - else - triggerTypes.second = it->second.first; - } + /* + * The following is a table of all triggers for item + * effects. + * The first element defines the trigger used for this + * trigger, and the second defines the default + * trigger to use for dispelling. + */ + triggerTable["in-inventory"].apply = ITT_IN_INVY; + triggerTable["in-inventory"].dispell = ITT_LEAVE_INVY; + triggerTable["activation"].apply = ITT_ACTIVATE; + triggerTable["activation"].dispell = ITT_NULL; + triggerTable["equip"].apply = ITT_EQUIP; + triggerTable["equip"].dispell = ITT_UNEQUIP; + triggerTable["leave-inventory"].apply = ITT_LEAVE_INVY; + triggerTable["leave-inventory"].dispell = ITT_NULL; + triggerTable["unequip"].apply = ITT_UNEQUIP; + triggerTable["unequip"].dispell = ITT_NULL; + triggerTable["equip-change"].apply = ITT_EQUIPCHG; + triggerTable["equip-change"].dispell = ITT_NULL; + triggerTable["null"].apply = ITT_NULL; + triggerTable["null"].dispell = ITT_NULL; + } + + std::map::iterator + it = triggerTable.find(triggerName); + + if (it == triggerTable.end()) { + LOG_WARN("Item Manager: Unable to find effect trigger type \"" + << triggerName << "\", skipping!"); + return; + } + triggerType = it->second; + + // Overwrite dispell trigger if given + if (!dispellTrigger.empty()) + { + if ((it = triggerTable.find(dispellTrigger)) == triggerTable.end()) + LOG_WARN("Item Manager: Unable to find dispell effect " + "trigger type \"" << dispellTrigger << "\"!"); + else + triggerType.dispell = it->second.apply; } for_each_xml_child_node(subNode, effectNode) { if (xmlStrEqual(subNode->name, BAD_CAST "modifier")) { - std::string tag = XML::getProperty(subNode, "attribute", std::string()); + std::string tag = XML::getProperty(subNode, "attribute", + std::string()); if (tag.empty()) { LOG_WARN("Item Manager: Warning, modifier found " @@ -383,11 +383,13 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) value, item->getDatabaseID(), duration), - triggerTypes.first, triggerTypes.second); + triggerType.apply, triggerType.dispell); } else if (xmlStrEqual(subNode->name, BAD_CAST "attack")) { - // TODO - URGENT + AttackInfo *attackInfo = AttackInfo::readAttackNode(subNode); + item->addAttack(attackInfo, triggerType.apply, triggerType.dispell); + } // Having a dispell for the next three is nonsensical. else if (xmlStrEqual(subNode->name, BAD_CAST "cooldown")) @@ -402,7 +404,7 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) } else if (xmlStrEqual(subNode->name, BAD_CAST "consumes")) { - item->addEffect(new ItemEffectConsumes, triggerTypes.first); + item->addEffect(new ItemEffectConsumes, triggerType.apply); } else if (xmlStrEqual(subNode->name, BAD_CAST "scriptevent")) { @@ -423,8 +425,8 @@ void ItemManager::readEffectNode(xmlNodePtr effectNode, ItemClass *item) item->addEffect(new ItemEffectScript(item, activateEventName, dispellEventName), - triggerTypes.first, - triggerTypes.second); + triggerType.apply, + triggerType.dispell); } } } diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index 30c38da..26dc95f 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -47,12 +47,20 @@ struct MonsterTargetEventDispatch: EventDispatch static MonsterTargetEventDispatch monsterTargetEventDispatch; +MonsterClass::~MonsterClass() +{ + for (std::vector::iterator it = mAttacks.begin(), + it_end = mAttacks.end(); it != it_end; ++it) + { + delete *it; + } +} + Monster::Monster(MonsterClass *specy): Being(OBJECT_MONSTER), mSpecy(specy), mTargetListener(&monsterTargetEventDispatch), - mOwner(NULL), - mCurrentAttack(NULL) + mOwner(NULL) { LOG_DEBUG("Monster spawned! (id: " << mSpecy->getId() << ")."); @@ -89,11 +97,14 @@ Monster::Monster(MonsterClass *specy): setAttribute(it2->first, mutation ? - attr * (100 + (rand()%(mutation << 1)) - mutation) / 100.0 : + attr * (100 + (rand() % (mutation * 2)) - mutation) / 100.0 : attr); } } + mDamageMutation = mutation ? + (100 + (rand() % (mutation * 2)) - mutation) / 100.0 : 1; + setSize(specy->getSize()); setGender(specy->getGender()); @@ -104,6 +115,14 @@ Monster::Monster(MonsterClass *specy): mAttackPositions.push_back(AttackPosition(0, -dist, DOWN)); mAttackPositions.push_back(AttackPosition(0, dist, UP)); + // Take attacks from specy + std::vector &attacks = specy->getAttackInfos(); + for (std::vector::iterator it = attacks.begin(), + it_end = attacks.end(); it != it_end; ++it) + { + addAttack(*it); + } + // Load default script loadScript(specy->getScript()); } @@ -143,47 +162,47 @@ void Monster::update() script->execute(); } - // Cancel the rest when we are currently performing an attack - if (!mAttackTimeout.expired()) - return; - refreshTarget(); - if (!mTarget) + // Cancel the rest when we have a target + if (mTarget) + return; + + // We have no target - let's wander around + if (mStrollTimeout.expired() && getPosition() == getDestination()) { - // We have no target - let's wander around - if (mStrollTimeout.expired() && getPosition() == getDestination()) + if (mKillStealProtectedTimeout.expired()) { - if (mKillStealProtectedTimeout.expired()) + unsigned range = mSpecy->getStrollRange(); + if (range) { - unsigned range = mSpecy->getStrollRange(); - if (range) - { - Point randomPos(rand() % (range * 2 + 1) - - range + getPosition().x, - rand() % (range * 2 + 1) - - range + getPosition().y); - // Don't allow negative destinations, to avoid rounding - // problems when divided by tile size - if (randomPos.x >= 0 && randomPos.y >= 0) - setDestination(randomPos); - } - mStrollTimeout.set(10 + rand() % 10); + Point randomPos(rand() % (range * 2 + 1) + - range + getPosition().x, + rand() % (range * 2 + 1) + - range + getPosition().y); + // Don't allow negative destinations, to avoid rounding + // problems when divided by tile size + if (randomPos.x >= 0 && randomPos.y >= 0) + setDestination(randomPos); } + mStrollTimeout.set(10 + rand() % 10); } } - - if (mAction == ATTACK) - processAttack(); } void Monster::refreshTarget() { + // We are dead and sadly not possible to keep attacking :( + if (mAction == DEAD) + return; + // Check potential attack positions - Being *bestAttackTarget = mTarget = NULL; int bestTargetPriority = 0; + Being *bestTarget = 0; Point bestAttackPosition; - BeingDirection bestAttackDirection = DOWN; + + // reset Target. We will find a new one if possible + mTarget = 0; // Iterate through objects nearby int aroundArea = Configuration::getValue("game_visualRange", 448); @@ -229,60 +248,28 @@ void Monster::refreshTarget() targetPriority); if (posPriority > bestTargetPriority) { - bestAttackTarget = mTarget = target; bestTargetPriority = posPriority; + bestTarget = target; bestAttackPosition = attackPosition; - bestAttackDirection = j->direction; } } } - - // Check if an enemy has been found - if (bestAttackTarget) + if (bestTarget) { - // Check which attacks have a chance to hit the target - MonsterAttacks allAttacks = mSpecy->getAttacks(); - std::map workingAttacks; - int prioritySum = 0; - - const int distX = getPosition().x - bestAttackTarget->getPosition().x; - const int distY = getPosition().y - bestAttackTarget->getPosition().y; - const int distSquare = (distX * distX + distY * distY); - - for (MonsterAttacks::iterator i = allAttacks.begin(); - i != allAttacks.end(); - i++) + mTarget = bestTarget; + if (bestAttackPosition == getPosition()) { - int maxDist = (*i)->range + bestAttackTarget->getSize(); - - if (maxDist * maxDist >= distSquare) - { - prioritySum += (*i)->priority; - workingAttacks[prioritySum] = (*i); - } - } - - if (workingAttacks.empty() || !prioritySum) - { //when no attack can hit move closer to attack position - setDestination(bestAttackPosition); + mAction = ATTACK; + updateDirection(getPosition(), mTarget->getPosition()); } else { - // Prepare for using a random attack which can hit the enemy - // Stop movement - setDestination(getPosition()); - // Turn into direction of enemy - setDirection(bestAttackDirection); - // Perform a random attack based on priority - mCurrentAttack = - workingAttacks.upper_bound(rand()%prioritySum)->second; - setAction(ATTACK); - raiseUpdateFlags(UPDATEFLAG_ATTACK); + setDestination(bestAttackPosition); } } } -void Monster::processAttack() +void Monster::processAttack(Attack &attack) { if (!mTarget) { @@ -290,37 +277,25 @@ void Monster::processAttack() return; } - if (!mCurrentAttack) - return; - - mAttackTimeout.set(mCurrentAttack->aftDelay - + mCurrentAttack->preDelay); - - float damageFactor = mCurrentAttack->damageFactor; - - Damage dmg; + Damage dmg = attack.getAttackInfo()->getDamage(); dmg.skill = 0; - dmg.base = getModifiedAttribute(MOB_ATTR_PHY_ATK_MIN) * damageFactor; - dmg.delta = getModifiedAttribute(MOB_ATTR_PHY_ATK_DELTA) * damageFactor; - dmg.cth = getModifiedAttribute(ATTR_ACCURACY); - dmg.element = mCurrentAttack->element; - dmg.range = mCurrentAttack->range; + dmg.base *= mDamageMutation; + dmg.delta *= mDamageMutation; + + int hit = performAttack(mTarget, attack.getAttackInfo()->getDamage()); - int hit = performAttack(mTarget, dmg); + const Script::Ref &scriptCallback = + attack.getAttackInfo()->getScriptCallback(); - if (!mCurrentAttack->scriptEvent.empty() && hit > -1) + if (scriptCallback.isValid() && hit > -1) { - Script::Ref function = mSpecy->getEventCallback(mCurrentAttack->scriptEvent); - if (function.isValid()) - { - Script *script = ScriptManager::currentState(); - script->setMap(getMap()); - script->prepare(function); - script->push(this); - script->push(mTarget); - script->push(hit); - script->execute(); - } + Script *script = ScriptManager::currentState(); + script->setMap(getMap()); + script->prepare(scriptCallback); + script->push(this); + script->push(mTarget); + script->push(hit); + script->execute(); } } @@ -513,9 +488,6 @@ bool Monster::recalculateBaseAttribute(unsigned int attr) { // Those a set only at load time. case ATTR_MAX_HP: - case MOB_ATTR_PHY_ATK_MIN: - case MOB_ATTR_PHY_ATK_DELTA: - case MOB_ATTR_MAG_ATK: case ATTR_DODGE: case ATTR_MAGIC_DODGE: case ATTR_ACCURACY: diff --git a/src/game-server/monster.h b/src/game-server/monster.h index 5da975e..600196f 100644 --- a/src/game-server/monster.h +++ b/src/game-server/monster.h @@ -54,7 +54,7 @@ struct MonsterAttack unsigned id; int priority; float damageFactor; - int element; + Element element; DamageType type; int preDelay; int aftDelay; @@ -62,8 +62,6 @@ struct MonsterAttack std::string scriptEvent; }; -typedef std::vector< MonsterAttack *> MonsterAttacks; - /** * Class describing the characteristics of a generic monster. */ @@ -85,6 +83,8 @@ class MonsterClass mOptimalLevel(0) {} + ~MonsterClass(); + /** * Returns monster type. This is the Id of the monster class. */ @@ -188,10 +188,10 @@ class MonsterClass unsigned getAttackDistance() const { return mAttackDistance; } /** Adds an attack to the monsters repertoire. */ - void addAttack(MonsterAttack *type) { mAttacks.push_back(type); } + void addAttack(AttackInfo *info) { mAttacks.push_back(info); } /** Returns all attacks of the monster. */ - const MonsterAttacks &getAttacks() const { return mAttacks; } + std::vector &getAttackInfos() { return mAttacks; } /** sets the script file for the monster */ void setScript(const std::string &filename) { mScript = filename; } @@ -205,18 +205,12 @@ class MonsterClass void setDamageCallback(Script *script) { script->assignCallback(mDamageCallback); } - void setEventCallback(const std::string &event, Script *script) - { script->assignCallback(mEventCallbacks[event]); } - Script::Ref getUpdateCallback() const { return mUpdateCallback; } Script::Ref getDamageCallback() const { return mDamageCallback; } - Script::Ref getEventCallback(const std::string &event) const - { return mEventCallbacks.value(event); } - private: unsigned short mId; std::string mName; @@ -234,7 +228,7 @@ class MonsterClass int mMutation; int mAttackDistance; int mOptimalLevel; - MonsterAttacks mAttacks; + std::vector mAttacks; std::string mScript; /** @@ -247,12 +241,6 @@ class MonsterClass */ Script::Ref mDamageCallback; - /** - * Named event callbacks. Currently only used for custom attack - * callbacks. - */ - utils::NameMap mEventCallbacks; - friend class MonsterManager; friend class Monster; }; @@ -300,21 +288,15 @@ class Monster : public Being void refreshTarget(); /** - * Performs an attack, if needed. + * Performs an attack */ - void processAttack(); + virtual void processAttack(Attack &attack); /** * Loads a script file for this monster */ void loadScript(const std::string &scriptName); - /** - * Gets the attack id the being is currently performing. - */ - virtual int getAttackId() const - { return mCurrentAttack->id; } - /** * Kills the being. */ @@ -371,6 +353,9 @@ class Monster : public Being */ Character *mOwner; + /** Factor for damage mutation */ + unsigned mDamageMutation; + /** List of characters and their skills that attacked this monster. */ std::map > mExpReceivers; @@ -380,9 +365,6 @@ class Monster : public Being */ std::set mLegalExpReceivers; - /** Attack the monster is currently performing. */ - MonsterAttack *mCurrentAttack; - /** * Set positions relative to target from which the monster can attack. */ @@ -394,8 +376,6 @@ class Monster : public Being Timeout mKillStealProtectedTimeout; /** Time until dead monster is removed */ Timeout mDecayTimeout; - /** Time until monster can attack again */ - Timeout mAttackTimeout; friend struct MonsterTargetEventDispatch; }; diff --git a/src/game-server/monstermanager.cpp b/src/game-server/monstermanager.cpp index 2e772a3..0e98f94 100644 --- a/src/game-server/monstermanager.cpp +++ b/src/game-server/monstermanager.cpp @@ -32,28 +32,6 @@ #define DEFAULT_MONSTER_SIZE 16 #define DEFAULT_MONSTER_SPEED 4.0f -Element elementFromString (const std::string &name) -{ - static std::map table; - - if (table.empty()) - { - table["neutral"] = ELEMENT_NEUTRAL; - table["fire"] = ELEMENT_FIRE; - table["water"] = ELEMENT_WATER; - table["earth"] = ELEMENT_EARTH; - table["air"] = ELEMENT_AIR; - table["lightning"] = ELEMENT_LIGHTNING; - table["metal"] = ELEMENT_METAL; - table["wood"] = ELEMENT_WOOD; - table["ice"] = ELEMENT_ICE; - } - - std::map::iterator val = table.find(name); - - return val == table.end() ? ELEMENT_ILLEGAL : (*val).second; -} - void MonsterManager::reload() { deinitialize(); @@ -150,12 +128,6 @@ void MonsterManager::initialize() monster->setAttribute(ATTR_MAX_HP, hp); monster->setAttribute(ATTR_HP, hp); - monster->setAttribute(MOB_ATTR_PHY_ATK_MIN, - XML::getProperty(subnode, "attack-min", -1)); - monster->setAttribute(MOB_ATTR_PHY_ATK_DELTA, - XML::getProperty(subnode, "attack-delta", -1)); - monster->setAttribute(MOB_ATTR_MAG_ATK, - XML::getProperty(subnode, "attack-magic", -1)); monster->setAttribute(ATTR_DODGE, XML::getProperty(subnode, "evade", -1)); monster->setAttribute(ATTR_MAGIC_DODGE, @@ -249,62 +221,28 @@ void MonsterManager::initialize() } else if (xmlStrEqual(subnode->name, BAD_CAST "attack")) { - MonsterAttack *att = new MonsterAttack; - att->id = XML::getProperty(subnode, "id", 0); - att->priority = XML::getProperty(subnode, "priority", 1); - att->damageFactor = XML::getFloatProperty(subnode, - "damage-factor", 1.0f); - att->preDelay = XML::getProperty(subnode, "pre-delay", 1); - att->aftDelay = XML::getProperty(subnode, "aft-delay", 0); - att->range = XML::getProperty(subnode, "range", 0); - att->scriptEvent = XML::getProperty(subnode, "script-event", - std::string()); - std::string sElement = XML::getProperty(subnode, - "element", "neutral"); - att->element = elementFromString(sElement); - std::string sType = XML::getProperty(subnode, - "type", "physical"); - + AttackInfo *att = AttackInfo::readAttackNode(subnode); bool validMonsterAttack = true; - if (sType == "physical") - { - att->type = DAMAGE_PHYSICAL; - } - else if (sType == "magical" || sType == "magic") - { - att->type = DAMAGE_MAGICAL; - } - else if (sType == "other") - { - att->type = DAMAGE_OTHER; - } - else - { - LOG_WARN("Monster manager " << mMonsterReferenceFile - << ": unknown damage type '" << sType << "'."); - validMonsterAttack = false; - } - if (att->id < 1) + if (att->getDamage().id < 1) { LOG_WARN(mMonsterReferenceFile << ": Attack without ID for monster Id:" << id << " (" << name << ") - attack ignored"); validMonsterAttack = false; } - else if (att->element == ELEMENT_ILLEGAL) + else if (att->getDamage().element == ELEMENT_ILLEGAL) { LOG_WARN(mMonsterReferenceFile - << ": Attack with unknown element \"" - << sElement << "\" for monster Id:" << id - << " (" << name << ") - attack ignored"); + << ": Attack with unknown element for monster Id:" + << id << " (" << name << ") - attack ignored"); validMonsterAttack = false; } - else if (att->type == -1) + else if (att->getDamage().type == DAMAGE_OTHER) { LOG_WARN(mMonsterReferenceFile - << ": Attack with unknown type \"" << sType << "\"" - << " for monster Id:" << id + << ": Attack with unknown damage type " + << "for monster Id:" << id << " (" << name << ")"); validMonsterAttack = false; } diff --git a/src/game-server/skillmanager.cpp b/src/game-server/skillmanager.cpp index b5a94cb..52e95e9 100644 --- a/src/game-server/skillmanager.cpp +++ b/src/game-server/skillmanager.cpp @@ -167,14 +167,14 @@ void SkillManager::printDebugSkillTable() } } -unsigned int SkillManager::getId(const std::string &set, - const std::string &name) const +unsigned SkillManager::getId(const std::string &set, + const std::string &name) const { std::string key = utils::toLower(set) + "_" + utils::toLower(name); return getId(key); } -unsigned int SkillManager::getId(const std::string &skillName) const +unsigned SkillManager::getId(const std::string &skillName) const { SkillInfo *skillInfo = mNamedSkillsInfo.value(skillName); return skillInfo ? skillInfo->id : 0; @@ -191,3 +191,9 @@ const std::string SkillManager::getSetName(unsigned int id) const SkillsInfo::const_iterator it = mSkillsInfo.find(id); return it != mSkillsInfo.end() ? it->second->setName : ""; } + +bool SkillManager::exists(unsigned id) const +{ + SkillsInfo::const_iterator it = mSkillsInfo.find(id); + return it != mSkillsInfo.end(); +} diff --git a/src/game-server/skillmanager.h b/src/game-server/skillmanager.h index 36e2485..f296e19 100644 --- a/src/game-server/skillmanager.h +++ b/src/game-server/skillmanager.h @@ -60,6 +60,8 @@ class SkillManager const std::string getSkillName(unsigned int id) const; const std::string getSetName(unsigned int id) const; + bool exists(unsigned id) const; + unsigned int getDefaultSkillId() const { return mDefaultSkillId; } private: diff --git a/src/game-server/timeout.h b/src/game-server/timeout.h index 49805c0..ce15d0b 100644 --- a/src/game-server/timeout.h +++ b/src/game-server/timeout.h @@ -62,7 +62,7 @@ class Timeout /** * Returns whether the timeout has expired. */ - bool expired() const { return remaining() < 0; } + bool expired() const { return remaining() <= 0; } /** * Returns whether the timeout was reached in the current tick. -- cgit