/* * 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 "abilitycomponent.h" #include "game-server/being.h" #include "game-server/entity.h" #include "scripting/scriptmanager.h" #include "utils/logger.h" AbilityComponent::AbilityComponent(Entity &entity): mLastUsedAbilityId(0), mLastTargetBeingId(0) { entity.getComponent()->signal_attribute_changed.connect( sigc::mem_fun(this, &AbilityComponent::attributeChanged)); } void AbilityComponent::update(Entity &entity) { // Update ability recharge for (auto &it : mAbilities) { AbilityValue &s = it.second; if (s.abilityInfo->rechargeable && s.currentPoints < s.abilityInfo->neededPoints) { auto *beingComponent = entity.getComponent(); const double rechargeSpeed = beingComponent->getModifiedAttribute( s.abilityInfo->rechargeAttribute); s.currentPoints += (int)rechargeSpeed; if (s.currentPoints >= s.abilityInfo->neededPoints && s.abilityInfo->rechargedCallback.isValid()) { Script *script = ScriptManager::currentState(); script->prepare(s.abilityInfo->rechargedCallback); script->push(&entity); script->push(s.abilityInfo->id); script->execute(entity.getMap()); } } } } /** * Removes an available ability action */ bool AbilityComponent::takeAbility(int id) { AbilityMap::iterator i = mAbilities.find(id); if (i != mAbilities.end()) { mAbilities.erase(i); signal_ability_took.emit(id); return true; } return false; } bool AbilityComponent::abilityUseCheck(AbilityMap::iterator it) { if (!mCooldown.expired()) return false; if (it == mAbilities.end()) { LOG_INFO("Character uses ability " << it->first << " without authorization."); return false; } //check if the ability is currently recharged AbilityValue &ability = it->second; if (ability.abilityInfo->rechargeable && ability.currentPoints < ability.abilityInfo->neededPoints) { LOG_INFO("Character uses ability " << it->first << " which is not recharged. (" << ability.currentPoints << "/" << ability.abilityInfo->neededPoints << ")"); return false; } if (!ability.abilityInfo->useCallback.isValid()) { LOG_WARN("No callback for use of ability " << ability.abilityInfo->categoryName << "/" << ability.abilityInfo->name << ". Ignoring ability."); return false; } return true; } /** * makes the character perform a ability on a being * when it is allowed to do so */ void AbilityComponent::useAbilityOnBeing(Entity &user, int id, Entity *b) { AbilityMap::iterator it = mAbilities.find(id); if (!abilityUseCheck(it)) return; AbilityValue &ability = it->second; if (ability.abilityInfo->target != AbilityManager::TARGET_BEING) return; if (ability.abilityInfo->autoconsume) { ability.currentPoints = 0; signal_ability_changed.emit(id); startCooldown(user, ability.abilityInfo); } //tell script engine to cast the spell Script *script = ScriptManager::currentState(); script->prepare(ability.abilityInfo->useCallback); script->push(&user); script->push(b); script->push(ability.abilityInfo->id); script->execute(user.getMap()); mLastUsedAbilityId = id; if (b) mLastTargetBeingId = b->getComponent()->getPublicID(); else mLastTargetBeingId = 0; user.getComponent()->raiseUpdateFlags( UPDATEFLAG_ABILITY_ON_BEING); } /** * makes the character perform a ability on a map point * when it is allowed to do so */ void AbilityComponent::useAbilityOnPoint(Entity &user, int id, int x, int y) { AbilityMap::iterator it = mAbilities.find(id); if (!abilityUseCheck(it)) return; AbilityValue &ability = it->second; if (ability.abilityInfo->target != AbilityManager::TARGET_POINT) return; if (ability.abilityInfo->autoconsume) { ability.currentPoints = 0; signal_ability_changed.emit(id); startCooldown(user, ability.abilityInfo); } //tell script engine to cast the spell Script *script = ScriptManager::currentState(); script->prepare(ability.abilityInfo->useCallback); script->push(&user); script->push(x); script->push(y); script->push(ability.abilityInfo->id); script->execute(user.getMap()); mLastUsedAbilityId = id; mLastTargetPoint = Point(x, y); user.getComponent()->raiseUpdateFlags( UPDATEFLAG_ABILITY_ON_POINT); } /** * Allows a character to perform a ability */ bool AbilityComponent::giveAbility(int id, int currentPoints) { if (mAbilities.find(id) == mAbilities.end()) { const AbilityManager::AbilityInfo *abilityInfo = abilityManager->getAbilityInfo(id); if (!abilityInfo) { LOG_ERROR("Tried to give not existing ability id " << id << "."); return false; } mAbilities.insert(std::pair( id, AbilityValue(currentPoints, abilityInfo))); signal_ability_changed.emit(id); return true; } return false; } /** * Sets new current mana + makes sure that the client will get informed. */ bool AbilityComponent::setAbilityMana(int id, int mana) { AbilityMap::iterator it = mAbilities.find(id); if (it != mAbilities.end()) { it->second.currentPoints = mana; signal_ability_changed.emit(id); return true; } return false; } void AbilityComponent::startCooldown( Entity &entity, const AbilityManager::AbilityInfo *abilityInfo) { unsigned cooldownAttribute = abilityInfo->cooldownAttribute; auto *bc = entity.getComponent(); int cooldown = (int)bc->getModifiedAttribute(cooldownAttribute); // Enforce a minimum cooldown of 1 tick to prevent syncing issues cooldown = std::max(cooldown, 1); mCooldown.set(cooldown); signal_cooldown_activated.emit(); } void AbilityComponent::attributeChanged(Entity *entity, unsigned attr) { for (auto &abilityIt : mAbilities) { // Inform the client about rechargespeed changes if (abilityIt.second.abilityInfo->rechargeAttribute == attr) signal_ability_changed.emit(abilityIt.first); } }