/** * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * * Copyright (C) 2005-2016 MaNGOS project * * This program 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 * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * World of Warcraft, and all World of Warcraft or Warcraft art, images, * and lore are copyrighted by Blizzard Entertainment, Inc. */ #include "TargetedMovementGenerator.h" #include "ByteBuffer.h" #include "Errors.h" #include "PathFinder.h" #include "Unit.h" #include "Creature.h" #include "Player.h" #include "World.h" #include "movement/MoveSplineInit.h" #include "movement/MoveSpline.h" //-----------------------------------------------// template void TargetedMovementGeneratorMedium::_setTargetLocation(T& owner, bool updateDestination) { if (!i_target.isValid() || !i_target->IsInWorld()) { return; } if (owner.hasUnitState(UNIT_STAT_NOT_MOVE)) { return; } float x, y, z; // i_path can be NULL in case this is the first call for this MMGen (via Update) // Can happen for example if no path was created on MMGen-Initialize because of the owner being stunned if (updateDestination || !i_path) { owner.GetPosition(x, y, z); // prevent redundant micro-movement for pets, other followers. if (!RequiresNewPosition(owner, x, y, z)) { if (!owner.movespline->Finalized()) { return; } } // Chase Movement and angle == 0 case: Chase to current angle else if (this->GetMovementGeneratorType() == CHASE_MOTION_TYPE && i_angle == 0.0f) { i_target->GetNearPoint(&owner, x, y, z, owner.GetObjectBoundingRadius(), this->GetDynamicTargetDistance(owner, false), i_target->GetAngle(&owner)); } // Targeted movement to at i_offset distance from target and i_angle from target facing else { i_target->GetNearPoint(&owner, x, y, z, owner.GetObjectBoundingRadius(), this->GetDynamicTargetDistance(owner, false), i_target->GetOrientation() + i_angle); } } else { // the destination has not changed, we just need to refresh the path (usually speed change) G3D::Vector3 end = i_path->getEndPosition(); x = end.x; y = end.y; z = end.z; } if (!i_path) { i_path = new PathFinder(&owner); } // allow pets following their master to cheat while generating paths bool forceDest = (owner.GetTypeId() == TYPEID_UNIT && ((Creature*)&owner)->IsPet() && owner.hasUnitState(UNIT_STAT_FOLLOW)); i_path->calculate(x, y, z, forceDest); if (i_path->getPathType() & PATHFIND_NOPATH) { return; } D::_addUnitStateMove(owner); i_targetReached = false; m_speedChanged = false; Movement::MoveSplineInit init(owner); init.MovebyPath(i_path->getPath()); init.SetWalk(((D*)this)->EnableWalking()); init.Launch(); } template bool TargetedMovementGeneratorMedium::Update(T& owner, const uint32& time_diff) { if (!i_target.isValid() || !i_target->IsInWorld()) { return false; } if (!owner.IsAlive()) { return true; } if (owner.hasUnitState(UNIT_STAT_NOT_MOVE)) { D::_clearUnitStateMove(owner); return true; } if (this->GetMovementGeneratorType() == CHASE_MOTION_TYPE && owner.hasUnitState(UNIT_STAT_NO_COMBAT_MOVEMENT)) { D::_clearUnitStateMove(owner); return true; } // prevent movement while casting spells with cast time or channel time if (owner.IsNonMeleeSpellCasted(false, false, true)) { if (!owner.IsStopped()) { owner.StopMoving(); } return true; } // prevent crash after creature killed pet if (static_cast(this)->_lostTarget(owner)) { D::_clearUnitStateMove(owner); return true; } bool targetMoved = false; i_recheckDistance.Update(time_diff); if (i_recheckDistance.Passed()) { i_recheckDistance.Reset(this->GetMovementGeneratorType() == FOLLOW_MOTION_TYPE ? 50 : 100); G3D::Vector3 dest = owner.movespline->FinalDestination(); targetMoved = RequiresNewPosition(owner, dest.x, dest.y, dest.z); if (!targetMoved) { // This unit is in hitbox of target // howewer we have to check if the target not moved a bit to update the orientation // client do it automatically 'visually' but it need this new orientation send or it will retrieve old orientation in some case (like stun) G3D::Vector3 currTargetPos; i_target->GetPosition(currTargetPos.x, currTargetPos.y, currTargetPos.z); if (owner.movespline->Finalized() && currTargetPos != m_prevTargetPos) { // position changed we need to adjust owner orientation to continue facing it m_prevTargetPos = currTargetPos; owner.SetInFront(i_target.getTarget()); // set movementinfo orientation, needed for next movement if any float angle = owner.GetAngle(i_target.getTarget()); owner.SetFacingTo(angle); // inform client that orientation changed } } } if (m_speedChanged || targetMoved) { _setTargetLocation(owner, targetMoved); } if (owner.movespline->Finalized()) { if (!i_targetReached) { i_targetReached = true; static_cast(this)->_reachTarget(owner); } } return true; } template bool TargetedMovementGeneratorMedium::IsReachable() const { return (i_path) ? (i_path->getPathType() & PATHFIND_NORMAL) : true; } template bool TargetedMovementGeneratorMedium::RequiresNewPosition(T& owner, float x, float y, float z) const { // More distance let have better performance, less distance let have more sensitive reaction at target move. if (owner.GetTypeId() == TYPEID_UNIT && ((Creature*)&owner)->CanFly()) { return !i_target->IsWithinDist3d(x, y, z, this->GetDynamicTargetDistance(owner, true)); } else { return !i_target->IsWithinDist2d(x, y, this->GetDynamicTargetDistance(owner, true)); } } //-----------------------------------------------// template void ChaseMovementGenerator::_clearUnitStateMove(T& u) { u.clearUnitState(UNIT_STAT_CHASE_MOVE); } template void ChaseMovementGenerator::_addUnitStateMove(T& u) { u.addUnitState(UNIT_STAT_CHASE_MOVE); } template bool ChaseMovementGenerator::_lostTarget(T& u) const { return u.getVictim() != this->GetTarget(); } template void ChaseMovementGenerator::_reachTarget(T& owner) { if (owner.CanReachWithMeleeAttack(this->i_target.getTarget())) { owner.Attack(this->i_target.getTarget(), true); } } template<> void ChaseMovementGenerator::Initialize(Player& owner) { owner.addUnitState(UNIT_STAT_CHASE); // _MOVE set in _SetTargetLocation after required checks _setTargetLocation(owner, true); i_target->GetPosition(m_prevTargetPos.x, m_prevTargetPos.y, m_prevTargetPos.z); } template<> void ChaseMovementGenerator::Initialize(Creature& owner) { owner.SetWalk(false, false); // Chase movement is running owner.addUnitState(UNIT_STAT_CHASE); // _MOVE set in _SetTargetLocation after required checks _setTargetLocation(owner, true); i_target->GetPosition(m_prevTargetPos.x, m_prevTargetPos.y, m_prevTargetPos.z); } template void ChaseMovementGenerator::Finalize(T& owner) { owner.clearUnitState(UNIT_STAT_CHASE | UNIT_STAT_CHASE_MOVE); } template void ChaseMovementGenerator::Interrupt(T& owner) { owner.InterruptMoving(); owner.clearUnitState(UNIT_STAT_CHASE | UNIT_STAT_CHASE_MOVE); } template void ChaseMovementGenerator::Reset(T& owner) { Initialize(owner); } // Chase-Movement: These factors depend on combat-reach distance #define CHASE_DEFAULT_RANGE_FACTOR 0.5f #define CHASE_RECHASE_RANGE_FACTOR 0.75f template float ChaseMovementGenerator::GetDynamicTargetDistance(T& owner, bool forRangeCheck) const { if (!forRangeCheck) { return this->i_offset + CHASE_DEFAULT_RANGE_FACTOR * this->i_target->GetCombatReach(&owner); } return CHASE_RECHASE_RANGE_FACTOR * this->i_target->GetCombatReach(&owner) - this->i_target->GetObjectBoundingRadius(); } //-----------------------------------------------// template void FollowMovementGenerator::_clearUnitStateMove(T& u) { u.clearUnitState(UNIT_STAT_FOLLOW_MOVE); } template void FollowMovementGenerator::_addUnitStateMove(T& u) { u.addUnitState(UNIT_STAT_FOLLOW_MOVE); } template<> bool FollowMovementGenerator::EnableWalking() const { return i_target.isValid() && i_target->IsWalking(); } template<> bool FollowMovementGenerator::EnableWalking() const { return false; } template<> void FollowMovementGenerator::_updateSpeed(Player& /*u*/) { // nothing to do for Player } template<> void FollowMovementGenerator::_updateSpeed(Creature& u) { // pet only sync speed with owner if (!((Creature&)u).IsPet() || !i_target.isValid() || i_target->GetObjectGuid() != u.GetOwnerGuid()) { return; } u.UpdateSpeed(MOVE_RUN, true); u.UpdateSpeed(MOVE_WALK, true); u.UpdateSpeed(MOVE_SWIM, true); } template<> void FollowMovementGenerator::Initialize(Player& owner) { owner.addUnitState(UNIT_STAT_FOLLOW); // _MOVE set in _SetTargetLocation after required checks _updateSpeed(owner); _setTargetLocation(owner, true); } template<> void FollowMovementGenerator::Initialize(Creature& owner) { owner.addUnitState(UNIT_STAT_FOLLOW); // _MOVE set in _SetTargetLocation after required checks _updateSpeed(owner); _setTargetLocation(owner, true); } template void FollowMovementGenerator::Finalize(T& owner) { owner.clearUnitState(UNIT_STAT_FOLLOW | UNIT_STAT_FOLLOW_MOVE); _updateSpeed(owner); } template void FollowMovementGenerator::Interrupt(T& owner) { owner.InterruptMoving(); owner.clearUnitState(UNIT_STAT_FOLLOW | UNIT_STAT_FOLLOW_MOVE); _updateSpeed(owner); } template void FollowMovementGenerator::Reset(T& owner) { Initialize(owner); } // This factor defines how much of the bounding-radius (as measurement of size) will be used for recalculating a new following position // The smaller, the more micro movement, the bigger, possibly no proper movement updates #define FOLLOW_RECALCULATE_FACTOR 1.0f // This factor defines when the distance of a follower will have impact onto following-position updates #define FOLLOW_DIST_GAP_FOR_DIST_FACTOR 3.0f // This factor defines how much of the follow-distance will be used as sloppyness value (if the above distance is exceeded) #define FOLLOW_DIST_RECALCULATE_FACTOR 1.0f template float FollowMovementGenerator::GetDynamicTargetDistance(T& owner, bool forRangeCheck) const { if (!forRangeCheck) { return this->i_offset + owner.GetObjectBoundingRadius() + this->i_target->GetObjectBoundingRadius(); } float allowed_dist = sWorld.getConfig(CONFIG_FLOAT_RATE_TARGET_POS_RECALCULATION_RANGE) - this->i_target->GetObjectBoundingRadius(); allowed_dist += FOLLOW_RECALCULATE_FACTOR * (owner.GetObjectBoundingRadius() + this->i_target->GetObjectBoundingRadius()); if (this->i_offset > FOLLOW_DIST_GAP_FOR_DIST_FACTOR) { allowed_dist += FOLLOW_DIST_RECALCULATE_FACTOR * this->i_offset; } return allowed_dist; } //-----------------------------------------------// template void TargetedMovementGeneratorMedium >::_setTargetLocation(Player&, bool); template void TargetedMovementGeneratorMedium >::_setTargetLocation(Player&, bool); template void TargetedMovementGeneratorMedium >::_setTargetLocation(Creature&, bool); template void TargetedMovementGeneratorMedium >::_setTargetLocation(Creature&, bool); template bool TargetedMovementGeneratorMedium >::Update(Player&, const uint32&); template bool TargetedMovementGeneratorMedium >::Update(Player&, const uint32&); template bool TargetedMovementGeneratorMedium >::Update(Creature&, const uint32&); template bool TargetedMovementGeneratorMedium >::Update(Creature&, const uint32&); template bool TargetedMovementGeneratorMedium >::IsReachable() const; template bool TargetedMovementGeneratorMedium >::IsReachable() const; template bool TargetedMovementGeneratorMedium >::IsReachable() const; template bool TargetedMovementGeneratorMedium >::IsReachable() const; template void ChaseMovementGenerator::_clearUnitStateMove(Player& u); template void ChaseMovementGenerator::_addUnitStateMove(Creature& u); template bool ChaseMovementGenerator::_lostTarget(Player& u) const; template bool ChaseMovementGenerator::_lostTarget(Creature& u) const; template void ChaseMovementGenerator::_reachTarget(Player&); template void ChaseMovementGenerator::_reachTarget(Creature&); template void ChaseMovementGenerator::Finalize(Player&); template void ChaseMovementGenerator::Finalize(Creature&); template void ChaseMovementGenerator::Interrupt(Player&); template void ChaseMovementGenerator::Interrupt(Creature&); template void ChaseMovementGenerator::Reset(Player&); template void ChaseMovementGenerator::Reset(Creature&); template float ChaseMovementGenerator::GetDynamicTargetDistance(Creature&, bool) const; template float ChaseMovementGenerator::GetDynamicTargetDistance(Player&, bool) const; template void FollowMovementGenerator::_clearUnitStateMove(Player& u); template void FollowMovementGenerator::_addUnitStateMove(Creature& u); template void FollowMovementGenerator::Finalize(Player&); template void FollowMovementGenerator::Finalize(Creature&); template void FollowMovementGenerator::Interrupt(Player&); template void FollowMovementGenerator::Interrupt(Creature&); template void FollowMovementGenerator::Reset(Player&); template void FollowMovementGenerator::Reset(Creature&); template float FollowMovementGenerator::GetDynamicTargetDistance(Creature&, bool) const; template float FollowMovementGenerator::GetDynamicTargetDistance(Player&, bool) const;