/* * 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 . */ #include "combatcomponent.h" #include "game-server/being.h" #include "game-server/mapcomposite.h" #include "utils/logger.h" CombatComponent::CombatComponent(Entity &being): mTarget(nullptr), mCurrentAttack(nullptr) { being.getComponent()->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) { auto *beingComponent = entity.getComponent(); if (beingComponent->getAction() != ATTACK || !mTarget) return; std::vector attacksReady; mAttacks.getUsuableAttacks(&attacksReady); if (Attack *triggerableAttack = mAttacks.getTriggerableAttack()) { processAttack(entity, *triggerableAttack); mAttacks.markAttackAsTriggered(); } // Deal with the ATTACK action. if (attacksReady.empty()) return; Attack *highestPriorityAttack = 0; // Performs all ready attacks. for (std::vector::const_iterator it = attacksReady.begin(), it_end = attacksReady.end(); it != it_end; ++it) { const Point &attackerPosition = entity.getComponent()->getPosition(); const Point &targetPosition = mTarget->getComponent()->getPosition(); // check if target is in range using the pythagorean theorem int distx = attackerPosition.x - targetPosition.x; int disty = attackerPosition.y - targetPosition.y; int distSquare = (distx * distx + disty * disty); AttackInfo *info = (*it)->getAttackInfo(); int maxDist = info->getDamage().range + entity.getComponent()->getSize(); if (distSquare <= maxDist * maxDist && (!highestPriorityAttack || highestPriorityAttack->getAttackInfo()->getPriority() < info->getPriority())) { highestPriorityAttack = *it; } } if (highestPriorityAttack) { mAttacks.startAttack(highestPriorityAttack); mCurrentAttack = highestPriorityAttack; const Point &point = entity.getComponent()->getPosition(); beingComponent->setDestination(entity, point); // TODO: Turn into direction of enemy entity.getComponent()->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(Entity &target, Entity *source, const Damage &damage) { auto *beingComponent = target.getComponent(); 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: { const double dodge = beingComponent->getModifiedAttribute(ATTR_DODGE); if (!damage.trueStrike && rand()%((int)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 { const double defense = beingComponent->getModifiedAttribute(ATTR_DEFENSE); HPloss = HPloss * (1.0 - (0.0159375f * defense) / (1.0 + 0.017 * defense)) + (rand()%((HPloss / 16) + 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 beingComponent.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 = beingComponent->getAttribute(ATTR_HP); LOG_DEBUG("Being " << target.getComponent()->getPublicID() << " suffered " << HPloss << " damage. HP: " << HP->getModifiedAttribute() << "/" << beingComponent->getModifiedAttribute(ATTR_MAX_HP)); beingComponent->setAttribute(target, 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(Entity &source, Attack &attack) { performAttack(source, attack.getAttackInfo()->getDamage()); } /** * Adds an attack to the available attacks */ void CombatComponent::addAttack(AttackInfo *attackInfo) { mAttacks.add(this, attackInfo); } /** * Removes an attack from the available attacks */ void CombatComponent::removeAttack(AttackInfo *attackInfo) { mAttacks.remove(this, attackInfo); } /** * Performs an attack. */ int CombatComponent::performAttack(Entity &source, const Damage &dmg) { // check target legality if (!mTarget || mTarget == &source || mTarget->getComponent()->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()->damage(*mTarget, &source, dmg); }