summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/common/defines.h49
-rw-r--r--src/game-server/attack.cpp135
-rw-r--r--src/game-server/attack.h207
-rw-r--r--src/game-server/autoattack.cpp77
-rw-r--r--src/game-server/autoattack.h153
-rw-r--r--src/game-server/being.cpp86
-rw-r--r--src/game-server/being.h46
-rw-r--r--src/game-server/character.cpp98
-rw-r--r--src/game-server/character.h7
-rw-r--r--src/game-server/item.cpp47
-rw-r--r--src/game-server/item.h58
-rw-r--r--src/game-server/itemmanager.cpp120
-rw-r--r--src/game-server/mapcomposite.cpp2
-rw-r--r--src/game-server/monster.cpp182
-rw-r--r--src/game-server/monster.h48
-rw-r--r--src/game-server/monstermanager.cpp85
-rw-r--r--src/game-server/timeout.h2
-rw-r--r--src/scripting/lua.cpp153
-rw-r--r--src/scripting/luascript.cpp1
-rw-r--r--src/scripting/luautil.h10
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 &currentPos,
+ 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 &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 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);