mangos/src/game/MotionGenerators/WaypointMovementGenerator.cpp
Elmsroth b4e54bc4de
Multiple fixes (#116)
* Fix usage of Gnomish Universal Remote (ItemID: 7506)

https://classic.wowhead.com/item=7506/gnomish-universal-remote
Will now apply correct random spells :
Spells :  8345 - Control the machine | 8346 = Malfunction the machine (root) | 8347 = Taunt/enrage the machine

* Fix Spells "Hate to Zero" 


https://classic.wowhead.com/spell=9204/hate-to-zero#see-also-other
SpellIDs : 9204 | 20538  | 26569 | 26637

* Upgrade Unit::RemoveAllAurasOnEvade method

As specific list of spell used when evading to remove all auras except some special auras

* Fix npc_escortAI - Properly despawn pets that act as escorts

Thanks to caa548ca7d

* Fix autoshot not reinstating 0.5 sec cooldown on stopping (adapt Unit::IsNonMeleeSpellCasted method)

Source : 20602b3ead
This fix needed to rewrite and add more stuff to be fully ported from CMangos.

* Fix build compile

Implement "IsClientControlled()" missing
Rename some defines to match CMangos ones which are more relevant and would facilitate backports.
2020-11-02 21:44:10 +00:00

549 lines
16 KiB
C++

/**
* 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-2020 MaNGOS <https://getmangos.eu>
*
* 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 "WaypointMovementGenerator.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "Creature.h"
#include "CreatureAI.h"
#include "WaypointManager.h"
#include "ScriptMgr.h"
#include "movement/MoveSplineInit.h"
#include "movement/MoveSpline.h"
#include <cassert>
//-----------------------------------------------//
void WaypointMovementGenerator<Creature>::LoadPath(Creature& creature, int32 pathId, WaypointPathOrigin wpOrigin, uint32 overwriteEntry)
{
DETAIL_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "LoadPath: loading waypoint path for %s", creature.GetGuidStr().c_str());
if (!overwriteEntry)
{
overwriteEntry = creature.GetEntry();
}
if (wpOrigin == PATH_NO_PATH && pathId == 0)
{
i_path = sWaypointMgr.GetDefaultPath(overwriteEntry, creature.GetGUIDLow(), &m_PathOrigin);
}
else
{
m_PathOrigin = wpOrigin == PATH_NO_PATH ? PATH_FROM_ENTRY : wpOrigin;
i_path = sWaypointMgr.GetPathFromOrigin(overwriteEntry, creature.GetGUIDLow(), pathId, m_PathOrigin);
}
m_pathId = pathId;
// No movement found for entry nor guid
if (!i_path)
{
if (m_PathOrigin == PATH_FROM_EXTERNAL)
{
sLog.outErrorScriptLib("WaypointMovementGenerator::LoadPath: %s doesn't have waypoint path %i", creature.GetGuidStr().c_str(), pathId);
}
else
{
sLog.outErrorDb("WaypointMovementGenerator::LoadPath: %s doesn't have waypoint path %i", creature.GetGuidStr().c_str(), pathId);
}
return;
}
if (i_path->empty())
{
return;
}
// Initialize the i_currentNode to point to the first node
i_currentNode = i_path->begin()->first;
m_lastReachedWaypoint = 0;
}
void WaypointMovementGenerator<Creature>::Initialize(Creature& creature)
{
creature.addUnitState(UNIT_STAT_ROAMING);
creature.clearUnitState(UNIT_STAT_WAYPOINT_PAUSED);
}
void WaypointMovementGenerator<Creature>::InitializeWaypointPath(Creature& u, int32 id, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry)
{
LoadPath(u, id, wpSource, overwriteEntry);
i_nextMoveTime.Reset(initialDelay);
// Start moving if possible
StartMove(u);
}
void WaypointMovementGenerator<Creature>::Finalize(Creature& creature)
{
creature.clearUnitState(UNIT_STAT_ROAMING | UNIT_STAT_ROAMING_MOVE);
creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE), false);
}
void WaypointMovementGenerator<Creature>::Interrupt(Creature& creature)
{
creature.InterruptMoving();
creature.clearUnitState(UNIT_STAT_ROAMING | UNIT_STAT_ROAMING_MOVE);
creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE), false);
}
void WaypointMovementGenerator<Creature>::Reset(Creature& creature)
{
creature.addUnitState(UNIT_STAT_ROAMING);
StartMove(creature);
}
void WaypointMovementGenerator<Creature>::OnArrived(Creature& creature)
{
if (!i_path || i_path->empty())
{
return;
}
m_lastReachedWaypoint = i_currentNode;
if (m_isArrivalDone)
{
return;
}
creature.clearUnitState(UNIT_STAT_ROAMING_MOVE);
m_isArrivalDone = true;
WaypointPath::const_iterator currPoint = i_path->find(i_currentNode);
MANGOS_ASSERT(currPoint != i_path->end());
WaypointNode const& node = currPoint->second;
if (node.script_id)
{
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Creature movement start script %u at point %u for %s.", node.script_id, i_currentNode, creature.GetGuidStr().c_str());
creature.GetMap()->ScriptsStart(DBS_ON_CREATURE_MOVEMENT, node.script_id, &creature, &creature);
}
// We have reached the destination and can process behavior
if (WaypointBehavior* behavior = node.behavior)
{
if (behavior->emote != 0)
{
creature.HandleEmote(behavior->emote);
}
if (behavior->spell != 0)
{
creature.CastSpell(&creature, behavior->spell, false);
}
if (behavior->model1 != 0)
{
creature.SetDisplayId(behavior->model1);
}
if (behavior->textid[0])
{
int32 textId = behavior->textid[0];
// Not only one text is set
if (behavior->textid[1])
{
// Select one from max 5 texts (0 and 1 already checked)
int i = 2;
for (; i < MAX_WAYPOINT_TEXT; ++i)
{
if (!behavior->textid[i])
{
break;
}
}
textId = behavior->textid[urand(0, i - 1)];
}
if (MangosStringLocale const* textData = sObjectMgr.GetMangosStringLocale(textId))
{
creature.MonsterText(textData, NULL);
}
else
{
sLog.outErrorDb("%s reached waypoint %u, attempted to do text %i, but required text-data could not be found", creature.GetGuidStr().c_str(), i_currentNode, textId);
}
}
}
// Inform script
if (creature.AI())
{
uint32 type = WAYPOINT_MOTION_TYPE;
if (m_PathOrigin == PATH_FROM_EXTERNAL && m_pathId > 0)
{
type = EXTERNAL_WAYPOINT_MOVE + m_pathId;
}
creature.AI()->MovementInform(type, i_currentNode);
}
// Wait delay ms
Stop(node.delay);
}
void WaypointMovementGenerator<Creature>::StartMove(Creature& creature)
{
if (!i_path || i_path->empty())
{
return;
}
if (Stopped(creature))
{
return;
}
if (!creature.IsAlive() || creature.hasUnitState(UNIT_STAT_NOT_MOVE))
{
return;
}
WaypointPath::const_iterator currPoint = i_path->find(i_currentNode);
MANGOS_ASSERT(currPoint != i_path->end());
if (WaypointBehavior* behavior = currPoint->second.behavior)
{
if (behavior->model2 != 0)
{
creature.SetDisplayId(behavior->model2);
}
creature.SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
}
if (m_isArrivalDone)
{
bool reachedLast = false;
++currPoint;
if (currPoint == i_path->end())
{
reachedLast = true;
currPoint = i_path->begin();
}
// Inform AI
if (creature.AI() && m_PathOrigin == PATH_FROM_EXTERNAL && m_pathId > 0)
{
if (!reachedLast)
{
creature.AI()->MovementInform(EXTERNAL_WAYPOINT_MOVE_START + m_pathId, currPoint->first);
}
else
{
creature.AI()->MovementInform(EXTERNAL_WAYPOINT_FINISHED_LAST + m_pathId, currPoint->first);
}
if (creature.IsDead() || !creature.IsInWorld()) // Might have happened with above calls
{
return;
}
}
i_currentNode = currPoint->first;
}
m_isArrivalDone = false;
creature.addUnitState(UNIT_STAT_ROAMING_MOVE);
WaypointNode const& nextNode = currPoint->second;;
Movement::MoveSplineInit init(creature);
init.MoveTo(nextNode.x, nextNode.y, nextNode.z, true);
if (nextNode.orientation != 100 && nextNode.delay != 0)
{
init.SetFacing(nextNode.orientation);
}
creature.SetWalk(!creature.hasUnitState(UNIT_STAT_RUNNING_STATE) && !creature.IsLevitating(), false);
init.Launch();
}
bool WaypointMovementGenerator<Creature>::Update(Creature& creature, const uint32& diff)
{
// Waypoint movement can be switched on/off
// This is quite handy for escort quests and other stuff
if (creature.hasUnitState(UNIT_STAT_NOT_MOVE))
{
creature.clearUnitState(UNIT_STAT_ROAMING_MOVE);
return true;
}
// prevent a crash at empty waypoint path.
if (!i_path || i_path->empty())
{
creature.clearUnitState(UNIT_STAT_ROAMING_MOVE);
return true;
}
if (Stopped(creature))
{
if (CanMove(diff, creature))
{
StartMove(creature);
}
}
else
{
if (creature.IsStopped())
{
Stop(STOP_TIME_FOR_PLAYER);
}
else if (creature.movespline->Finalized())
{
OnArrived(creature);
StartMove(creature);
}
}
return true;
}
bool WaypointMovementGenerator<Creature>::Stopped(Creature& u)
{
return !i_nextMoveTime.Passed() || u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED);
}
bool WaypointMovementGenerator<Creature>::CanMove(int32 diff, Creature& u)
{
i_nextMoveTime.Update(diff);
if (i_nextMoveTime.Passed() && u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED))
{
i_nextMoveTime.Reset(1);
}
return i_nextMoveTime.Passed() && !u.hasUnitState(UNIT_STAT_WAYPOINT_PAUSED);
}
bool WaypointMovementGenerator<Creature>::GetResetPosition(Creature&, float& x, float& y, float& z, float& o) const
{
// prevent a crash at empty waypoint path.
if (!i_path || i_path->empty())
{
return false;
}
WaypointPath::const_iterator lastPoint = i_path->find(m_lastReachedWaypoint);
// Special case: Before the first waypoint is reached, m_lastReachedWaypoint is set to 0 (which may not be contained in i_path)
if (!m_lastReachedWaypoint && lastPoint == i_path->end())
{
return false;
}
MANGOS_ASSERT(lastPoint != i_path->end());
WaypointNode const* curWP = &(lastPoint->second);
x = curWP->x;
y = curWP->y;
z = curWP->z;
if (curWP->orientation != 100)
{
o = curWP->orientation;
}
else // Calculate the resulting angle based on positions between previous and current waypoint
{
WaypointNode const* prevWP;
if (lastPoint != i_path->begin()) // Not the first waypoint
{
--lastPoint;
prevWP = &(lastPoint->second);
}
else // Take the last waypoint (crbegin()) as previous
{
prevWP = &(i_path->rbegin()->second);
}
float dx = x - prevWP->x;
float dy = y - prevWP->y;
o = atan2(dy, dx); // returns value between -Pi..Pi
o = (o >= 0) ? o : 2 * M_PI_F + o;
}
return true;
}
void WaypointMovementGenerator<Creature>::GetPathInformation(std::ostringstream& oss) const
{
oss << "WaypointMovement: Last Reached WP: " << m_lastReachedWaypoint << " ";
oss << "(Loaded path " << m_pathId << " from " << WaypointManager::GetOriginString(m_PathOrigin) << ")\n";
}
void WaypointMovementGenerator<Creature>::AddToWaypointPauseTime(int32 waitTimeDiff)
{
if (!i_nextMoveTime.Passed())
{
// Prevent <= 0, the code in Update requires to catch the change from moving to not moving
int32 newWaitTime = i_nextMoveTime.GetExpiry() + waitTimeDiff;
i_nextMoveTime.Reset(newWaitTime > 0 ? newWaitTime : 1);
}
}
bool WaypointMovementGenerator<Creature>::SetNextWaypoint(uint32 pointId)
{
if (!i_path || i_path->empty())
{
return false;
}
WaypointPath::const_iterator currPoint = i_path->find(pointId);
if (currPoint == i_path->end())
{
return false;
}
// Allow Moving with next tick
// Handle allow movement this way to not interact with PAUSED state.
// If this function is called while PAUSED, it will move properly when unpaused.
i_nextMoveTime.Reset(1);
m_isArrivalDone = false;
// Set the point
i_currentNode = pointId;
return true;
}
//----------------------------------------------------//
uint32 FlightPathMovementGenerator::GetPathAtMapEnd() const
{
if (i_currentNode >= i_path->size())
{
return i_path->size();
}
uint32 curMapId = (*i_path)[i_currentNode].mapid;
for (uint32 i = i_currentNode; i < i_path->size(); ++i)
{
if ((*i_path)[i].mapid != curMapId)
{
return i;
}
}
return i_path->size();
}
void FlightPathMovementGenerator::Initialize(Player& player)
{
Reset(player);
}
void FlightPathMovementGenerator::Finalize(Player& player)
{
// remove flag to prevent send object build movement packets for flight state and crash (movement generator already not at top of stack)
player.clearUnitState(UNIT_STAT_TAXI_FLIGHT);
player.Unmount();
player.RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CLIENT_CONTROL_LOST | UNIT_FLAG_TAXI_FLIGHT);
if (player.m_taxi.empty())
{
player.GetHostileRefManager().setOnlineOfflineState(true);
if (player.pvpInfo.inHostileArea)
{
player.CastSpell(&player, 2479, true);
}
// update z position to ground and orientation for landing point
// this prevent cheating with landing point at lags
// when client side flight end early in comparison server side
player.StopMoving(true);
}
}
void FlightPathMovementGenerator::Interrupt(Player& player)
{
player.clearUnitState(UNIT_STAT_TAXI_FLIGHT);
}
#define PLAYER_FLIGHT_SPEED 32.0f
void FlightPathMovementGenerator::Reset(Player& player)
{
player.GetHostileRefManager().setOnlineOfflineState(false);
player.addUnitState(UNIT_STAT_TAXI_FLIGHT);
player.SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_CLIENT_CONTROL_LOST | UNIT_FLAG_TAXI_FLIGHT);
Movement::MoveSplineInit init(player);
uint32 end = GetPathAtMapEnd();
for (uint32 i = GetCurrentNode(); i != end; ++i)
{
G3D::Vector3 vertice((*i_path)[i].x, (*i_path)[i].y, (*i_path)[i].z);
init.Path().push_back(vertice);
}
init.SetFirstPointId(GetCurrentNode());
init.SetFly();
init.SetVelocity(PLAYER_FLIGHT_SPEED);
init.Launch();
}
bool FlightPathMovementGenerator::Update(Player& player, const uint32& /*diff*/)
{
uint32 pointId = (uint32)player.movespline->currentPathIdx();
if (pointId > i_currentNode)
{
bool departureEvent = true;
do
{
if (pointId == i_currentNode)
{
break;
}
i_currentNode += (uint32)departureEvent;
departureEvent = !departureEvent;
}
while (true);
}
return i_currentNode < (i_path->size() - 1);
}
void FlightPathMovementGenerator::SetCurrentNodeAfterTeleport()
{
if (i_path->empty())
{
return;
}
uint32 map0 = (*i_path)[0].mapid;
for (size_t i = 1; i < i_path->size(); ++i)
{
if ((*i_path)[i].mapid != map0)
{
i_currentNode = i;
return;
}
}
}
bool FlightPathMovementGenerator::GetResetPosition(Player&, float& x, float& y, float& z, float& o) const
{
const TaxiPathNodeEntry& node = (*i_path)[i_currentNode];
x = node.x;
y = node.y;
z = node.z;
return true;
}