summaryrefslogtreecommitdiffstats
path: root/src/game-server/being.cpp
diff options
context:
space:
mode:
authorFreeyorp <Freeyorp101@hotmail.com>2010-05-17 20:55:06 +1200
committerFreeyorp <Freeyorp101@hotmail.com>2010-07-10 21:51:07 +1200
commit98cdcb1de4f422255aa5ef924042ae7d00a5b968 (patch)
tree1746776580502fb007581f171fa89638ab6bc64f /src/game-server/being.cpp
parent26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2 (diff)
downloadmanaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.gz
manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.tar.xz
manaserv-98cdcb1de4f422255aa5ef924042ae7d00a5b968.zip
New attribute system and major changes to many low-level areas.
Attribute system: Structure is no longer completely hardcoded. Attributes and structure is defined by new xml file (defaulting to stats.xml) Structure defines non-base modifications to an attribute, to be used by modifiers from items, effects, etc. Calculating the base value for core attributes is still done in C++ (and for such fundamental elements the only reason I can think of to do it any other way is perhaps being able to quickly change scripts without a compile could be useful for testing, but such things are a low priority anyway) Item structure: Modifiers are now through triggers rather than single events. This also removes hardcoded types - an item could be both able to be equipped and be able to be activated. Item activation no longer consumes by default, this must be specified by the property <consumes /> inside the trigger. Currently only attribute modifications, autoattacks, and consumes are defined as effects, but stubs for others do exist. Autoattacks are currently non-functional, and this should be rectified with some urgency. Auto Attacks: AutoAttacks are now separate entities, though not fully complete, nor fully integrated with all beings yet. Integration with the Character class is urgent, integration with other Being children less so. When fully integrated this will allow for multiple autoattacks, through equipping multiple items with this as an equip effect or even through other means if needed. Equipment structure: As ItemClass types are no longer hardcoded, so too are equip types. An item have multiple ways to be equipped across multiple equipment slots with any number in each slot. Character maximums are global but configurable. Miscellaneous: Speed, money, and weight are now attributes. Some managers have been changed into classes such that their associated classes can have them as friends, to avoid (ab)use of public accessors. The serialise procedure should also be set as a friend of Character (both in the account- and game- server) as well; having public accessors returning iterators is simply ridiculous. Some start for such cleanups have been made, but this is not the primary focus here. Significant work will need to be done before this is resolved completely, but the start is there. BuySell::registerPlayerItems() has been completely disabled temporarily. The previous function iterated through equipment, yet in the context I think it is intended to fill items? I have been unable to update this function to fit the modifications made to the Inventory/Equipment/Possessions, as I am unsure what exactly what it should be doing. ItemClass::mSpriteId was previously unused, so had been removed, but I notice that it was used when transmitting equipment to nearby clients. Experimentation showed that this value was never set to anything other than 0, and so has been left out of the ItemManager rewrite. I am not entirely sure what is happening here, but it should be worth looking into at a later time, as I am not sure how equipment appearences would be sent otherwise.
Diffstat (limited to 'src/game-server/being.cpp')
-rw-r--r--src/game-server/being.cpp241
1 files changed, 141 insertions, 100 deletions
diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp
index 0e885de..65b2ac2 100644
--- a/src/game-server/being.cpp
+++ b/src/game-server/being.cpp
@@ -24,6 +24,8 @@
#include "defines.h"
#include "common/configuration.hpp"
+#include "game-server/attributemanager.hpp"
+#include "game-server/character.hpp"
#include "game-server/collisiondetection.hpp"
#include "game-server/eventlistener.hpp"
#include "game-server/mapcomposite.hpp"
@@ -36,64 +38,92 @@ Being::Being(ThingType type):
Actor(type),
mAction(STAND),
mTarget(NULL),
- mSpeed(0),
mDirection(0)
{
- Attribute attr = { 0, 0 };
- mAttributes.resize(NB_BEING_ATTRIBUTES + CHAR_ATTR_NB, attr);
+ const AttributeScopes &attr = attributeManager->getAttributeInfoForType(ATTR_BEING);
+ LOG_DEBUG("Being creation: initialisation of " << attr.size() << " attributes.");
+ for (AttributeScopes::const_iterator it1 = attr.begin(),
+ it1_end = attr.end();
+ it1 != it1_end;
+ ++it1)
+ {
+ if (mAttributes.count(it1->first))
+ LOG_WARN("Redefinition of attribute '" << it1->first << "'!");
+ LOG_DEBUG("Attempting to create attribute '" << it1->first << "'.");
+ mAttributes.insert(std::make_pair(it1->first,
+ Attribute(*it1->second)));
+
+ }
+ // TODO: Way to define default base values?
+ // Should this be handled by the virtual modifiedAttribute?
+ // URGENT either way
+#if 0
// Initialize element resistance to 100 (normal damage).
- for (int i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i)
+ for (i = BASE_ELEM_BEGIN; i < BASE_ELEM_END; ++i)
{
- mAttributes[i].base = 100;
+ mAttributes[i] = Attribute(TY_ST);
+ mAttributes[i].setBase(100);
}
+#endif
}
-int Being::damage(Actor *, const Damage &damage)
+int Being::damage(Actor *source, const Damage &damage)
{
if (mAction == DEAD)
return 0;
int HPloss = damage.base;
if (damage.delta)
- {
- HPloss += rand() / (RAND_MAX / (damage.delta + 1));
- }
+ HPloss += rand() * (damage.delta + 1) / RAND_MAX;
- int hitThrow = rand()%(damage.cth + 1);
- int evadeThrow = rand()%(getModifiedAttribute(BASE_ATTR_EVADE) + 1);
- if (evadeThrow > hitThrow)
- {
- HPloss = 0;
- }
-
- /* Elemental modifier at 100 means normal damage. At 0, it means immune.
- And at 200, it means vulnerable (double damage). */
- int mod1 = getModifiedAttribute(BASE_ELEM_BEGIN + damage.element);
- HPloss = HPloss * (mod1 / 100);
- /* Defence is an absolute value which is subtracted from the damage total. */
- int mod2 = 0;
+ // TODO magical attacks and associated elemental modifiers
switch (damage.type)
{
case DAMAGE_PHYSICAL:
- mod2 = getModifiedAttribute(BASE_ATTR_PHY_RES);
- HPloss = HPloss - mod2;
+ if (!damage.trueStrike &&
+ rand()%((int) getModifiedAttribute(ATTR_DODGE) + 1) >
+ rand()%(damage.cth + 1))
+ {
+ HPloss = 0;
+ // TODO Process triggers for a dodged physical attack here.
+ // If there is an attacker included, also process triggers for the attacker (failed physical strike)
+ }
+ else
+ {
+ HPloss = HPloss * (1.0 - (0.0159375f *
+ getModifiedAttribute(ATTR_DEFENSE)) /
+ (1.0 + 0.017 *
+ getModifiedAttribute(ATTR_DEFENSE))) +
+ (rand()%((HPloss >> 4) + 1));
+ // TODO Process triggers for receiving damage here.
+ // If there is an attacker included, also process triggers for the attacker (successful physical strike)
+ }
break;
case DAMAGE_MAGICAL:
- mod2 = getModifiedAttribute(BASE_ATTR_MAG_RES);
- HPloss = HPloss / (mod2 + 1);
+#if 0
+ getModifiedAttribute(BASE_ELEM_BEGIN + damage.element);
+#else
+ LOG_WARN("Attempt to use magical type damage! This has not been"
+ "implemented yet and should not be used!");
+ HPloss = 0;
+#endif
+ case DAMAGE_DIRECT:
break;
default:
+ LOG_WARN("Unknown damage type '" << damage.type << "'!");
break;
}
if (HPloss > 0)
{
mHitsTaken.push_back(HPloss);
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: "<<HP.base + HP.mod<<"/"<<HP.base);
- HP.mod -= HPloss;
- modifiedAttribute(BASE_ATTR_HP);
- setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit
+ Attribute &HP = mAttributes.at(ATTR_HP);
+ LOG_DEBUG("Being " << getPublicID() << " suffered "<<HPloss<<" damage. HP: "
+ << HP.getModifiedAttribute() << "/"
+ << mAttributes.at(ATTR_MAX_HP).getModifiedAttribute());
+ HP.setBase(HP.getBase() - HPloss);
+ modifiedAttribute(ATTR_HP);
+ setTimerSoft(T_B_HP_REGEN, Configuration::getValue("hpRegenBreakAfterHit", 0)); // no HP regen after being hit if this is set.
} else {
HPloss = 0;
}
@@ -103,17 +133,23 @@ int Being::damage(Actor *, const Damage &damage)
void Being::heal()
{
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- HP.mod = HP.base;
- modifiedAttribute(BASE_ATTR_HP);
+ Attribute &hp = mAttributes.at(ATTR_HP);
+ Attribute &maxHp = mAttributes.at(ATTR_MAX_HP);
+ if (maxHp.getModifiedAttribute() == hp.getModifiedAttribute()) return; // Full hp, do nothing.
+ hp.clearMods(); // Reset all modifications present in hp
+ hp.setBase(maxHp.getModifiedAttribute());
+ modifiedAttribute(ATTR_HP);
}
-void Being::heal(int hp)
+void Being::heal(int gain)
{
- Attribute &HP = mAttributes[BASE_ATTR_HP];
- HP.mod += hp;
- if (HP.mod > HP.base) HP.mod = HP.base;
- modifiedAttribute(BASE_ATTR_HP);
+ Attribute &hp = mAttributes.at(ATTR_HP);
+ Attribute &maxHp = mAttributes.at(ATTR_MAX_HP);
+ if (maxHp.getModifiedAttribute() == hp.getModifiedAttribute()) return; // Full hp, do nothing.
+ hp.setBase(hp.getBase() + gain);
+ if (hp.getModifiedAttribute() > maxHp.getModifiedAttribute()) // Cannot go over maximum hitpoints.
+ hp.setBase(maxHp.getModifiedAttribute());
+ modifiedAttribute(ATTR_HP);
}
void Being::died()
@@ -154,18 +190,10 @@ Path Being::findPath()
return map->findPath(startX, startY, destX, destY, getWalkMask());
}
-void Being::setSpeed(float s)
-{
- if (s > 0)
- mSpeed = (int)(32000 / (s * (float)DEFAULT_TILE_LENGTH));
- else
- mSpeed = 0;
-}
-
void Being::move()
{
- // Don't deal with not moving beings
- if (mSpeed <= 0 && mSpeed >= 32000)
+ // Immobile beings cannot move.
+ if (!checkAttributeExists(ATTR_MOVE_SPEED_RAW) || !getModifiedAttribute(ATTR_MOVE_SPEED_RAW))
return;
mOld = getPosition();
@@ -233,9 +261,10 @@ void Being::move()
{
Position next = mPath.front();
mPath.pop_front();
- // 362 / 256 is square root of 2, used for walking diagonally
- mActionTime += (prev.x != next.x && prev.y != next.y)
- ? mSpeed * 362 / 256 : mSpeed;
+ // SQRT2 is used for diagonal movement.
+ mActionTime += (prev.x == next.x || prev.y == next.y) ?
+ getModifiedAttribute(ATTR_MOVE_SPEED_RAW) :
+ getModifiedAttribute(ATTR_MOVE_SPEED_RAW) * SQRT2;
if (mPath.empty())
{
// skip last tile center
@@ -264,6 +293,10 @@ int Being::directionToAngle(int direction)
}
}
+int Being::performAttack(Being *target, const Damage &damage) {
+ return performAttack(target, damage.range, damage);
+}
+
int Being::performAttack(Being *target, unsigned range, const Damage &damage)
{
// check target legality
@@ -281,7 +314,7 @@ int Being::performAttack(Being *target, unsigned range, const Damage &damage)
if (maxDist * maxDist < distSquare)
return -1;
- mActionTime += 1000; // set to 10 ticks wait time
+ //mActionTime += 1000; // No tick. Auto-attacks should have their own, built-in delays.
return (mTarget->damage(this, damage));
}
@@ -296,41 +329,60 @@ void Being::setAction(Action action)
}
}
-void Being::applyModifier(int attr, int amount, int duration, int lvl)
+void Being::applyModifier(unsigned int attr, double value, unsigned int layer,
+ unsigned int duration, unsigned int id)
{
- if (duration)
- {
- AttributeModifier mod;
- mod.attr = attr;
- mod.value = amount;
- mod.duration = duration;
- mod.level = lvl;
- mModifiers.push_back(mod);
- }
- mAttributes[attr].mod += amount;
+ mAttributes.at(attr).add(duration, value, layer, id);
+ modifiedAttribute(attr);
+}
+
+bool Being::removeModifier(unsigned int attr, double value, unsigned int layer,
+ unsigned int id, bool fullcheck)
+{
+ bool ret = mAttributes.at(attr).remove(value, layer, id, fullcheck);
modifiedAttribute(attr);
+ return ret;
}
-void Being::dispellModifiers(int level)
+void Being::setAttribute(unsigned int id, double value, bool calc)
{
- AttributeModifiers::iterator i = mModifiers.begin();
- while (i != mModifiers.end())
+ AttributeMap::iterator ret = mAttributes.find(id);
+ if (ret == mAttributes.end())
{
- if (i->level && i->level <= level)
- {
- mAttributes[i->attr].mod -= i->value;
- modifiedAttribute(i->attr);
- i = mModifiers.erase(i);
- continue;
- }
- ++i;
+ /*
+ * The attribute does not yet exist, so we must attempt to create it.
+ */
+ LOG_ERROR("Being: Attempt to access non-existing attribute '" << id << "'!");
+ LOG_WARN("Being: Creation of new attributes dynamically is not "
+ "implemented yet!");
+ }
+ else {
+ ret->second.setBase(value);
+ if (calc)
+ modifiedAttribute(id);
}
}
-int Being::getModifiedAttribute(int attr) const
+double Being::getAttribute(unsigned int id) const
+{
+ AttributeMap::const_iterator ret = mAttributes.find(id);
+ if (ret == mAttributes.end()) return 0;
+ return ret->second.getBase();
+}
+
+
+double Being::getModifiedAttribute(unsigned int id) const
+{
+ AttributeMap::const_iterator ret = mAttributes.find(id);
+ if (ret == mAttributes.end()) return 0;
+ return ret->second.getModifiedAttribute();
+}
+
+void Being::setModAttribute(unsigned int id, double value)
{
- int res = mAttributes[attr].base + mAttributes[attr].mod;
- return res <= 0 ? 0 : res;
+ // No-op to satisfy shared structure.
+ // The game-server calculates this manually.
+ return;
}
void Being::applyStatusEffect(int id, int timer)
@@ -389,15 +441,15 @@ void Being::update()
if (i->second > -1) i->second--;
}
- int oldHP = getModifiedAttribute(BASE_ATTR_HP);
+ int oldHP = getModifiedAttribute(ATTR_HP);
int newHP = oldHP;
- int maxHP = getAttribute(BASE_ATTR_HP);
+ int maxHP = getModifiedAttribute(ATTR_MAX_HP);
// Regenerate HP
if (mAction != DEAD && !isTimerRunning(T_B_HP_REGEN))
{
setTimerHard(T_B_HP_REGEN, TICKS_PER_HP_REGENERATION);
- newHP += getModifiedAttribute(BASE_ATTR_HP_REGEN);
+ newHP += getModifiedAttribute(ATTR_HP_REGEN);
}
// Cap HP at maximum
if (newHP > maxHP)
@@ -407,24 +459,16 @@ void Being::update()
// Only update HP when it actually changed to avoid network noise
if (newHP != oldHP)
{
- applyModifier(BASE_ATTR_HP, newHP - oldHP);
+ mAttributes.at(ATTR_HP).setBase(newHP);
raiseUpdateFlags(UPDATEFLAG_HEALTHCHANGE);
}
// Update lifetime of effects.
- AttributeModifiers::iterator i = mModifiers.begin();
- while (i != mModifiers.end())
- {
- --i->duration;
- if (!i->duration)
- {
- mAttributes[i->attr].mod -= i->value;
- modifiedAttribute(i->attr);
- i = mModifiers.erase(i);
- continue;
- }
- ++i;
- }
+ for (AttributeMap::iterator it = mAttributes.begin();
+ it != mAttributes.end();
+ ++it)
+ if (it->second.tick())
+ modifiedAttribute(it->first);
// Update and run status effects
StatusEffects::iterator it = mStatus.begin();
@@ -432,9 +476,7 @@ void Being::update()
{
it->second.time--;
if (it->second.time > 0 && mAction != DEAD)
- {
it->second.status->tick(this, it->second.time);
- }
if (it->second.time <= 0 || mAction == DEAD)
{
@@ -445,10 +487,8 @@ void Being::update()
}
// Check if being died
- if (getModifiedAttribute(BASE_ATTR_HP) <= 0 && mAction != DEAD)
- {
+ if (getModifiedAttribute(ATTR_HP) <= 0 && mAction != DEAD)
died();
- }
}
void Being::setTimerSoft(TimerID id, int value)
@@ -484,3 +524,4 @@ bool Being::isTimerJustFinished(TimerID id) const
{
return getTimer(id) == 0;
}
+