diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/common/defines.h | 49 | ||||
-rw-r--r-- | src/game-server/attack.cpp | 135 | ||||
-rw-r--r-- | src/game-server/attack.h | 207 | ||||
-rw-r--r-- | src/game-server/autoattack.cpp | 77 | ||||
-rw-r--r-- | src/game-server/autoattack.h | 153 | ||||
-rw-r--r-- | src/game-server/being.cpp | 86 | ||||
-rw-r--r-- | src/game-server/being.h | 46 | ||||
-rw-r--r-- | src/game-server/character.cpp | 98 | ||||
-rw-r--r-- | src/game-server/character.h | 7 | ||||
-rw-r--r-- | src/game-server/item.cpp | 47 | ||||
-rw-r--r-- | src/game-server/item.h | 58 | ||||
-rw-r--r-- | src/game-server/itemmanager.cpp | 120 | ||||
-rw-r--r-- | src/game-server/mapcomposite.cpp | 2 | ||||
-rw-r--r-- | src/game-server/monster.cpp | 182 | ||||
-rw-r--r-- | src/game-server/monster.h | 48 | ||||
-rw-r--r-- | src/game-server/monstermanager.cpp | 85 | ||||
-rw-r--r-- | src/game-server/timeout.h | 2 | ||||
-rw-r--r-- | src/scripting/lua.cpp | 153 | ||||
-rw-r--r-- | src/scripting/luascript.cpp | 1 | ||||
-rw-r--r-- | src/scripting/luautil.h | 10 |
21 files changed, 945 insertions, 625 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c1a5bd05..5a4b38fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -193,12 +193,12 @@ SET(SRCS_MANASERVGAME game-server/accountconnection.cpp game-server/actor.h game-server/actor.cpp + game-server/attack.h + game-server/attack.cpp game-server/attribute.h game-server/attribute.cpp game-server/attributemanager.h game-server/attributemanager.cpp - game-server/autoattack.h - game-server/autoattack.cpp game-server/being.h game-server/being.cpp game-server/buysell.h diff --git a/src/common/defines.h b/src/common/defines.h index 3c7d8463..d572df1f 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<const std::string, Element> 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<const std::string, Element>::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<const std::string, DamageType> table; + + if (table.empty()) + { + table["physical"] = DAMAGE_PHYSICAL; + table["magical"] = DAMAGE_MAGICAL; + table["direct"] = DAMAGE_DIRECT; + table["other"] = DAMAGE_OTHER; + } + + std::map<const std::string, DamageType>::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 AutoAttacks 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 new file mode 100644 index 00000000..9118b10c --- /dev/null +++ b/src/game-server/attack.cpp @@ -0,0 +1,135 @@ +/* + * The Mana Server + * Copyright (C) 2010 The Mana Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "attack.h" + +#include "common/defines.h" + +#include "game-server/character.h" +#include "game-server/skillmanager.h" + +AttackInfo *AttackInfo::readAttackNode(xmlNodePtr node) +{ + 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 (skillId == 0) + { + 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.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::add(AttackInfo *attackInfo) +{ + mAttacks.push_back(Attack(attackInfo)); +} + +void Attacks::remove(AttackInfo *attackInfo) +{ + for (std::vector<Attack>::iterator it = mAttacks.begin(), + it_end = mAttacks.end(); it != it_end; ++it) + { + if ((*it).getAttackInfo() == attackInfo) + { + if (mCurrentAttack && mCurrentAttack->getAttackInfo() == attackInfo) + mCurrentAttack = 0; + mAttacks.erase(it); + return; + } + } +} + +void Attacks::markAttackAsTriggered() +{ + mCurrentAttack->markAsTriggered(); + mCurrentAttack = 0; +} + +Attack *Attacks::getTriggerableAttack() +{ + if (!mCurrentAttack) + return 0; + + int cooldownTime = mCurrentAttack->getAttackInfo()->getCooldownTime(); + if (mAttackTimer.remaining() <= cooldownTime) + { + return mCurrentAttack; + } + + return 0; +} + +void Attacks::startAttack(Attack *attack) +{ + mCurrentAttack = attack; + mAttackTimer.set(attack->getAttackInfo()->getWarmupTime() + + attack->getAttackInfo()->getCooldownTime()); +} + +void Attacks::tick(std::vector<Attack *> *ret) +{ + // we have a current Attack + if (!mAttackTimer.expired() && mCurrentAttack) + return; + for (std::vector<Attack>::iterator it = mAttacks.begin(); + it != mAttacks.end(); ++it) + { + Attack &attack = *it; + + if (ret && attack.isUsuable()) + { + ret->push_back(&attack); + } + } +} diff --git a/src/game-server/attack.h b/src/game-server/attack.h new file mode 100644 index 00000000..7270a007 --- /dev/null +++ b/src/game-server/attack.h @@ -0,0 +1,207 @@ +/* + * The Mana Server + * Copyright (C) 2010 The Mana Development Team + * + * This file is part of The Mana Server. + * + * The Mana Server is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * The Mana Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ATTACK_H +#define ATTACK_H + +#include <cstddef> +#include <list> + +#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 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), + cth(0), + element(ELEMENT_NEUTRAL), + type(DAMAGE_OTHER), + trueStrike(false), + range(DEFAULT_TILE_LENGTH) + {} +}; + +/** + * Class that stores information about an auto-attack + */ + +class Character; + +struct AttackInfo +{ + public: + AttackInfo(unsigned priority, const Damage &damage, + unsigned short warmupTime, unsigned short cooldownTime, + unsigned short reuseTime): + mDamage(damage), + mCooldownTime(cooldownTime), + mWarmupTime(warmupTime), + mReuseTime(reuseTime), + mPriority(priority) + {} + + unsigned short getWarmupTime() const + { return mWarmupTime; } + + unsigned short getCooldownTime() const + { return mCooldownTime; } + + unsigned short getReuseTime() const + { return mReuseTime; } + + 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; + + /** + * Value to reset the timer to (warmup + cooldown) + */ + unsigned short mCooldownTime; + + /** + * Pre-attack delay tick. + * This MUST be smaller than or equal to the aspd! + * So the attack triggers where timer == warmup, having gone through + * aspd - warmup ticks. + */ + unsigned short 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; +}; + +/** + * Helper class for storing multiple auto-attacks. + */ +class Attacks +{ + public: + Attacks(): + mCurrentAttack(0) + {} + + /** + * Whether the being has at least one auto attack that is ready. + */ + void add(AttackInfo *); + void remove(AttackInfo *); + void markAttackAsTriggered(); + Attack *getTriggerableAttack(); + void startAttack(Attack *attack); + void tick(std::vector<Attack *> *ret); + + /** + * Tells the number of attacks available + */ + unsigned getNumber() + { return mAttacks.size(); } + + private: + std::vector<Attack> mAttacks; + + Attack *mCurrentAttack; + + /** + * when greater than cooldown -> warming up + * when equals cooldown -> trigger attack + * when smaller -> cooling down + */ + Timeout mAttackTimer; +}; + +#endif // ATTACK_H diff --git a/src/game-server/autoattack.cpp b/src/game-server/autoattack.cpp deleted file mode 100644 index d8425d50..00000000 --- a/src/game-server/autoattack.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * The Mana Server - * Copyright (C) 2010 The Mana Development Team - * - * This file is part of The Mana Server. - * - * The Mana Server is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * The Mana Server is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. - */ - -#include "autoattack.h" - -void AutoAttacks::add(const AutoAttack &autoAttack) -{ - mAutoAttacks.push_back(autoAttack); - // Slow, but safe. - mAutoAttacks.sort(); -} - -void AutoAttacks::clear() -{ - mAutoAttacks.clear(); -} - -void AutoAttacks::stop() -{ - for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); - it != mAutoAttacks.end(); ++it) - { - it->halt(); - } - mActive = false; -} - -void AutoAttacks::start() -{ - for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); - it != mAutoAttacks.end(); ++it) - { - // If the attack is inactive, we hard reset it. - if (!it->getTimer()) - it->reset(); - else - it->softReset(); - } - mActive = true; -} - -void AutoAttacks::tick(std::list<AutoAttack> *ret) -{ - for (std::list<AutoAttack>::iterator it = mAutoAttacks.begin(); - it != mAutoAttacks.end(); ++it) - { - if (it->tick()) - { - if (mActive) - it->reset(); - else - it->halt(); - } - - if (ret && it->isReady()) - { - ret->push_back(*it); - } - } -} diff --git a/src/game-server/autoattack.h b/src/game-server/autoattack.h deleted file mode 100644 index a1e22aee..00000000 --- a/src/game-server/autoattack.h +++ /dev/null @@ -1,153 +0,0 @@ -/* - * The Mana Server - * Copyright (C) 2010 The Mana Development Team - * - * This file is part of The Mana Server. - * - * The Mana Server is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * any later version. - * - * The Mana Server is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with The Mana Server. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef AUTOATTACK_H -#define AUTOATTACK_H - -#include <cstddef> -#include <list> - -#include "common/defines.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 */ - - Damage(): - skill(0), - base(0), - delta(0), - cth(0), - element(ELEMENT_NEUTRAL), - type(DAMAGE_OTHER), - trueStrike(false), - range(DEFAULT_TILE_LENGTH) - {} -}; - -/** - * Class that stores information about an auto-attack - */ - -class AutoAttack -{ - public: - AutoAttack(Damage &damage, unsigned int warmup, unsigned int cooldown): - mDamage(damage), - mTimer(0), - mAspd(cooldown), - mWarmup(warmup && warmup < cooldown ? warmup : cooldown >> 2) - {} - - 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); } - - bool operator<(const AutoAttack &rhs) const - { return mTimer < rhs.mTimer; } - - const Damage &getDamage() const { return mDamage; } - - private: - Damage mDamage; - - /** - * Internal timer that is modified each tick. - * - * When > warmup, the attack is warming up before a strike - * When = warmup, the attack triggers, dealing damage to the target - * *if* the target is still in range. - * (The attack is canceled when the target moves out of range before - * the attack can hit, there should be a trigger for scripts here - * too) - * (Should the character automatically persue when the target is still - * visible in this case?) - * When < warmup, the attack is cooling down after a strike. When in - * cooldown, the timer should not be soft-reset. - * When 0, the attack is inactive (the character is doing something - * other than attacking and the attack is not in cooldown) - */ - unsigned short mTimer; - - /** - * Value to reset the timer to (warmup + cooldown) - */ - unsigned short mAspd; - - /** - * Pre-attack delay tick. - * This MUST be smaller than or equal to the aspd! - * So the attack triggers where timer == warmup, having gone through - * aspd - warmup ticks. - */ - unsigned short mWarmup; -}; - -/** - * Helper class for storing multiple auto-attacks. - */ -class AutoAttacks -{ - public: - /** - * Whether the being has at least one auto attack that is ready. - */ - void add(const AutoAttack &); - void clear(); // Wipe the list completely (used in place of remove for now; FIXME) - void start(); - void stop(); // If the character does some action other than attacking, reset all warmups (NOT cooldowns!) - void tick(std::list<AutoAttack> *ret = 0); - - /** - * Tells the number of attacks available - */ - unsigned getAutoAttacksNumber() - { return mAutoAttacks.size(); } - - /** - * Tells whether the autoattacks are active. - */ - bool areActive() - { return mActive; } - - private: - /** - * Marks whether or not to keep auto-attacking. Cooldowns still need - * to be processed when false. - */ - bool mActive; - std::list<AutoAttack> mAutoAttacks; -}; - -#endif // AUTOATTACK_H diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp index 8c2b08c7..23d31d4c 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<Attack *> attacksReady; + mAttacks.tick(&attacksReady); + + if (Attack *triggerableAttack = mAttacks.getTriggerableAttack()) + { + processAttack(*triggerableAttack); + mAttacks.markAttackAsTriggered(); + } + + // Deal with the ATTACK action. + if (!attacksReady.empty()) + { + Attack *highestPriorityAttack = 0; + // Performs all ready attacks. + for (std::vector<Attack *>::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 || + info->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) - mAutoAttacks.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 8e7d6199..eee81470 100644 --- a/src/game-server/being.h +++ b/src/game-server/being.h @@ -29,7 +29,7 @@ #include "game-server/actor.h" #include "game-server/attribute.h" -#include "game-server/autoattack.h" +#include "game-server/attack.h" #include "game-server/timeout.h" class Being; @@ -87,6 +87,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. */ const Point &getDestination() const @@ -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,28 +303,35 @@ 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; AttributeMap mAttributes; - AutoAttacks mAutoAttacks; + Attacks mAttacks; StatusEffects mStatus; Being *mTarget; 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 a053cd83..edd8e681 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<AutoAttack> attacksReady; - mAutoAttacks.tick(&attacksReady); - - if (mAction != ATTACK || !mTarget) - { - mAutoAttacks.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 (!mAutoAttacks.getAutoAttacksNumber()) - { - 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(); - - AutoAttack knuckleAttack(knuckleDamage, 7, 3); - mAutoAttacks.add(knuckleAttack); - } - - if (attacksReady.empty()) - { - if (!mAutoAttacks.areActive()) - mAutoAttacks.start(); - } - else - { - // Performs all ready attacks. - for (std::list<AutoAttack>::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,25 @@ void Character::resumeNpcThread() } } +void Character::addAttack(AttackInfo *attackInfo) +{ + // Remove knuckle attack + Being::removeAttack(mKnuckleAttackInfo); + Being::addAttack(attackInfo); +} + +void Character::removeAttack(AttackInfo *attackInfo) +{ + // Add knuckle attack + if (mAttacks.getNumber() == 1) + { + + Being::addAttack(mKnuckleAttackInfo); + } + Being::removeAttack(attackInfo); +} + + void Character::disconnected() { mConnected = false; diff --git a/src/game-server/character.h b/src/game-server/character.h index b5ef578a..4dc077e8 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 @@ -431,6 +435,7 @@ class Character : public Being virtual BlockType getBlockType() const { return BLOCKTYPE_CHARACTER; } + private: bool specialUseCheck(SpecialMap::iterator it); @@ -527,6 +532,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 03aeebcb..cfb1cd9a 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -25,7 +25,7 @@ #include "game-server/item.h" #include "common/configuration.h" -#include "game-server/autoattack.h" +#include "game-server/attack.h" #include "game-server/attributemanager.h" #include "game-server/being.h" #include "game-server/state.h" @@ -47,15 +47,15 @@ void ItemEffectAttrMod::dispell(Being *itemUser) mId, !mDuration); } -bool ItemEffectAutoAttack::apply(Being * /* itemUser */) +bool ItemEffectAttack::apply(Being *itemUser) { - // TODO - STUB + itemUser->addAttack(mAttackInfo); return false; } -void ItemEffectAutoAttack::dispell(Being * /* itemUser */) +void ItemEffectAttack::dispell(Being *itemUser) { - // TODO + itemUser->removeAttack(mAttackInfo); } ItemEffectScript::~ItemEffectScript() @@ -98,26 +98,45 @@ void ItemEffectScript::dispell(Being *itemUser) } } +ItemClass::~ItemClass() +{ + resetEffects(); + for (std::vector<AttackInfo *>::iterator it = mAttackInfos.begin(), + it_end = mAttackInfos.end(); + it != it_end; ++it) + { + delete *it; + } +} + bool ItemClass::useTrigger(Being *itemUser, ItemTriggerType trigger) { if (!trigger) return false; - std::pair<std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator, - std::multimap< ItemTriggerType, ItemEffectInfo * >::iterator> - rn = mEffects.equal_range(trigger); + std::multimap<ItemTriggerType, ItemEffectInfo *>::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 1441e4bb..84583aa6 100644 --- a/src/game-server/item.h +++ b/src/game-server/item.h @@ -24,6 +24,7 @@ #include <vector> #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 ItemAutoAttackInfo -{ - unsigned int base; - unsigned int range; - unsigned int baseSpeed; - unsigned int skillId; - /// attribute id -> damage bonus per point - std::map< unsigned int, double > attrBonus; -}; - 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_AUTOATTACK, // Give the associated being an autoattack + 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); @@ -128,11 +127,17 @@ class ItemEffectAttrMod : public ItemEffectInfo unsigned int mId; }; -class ItemEffectAutoAttack : 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 @@ -181,8 +186,7 @@ class ItemClass mMaxPerSlot(maxperslot) {} - ~ItemClass() - { resetEffects(); } + ~ItemClass(); /** * Returns the name of the item type @@ -244,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<AttackInfo *> &getAttackInfos() + { return mAttackInfos; } + private: /** * Add an effect to a trigger @@ -297,6 +307,8 @@ class ItemClass */ utils::NameMap<Script::Ref> mEventCallbacks; + std::vector<AttackInfo *> mAttackInfos; + friend class ItemManager; }; diff --git a/src/game-server/itemmanager.cpp b/src/game-server/itemmanager.cpp index 8c74680e..946815ad 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<ItemTriggerType, ItemTriggerType> 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<const std::string, ItemTrigger> 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<const std::string, - std::pair<ItemTriggerType, ItemTriggerType> > - triggerTable; - if (triggerTable.empty()) - { - /* - * The following is a table of all triggers for item - * effects. - * The first element defines the trigger used for this - * trigger, and the second defines the default - * trigger to use for dispelling. - */ - triggerTable["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<const std::string, std::pair<ItemTriggerType, - ItemTriggerType> >::iterator - it = triggerTable.find(triggerName); - - if (it == triggerTable.end()) { - LOG_WARN("Item Manager: Unable to find effect trigger type \"" - << triggerName << "\", skipping!"); - 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<const std::string, ItemTrigger>::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 "autoattack")) + 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/mapcomposite.cpp b/src/game-server/mapcomposite.cpp index 8dd0503e..c3df7202 100644 --- a/src/game-server/mapcomposite.cpp +++ b/src/game-server/mapcomposite.cpp @@ -799,6 +799,7 @@ void MapComposite::initializeContent() if (npcId && !scriptText.empty()) { Script *script = ScriptManager::currentState(); + script->setMap(this); script->loadNPC(object->getName(), npcId, ManaServ::getGender(gender), object->getX(), object->getY(), @@ -815,6 +816,7 @@ void MapComposite::initializeContent() std::string scriptText = object->getProperty("TEXT"); Script *script = ScriptManager::currentState(); + script->setMap(this); if (!scriptFilename.empty()) { diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index 30c38da9..2bc542cd 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -47,12 +47,28 @@ struct MonsterTargetEventDispatch: EventDispatch static MonsterTargetEventDispatch monsterTargetEventDispatch; +MonsterClass::~MonsterClass() +{ + for (std::vector<AttackInfo *>::iterator it = mAttacks.begin(), + it_end = mAttacks.end(); it != it_end; ++it) + { + delete *it; + } +} + +float MonsterClass::getVulnerability(Element element) const +{ + Vulnerabilities::const_iterator it = mVulnerabilities.find(element); + if (it == mVulnerabilities.end()) + return 1.0f; + return it->second; +} + Monster::Monster(MonsterClass *specy): Being(OBJECT_MONSTER), mSpecy(specy), mTargetListener(&monsterTargetEventDispatch), - mOwner(NULL), - mCurrentAttack(NULL) + mOwner(NULL) { LOG_DEBUG("Monster spawned! (id: " << mSpecy->getId() << ")."); @@ -94,6 +110,9 @@ Monster::Monster(MonsterClass *specy): } } + mDamageMutation = mutation ? + (100 + (rand()%(mutation << 1)) - mutation) / 100.0 : 1; + setSize(specy->getSize()); setGender(specy->getGender()); @@ -104,6 +123,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<AttackInfo *> &attacks = specy->getAttackInfos(); + for (std::vector<AttackInfo *>::iterator it = attacks.begin(), + it_end = attacks.end(); it != it_end; ++it) + { + addAttack(*it); + } + // Load default script loadScript(specy->getScript()); } @@ -143,47 +170,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 +256,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<int, MonsterAttack *> 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 +285,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, dmg); + int hit = performAttack(mTarget, attack.getAttackInfo()->getDamage()); - if (!mCurrentAttack->scriptEvent.empty() && hit > -1) + const Script::Ref &scriptCallback = + attack.getAttackInfo()->getScriptCallback(); + + 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(); } } @@ -410,7 +393,11 @@ void Monster::changeAnger(Actor *target, int amount) int Monster::damage(Actor *source, const Damage &damage) { - int HPLoss = Being::damage(source, damage); + Damage newDamage = damage; + float factor = mSpecy->getVulnerability(newDamage.element); + newDamage.base = newDamage.base * factor; + newDamage.delta = newDamage.delta * factor; + int HPLoss = Being::damage(source, newDamage); if (source) { changeAnger(source, HPLoss); @@ -513,9 +500,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 5da975e9..a1a82eb5 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,7 +62,7 @@ struct MonsterAttack std::string scriptEvent; }; -typedef std::vector< MonsterAttack *> MonsterAttacks; +typedef std::map<Element, float> Vulnerabilities; /** * Class describing the characteristics of a generic monster. @@ -85,6 +85,8 @@ class MonsterClass mOptimalLevel(0) {} + ~MonsterClass(); + /** * Returns monster type. This is the Id of the monster class. */ @@ -188,10 +190,15 @@ 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<AttackInfo *> &getAttackInfos() { return mAttacks; } + + void setVulnerability(Element element, float factor) + { mVulnerabilities[element] = factor; } + + float getVulnerability(Element element) const; /** sets the script file for the monster */ void setScript(const std::string &filename) { mScript = filename; } @@ -205,18 +212,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 +235,8 @@ class MonsterClass int mMutation; int mAttackDistance; int mOptimalLevel; - MonsterAttacks mAttacks; + std::vector<AttackInfo *> mAttacks; + Vulnerabilities mVulnerabilities; std::string mScript; /** @@ -247,12 +249,6 @@ class MonsterClass */ Script::Ref mDamageCallback; - /** - * Named event callbacks. Currently only used for custom attack - * callbacks. - */ - utils::NameMap<Script::Ref> mEventCallbacks; - friend class MonsterManager; friend class Monster; }; @@ -300,9 +296,9 @@ 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 @@ -310,12 +306,6 @@ class Monster : public Being 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. */ void died(); @@ -371,6 +361,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<Character *, std::set <size_t> > mExpReceivers; @@ -380,9 +373,6 @@ class Monster : public Being */ std::set<Character *> mLegalExpReceivers; - /** Attack the monster is currently performing. */ - MonsterAttack *mCurrentAttack; - /** * Set positions relative to target from which the monster can attack. */ @@ -394,8 +384,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 7612ddc1..e0b45bad 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<const std::string, Element> 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<const std::string, Element>::iterator val = table.find(name); - - return val == table.end() ? ELEMENT_ILLEGAL : (*val).second; -} - void MonsterManager::reload() { deinitialize(); @@ -136,12 +114,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, @@ -235,62 +207,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; } @@ -312,6 +250,13 @@ void MonsterManager::initialize() std::string val = (char *)filename; monster->setScript(val); } + else if (xmlStrEqual(subnode->name, BAD_CAST "vulnerability")) + { + Element element = elementFromString( + XML::getProperty(subnode, "element", std::string())); + float factor = XML::getFloatProperty(subnode, "factor", 1.0); + monster->setVulnerability(element, factor); + } } monster->setDrops(drops); diff --git a/src/game-server/timeout.h b/src/game-server/timeout.h index 49805c0a..ce15d0ba 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. diff --git a/src/scripting/lua.cpp b/src/scripting/lua.cpp index d3218a65..fba8b4d2 100644 --- a/src/scripting/lua.cpp +++ b/src/scripting/lua.cpp @@ -1005,7 +1005,7 @@ static int being_damage(lua_State *s) dmg.delta = luaL_checkint(s, 3); dmg.cth = luaL_checkint(s, 4); dmg.type = (DamageType)luaL_checkint(s, 5); - dmg.element = luaL_checkint(s, 6); + dmg.element = (Element)luaL_checkint(s, 6); Being *source = 0; if (lua_gettop(s) >= 7) { @@ -1273,13 +1273,11 @@ static int monster_class_on_damage(lua_State *s) return 0; } -static int monster_class_on(lua_State *s) +static int monster_class_attacks(lua_State *s) { MonsterClass *monsterClass = LuaMonsterClass::check(s, 1); - const char *event = luaL_checkstring(s, 2); - luaL_checktype(s, 3, LUA_TFUNCTION); - monsterClass->setEventCallback(event, getScript(s)); - return 0; + pushSTLContainer(s, monsterClass->getAttackInfos()); + return 1; } /** @@ -2174,6 +2172,14 @@ static int item_class_on(lua_State *s) return 0; } +static int item_class_attacks(lua_State *s) +{ + ItemClass *itemClass = LuaItemClass::check(s, 1); + std::vector<AttackInfo *> attacks = itemClass->getAttackInfos(); + pushSTLContainer<AttackInfo *>(s, attacks); + return 1; +} + /** * drop_item(int x, int y, int id || string name[, int number]): bool * Creates an item stack on the floor. @@ -2440,6 +2446,113 @@ static int specialinfo_on_use(lua_State *s) return 0; } +static int attack_get_priority(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + lua_pushinteger(s, attack->getPriority()); + return 1; +} + +static int attack_get_cooldowntime(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + lua_pushinteger(s, attack->getCooldownTime()); + return 1; +} + +static int attack_get_warmuptime(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + lua_pushinteger(s, attack->getWarmupTime()); + return 1; +} + +static int attack_get_reusetime(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + lua_pushinteger(s, attack->getReuseTime()); + return 1; +} + +static int attack_get_damage(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + LuaDamage::push(s, &attack->getDamage()); + return 1; +} + +static int attack_on_attack(lua_State *s) +{ + AttackInfo *attack = LuaAttackInfo::check(s, 1); + luaL_checktype(s, 2, LUA_TFUNCTION); + attack->setCallback(getScript(s)); + return 0; +} + +static int damage_get_id(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->id); + return 1; +} + + +static int damage_get_skill(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->skill); + return 1; +} + +static int damage_get_base(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->base); + return 1; +} + +static int damage_get_delta(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->delta); + return 1; +} + +static int damage_get_cth(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->cth); + return 1; +} + +static int damage_get_element(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->element); + return 1; +} + +static int damage_get_type(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->type); + return 1; +} + +static int damage_is_truestrike(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushboolean(s, damage->trueStrike); + return 1; +} + +static int damage_get_range(lua_State *s) +{ + Damage *damage = LuaDamage::check(s, 1); + lua_pushinteger(s, damage->range); + return 1; +} + static int require_loader(lua_State *s) { // Add .lua extension (maybe only do this when it doesn't have it already) @@ -2595,8 +2708,32 @@ LuaScript::LuaScript(): luaL_register(mRootState, NULL, callbacks); lua_pop(mRootState, 1); // pop the globals table + static luaL_Reg const members_AttackInfo[] = { + { "priority", &attack_get_priority }, + { "cooldowntime", &attack_get_cooldowntime }, + { "warmuptime", &attack_get_warmuptime }, + { "reusetime", &attack_get_reusetime }, + { "damage", &attack_get_damage }, + { "on_attack", &attack_on_attack }, + { NULL, NULL } + }; + + static luaL_Reg const members_Damage[] = { + { "id", &damage_get_id }, + { "skill", &damage_get_skill }, + { "base", &damage_get_base }, + { "delta", &damage_get_delta }, + { "cth", &damage_get_cth }, + { "element", &damage_get_element }, + { "type", &damage_get_type }, + { "is_truestrike", &damage_is_truestrike }, + { "range", &damage_get_range }, + { NULL, NULL } + }; + static luaL_Reg const members_ItemClass[] = { { "on", &item_class_on }, + { "attacks", &item_class_attacks }, { NULL, NULL } }; @@ -2611,7 +2748,7 @@ LuaScript::LuaScript(): static luaL_Reg const members_MonsterClass[] = { { "on_update", &monster_class_on_update }, { "on_damage", &monster_class_on_damage }, - { "on", &monster_class_on }, + { "attacks", &monster_class_attacks }, { NULL, NULL } }; @@ -2630,6 +2767,8 @@ LuaScript::LuaScript(): { NULL, NULL} }; + LuaAttackInfo::registerType(mRootState, "Attack", members_AttackInfo); + LuaDamage::registerType(mRootState, "Damage", members_Damage); LuaItemClass::registerType(mRootState, "ItemClass", members_ItemClass); LuaMapObject::registerType(mRootState, "MapObject", members_MapObject); LuaMonsterClass::registerType(mRootState, "MonsterClass", members_MonsterClass); diff --git a/src/scripting/luascript.cpp b/src/scripting/luascript.cpp index bdff6a12..e2b127f7 100644 --- a/src/scripting/luascript.cpp +++ b/src/scripting/luascript.cpp @@ -234,6 +234,7 @@ void LuaScript::load(const char *prog, const char *name) << lua_tostring(mRootState, -1)); lua_pop(mRootState, 1); } + setMap(0); } void LuaScript::processDeathEvent(Being *entity) diff --git a/src/scripting/luautil.h b/src/scripting/luautil.h index 36ed80f4..1ff2ab8d 100644 --- a/src/scripting/luautil.h +++ b/src/scripting/luautil.h @@ -34,10 +34,12 @@ extern "C" { #include <set> #include <vector> +#include "game-server/attack.h" #include "game-server/specialmanager.h" class Being; class Character; +class Entity; class ItemClass; class MapComposite; class MapObject; @@ -45,7 +47,6 @@ class Monster; class MonsterClass; class NPC; class StatusEffect; -class Entity; void raiseWarning(lua_State *s, const char *format, ...); @@ -147,6 +148,8 @@ private: template <typename T> const char * LuaUserData<T>::mTypeName; +typedef LuaUserData<AttackInfo> LuaAttackInfo; +typedef LuaUserData<Damage> LuaDamage; typedef LuaUserData<ItemClass> LuaItemClass; typedef LuaUserData<MapObject> LuaMapObject; typedef LuaUserData<MonsterClass> LuaMonsterClass; @@ -198,6 +201,11 @@ inline void push(lua_State *s, double val) lua_pushnumber(s, val); } +inline void push(lua_State *s, AttackInfo *val) +{ + LuaAttackInfo::push(s, val); +} + inline void push(lua_State *s, MapObject *val) { LuaMapObject::push(s, val); |