/* * Copyright (C) 2005-2010 MaNGOS * * 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 */ /* creature_movement Table alter table creature_movement add `textid1` int(11) NOT NULL default '0'; alter table creature_movement add `textid2` int(11) NOT NULL default '0'; alter table creature_movement add `textid3` int(11) NOT NULL default '0'; alter table creature_movement add `textid4` int(11) NOT NULL default '0'; alter table creature_movement add `textid5` int(11) NOT NULL default '0'; alter table creature_movement add `emote` int(10) unsigned default '0'; alter table creature_movement add `spell` int(5) unsigned default '0'; alter table creature_movement add `wpguid` int(11) default '0'; */ #include #include "WaypointMovementGenerator.h" #include "ObjectMgr.h" #include "Creature.h" #include "DestinationHolderImp.h" #include "CreatureAI.h" #include "WaypointManager.h" #include "WorldPacket.h" #include "ScriptCalls.h" #include //-----------------------------------------------// void WaypointMovementGenerator::LoadPath(Creature &creature) { DETAIL_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "LoadPath: loading waypoint path for creature %u, %u", creature.GetGUIDLow(), creature.GetDBTableGUIDLow()); i_path = sWaypointMgr.GetPath(creature.GetDBTableGUIDLow()); // We may LoadPath() for several occasions: // 1: When creature.MovementType=2 // 1a) Path is selected by creature.guid == creature_movement.id // 1b) Path for 1a) does not exist and then use path from creature.GetEntry() == creature_movement_template.entry // 2: When creature_template.MovementType=2 // 2a) Creature is summoned and has creature_template.MovementType=2 // Creators need to be sure that creature_movement_template is always valid for summons. // Mob that can be summoned anywhere should not have creature_movement_template for example. // No movement found for guid if (!i_path) { i_path = sWaypointMgr.GetPathTemplate(creature.GetEntry()); // No movement found for entry if (!i_path) { sLog.outErrorDb("WaypointMovementGenerator::LoadPath: creature %s (Entry: %u GUID: %u) doesn't have waypoint path", creature.GetName(), creature.GetEntry(), creature.GetDBTableGUIDLow()); return; } } // We have to set the destination here (for the first point), right after Initialize. Without, we may not have valid xyz for GetResetPosition CreatureTraveller traveller(creature); if (creature.CanFly()) creature.AddSplineFlag(SPLINEFLAG_UNKNOWN7); const WaypointNode &node = i_path->at(i_currentNode); i_destinationHolder.SetDestination(traveller, node.x, node.y, node.z); i_nextMoveTime.Reset(i_destinationHolder.GetTotalTravelTime()); } void WaypointMovementGenerator::Initialize(Creature &creature) { LoadPath(creature); creature.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE); } void WaypointMovementGenerator::Finalize(Creature &creature) { creature.clearUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE); } void WaypointMovementGenerator::Interrupt(Creature &creature) { creature.clearUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE); } void WaypointMovementGenerator::Reset(Creature &creature) { SetStoppedByPlayer(false); i_nextMoveTime.Reset(0); creature.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE); } bool WaypointMovementGenerator::Update(Creature &creature, const uint32 &diff) { if (!&creature) return true; // 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 (i_currentNode >= i_path->size()) { sLog.outError("WaypointMovement currentNode (%u) is equal or bigger than path size (creature entry %u)", i_currentNode, creature.GetEntry()); i_currentNode = 0; } CreatureTraveller traveller(creature); i_nextMoveTime.Update(diff); if (i_destinationHolder.UpdateTraveller(traveller, diff, false, true)) { if (!IsActive(creature)) // force stop processing (movement can move out active zone with cleanup movegens list) return true; // not expire now, but already lost } // creature has been stopped in middle of the waypoint segment if (!i_destinationHolder.HasArrived() && creature.IsStopped()) { // Timer has elapsed, meaning this part controlled it if (i_nextMoveTime.Passed()) { SetStoppedByPlayer(false); creature.addUnitState(UNIT_STAT_ROAMING_MOVE); if (creature.CanFly()) creature.AddSplineFlag(SPLINEFLAG_UNKNOWN7); // Now we re-set destination to same node and start travel const WaypointNode &node = i_path->at(i_currentNode); i_destinationHolder.SetDestination(traveller, node.x, node.y, node.z); i_nextMoveTime.Reset(i_destinationHolder.GetTotalTravelTime()); } else // if( !i_nextMoveTime.Passed()) { // unexpected end of timer && creature stopped && not at end of segment if (!IsStoppedByPlayer()) { // Put 30 seconds delay i_destinationHolder.IncreaseTravelTime(STOP_TIME_FOR_PLAYER); i_nextMoveTime.Reset(STOP_TIME_FOR_PLAYER); SetStoppedByPlayer(true); // Mark we did it } } return true; // Abort here this update } if (creature.IsStopped()) { if (!m_isArrivalDone) { if (i_path->at(i_currentNode).orientation != 100) creature.SetOrientation(i_path->at(i_currentNode).orientation); if (i_path->at(i_currentNode).script_id) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Creature movement start script %u at point %u for creature %u (entry %u).", i_path->at(i_currentNode).script_id, i_currentNode, creature.GetDBTableGUIDLow(), creature.GetEntry()); creature.GetMap()->ScriptsStart(sCreatureMovementScripts, i_path->at(i_currentNode).script_id, &creature, &creature); } // We have reached the destination and can process behavior if (WaypointBehavior *behavior = i_path->at(i_currentNode).behavior) { if (behavior->emote != 0) creature.HandleEmote(behavior->emote); if (behavior->spell != 0) { creature.CastSpell(&creature, behavior->spell, false); if (!IsActive(creature)) // force stop processing (cast can change movegens list) return true; // not expire now, but already lost } if (behavior->model1 != 0) creature.SetDisplayId(behavior->model1); if (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; } creature.Say(behavior->textid[rand() % i], 0, 0); } else creature.Say(behavior->textid[0], 0, 0); } } // wpBehaviour found // Can only do this once for the node m_isArrivalDone = true; // Inform script MovementInform(creature); if (!IsActive(creature)) // force stop processing (movement can move out active zone with cleanup movegens list) return true; // not expire now, but already lost // prevent a crash at empty waypoint path. if (!i_path || i_path->empty() || i_currentNode >= i_path->size()) { creature.clearUnitState(UNIT_STAT_ROAMING_MOVE); return true; } } } // i_creature.IsStopped() // This is at the end of waypoint segment (incl. was previously stopped by player, extending the time) if (i_nextMoveTime.Passed()) { // If stopped then begin a new move segment if (creature.IsStopped()) { creature.addUnitState(UNIT_STAT_ROAMING_MOVE); if (creature.CanFly()) creature.AddSplineFlag(SPLINEFLAG_UNKNOWN7); if (WaypointBehavior *behavior = i_path->at(i_currentNode).behavior) { if (behavior->model2 != 0) creature.SetDisplayId(behavior->model2); creature.SetUInt32Value(UNIT_NPC_EMOTESTATE, 0); } // behavior for "departure" of the current node is done m_isArrivalDone = false; // Proceed with increment current node and then send to the next destination ++i_currentNode; // Oops, end of the line so need to start from the beginning if (i_currentNode >= i_path->size()) i_currentNode = 0; if (i_path->at(i_currentNode).orientation != 100) creature.SetOrientation(i_path->at(i_currentNode).orientation); const WaypointNode &node = i_path->at(i_currentNode); i_destinationHolder.SetDestination(traveller, node.x, node.y, node.z); i_nextMoveTime.Reset(i_destinationHolder.GetTotalTravelTime()); } else { // If not stopped then stop it creature.clearUnitState(UNIT_STAT_ROAMING_MOVE); SetStoppedByPlayer(false); // Set TimeTracker to waittime for the current node i_nextMoveTime.Reset(i_path->at(i_currentNode).delay); } } return true; } void WaypointMovementGenerator::MovementInform(Creature &creature) { if (creature.AI()) creature.AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); } bool WaypointMovementGenerator::GetResetPosition(Creature&, float& x, float& y, float& z) { return PathMovementBase::GetPosition(x,y,z); } //----------------------------------------------------// 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); float x, y, z; i_destinationHolder.GetLocationNow(player.GetBaseMap(), x, y, z); player.SetPosition(x, y, z, player.GetOrientation()); player.Unmount(); player.RemoveFlag(UNIT_FIELD_FLAGS,UNIT_FLAG_DISABLE_MOVE | 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(); } } void FlightPathMovementGenerator::Interrupt(Player & player) { player.clearUnitState(UNIT_STAT_TAXI_FLIGHT); } void FlightPathMovementGenerator::Reset(Player & player) { player.getHostileRefManager().setOnlineOfflineState(false); player.addUnitState(UNIT_STAT_TAXI_FLIGHT); player.SetFlag(UNIT_FIELD_FLAGS,UNIT_FLAG_DISABLE_MOVE | UNIT_FLAG_TAXI_FLIGHT); Traveller traveller(player); // do not send movement, it was sent already i_destinationHolder.SetDestination(traveller, (*i_path)[i_currentNode].x, (*i_path)[i_currentNode].y, (*i_path)[i_currentNode].z, false); player.SendMonsterMoveByPath(GetPath(),GetCurrentNode(),GetPathAtMapEnd(), SplineFlags(SPLINEFLAG_WALKMODE|SPLINEFLAG_FLYING)); } bool FlightPathMovementGenerator::Update(Player &player, const uint32 &diff) { if (MovementInProgress()) { Traveller traveller(player); if( i_destinationHolder.UpdateTraveller(traveller, diff, false) ) { if (!IsActive(player)) // force stop processing (movement can move out active zone with cleanup movegens list) return true; // not expire now, but already lost i_destinationHolder.ResetUpdate(FLIGHT_TRAVEL_UPDATE); if (i_destinationHolder.HasArrived()) { DoEventIfAny(player,(*i_path)[i_currentNode],false); uint32 curMap = (*i_path)[i_currentNode].mapid; ++i_currentNode; if (MovementInProgress()) { DoEventIfAny(player,(*i_path)[i_currentNode],true); DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "loading node %u for player %s", i_currentNode, player.GetName()); if ((*i_path)[i_currentNode].mapid == curMap) { // do not send movement, it was sent already i_destinationHolder.SetDestination(traveller, (*i_path)[i_currentNode].x, (*i_path)[i_currentNode].y, (*i_path)[i_currentNode].z, false); } return true; } } else return true; } else return true; } // we have arrived at the end of the path return false; } 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; } } } void FlightPathMovementGenerator::DoEventIfAny(Player& player, TaxiPathNodeEntry const& node, bool departure) { if (uint32 eventid = departure ? node.departureEventID : node.arrivalEventID) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Taxi %s event %u of node %u of path %u for player %s", departure ? "departure" : "arrival", eventid, node.index, node.path, player.GetName()); if (!Script->ProcessEventId(eventid, &player, &player, departure)) player.GetMap()->ScriptsStart(sEventScripts, eventid, &player, &player); } } // // Unique1's ASTAR Pathfinding Code... For future use & reference... // #ifdef __PATHFINDING__ int GetFCost(int to, int num, int parentNum, float *gcost); // Below... int ShortenASTARRoute(short int *pathlist, int number) { // Wrote this to make the routes a little smarter (shorter)... No point looping back to the same places... Unique1 short int temppathlist[MAX_PATHLIST_NODES]; int count = 0; // int count2 = 0; int temp, temp2; int link; int upto = 0; for (temp = number; temp >= 0; temp--) { qboolean shortened = qfalse; for (temp2 = 0; temp2 < temp; temp2++) { for (link = 0; link < nodes[pathlist[temp]].enodenum; link++) { if (nodes[pathlist[temp]].links[link].flags & PATH_BLOCKED) continue; //if ((bot->client->ps.eFlags & EF_TANK) && nodes[bot->current_node].links[link].flags & PATH_NOTANKS) //if this path is blocked, skip it // continue; //if (nodes[nodes[pathlist[temp]].links[link].targetNode].origin[2] > nodes[pathlist[temp]].origin[2] + 32) // continue; if (nodes[pathlist[temp]].links[link].targetNode == pathlist[temp2]) { // Found a shorter route... //if (OrgVisible(nodes[pathlist[temp2]].origin, nodes[pathlist[temp]].origin, -1)) { temppathlist[count] = pathlist[temp2]; temp = temp2; ++count; shortened = qtrue; } } } } if (!shortened) { temppathlist[count] = pathlist[temp]; ++count; } } upto = count; for (temp = 0; temp < count; temp++) { pathlist[temp] = temppathlist[upto]; --upto; } G_Printf("ShortenASTARRoute: Path size reduced from %i to %i nodes...n", number, count); return count; } /* =========================================================================== CreatePathAStar This function uses the A* pathfinding algorithm to determine the shortest path between any two nodes. It's fairly complex, so I'm not really going to explain it much. Look up A* and binary heaps for more info. pathlist stores the ideal path between the nodes, in reverse order, and the return value is the number of nodes in that path =========================================================================== */ int CreatePathAStar(gentity_t *bot, int from, int to, short int *pathlist) { //all the data we have to hold...since we can't do dynamic allocation, has to be MAX_NODES //we can probably lower this later - eg, the open list should never have more than at most a few dozen items on it short int openlist[MAX_NODES+1]; //add 1 because it's a binary heap, and they don't use 0 - 1 is the first used index float gcost[MAX_NODES]; int fcost[MAX_NODES]; char list[MAX_NODES]; //0 is neither, 1 is open, 2 is closed - char because it's the smallest data type short int parent[MAX_NODES]; short int numOpen = 0; short int atNode, temp, newnode=-1; qboolean found = qfalse; int count = -1; float gc; int i, u, v, m; vec3_t vec; //clear out all the arrays memset(openlist, 0, sizeof(short int)*(MAX_NODES+1)); memset(fcost, 0, sizeof(int)*MAX_NODES); memset(list, 0, sizeof(char)*MAX_NODES); memset(parent, 0, sizeof(short int)*MAX_NODES); memset(gcost, -1, sizeof(float)*MAX_NODES); //make sure we have valid data before calculating everything if ((from == NODE_INVALID) || (to == NODE_INVALID) || (from >= MAX_NODES) || (to >= MAX_NODES) || (from == to)) return -1; openlist[1] = from; //add the starting node to the open list ++numOpen; gcost[from] = 0; //its f and g costs are obviously 0 fcost[from] = 0; while (1) { if (numOpen != 0) //if there are still items in the open list { //pop the top item off of the list atNode = openlist[1]; list[atNode] = 2; //put the node on the closed list so we don't check it again --numOpen; openlist[1] = openlist[numOpen+1]; //move the last item in the list to the top position v = 1; //this while loop reorders the list so that the new lowest fcost is at the top again while (1) { u = v; if ((2*u+1) < numOpen) //if both children exist { if (fcost[openlist[u]] >= fcost[openlist[2*u]]) v = 2*u; if (fcost[openlist[v]] >= fcost[openlist[2*u+1]]) v = 2*u+1; } else { if ((2*u) < numOpen) //if only one child exists { if (fcost[openlist[u]] >= fcost[openlist[2*u]]) v = 2*u; } } if (u != v) //if they're out of order, swap this item with its parent { temp = openlist[u]; openlist[u] = openlist[v]; openlist[v] = temp; } else break; } for (i = 0; i < nodes[atNode].enodenum; ++i) //loop through all the links for this node { newnode = nodes[atNode].links[i].targetNode; //if this path is blocked, skip it if (nodes[atNode].links[i].flags & PATH_BLOCKED) continue; //if this path is blocked, skip it if (bot->client && (bot->client->ps.eFlags & EF_TANK) && nodes[atNode].links[i].flags & PATH_NOTANKS) continue; //skip any unreachable nodes if (bot->client && (nodes[newnode].type & NODE_ALLY_UNREACHABLE) && (bot->client->sess.sessionTeam == TEAM_ALLIES)) continue; if (bot->client && (nodes[newnode].type & NODE_AXIS_UNREACHABLE) && (bot->client->sess.sessionTeam == TEAM_AXIS)) continue; if (list[newnode] == 2) //if this node is on the closed list, skip it continue; if (list[newnode] != 1) //if this node is not already on the open list { openlist[++numOpen] = newnode; //add the new node to the open list list[newnode] = 1; parent[newnode] = atNode; //record the node's parent if (newnode == to) //if we've found the goal, don't keep computing paths! break; //this will break the 'for' and go all the way to 'if (list[to] == 1)' //store it's f cost value fcost[newnode] = GetFCost(to, newnode, parent[newnode], gcost); //this loop re-orders the heap so that the lowest fcost is at the top m = numOpen; while (m != 1) //while this item isn't at the top of the heap already { //if it has a lower fcost than its parent if (fcost[openlist[m]] <= fcost[openlist[m/2]]) { temp = openlist[m/2]; openlist[m/2] = openlist[m]; openlist[m] = temp; //swap them m /= 2; } else break; } } else //if this node is already on the open list { gc = gcost[atNode]; VectorSubtract(nodes[newnode].origin, nodes[atNode].origin, vec); gc += VectorLength(vec); //calculate what the gcost would be if we reached this node along the current path if (gc < gcost[newnode]) //if the new gcost is less (ie, this path is shorter than what we had before) { parent[newnode] = atNode; //set the new parent for this node gcost[newnode] = gc; //and the new g cost for (i = 1; i < numOpen; ++i) //loop through all the items on the open list { if (openlist[i] == newnode) //find this node in the list { //calculate the new fcost and store it fcost[newnode] = GetFCost(to, newnode, parent[newnode], gcost); //reorder the list again, with the lowest fcost item on top m = i; while (m != 1) { //if the item has a lower fcost than it's parent if (fcost[openlist[m]] < fcost[openlist[m/2]]) { temp = openlist[m/2]; openlist[m/2] = openlist[m]; openlist[m] = temp; //swap them m /= 2; } else break; } break; //exit the 'for' loop because we already changed this node } //if } //for } //if (gc < gcost[newnode]) } //if (list[newnode] != 1) --> else } //for (loop through links) } //if (numOpen != 0) else { found = qfalse; //there is no path between these nodes break; } if (list[to] == 1) //if the destination node is on the open list, we're done { found = qtrue; break; } } //while (1) if (found == qtrue) //if we found a path { //G_Printf("%s - path found!n", bot->client->pers.netname); count = 0; temp = to; //start at the end point while (temp != from) //travel along the path (backwards) until we reach the starting point { pathlist[count++] = temp; //add the node to the pathlist and increment the count temp = parent[temp]; //move to the parent of this node to continue the path } pathlist[count++] = from; //add the beginning node to the end of the pathlist #ifdef __BOT_SHORTEN_ROUTING__ count = ShortenASTARRoute(pathlist, count); // This isn't working... Dunno why.. Unique1 #endif //__BOT_SHORTEN_ROUTING__ } else { //G_Printf("^1*** ^4BOT DEBUG^5: (CreatePathAStar) There is no route between node ^7%i^5 and node ^7%i^5.n", from, to); count = CreateDumbRoute(from, to, pathlist); if (count > 0) { #ifdef __BOT_SHORTEN_ROUTING__ count = ShortenASTARRoute(pathlist, count); // This isn't working... Dunno why.. Unique1 #endif //__BOT_SHORTEN_ROUTING__ return count; } } return count; //return the number of nodes in the path, -1 if not found } /* =========================================================================== GetFCost Utility function used by A* pathfinding to calculate the cost to move between nodes towards a goal. Using the A* algorithm F = G + H, G here is the distance along the node paths the bot must travel, and H is the straight-line distance to the goal node. Returned as an int because more precision is unnecessary and it will slightly speed up heap access =========================================================================== */ int GetFCost(int to, int num, int parentNum, float *gcost) { float gc = 0; float hc = 0; vec3_t v; if (gcost[num] == -1) { if (parentNum != -1) { gc = gcost[parentNum]; VectorSubtract(nodes[num].origin, nodes[parentNum].origin, v); gc += VectorLength(v); } gcost[num] = gc; } else gc = gcost[num]; VectorSubtract(nodes[to].origin, nodes[num].origin, v); hc = VectorLength(v); return (int)(gc + hc); } #endif //__PATHFINDING__