summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorErik Schilling <ablu.erikschilling@googlemail.com>2012-06-10 15:11:01 +0200
committerErik Schilling <ablu.erikschilling@googlemail.com>2013-01-08 16:58:57 +0100
commit6f287f239e9d94707735b183d6c6b89eea9fef20 (patch)
tree694427dbff77f73a1a3a4f84b4f53269a4763f74
parent0f0004ff3e286270c0425642a5669661ef6cb592 (diff)
downloadmanaserv-6f287f239e9d94707735b183d6c6b89eea9fef20.tar.gz
manaserv-6f287f239e9d94707735b183d6c6b89eea9fef20.tar.xz
manaserv-6f287f239e9d94707735b183d6c6b89eea9fef20.zip
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
-rw-r--r--example/attributes.xml17
-rw-r--r--example/items.xml17
-rw-r--r--example/monsters.xml98
-rw-r--r--src/common/defines.h49
-rw-r--r--src/game-server/attack.cpp124
-rw-r--r--src/game-server/attack.h171
-rw-r--r--src/game-server/being.cpp86
-rw-r--r--src/game-server/being.h42
-rw-r--r--src/game-server/character.cpp94
-rw-r--r--src/game-server/character.h6
-rw-r--r--src/game-server/item.cpp42
-rw-r--r--src/game-server/item.h53
-rw-r--r--src/game-server/itemmanager.cpp118
-rw-r--r--src/game-server/monster.cpp170
-rw-r--r--src/game-server/monster.h42
-rw-r--r--src/game-server/monstermanager.cpp78
-rw-r--r--src/game-server/skillmanager.cpp12
-rw-r--r--src/game-server/skillmanager.h2
-rw-r--r--src/game-server/timeout.h2
19 files changed, 688 insertions, 535 deletions
diff --git a/example/attributes.xml b/example/attributes.xml
index 69346e5..e0e8829 100644
--- a/example/attributes.xml
+++ b/example/attributes.xml
@@ -173,21 +173,4 @@
<modifier stacktype="stackable" modtype="additive" tag="cap1" effect="Capacity %+.2f" />
</attribute>
<!-- End of core-attributes definition -->
-
- <!-- Temporary definitions for monsters until the auto-attack system is finished. -->
- <attribute id="20" name="physical attack (minimum)"
- modifiable="false"
- scope="monster"
- minimum="0"
- maximum="255" />
- <attribute id="21" name="physical attack (delta)"
- modifiable="false"
- scope="monster"
- minimum="0"
- maximum="255" />
- <attribute id="22" name="magical attack"
- modifiable="false"
- scope="monster"
- minimum="0"
- maximum="255" />
</attributes>
diff --git a/example/items.xml b/example/items.xml
index e58949e..3ed6ec9 100644
--- a/example/items.xml
+++ b/example/items.xml
@@ -117,9 +117,12 @@
</equip>
<effect trigger="equip">
<modifier attribute="acc1" value="1.0" />
- <attack skill="102" warmupspeed="10" cooldownspeed="166"
- basedamage="50" deltadamage="10" chancetohit="2" range="32"
- element="neutral" damagetype="physical" />
+ <attack skill="102" warmuptime="1" cooldowntime="5" reusetime="1"
+ basedamage="1" deltadamage="10" chancetohit="200" range="320"
+ element="neutral" type="physical" />
+ <attack skill="102" priority="2" warmuptime="100" cooldowntime="10" reusetime="100"
+ basedamage="50" deltadamage="10" chancetohit="200" range="320"
+ element="neutral" type="physical" />
</effect>
<effect trigger="in-inventory">
<modifier attribute="cap1" value="-2000" />
@@ -134,9 +137,9 @@
<slot type="hand" required="2" />
</equip>
<effect trigger="equip">
- <attack skill="106" warmupspeed="10" cooldownspeed="166"
+ <attack skill="106" warmuptime="10" cooldowntime="166"
basedamage="50" deltadamage="10" chancetohit="2" range="160"
- element="neutral" damagetype="physical" />
+ element="neutral" type="physical" />
<modifier attribute="acc1" value="1.5" />
</effect>
<effect trigger="in-inventory">
@@ -152,9 +155,9 @@
<slot type="hand" required="1" />
</equip>
<effect trigger="equip">
- <attack skill="100" warmupspeed="10" cooldownspeed="166"
+ <attack skill="100" warmuptime="10" cooldowntime="10" reusetime="20"
basedamage="50" deltadamage="10" chancetohit="2" range="32"
- element="neutral" damagetype="physical" />
+ element="neutral" type="physical" />
<modifier attribute="acc1" value="1.5" />
</effect>
<effect trigger="in-inventory">
diff --git a/example/monsters.xml b/example/monsters.xml
index 962b137..5e73e93 100644
--- a/example/monsters.xml
+++ b/example/monsters.xml
@@ -21,10 +21,6 @@ attributes <TAG>: Tells all the monsters attribute. These attribute, as for ite
size[integer]: The monster maximal amplitude in pixels. Used to compute player's hit area.
speed[float]: The monster's speed in tiles per second.
(A tile is the smallest square map unit: by default, a tile is 32 pixel long.)
- attack-min[integer]: The minimal attack strength of the monster. If your hasn't got any armor,
- there are the minimal hit points he will lose when hit by the monster.
- attack-delta[integer]: The amplitude between minimal and maximal damages the monster can do.
- attack-magic[integer]: The magical attacks are removing hp when hit but are computed against magical defense instead.
mutation[integer]: The mutation indicates the amplitude in percent where attributes get modified with.
For instance, with a mutation of 50, each attribute can be altered to become 100% to 149% of what they are.
vulnerability<TAG>: Tells the monster specific vulnerability to an element.
@@ -46,12 +42,9 @@ exp<TAG>: Tells how much experience point a monster is giving up
<drop item="3" percent="2.8"/>
<drop item="4" percent="0.7"/>
<attributes
- hp="20"
+ hp="200"
size="4"
speed="2.0"
- attack-min="10"
- attack-delta="2"
- attack-magic="0"
hit="10"
evade="5"
magical-evade="5"
@@ -73,12 +66,28 @@ exp<TAG>: Tells how much experience point a monster is giving up
<attack id="1"
priority="1"
type="physical"
- pre-delay="10"
- aft-delay="5"
- damage-factor="1"
+ warmuptime="10"
+ cooldowntime="10"
+ reusetime="10"
+ basedamage="10"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
+ range="32"
+ animation="attack"
+ />
+ <attack id="2"
+ priority="2"
+ type="physical"
+ warmuptime="0"
+ cooldowntime="100"
+ reusetime="100"
+ basedamage="100"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
range="32"
animation="attack"
- script-event="strike"
/>
<script>testmonster.lua</script> <!-- only Proof of Concept-->
</monster>
@@ -99,9 +108,6 @@ exp<TAG>: Tells how much experience point a monster is giving up
hp="20"
size="8"
speed="6.0"
- attack-min="10"
- attack-delta="2"
- attack-magic="0"
hit="10"
evade="10"
magical-evade="10"
@@ -120,21 +126,29 @@ exp<TAG>: Tells how much experience point a monster is giving up
<attack id="1"
priority="1"
type="physical"
- pre-delay="5"
- aft-delay="15"
- damage-factor="3"
- range="64"
- particle-effect="graphics/particles/attack.particle.xml"
- action="special1"
+ warmuptime="10"
+ cooldowntime="100"
+ reusetime="100"
+ basedamage="20"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
+ range="32"
+ animation="attack"
/>
<!-- fast, weak, short-range scissor attack -->
<attack id="2"
- priority="4"
+ priority="2"
type="physical"
- pre-delay="3"
- aft-delay="3"
- damage-factor="1"
+ warmuptime="0"
+ cooldowntime="5"
+ reusetime="1"
+ basedamage="5"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
range="32"
+ animation="attack"
/>
</monster>
@@ -155,9 +169,6 @@ exp<TAG>: Tells how much experience point a monster is giving up
hp="20"
size="8"
speed="6.0"
- attack-min="20"
- attack-delta="10"
- attack-magic="0"
hit="30"
evade="30"
magical-evade="30"
@@ -177,11 +188,15 @@ exp<TAG>: Tells how much experience point a monster is giving up
<attack id="1"
priority="1"
type="physical"
- element="fire"
- pre-delay="5"
- aft-delay="10"
- damage-factor="1"
- range="64"
+ warmuptime="0"
+ cooldowntime="100"
+ reusetime="100"
+ basedamage="100"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
+ range="32"
+ animation="attack"
/>
</monster>
@@ -200,15 +215,25 @@ exp<TAG>: Tells how much experience point a monster is giving up
hp="200"
size="4"
speed="1.0"
- attack-min="2"
- attack-delta="10"
- attack-magic="0"
hit="100"
evade="10"
magical-evade="10"
physical-defence="0"
magical-defence="0"
/>
+ <attack id="1"
+ priority="1"
+ type="physical"
+ warmuptime="0"
+ cooldowntime="100"
+ reusetime="100"
+ basedamage="100"
+ deltadamage="1"
+ chancetohit="1000"
+ element="neutral"
+ range="32"
+ animation="attack"
+ />
<!-- Is fulfilling some unknown purpose that requires it to move around
a lot and leaves no time for pursing attackers -->
<behavior
@@ -216,7 +241,6 @@ exp<TAG>: Tells how much experience point a monster is giving up
cowardly="false"
track-range="4"
stroll-range="24"
- attack-distance="32"
/>
</monster>
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<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 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 <cassert>
+
+#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<Attack>::iterator it = mAttacks.begin();
- it != mAttacks.end(); ++it)
+ for (std::vector<Attack>::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<Attack>::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<Attack> *ret)
+void Attacks::startAttack(Attack *attack)
{
- for (std::list<Attack>::iterator it = mAttacks.begin();
+ mCurrentAttack = attack;
+ mAttackTimer.set(attack->getAttackInfo()->getWarmupTime() +
+ attack->getAttackInfo()->getCooldownTime());
+}
+
+void Attacks::getUsuableAttacks(std::vector<Attack *> *ret)
+{
+ assert(ret != 0);
+
+ // we have a current Attack
+ if ((!mAttackTimer.expired() && mCurrentAttack))
+ return;
+ for (std::vector<Attack>::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<Attack> *ret = 0);
+ Attacks():
+ mCurrentAttack(0)
+ {}
+
+ void add(AttackInfo *);
+ void remove(AttackInfo *);
+ void markAttackAsTriggered();
+ Attack *getTriggerableAttack();
+ void startAttack(Attack *attack);
+ void getUsuableAttacks(std::vector<Attack *> *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<Attack> 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<Attack> 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<Attack *> 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<Attack *>::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
@@ -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,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 &currentPos,
+ 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 &currentPos,
- 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<Attack> 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<Attack>::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<AttackInfo *>::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<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 872e52c..7004a2d 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 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<AttackInfo *> &getAttackInfos()
+ { return mAttackInfos; }
+
private:
/**
* Add an effect to a trigger
@@ -277,6 +288,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 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<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 "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<AttackInfo *>::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<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 +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<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 +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<AttackInfo *> &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<AttackInfo *> 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<Script::Ref> mEventCallbacks;
-
friend class MonsterManager;
friend class Monster;
};
@@ -300,9 +288,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 +298,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 +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<Character *, std::set <size_t> > mExpReceivers;
@@ -380,9 +365,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 +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<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();
@@ -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.