diff options
author | Erik Schilling <ablu.erikschilling@googlemail.com> | 2013-03-30 09:29:08 +0100 |
---|---|---|
committer | Erik Schilling <ablu.erikschilling@googlemail.com> | 2013-04-02 13:19:11 +0200 |
commit | 585a33e221a7ee392791f4fd5a5ec9214b8fe868 (patch) | |
tree | 3aa60a57ff7cdd8f57761d620352fd2ad4d0dd6f /src/game-server | |
parent | 4dfc82415691fe298f21bb2f81fed5c168ee14e5 (diff) | |
download | manaserv-585a33e221a7ee392791f4fd5a5ec9214b8fe868.tar.gz manaserv-585a33e221a7ee392791f4fd5a5ec9214b8fe868.tar.xz manaserv-585a33e221a7ee392791f4fd5a5ec9214b8fe868.zip |
Moved fighting code into a component
All damage dealing is now handeled via CombatComponent.
Monsters use a derived MonsterCombatComponent since they can have a damage
mutation and have a seperate script callback.
The wirering with Being is still not optional since most of the stuff does
not exist as components.
Things done:
- Seperated the fighting code from Being and only let Characters and Monsters
add the Component (less overhead for npcs)
- Added a getter for Attribute values to prevent searching it all the time in
non Being members
- Fixed the type if the damage mutation to double (no idea why it was int)
I did not want to copy it over incorrectly
- Removed the addAttack/removeAttack overrides in Character and made the
knuckleAttack being added based on newly added signals
Future TODOS:
- Remove depedency on Being as soon all needed dependencies are available
as components of Entity
- Move the monster script callback into the general combatcomponent and
make it usuable for characters too
Diffstat (limited to 'src/game-server')
-rw-r--r-- | src/game-server/attack.cpp | 2 | ||||
-rw-r--r-- | src/game-server/attack.h | 8 | ||||
-rw-r--r-- | src/game-server/being.cpp | 171 | ||||
-rw-r--r-- | src/game-server/being.h | 86 | ||||
-rw-r--r-- | src/game-server/buysell.cpp | 6 | ||||
-rw-r--r-- | src/game-server/character.cpp | 55 | ||||
-rw-r--r-- | src/game-server/character.h | 4 | ||||
-rw-r--r-- | src/game-server/combatcomponent.cpp | 213 | ||||
-rw-r--r-- | src/game-server/combatcomponent.h | 144 | ||||
-rw-r--r-- | src/game-server/commandhandler.cpp | 2 | ||||
-rw-r--r-- | src/game-server/component.h | 1 | ||||
-rw-r--r-- | src/game-server/gamehandler.cpp | 3 | ||||
-rw-r--r-- | src/game-server/item.cpp | 5 | ||||
-rw-r--r-- | src/game-server/mapcomposite.cpp | 5 | ||||
-rw-r--r-- | src/game-server/monster.cpp | 117 | ||||
-rw-r--r-- | src/game-server/monster.h | 15 | ||||
-rw-r--r-- | src/game-server/monstercombatcomponent.cpp | 99 | ||||
-rw-r--r-- | src/game-server/monstercombatcomponent.h | 54 | ||||
-rw-r--r-- | src/game-server/state.cpp | 14 | ||||
-rw-r--r-- | src/game-server/trade.cpp | 8 |
20 files changed, 640 insertions, 372 deletions
diff --git a/src/game-server/attack.cpp b/src/game-server/attack.cpp index 1825b86..a1300e4 100644 --- a/src/game-server/attack.cpp +++ b/src/game-server/attack.cpp @@ -75,6 +75,7 @@ AttackInfo *AttackInfo::readAttackNode(xmlNodePtr node) void Attacks::add(AttackInfo *attackInfo) { mAttacks.push_back(Attack(attackInfo)); + attack_added.emit(*mAttacks.rbegin()); } void Attacks::remove(AttackInfo *attackInfo) @@ -86,6 +87,7 @@ void Attacks::remove(AttackInfo *attackInfo) { if (mCurrentAttack && mCurrentAttack->getAttackInfo() == attackInfo) mCurrentAttack = 0; + attack_removed.emit(*it); mAttacks.erase(it); return; } diff --git a/src/game-server/attack.h b/src/game-server/attack.h index 0bcc361..cecb4fd 100644 --- a/src/game-server/attack.h +++ b/src/game-server/attack.h @@ -24,6 +24,9 @@ #include <cstddef> #include <list> +#include <sigc++/signal.h> +#include <sigc++/trackable.h> + #include "common/defines.h" #include "scripting/script.h" @@ -168,7 +171,7 @@ class Attack /** * Helper class for storing multiple auto-attacks. */ -class Attacks +class Attacks : public sigc::trackable { public: Attacks(): @@ -188,6 +191,9 @@ class Attacks unsigned getNumber() { return mAttacks.size(); } + sigc::signal<void, Attack &> attack_added; + sigc::signal<void, Attack &> attack_removed; + private: std::vector<Attack> mAttacks; diff --git a/src/game-server/being.cpp b/src/game-server/being.cpp index 9112a1b..42d3404 100644 --- a/src/game-server/being.cpp +++ b/src/game-server/being.cpp @@ -27,6 +27,7 @@ #include "game-server/attributemanager.h" #include "game-server/character.h" #include "game-server/collisiondetection.h" +#include "game-server/combatcomponent.h" #include "game-server/mapcomposite.h" #include "game-server/effect.h" #include "game-server/skillmanager.h" @@ -43,9 +44,7 @@ Script::Ref Being::mRecalculateBaseAttributeCallback; Being::Being(EntityType type): Actor(type), mAction(STAND), - mTarget(NULL), mGender(GENDER_UNSPECIFIED), - mCurrentAttack(0), mDirection(DOWN), mEmoteId(0) { @@ -87,74 +86,7 @@ void Being::triggerEmote(int id) raiseUpdateFlags(UPDATEFLAG_EMOTE); } -int Being::damage(Actor * /* source */, const Damage &damage) -{ - if (mAction == DEAD) - return 0; - - int HPloss = damage.base; - if (damage.delta) - HPloss += rand() * (damage.delta + 1) / RAND_MAX; - - // TODO magical attacks and associated elemental modifiers - switch (damage.type) - { - case DAMAGE_PHYSICAL: - 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: -#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 - break; - case DAMAGE_DIRECT: - break; - default: - LOG_WARN("Unknown damage type '" << damage.type << "'!"); - break; - } - - if (HPloss > 0) - { - mHitsTaken.push_back(HPloss); - Attribute &HP = mAttributes.at(ATTR_HP); - LOG_DEBUG("Being " << getPublicID() << " suffered " << HPloss - << " damage. HP: " - << HP.getModifiedAttribute() << "/" - << mAttributes.at(ATTR_MAX_HP).getModifiedAttribute()); - setAttribute(ATTR_HP, HP.getBase() - HPloss); - // No HP regen after being hit if this is set. - mHealthRegenerationTimeout.setSoft( - Configuration::getValue("game_hpRegenBreakAfterHit", 0)); - } - else - { - HPloss = 0; - } - return HPloss; -} void Being::heal() { @@ -191,71 +123,9 @@ void Being::died() // dead beings stay where they are clearDestination(); - // reset target - mTarget = NULL; - signal_died.emit(this); } -void Being::processAttacks() -{ - if (mAction != ATTACK || !mTarget) - return; - - // Ticks attacks even when not attacking to permit cooldowns and warmups. - std::vector<Attack *> attacksReady; - mAttacks.getUsuableAttacks(&attacksReady); - - if (Attack *triggerableAttack = mAttacks.getTriggerableAttack()) - { - processAttack(*triggerableAttack); - mAttacks.markAttackAsTriggered(); - } - - // Deal with the ATTACK action. - if (attacksReady.empty()) - return; - - Attack *highestPriorityAttack = 0; - // Performs all ready attacks. - for (std::vector<Attack *>::const_iterator it = attacksReady.begin(), - it_end = attacksReady.end(); it != it_end; ++it) - { - // check if target is in range using the pythagorean theorem - int distx = this->getPosition().x - mTarget->getPosition().x; - int disty = this->getPosition().y - mTarget->getPosition().y; - int distSquare = (distx * distx + disty * disty); - AttackInfo *info = (*it)->getAttackInfo(); - int maxDist = info->getDamage().range + getSize(); - - if (distSquare <= maxDist * maxDist && - (!highestPriorityAttack || - highestPriorityAttack->getAttackInfo()->getPriority() - < info->getPriority())) - { - highestPriorityAttack = *it; - } - } - if (highestPriorityAttack) - { - mAttacks.startAttack(highestPriorityAttack); - mCurrentAttack = highestPriorityAttack; - setDestination(getPosition()); - // TODO: Turn into direction of enemy - raiseUpdateFlags(UPDATEFLAG_ATTACK); - } -} - -void Being::addAttack(AttackInfo *attackInfo) -{ - mAttacks.add(attackInfo); -} - -void Being::removeAttack(AttackInfo *attackInfo) -{ - mAttacks.remove(attackInfo); -} - void Being::setDestination(const Point &dst) { mDst = dst; @@ -480,23 +350,6 @@ int Being::directionToAngle(int direction) } } -int Being::performAttack(Being *target, const Damage &dmg) -{ - // check target legality - if (!target - || target == this - || target->getAction() == DEAD - || !target->canFight()) - return -1; - - if (getMap()->getPvP() == PVP_NONE - && target->getType() == OBJECT_CHARACTER - && getType() == OBJECT_CHARACTER) - return -1; - - return target->damage(this, dmg); -} - void Being::setAction(BeingAction action) { mAction = action; @@ -547,7 +400,7 @@ void Being::setAttribute(unsigned id, double value) } } -double Being::getAttribute(unsigned id) const +const Attribute *Being::getAttribute(unsigned id) const { AttributeMap::const_iterator ret = mAttributes.find(id); if (ret == mAttributes.end()) @@ -556,6 +409,18 @@ double Being::getAttribute(unsigned id) const << id << " not found! Returning 0."); return 0; } + return &ret->second; +} + +double Being::getAttributeBase(unsigned id) const +{ + AttributeMap::const_iterator ret = mAttributes.find(id); + if (ret == mAttributes.end()) + { + LOG_DEBUG("Being::getAttributeBase: Attribute " + << id << " not found! Returning 0."); + return 0; + } return ret->second.getBase(); } @@ -594,7 +459,7 @@ void Being::recalculateBaseAttribute(unsigned attr) { double newBase = utils::tpsToRawSpeed( getModifiedAttribute(ATTR_MOVE_SPEED_TPS)); - if (newBase != getAttribute(attr)) + if (newBase != getAttributeBase(attr)) setAttribute(attr, newBase); return; } @@ -742,8 +607,6 @@ void Being::update() if (getModifiedAttribute(ATTR_HP) <= 0 && mAction != DEAD) died(); - processAttacks(); - Actor::update(); } @@ -754,7 +617,3 @@ void Being::inserted(Entity *) mOld = getPosition(); } -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 1d1f420..ce45d59 100644 --- a/src/game-server/being.h +++ b/src/game-server/being.h @@ -47,11 +47,6 @@ struct Status typedef std::map< int, Status > StatusEffects; /** - * Type definition for a list of hits - */ -typedef std::vector<unsigned> Hits; - -/** * Generic being (living actor). Keeps direction, destination and a few other * relevant properties. Used for characters & monsters (all animated objects). */ @@ -68,13 +63,6 @@ class Being : public Actor */ virtual void update(); - /** - * Takes a damage structure, computes the real damage based on the - * stats, deducts the result from the hitpoints and adds the result to - * the HitsTaken list. - */ - virtual int damage(Actor *source, const Damage &damage); - /** Restores all hit points of the being */ void heal(); @@ -87,21 +75,6 @@ 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 @@ -133,25 +106,6 @@ class Being : public Actor BeingDirection getDirection() const { return mDirection; } - - /** - * Gets the damage list. - */ - const Hits &getHitsTaken() const - { return mHitsTaken; } - - /** - * Clears the damage list. - */ - void clearHitsTaken() - { mHitsTaken.clear(); } - - /** - * Performs an attack. - * Return Value: damage inflicted or -1 when illegal target - */ - int performAttack(Being *target, const Damage &dmg); - /** * Sets the current action. */ @@ -164,15 +118,6 @@ class Being : public Actor { return mAction; } /** - * Gets the attack id the being is currently performing. - * For being, this is defaulted to the first one (1). - */ - virtual int getAttackId() const - { return mCurrentAttack ? - mCurrentAttack->getAttackInfo()->getDamage().id : 0; - } - - /** * Moves the being toward its destination. */ void move(); @@ -195,9 +140,14 @@ class Being : public Actor void setAttribute(unsigned id, double value); /** - * Gets an attribute. + * Gets an attribute or 0 if not existing. + */ + const Attribute *getAttribute(unsigned id) const; + + /** + * Gets an attribute base. */ - double getAttribute(unsigned id) const; + double getAttributeBase(unsigned id) const; /** * Gets an attribute after applying modifiers. @@ -285,18 +235,6 @@ class Being : public Actor */ static int directionToAngle(int direction); - /** - * Get Target - */ - Being *getTarget() const - { return mTarget; } - - /** - * Set Target - */ - void setTarget(Being *target) - { mTarget = target; } - static void setUpdateDerivedAttributesCallback(Script *script) { script->assignCallback(mRecalculateDerivedAttributesCallback); } @@ -318,11 +256,6 @@ class Being : public Actor protected: /** - * Performs an attack - */ - virtual void processAttack(Attack &attack); - - /** * Update the being direction when moving so avoid directions desyncs * with other clients. */ @@ -333,14 +266,10 @@ class Being : public Actor BeingAction mAction; AttributeMap mAttributes; - 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); @@ -355,7 +284,6 @@ class Being : public Actor BeingDirection mDirection; /**< Facing direction. */ std::string mName; - Hits mHitsTaken; /**< List of punches taken since last update. */ /** Time until hp is regenerated again */ Timeout mHealthRegenerationTimeout; diff --git a/src/game-server/buysell.cpp b/src/game-server/buysell.cpp index 1a0d8d9..ed6d92e 100644 --- a/src/game-server/buysell.cpp +++ b/src/game-server/buysell.cpp @@ -154,15 +154,15 @@ void BuySell::perform(int id, int amount) { amount -= inv.remove(id, amount); mChar->setAttribute(mCurrencyId, - mChar->getAttribute(mCurrencyId) + + mChar->getAttributeBase(mCurrencyId) + amount * i->cost); } else { - amount = std::min(amount, ((int) mChar->getAttribute(mCurrencyId)) / i->cost); + amount = std::min(amount, ((int) mChar->getAttributeBase(mCurrencyId)) / i->cost); amount -= inv.insert(id, amount); mChar->setAttribute(mCurrencyId, - mChar->getAttribute(mCurrencyId) - + mChar->getAttributeBase(mCurrencyId) - amount * i->cost); } if (i->amount) diff --git a/src/game-server/character.cpp b/src/game-server/character.cpp index 8cc0e65..994e331 100644 --- a/src/game-server/character.cpp +++ b/src/game-server/character.cpp @@ -22,8 +22,10 @@ #include "common/configuration.h" #include "game-server/accountconnection.h" +#include "game-server/attack.h" #include "game-server/attributemanager.h" #include "game-server/buysell.h" +#include "game-server/combatcomponent.h" #include "game-server/inventory.h" #include "game-server/item.h" #include "game-server/itemmanager.h" @@ -99,14 +101,13 @@ Character::Character(MessageIn &msg): setWalkMask(Map::BLOCKMASK_WALL); setBlockType(BLOCKTYPE_CHARACTER); - // Get character data. - mDatabaseID = msg.readInt32(); - setName(msg.readString()); - deserializeCharacterData(*this, msg); - mOld = getPosition(); - Inventory(this).initialize(); - modifiedAllAttribute(); - setSize(16); + + CombatComponent *combatcomponent = new CombatComponent(*this); + addComponent(combatcomponent); + combatcomponent->getAttacks().attack_added.connect( + sigc::mem_fun(this, &Character::attackAdded)); + combatcomponent->getAttacks().attack_removed.connect( + sigc::mem_fun(this, &Character::attackRemoved)); // Default knuckle attack int damageBase = this->getModifiedAttribute(ATTR_STR); @@ -121,7 +122,16 @@ Character::Character(MessageIn &msg): knuckleDamage.range = DEFAULT_TILE_LENGTH; mKnuckleAttackInfo = new AttackInfo(0, knuckleDamage, 7, 3, 0); - addAttack(mKnuckleAttackInfo); + combatcomponent->addAttack(mKnuckleAttackInfo); + + // Get character data. + mDatabaseID = msg.readInt32(); + setName(msg.readString()); + deserializeCharacterData(*this, msg); + mOld = getPosition(); + Inventory(this).initialize(); + modifiedAllAttribute(); + setSize(16); } Character::~Character() @@ -199,7 +209,7 @@ void Character::respawn() // Make it alive again setAction(STAND); // Reset target - mTarget = NULL; + getComponent<CombatComponent>()->clearTarget(); // Execute respawn callback when set if (executeCallback(mDeathAcceptedCallback, this)) @@ -422,7 +432,7 @@ void Character::sendStatus() { int attr = *i; attribMsg.writeInt16(attr); - attribMsg.writeInt32(getAttribute(attr) * 256); + attribMsg.writeInt32(getAttributeBase(attr) * 256); attribMsg.writeInt32(getModifiedAttribute(attr) * 256); } if (attribMsg.getLength() > 2) gameHandler->sendTo(this, attribMsg); @@ -497,7 +507,7 @@ void Character::flagAttribute(int attr) { // Inform the client of this attribute modification. accountHandler->updateAttributes(getDatabaseID(), attr, - getAttribute(attr), + getAttributeBase(attr), getModifiedAttribute(attr)); mModifiedAttributes.insert(attr); } @@ -655,7 +665,7 @@ AttribmodResponseCode Character::useCharacterPoint(size_t attribute) return ATTRIBMOD_NO_POINTS_LEFT; --mCharacterPoints; - setAttribute(attribute, getAttribute(attribute) + 1); + setAttribute(attribute, getAttributeBase(attribute) + 1); updateDerivedAttributes(attribute); return ATTRIBMOD_OK; } @@ -666,12 +676,12 @@ AttribmodResponseCode Character::useCorrectionPoint(size_t attribute) return ATTRIBMOD_INVALID_ATTRIBUTE; if (!mCorrectionPoints) return ATTRIBMOD_NO_POINTS_LEFT; - if (getAttribute(attribute) <= 1) + if (getAttributeBase(attribute) <= 1) return ATTRIBMOD_DENIED; --mCorrectionPoints; ++mCharacterPoints; - setAttribute(attribute, getAttribute(attribute) - 1); + setAttribute(attribute, getAttributeBase(attribute) - 1); updateDerivedAttributes(attribute); return ATTRIBMOD_OK; } @@ -704,19 +714,20 @@ void Character::resumeNpcThread() } } -void Character::addAttack(AttackInfo *attackInfo) +void Character::attackAdded(Attack &attack) { // Remove knuckle attack - Being::addAttack(attackInfo); - Being::removeAttack(mKnuckleAttackInfo); + if (attack.getAttackInfo() != mKnuckleAttackInfo) + getComponent<CombatComponent>()->removeAttack(mKnuckleAttackInfo); } -void Character::removeAttack(AttackInfo *attackInfo) +void Character::attackRemoved(Attack &attack) { - Being::removeAttack(attackInfo); // Add knuckle attack - if (mAttacks.getNumber() == 0) - Being::addAttack(mKnuckleAttackInfo); + CombatComponent *combatComponent = getComponent<CombatComponent>(); + // 1 since the attack is not really removed yet. + if (combatComponent->getAttacks().getNumber() == 1) + combatComponent->addAttack(mKnuckleAttackInfo); } void Character::disconnected() diff --git a/src/game-server/character.h b/src/game-server/character.h index dc703c8..8b015f9 100644 --- a/src/game-server/character.h +++ b/src/game-server/character.h @@ -425,9 +425,9 @@ class Character : public Being void triggerLoginCallback(); - virtual void addAttack(AttackInfo *attackInfo); + void attackAdded(Attack &attackInfo); - virtual void removeAttack(AttackInfo *attackInfo); + void attackRemoved(Attack &attackInfo); sigc::signal<void, Character *> signal_disconnected; diff --git a/src/game-server/combatcomponent.cpp b/src/game-server/combatcomponent.cpp new file mode 100644 index 0000000..199126f --- /dev/null +++ b/src/game-server/combatcomponent.cpp @@ -0,0 +1,213 @@ +/* + * The Mana Server + * Copyright (C) 2013 The Mana Developers + * + * 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 "combatcomponent.h" + +#include "game-server/being.h" +#include "game-server/mapcomposite.h" + +#include "utils/logger.h" + +CombatComponent::CombatComponent(Being &being): + mTarget(0), + mCurrentAttack(0) +{ + being.signal_died.connect(sigc::mem_fun(this, + &CombatComponent::diedOrRemoved)); + being.signal_removed.connect(sigc::mem_fun(this, + &CombatComponent::diedOrRemoved)); +} + +CombatComponent::~CombatComponent() +{ +} + +void CombatComponent::update(Entity &entity) +{ + // Temponary for as long as Being is not split into Components + // Prevents to implement all at once + // TODO: remove this as soon as possible + Being &being = static_cast<Being&>(entity); + + + if (being.getAction() != ATTACK || !mTarget) + return; + + std::vector<Attack *> attacksReady; + mAttacks.getUsuableAttacks(&attacksReady); + + if (Attack *triggerableAttack = mAttacks.getTriggerableAttack()) + { + processAttack(being, *triggerableAttack); + mAttacks.markAttackAsTriggered(); + } + + // Deal with the ATTACK action. + if (attacksReady.empty()) + return; + + Attack *highestPriorityAttack = 0; + // Performs all ready attacks. + for (std::vector<Attack *>::const_iterator it = attacksReady.begin(), + it_end = attacksReady.end(); it != it_end; ++it) + { + // check if target is in range using the pythagorean theorem + int distx = being.getPosition().x - mTarget->getPosition().x; + int disty = being.getPosition().y - mTarget->getPosition().y; + int distSquare = (distx * distx + disty * disty); + AttackInfo *info = (*it)->getAttackInfo(); + int maxDist = info->getDamage().range + being.getSize(); + + if (distSquare <= maxDist * maxDist && + (!highestPriorityAttack || + highestPriorityAttack->getAttackInfo()->getPriority() + < info->getPriority())) + { + highestPriorityAttack = *it; + } + } + if (highestPriorityAttack) + { + mAttacks.startAttack(highestPriorityAttack); + mCurrentAttack = highestPriorityAttack; + being.setDestination(being.getPosition()); + // TODO: Turn into direction of enemy + being.raiseUpdateFlags(UPDATEFLAG_ATTACK); + } +} + +/** + * Takes a damage structure, computes the real damage based on the + * stats, deducts the result from the hitpoints and adds the result to + * the HitsTaken list. + */ +int CombatComponent::damage(Being &target, Being *source, const Damage &damage) +{ + int HPloss = damage.base; + if (damage.delta) + HPloss += rand() * (damage.delta + 1) / RAND_MAX; + + // TODO magical attacks and associated elemental modifiers + switch (damage.type) + { + case DAMAGE_PHYSICAL: + if (!damage.trueStrike && + rand()%((int) target.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 * + target.getModifiedAttribute(ATTR_DEFENSE)) / + (1.0 + 0.017 * + target.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: +#if 0 + target.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 + break; + case DAMAGE_DIRECT: + break; + default: + LOG_WARN("Unknown damage type '" << damage.type << "'!"); + break; + } + + if (HPloss > 0) + { + mHitsTaken.push_back(HPloss); + const Attribute *HP = target.getAttribute(ATTR_HP); + LOG_DEBUG("Being " << target.getPublicID() << " suffered " << HPloss + << " damage. HP: " + << HP->getModifiedAttribute() << "/" + << target.getModifiedAttribute(ATTR_MAX_HP)); + target.setAttribute(ATTR_HP, HP->getBase() - HPloss); + // No HP regen after being hit if this is set. + // TODO: Reenable this once the attributes are available as a component + // A bit too fuzzy to implement at the moment + //mHealthRegenerationTimeout.setSoft( + // Configuration::getValue("game_hpRegenBreakAfterHit", 0)); + } + else + { + HPloss = 0; + } + + signal_damaged.emit(source, damage, HPloss); + return HPloss; +} + +/** + * Performs an attack + */ +void CombatComponent::processAttack(Being &source, Attack &attack) +{ + performAttack(source, attack.getAttackInfo()->getDamage()); +} + +/** + * Adds an attack to the available attacks + */ +void CombatComponent::addAttack(AttackInfo *attackInfo) +{ + mAttacks.add(attackInfo); +} + +/** + * Removes an attack from the available attacks + */ +void CombatComponent::removeAttack(AttackInfo *attackInfo) +{ + mAttacks.remove(attackInfo); +} + +/** + * Performs an attack. + */ +int CombatComponent::performAttack(Being &source, const Damage &dmg) +{ + // check target legality + if (!mTarget + || mTarget == &source + || mTarget->getAction() == DEAD + || !mTarget->canFight()) + return -1; + + if (source.getMap()->getPvP() == PVP_NONE + && mTarget->getType() == OBJECT_CHARACTER + && source.getType() == OBJECT_CHARACTER) + return -1; + + return mTarget->getComponent<CombatComponent>()->damage(*mTarget, + &source, dmg); +} diff --git a/src/game-server/combatcomponent.h b/src/game-server/combatcomponent.h new file mode 100644 index 0000000..033b543 --- /dev/null +++ b/src/game-server/combatcomponent.h @@ -0,0 +1,144 @@ +/* + * The Mana Server + * Copyright (C) 2013 The Mana Developers + * + * 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 COMBATCOMPONENT_H +#define COMBATCOMPONENT_H + +#include "component.h" + +#include <vector> + +#include <sigc++/trackable.h> + +#include "game-server/attack.h" + +class Actor; +class Being; + +/** + * Type definition for a list of hits + */ +typedef std::vector<unsigned> Hits; + +class CombatComponent: public Component, public sigc::trackable +{ +public: + static const ComponentType type = CT_Fighting; + + CombatComponent(Being &being); + virtual ~CombatComponent(); + + void update(Entity &entity); + + void addAttack(AttackInfo *attack); + void removeAttack(AttackInfo *attackInfo); + Attacks &getAttacks(); + + const Hits &getHitsTaken() const; + void clearHitsTaken(); + + int performAttack(Being &source, const Damage &dmg); + virtual int damage(Being &target, Being *source, const Damage &damage); + + int getAttackId() const; + + Being *getTarget() const; + void setTarget(Being *target); + void clearTarget(); + + void diedOrRemoved(Entity *entity); + + sigc::signal<void, Being *, const Damage &, int> signal_damaged; + +protected: + virtual void processAttack(Being &source, Attack &attack); + + Being *mTarget; + Attacks mAttacks; + Attack *mCurrentAttack; // Last used attack + Hits mHitsTaken; //List of punches taken since last update. + +}; + +inline Attacks &CombatComponent::getAttacks() +{ + return mAttacks; +} + +/** + * Gets the damage list. + */ +inline const Hits &CombatComponent::getHitsTaken() const +{ + return mHitsTaken; +} + +/** + * Clears the damage list. + */ +inline void CombatComponent::clearHitsTaken() +{ + mHitsTaken.clear(); +} + +/** + * Gets the attack id the being is currently performing. + * For being, this is defaulted to the first one (1). + */ +inline int CombatComponent::getAttackId() const +{ + return mCurrentAttack ? + mCurrentAttack->getAttackInfo()->getDamage().id : 0; +} + +/** + * Get Target + */ +inline Being *CombatComponent::getTarget() const +{ + return mTarget; +} + +/** + * Set Target + */ +inline void CombatComponent::setTarget(Being *target) +{ + mTarget = target; +} + +/** + * Clears the target + */ +inline void CombatComponent::clearTarget() +{ + mTarget = 0; +} + +/** + * Handler for the died and removed event of the targeting being + * @param entity The removed/died being (not used here) + */ +inline void CombatComponent::diedOrRemoved(Entity *entity) +{ + clearTarget(); +} + +#endif // COMBATCOMPONENT_H diff --git a/src/game-server/commandhandler.cpp b/src/game-server/commandhandler.cpp index 001f65f..353c3ef 100644 --- a/src/game-server/commandhandler.cpp +++ b/src/game-server/commandhandler.cpp @@ -670,7 +670,7 @@ static void handleMoney(Character *player, std::string &args) value = utils::stringToInt(valuestr); // change how much money the player has - other->setAttribute(ATTR_GP , other->getAttribute(ATTR_GP) + value); + other->setAttribute(ATTR_GP , other->getAttributeBase(ATTR_GP) + value); // log transaction std::string msg = "User created " + valuestr + " money"; diff --git a/src/game-server/component.h b/src/game-server/component.h index 740668a..47e9dd0 100644 --- a/src/game-server/component.h +++ b/src/game-server/component.h @@ -26,6 +26,7 @@ class Entity; enum ComponentType { CT_Effect, + CT_Fighting, CT_Item, CT_Npc, CT_SpawnArea, diff --git a/src/game-server/gamehandler.cpp b/src/game-server/gamehandler.cpp index 51b64a2..9cec772 100644 --- a/src/game-server/gamehandler.cpp +++ b/src/game-server/gamehandler.cpp @@ -27,6 +27,7 @@ #include "common/transaction.h" #include "game-server/accountconnection.h" #include "game-server/buysell.h" +#include "game-server/combatcomponent.h" #include "game-server/commandhandler.h" #include "game-server/emotemanager.h" #include "game-server/inventory.h" @@ -649,7 +650,7 @@ void GameHandler::handleAttack(GameClient &client, MessageIn &message) Being *being = findBeingNear(client.character, id); if (being && being->getType() != OBJECT_NPC) { - client.character->setTarget(being); + client.character->getComponent<CombatComponent>()->setTarget(being); client.character->setAction(ATTACK); } } diff --git a/src/game-server/item.cpp b/src/game-server/item.cpp index b7b0d78..7295b95 100644 --- a/src/game-server/item.cpp +++ b/src/game-server/item.cpp @@ -25,6 +25,7 @@ #include "game-server/attack.h" #include "game-server/attributemanager.h" #include "game-server/being.h" +#include "game-server/combatcomponent.h" #include "game-server/state.h" #include "scripting/script.h" #include "scripting/scriptmanager.h" @@ -49,13 +50,13 @@ void ItemEffectAttrMod::dispell(Being *itemUser) bool ItemEffectAttack::apply(Being *itemUser) { - itemUser->addAttack(mAttackInfo); + itemUser->getComponent<CombatComponent>()->addAttack(mAttackInfo); return false; } void ItemEffectAttack::dispell(Being *itemUser) { - itemUser->removeAttack(mAttackInfo); + itemUser->getComponent<CombatComponent>()->removeAttack(mAttackInfo); } ItemEffectScript::~ItemEffectScript() diff --git a/src/game-server/mapcomposite.cpp b/src/game-server/mapcomposite.cpp index a1a3f98..f1b545e 100644 --- a/src/game-server/mapcomposite.cpp +++ b/src/game-server/mapcomposite.cpp @@ -26,6 +26,7 @@ #include "common/configuration.h" #include "common/resourcemanager.h" #include "game-server/character.h" +#include "game-server/combatcomponent.h" #include "game-server/mapcomposite.h" #include "game-server/map.h" #include "game-server/mapmanager.h" @@ -591,9 +592,9 @@ void MapComposite::remove(Entity *ptr) if ((*i)->canFight()) { Being *being = static_cast<Being*>(*i); - if (being->getTarget() == ptr) + if (being->getComponent<CombatComponent>()->getTarget() == ptr) { - being->setTarget(NULL); + being->getComponent<CombatComponent>()->clearTarget(); } } if (*i == ptr) diff --git a/src/game-server/monster.cpp b/src/game-server/monster.cpp index ceae82c..25abb68 100644 --- a/src/game-server/monster.cpp +++ b/src/game-server/monster.cpp @@ -28,6 +28,7 @@ #include "game-server/item.h" #include "game-server/map.h" #include "game-server/mapcomposite.h" +#include "game-server/monstercombatcomponent.h" #include "game-server/state.h" #include "scripting/scriptmanager.h" #include "utils/logger.h" @@ -98,9 +99,6 @@ Monster::Monster(MonsterClass *specy): } } - mDamageMutation = mutation ? - (100 + (rand() % (mutation * 2)) - mutation) / 100.0 : 1; - setSize(specy->getSize()); setGender(specy->getGender()); @@ -111,13 +109,16 @@ 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); - } + MonsterCombatComponent *combatComponent = + new MonsterCombatComponent(*this); + addComponent(combatComponent); + + double damageMutation = mutation ? + (100.0 + (rand() % (mutation * 2)) - mutation) / 100.0 : 1.0; + combatComponent->setDamageMutation(damageMutation); + + combatComponent->signal_damaged.connect( + sigc::mem_fun(this, &Monster::receivedDamage)); } Monster::~Monster() @@ -151,7 +152,7 @@ void Monster::update() refreshTarget(); // Cancel the rest when we have a target - if (mTarget) + if (getComponent<CombatComponent>()->getTarget()) return; // We have no target - let's wander around @@ -188,7 +189,7 @@ void Monster::refreshTarget() Point bestAttackPosition; // reset Target. We will find a new one if possible - mTarget = 0; + getComponent<CombatComponent>()->clearTarget(); // Iterate through objects nearby int aroundArea = Configuration::getValue("game_visualRange", 448); @@ -242,11 +243,11 @@ void Monster::refreshTarget() } if (bestTarget) { - mTarget = bestTarget; + getComponent<CombatComponent>()->setTarget(bestTarget); if (bestAttackPosition == getPosition()) { mAction = ATTACK; - updateDirection(getPosition(), mTarget->getPosition()); + updateDirection(getPosition(), bestTarget->getPosition()); } else { @@ -255,35 +256,6 @@ void Monster::refreshTarget() } } -void Monster::processAttack(Attack &attack) -{ - if (!mTarget) - { - setAction(STAND); - return; - } - - Damage dmg = attack.getAttackInfo()->getDamage(); - dmg.skill = 0; - dmg.base *= mDamageMutation; - dmg.delta *= mDamageMutation; - - int hit = performAttack(mTarget, attack.getAttackInfo()->getDamage()); - - const Script::Ref &scriptCallback = - attack.getAttackInfo()->getScriptCallback(); - - if (scriptCallback.isValid() && hit > -1) - { - Script *script = ScriptManager::currentState(); - script->prepare(scriptCallback); - script->push(this); - script->push(mTarget); - script->push(hit); - script->execute(getMap()); - } -} - int Monster::calculatePositionPriority(Point position, int targetPriority) { Point thisPos = getPosition(); @@ -375,44 +347,6 @@ std::map<Being *, int> Monster::getAngerList() const return result; } -int Monster::damage(Actor *source, const Damage &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); - - if (HPLoss && source && source->getType() == OBJECT_CHARACTER) - { - Character *s = static_cast< Character * >(source); - - mExpReceivers[s].insert(damage.skill); - if (mKillStealProtectedTimeout.expired() || mOwner == s - || mOwner->getParty() == s->getParty()) - { - mOwner = s; - mLegalExpReceivers.insert(s); - mKillStealProtectedTimeout.set(KILLSTEAL_PROTECTION_TIME); - } - } - - if (mSpecy->getDamageCallback().isValid()) - { - Script *script = ScriptManager::currentState(); - script->prepare(mSpecy->getDamageCallback()); - script->push(this); - script->push(source); - script->push(HPLoss); - // TODO: add exact damage parameters as well - script->execute(getMap()); - } - - return HPLoss; -} - void Monster::died() { if (mAction == DEAD) @@ -467,3 +401,24 @@ void Monster::died() } } } + + +void Monster::receivedDamage(Being *source, const Damage &damage, int hpLoss) +{ + if (source) + changeAnger(source, hpLoss); + + if (hpLoss && source && source->getType() == OBJECT_CHARACTER) + { + Character *s = static_cast< Character * >(source); + + mExpReceivers[s].insert(damage.skill); + if (mKillStealProtectedTimeout.expired() || mOwner == s + || mOwner->getParty() == s->getParty()) + { + mOwner = s; + mLegalExpReceivers.insert(s); + mKillStealProtectedTimeout.set(KILLSTEAL_PROTECTION_TIME); + } + } +} diff --git a/src/game-server/monster.h b/src/game-server/monster.h index e87180d..3bd34a9 100644 --- a/src/game-server/monster.h +++ b/src/game-server/monster.h @@ -292,15 +292,12 @@ class Monster : public Being void refreshTarget(); /** - * Performs an attack - */ - virtual void processAttack(Attack &attack); - - /** * Kills the being. */ void died(); + void receivedDamage(Being *attacker, const Damage &damage, int hpLoss); + /** * Alters hate for the monster */ @@ -309,11 +306,6 @@ class Monster : public Being std::map<Being *, int> getAngerList() const; /** - * Calls the damage function in Being and updates the aggro list - */ - virtual int damage(Actor *source, const Damage &damage); - - /** * Removes a being from the anger list. */ void forgetTarget(Entity *entity); @@ -342,9 +334,6 @@ 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; diff --git a/src/game-server/monstercombatcomponent.cpp b/src/game-server/monstercombatcomponent.cpp new file mode 100644 index 0000000..dc66955 --- /dev/null +++ b/src/game-server/monstercombatcomponent.cpp @@ -0,0 +1,99 @@ +/* + * The Mana Server + * Copyright (C) 2013 The Mana Developers + * + * 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 "game-server/monstercombatcomponent.h" + +#include "game-server/monster.h" +#include "scripting/scriptmanager.h" + +MonsterCombatComponent::MonsterCombatComponent(Monster &monster): + CombatComponent(monster), + mDamageMutation(0) +{ + // Take attacks from specy + std::vector<AttackInfo *> &attacks = monster.getSpecy()->getAttackInfos(); + for (std::vector<AttackInfo *>::iterator it = attacks.begin(), + it_end = attacks.end(); it != it_end; ++it) + { + addAttack(*it); + } +} + +/** + * Performs an attack + */ +void MonsterCombatComponent::processAttack(Being *source, Attack &attack) +{ + if (!mTarget) + { + source->setAction(STAND); + return; + } + + Damage dmg = attack.getAttackInfo()->getDamage(); + dmg.skill = 0; + dmg.base *= mDamageMutation; + dmg.delta *= mDamageMutation; + + int hit = performAttack(*mTarget, attack.getAttackInfo()->getDamage()); + + const Script::Ref &scriptCallback = + attack.getAttackInfo()->getScriptCallback(); + + if (scriptCallback.isValid() && hit > -1) + { + Script *script = ScriptManager::currentState(); + script->prepare(scriptCallback); + script->push(source); + script->push(mTarget); + script->push(hit); + script->execute(source->getMap()); + } +} + +/** + * Calls the damage function in Being and updates the aggro list + */ +int MonsterCombatComponent::damage(Being &target, + Being *source, + const Damage &damage) +{ + // Temporarily depend on monster as long as it does not exist as a component + Monster &monster = static_cast<Monster &>(target); + Damage newDamage = damage; + MonsterClass *specy = monster.getSpecy(); + float factor = specy->getVulnerability(newDamage.element); + newDamage.base = newDamage.base * factor; + newDamage.delta = newDamage.delta * factor; + int hpLoss = CombatComponent::damage(target, source, newDamage); + + + if (specy->getDamageCallback().isValid()) + { + Script *script = ScriptManager::currentState(); + script->prepare(specy->getDamageCallback()); + script->push(&target); + script->push(source); + script->push(hpLoss); + // TODO: add exact damage parameters as well + script->execute(monster.getMap()); + } + return hpLoss; +} diff --git a/src/game-server/monstercombatcomponent.h b/src/game-server/monstercombatcomponent.h new file mode 100644 index 0000000..766c773 --- /dev/null +++ b/src/game-server/monstercombatcomponent.h @@ -0,0 +1,54 @@ +/* + * The Mana Server + * Copyright (C) 2013 The Mana Developers + * + * 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 MONSTERCOMBATCOMPONENT_H +#define MONSTERCOMBATCOMPONENT_H + +#include "game-server/combatcomponent.h" + +#include "game-server/attack.h" +#include "game-server/being.h" + +class Monster; + +class MonsterCombatComponent: public CombatComponent +{ +public: + MonsterCombatComponent(Monster &monster); + + void processAttack(Being *source, Attack &attack); + int damage(Being &target, Being *source, const Damage &damage); + + void setDamageMutation(double mutation); + +private: + double mDamageMutation; +}; + +/** + * Sets the mutation of the damage compared to the default damage of the specy + * @param mutation + */ +inline void MonsterCombatComponent::setDamageMutation(double mutation) +{ + mDamageMutation = mutation; +} + +#endif /* MONSTERCOMBATCOMPONENT_H */ diff --git a/src/game-server/state.cpp b/src/game-server/state.cpp index 673d48f..eaffe77 100644 --- a/src/game-server/state.cpp +++ b/src/game-server/state.cpp @@ -22,11 +22,12 @@ #include "common/configuration.h" #include "game-server/accountconnection.h" +#include "game-server/effect.h" +#include "game-server/combatcomponent.h" #include "game-server/gamehandler.h" #include "game-server/inventory.h" #include "game-server/item.h" #include "game-server/itemmanager.h" -#include "game-server/effect.h" #include "game-server/map.h" #include "game-server/mapcomposite.h" #include "game-server/mapmanager.h" @@ -166,7 +167,9 @@ static void informPlayer(MapComposite *map, Character *p) MessageOut AttackMsg(GPMSG_BEING_ATTACK); AttackMsg.writeInt16(oid); AttackMsg.writeInt8(o->getDirection()); - AttackMsg.writeInt8(static_cast< Being * >(o)->getAttackId()); + CombatComponent *combatComponent = + o->getComponent<CombatComponent>(); + AttackMsg.writeInt8(combatComponent->getAttackId()); gameHandler->sendTo(p, AttackMsg); } @@ -217,8 +220,9 @@ static void informPlayer(MapComposite *map, Character *p) // Send damage messages. if (o->canFight()) { - Being *victim = static_cast< Being * >(o); - const Hits &hits = victim->getHitsTaken(); + CombatComponent *combatComponent = + o->getComponent<CombatComponent>(); + const Hits &hits = combatComponent->getHitsTaken(); for (Hits::const_iterator j = hits.begin(), j_end = hits.end(); j != j_end; ++j) { @@ -462,7 +466,7 @@ void GameState::update(int tick) a->clearUpdateFlags(); if (a->canFight()) { - static_cast< Being * >(a)->clearHitsTaken(); + a->getComponent<CombatComponent>()->clearHitsTaken(); } } } diff --git a/src/game-server/trade.cpp b/src/game-server/trade.cpp index e1779d2..0b906b1 100644 --- a/src/game-server/trade.cpp +++ b/src/game-server/trade.cpp @@ -131,14 +131,14 @@ void Trade::agree(Character *c) // Check if both player has the objects in their inventories // and enouth money, then swap them. Inventory v1(mChar1), v2(mChar2); - if (mChar1->getAttribute(mCurrencyId) >= mMoney1 - mMoney2 && - mChar2->getAttribute(mCurrencyId) >= mMoney2 - mMoney1 && + if (mChar1->getAttributeBase(mCurrencyId) >= mMoney1 - mMoney2 && + mChar2->getAttributeBase(mCurrencyId) >= mMoney2 - mMoney1 && perform(mItems1, v1, v2) && perform(mItems2, v2, v1)) { - mChar1->setAttribute(mCurrencyId, mChar1->getAttribute(mCurrencyId) + mChar1->setAttribute(mCurrencyId, mChar1->getAttributeBase(mCurrencyId) - mMoney1 + mMoney2); - mChar2->setAttribute(mCurrencyId, mChar2->getAttribute(mCurrencyId) + mChar2->setAttribute(mCurrencyId, mChar2->getAttributeBase(mCurrencyId) - mMoney2 + mMoney1); } else |