diff options
author | Freeyorp <Freeyorp101@hotmail.com> | 2010-05-17 20:55:06 +1200 |
---|---|---|
committer | Freeyorp <Freeyorp101@hotmail.com> | 2010-07-10 21:51:07 +1200 |
commit | 98cdcb1de4f422255aa5ef924042ae7d00a5b968 (patch) | |
tree | 1746776580502fb007581f171fa89638ab6bc64f /src/game-server/being.cpp | |
parent | 26d8eba0ad906cd9b4a95bbd94fc1556719fd5d2 (diff) | |
download | manaserv-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.cpp | 241 |
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; } + |