2 * Copyright (C) 2005-2008 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 creature_movement Table
22 alter table creature_movement add `text1` varchar(255) default NULL;
23 alter table creature_movement add `text2` varchar(255) default NULL;
24 alter table creature_movement add `text3` varchar(255) default NULL;
25 alter table creature_movement add `text4` varchar(255) default NULL;
26 alter table creature_movement add `text5` varchar(255) default NULL;
27 alter table creature_movement add `emote` int(10) unsigned default '0';
28 alter table creature_movement add `spell` int(5) unsigned default '0';
29 alter table creature_movement add `wpguid` int(11) default '0';
35 #include "WaypointMovementGenerator.h"
36 #include "ObjectMgr.h"
38 #include "DestinationHolderImp.h"
39 #include "CreatureAI.h"
40 #include "WaypointManager.h"
44 //-----------------------------------------------//
46 WaypointMovementGenerator
<Creature
>::LoadPath(Creature
&c
)
48 sLog
.outDetail("LoadPath: loading waypoint path for creature %d,%d", c
.GetGUIDLow(), c
.GetDBTableGUIDLow());
50 i_path
= WaypointMgr
.GetPath(c
.GetDBTableGUIDLow());
53 sLog
.outErrorDb("WaypointMovementGenerator::LoadPath: creature %s(%d) doesn't have waypoint path", c
.GetName(), c
.GetDBTableGUIDLow());
57 uint32 node_count
= i_path
->size();
58 i_hasDone
.resize(node_count
);
59 for(uint32 i
= 0; i
< node_count
-1; i
++)
62 // to prevent a misbehaviour inside "update"
63 // update is always called with the next wp - but the wpSys needs the current
64 // so when the routine is called the first time, wpSys gets the last waypoint
65 // and this prevents the system from performing text/emote, etc
66 i_hasDone
[node_count
- 1] = true;
70 WaypointMovementGenerator
<Creature
>::ClearWaypoints()
76 WaypointMovementGenerator
<Creature
>::Initialize()
81 WaypointMovementGenerator
<Creature
>::Update(Creature
&creature
, const uint32
&diff
)
86 // Waypoint movement can be switched on/off
87 // This is quite handy for escort quests and other stuff
88 if(creature
.hasUnitState(UNIT_STAT_ROOT
| UNIT_STAT_STUNNED
| UNIT_STAT_DISTRACTED
))
91 // prevent a crash at empty waypoint path.
92 if(!i_path
|| i_path
->empty())
95 // i_path was modified by chat commands for example
96 if(i_path
->size() != i_hasDone
.size())
97 i_hasDone
.resize(i_path
->size());
98 if(i_currentNode
>= i_path
->size())
101 CreatureTraveller
traveller(creature
);
103 i_nextMoveTime
.Update(diff
);
104 i_destinationHolder
.UpdateTraveller(traveller
, diff
, false, true);
106 // creature has been stoped in middle of the waypoint segment
107 if (!i_destinationHolder
.HasArrived() && creature
.IsStopped())
109 if( i_nextMoveTime
.Passed()) // Timer has elapsed, meaning this part controlled it
111 SetStopedByPlayer(false);
112 // Now we re-set destination to same node and start travel
113 creature
.addUnitState(UNIT_STAT_ROAMING
);
114 if (creature
.canFly())
115 creature
.AddUnitMovementFlag(MOVEMENTFLAG_FLYING2
);
116 const WaypointNode
&node
= i_path
->at(i_currentNode
);
117 i_destinationHolder
.SetDestination(traveller
, node
.x
, node
.y
, node
.z
);
118 i_nextMoveTime
.Reset(i_destinationHolder
.GetTotalTravelTime());
120 else // if( !i_nextMoveTime.Passed())
121 { // unexpected end of timer && creature stopped && not at end of segment
122 if (!IsStopedByPlayer())
123 { // Put 30 seconds delay
124 i_destinationHolder
.IncreaseTravelTime(STOP_TIME_FOR_PLAYER
);
125 i_nextMoveTime
.Reset(STOP_TIME_FOR_PLAYER
);
126 SetStopedByPlayer(true); // Mark we did it
129 return true; // Abort here this update
132 if( creature
.IsStopped())
134 uint32 idx
= i_currentNode
> 0 ? i_currentNode
-1 : i_path
->size()-1;
138 if (i_path
->at(idx
).orientation
!=100)
139 creature
.SetOrientation(i_path
->at(idx
).orientation
);
141 if(WaypointBehavior
*behavior
= i_path
->at(idx
).behavior
)
143 if(behavior
->emote
!= 0)
144 creature
.SetUInt32Value(UNIT_NPC_EMOTESTATE
,behavior
->emote
);
145 if(behavior
->spell
!= 0)
146 creature
.CastSpell(&creature
,behavior
->spell
, false);
147 if(behavior
->model1
!= 0)
148 creature
.SetDisplayId(behavior
->model1
);
149 if(!behavior
->text
[0].empty())
151 // Only one text is set
152 if( !behavior
->text
[1].empty() )
154 // Select one from max 5 texts (0 and 1 laready checked)
157 if( behavior
->text
[i
].empty() )
160 creature
.Say(behavior
->text
[rand() % i
].c_str(), 0, 0);
163 creature
.Say(behavior
->text
[0].c_str(), 0, 0);
166 i_hasDone
[idx
] = true;
167 MovementInform(creature
);
168 } // wpBehaviour found
169 } // HasDone == false
170 } // i_creature.IsStopped()
172 if( i_nextMoveTime
.Passed() ) // This is at the end of waypoint segment or has been stopped by player
174 if( creature
.IsStopped() ) // If stopped then begin a new move segment
176 creature
.addUnitState(UNIT_STAT_ROAMING
);
177 if (creature
.canFly())
178 creature
.AddUnitMovementFlag(MOVEMENTFLAG_FLYING2
);
179 const WaypointNode
&node
= i_path
->at(i_currentNode
);
180 i_destinationHolder
.SetDestination(traveller
, node
.x
, node
.y
, node
.z
);
181 i_nextMoveTime
.Reset(i_destinationHolder
.GetTotalTravelTime());
182 uint32 idx
= i_currentNode
> 0 ? i_currentNode
-1 : i_path
->size()-1;
184 if (i_path
->at(idx
).orientation
!=100)
185 creature
.SetOrientation(i_path
->at(idx
).orientation
);
187 if(WaypointBehavior
*behavior
= i_path
->at(idx
).behavior
)
189 i_hasDone
[idx
] = false;
190 if(behavior
->model2
!= 0)
191 creature
.SetDisplayId(behavior
->model2
);
193 creature
.SetUInt32Value(UNIT_NPC_EMOTESTATE
, 0);
196 else // If not stopped then stop it and set the reset of TimeTracker to waittime
198 creature
.StopMoving();
199 SetStopedByPlayer(false);
200 i_nextMoveTime
.Reset(i_path
->at(i_currentNode
).delay
);
202 if( i_currentNode
>= i_path
->size() )
209 void WaypointMovementGenerator
<Creature
>::MovementInform(Creature
&unit
)
212 unit
.AI()->MovementInform(WAYPOINT_MOTION_TYPE
, i_currentNode
);
215 //----------------------------------------------------//
217 FlightPathMovementGenerator::LoadPath(Player
&)
219 objmgr
.GetTaxiPathNodes(i_pathId
, i_path
,i_mapIds
);
223 FlightPathMovementGenerator::GetPathAtMapEnd() const
225 if(i_currentNode
>= i_mapIds
.size())
226 return i_mapIds
.size();
228 uint32 curMapId
= i_mapIds
[i_currentNode
];
229 for(uint32 i
= i_currentNode
; i
< i_mapIds
.size(); ++i
)
231 if(i_mapIds
[i
] != curMapId
)
235 return i_mapIds
.size();
239 FlightPathMovementGenerator::Initialize(Player
&player
)
241 player
.getHostilRefManager().setOnlineOfflineState(false);
242 player
.addUnitState(UNIT_STAT_IN_FLIGHT
);
243 player
.SetFlag(UNIT_FIELD_FLAGS
,UNIT_FLAG_DISABLE_MOVE
| UNIT_FLAG_TAXI_FLIGHT
);
245 Traveller
<Player
> traveller(player
);
246 // do not send movement, it was sent already
247 i_destinationHolder
.SetDestination(traveller
, i_path
[i_currentNode
].x
, i_path
[i_currentNode
].y
, i_path
[i_currentNode
].z
, false);
249 player
.SendMonsterMoveByPath(GetPath(),GetCurrentNode(),GetPathAtMapEnd(),MOVEMENTFLAG_WALK_MODE
|MOVEMENTFLAG_ONTRANSPORT
);
252 void FlightPathMovementGenerator::Finalize(Player
& player
)
256 i_destinationHolder
.GetLocationNow(player
.GetMapId(), x
, y
, z
);
257 player
.SetPosition(x
, y
, z
, player
.GetOrientation());
259 player
.clearUnitState(UNIT_STAT_IN_FLIGHT
);
261 player
.RemoveFlag(UNIT_FIELD_FLAGS
,UNIT_FLAG_DISABLE_MOVE
| UNIT_FLAG_TAXI_FLIGHT
);
263 if(player
.m_taxi
.empty())
265 player
.getHostilRefManager().setOnlineOfflineState(true);
266 if(player
.pvpInfo
.inHostileArea
)
267 player
.CastSpell(&player
, 2479, true);
269 player
.SetUnitMovementFlags(MOVEMENTFLAG_WALK_MODE
);
275 FlightPathMovementGenerator::Update(Player
&player
, const uint32
&diff
)
277 if( MovementInProgress() )
279 Traveller
<Player
> traveller(player
);
280 if( i_destinationHolder
.UpdateTraveller(traveller
, diff
, false) )
282 i_destinationHolder
.ResetUpdate(FLIGHT_TRAVEL_UPDATE
);
283 if( i_destinationHolder
.HasArrived() )
285 uint32 curMap
= i_mapIds
[i_currentNode
];
287 if( MovementInProgress() )
289 DEBUG_LOG("loading node %u for player %s", i_currentNode
, player
.GetName());
290 if(i_mapIds
[i_currentNode
]==curMap
)
292 // do not send movement, it was sent already
293 i_destinationHolder
.SetDestination(traveller
, i_path
[i_currentNode
].x
, i_path
[i_currentNode
].y
, i_path
[i_currentNode
].z
, false);
306 // we have arrived at the end of the path
311 FlightPathMovementGenerator::SetCurrentNodeAfterTeleport()
316 uint32 map0
= i_mapIds
[0];
317 for(int i
= 1; i
< i_mapIds
.size(); ++i
)
319 if(i_mapIds
[i
]!=map0
)
328 // Unique1's ASTAR Pathfinding Code... For future use & reference...
331 #ifdef __PATHFINDING__
333 int GetFCost(int to
, int num
, int parentNum
, float *gcost
); // Below...
335 int ShortenASTARRoute(short int *pathlist
, int number
)
336 { // Wrote this to make the routes a little smarter (shorter)... No point looping back to the same places... Unique1
337 short int temppathlist
[MAX_PATHLIST_NODES
];
344 for (temp
= number
; temp
>= 0; temp
--)
346 qboolean shortened
= qfalse
;
348 for (temp2
= 0; temp2
< temp
; temp2
++)
350 for (link
= 0; link
< nodes
[pathlist
[temp
]].enodenum
; link
++)
352 if (nodes
[pathlist
[temp
]].links
[link
].flags
& PATH_BLOCKED
)
355 //if ((bot->client->ps.eFlags & EF_TANK) && nodes[bot->current_node].links[link].flags & PATH_NOTANKS) //if this path is blocked, skip it
358 //if (nodes[nodes[pathlist[temp]].links[link].targetNode].origin[2] > nodes[pathlist[temp]].origin[2] + 32)
361 if (nodes
[pathlist
[temp
]].links
[link
].targetNode
== pathlist
[temp2
])
362 { // Found a shorter route...
363 //if (OrgVisible(nodes[pathlist[temp2]].origin, nodes[pathlist[temp]].origin, -1))
365 temppathlist
[count
] = pathlist
[temp2
];
376 temppathlist
[count
] = pathlist
[temp
];
383 for (temp
= 0; temp
< count
; temp
++)
385 pathlist
[temp
] = temppathlist
[upto
];
389 G_Printf("ShortenASTARRoute: Path size reduced from %i to %i nodes...n", number
, count
);
394 ===========================================================================
396 This function uses the A* pathfinding algorithm to determine the
397 shortest path between any two nodes.
398 It's fairly complex, so I'm not really going to explain it much.
399 Look up A* and binary heaps for more info.
400 pathlist stores the ideal path between the nodes, in reverse order,
401 and the return value is the number of nodes in that path
402 ===========================================================================
404 int CreatePathAStar(gentity_t
*bot
, int from
, int to
, short int *pathlist
)
406 //all the data we have to hold...since we can't do dynamic allocation, has to be MAX_NODES
407 //we can probably lower this later - eg, the open list should never have more than at most a few dozen items on it
408 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
409 float gcost
[MAX_NODES
];
410 int fcost
[MAX_NODES
];
411 char list
[MAX_NODES
]; //0 is neither, 1 is open, 2 is closed - char because it's the smallest data type
412 short int parent
[MAX_NODES
];
414 short int numOpen
= 0;
415 short int atNode
, temp
, newnode
=-1;
416 qboolean found
= qfalse
;
422 //clear out all the arrays
423 memset(openlist
, 0, sizeof(short int)*(MAX_NODES
+1));
424 memset(fcost
, 0, sizeof(int)*MAX_NODES
);
425 memset(list
, 0, sizeof(char)*MAX_NODES
);
426 memset(parent
, 0, sizeof(short int)*MAX_NODES
);
427 memset(gcost
, -1, sizeof(float)*MAX_NODES
);
429 //make sure we have valid data before calculating everything
430 if ((from
== NODE_INVALID
) || (to
== NODE_INVALID
) || (from
>= MAX_NODES
) || (to
>= MAX_NODES
) || (from
== to
))
433 openlist
[1] = from
; //add the starting node to the open list
435 gcost
[from
] = 0; //its f and g costs are obviously 0
440 if (numOpen
!= 0) //if there are still items in the open list
442 //pop the top item off of the list
443 atNode
= openlist
[1];
444 list
[atNode
] = 2; //put the node on the closed list so we don't check it again
447 openlist
[1] = openlist
[numOpen
+1]; //move the last item in the list to the top position
450 //this while loop reorders the list so that the new lowest fcost is at the top again
454 if ((2*u
+1) < numOpen
) //if both children exist
456 if (fcost
[openlist
[u
]] >= fcost
[openlist
[2*u
]])
458 if (fcost
[openlist
[v
]] >= fcost
[openlist
[2*u
+1]])
463 if ((2*u
) < numOpen
) //if only one child exists
465 if (fcost
[openlist
[u
]] >= fcost
[openlist
[2*u
]])
470 if (u
!= v
) //if they're out of order, swap this item with its parent
473 openlist
[u
] = openlist
[v
];
480 for (i
= 0; i
< nodes
[atNode
].enodenum
; i
++) //loop through all the links for this node
482 newnode
= nodes
[atNode
].links
[i
].targetNode
;
484 //if this path is blocked, skip it
485 if (nodes
[atNode
].links
[i
].flags
& PATH_BLOCKED
)
487 //if this path is blocked, skip it
488 if (bot
->client
&& (bot
->client
->ps
.eFlags
& EF_TANK
) && nodes
[atNode
].links
[i
].flags
& PATH_NOTANKS
)
490 //skip any unreachable nodes
491 if (bot
->client
&& (nodes
[newnode
].type
& NODE_ALLY_UNREACHABLE
) && (bot
->client
->sess
.sessionTeam
== TEAM_ALLIES
))
493 if (bot
->client
&& (nodes
[newnode
].type
& NODE_AXIS_UNREACHABLE
) && (bot
->client
->sess
.sessionTeam
== TEAM_AXIS
))
496 if (list
[newnode
] == 2) //if this node is on the closed list, skip it
499 if (list
[newnode
] != 1) //if this node is not already on the open list
501 openlist
[++numOpen
] = newnode
; //add the new node to the open list
503 parent
[newnode
] = atNode
; //record the node's parent
505 if (newnode
== to
) //if we've found the goal, don't keep computing paths!
506 break; //this will break the 'for' and go all the way to 'if (list[to] == 1)'
508 //store it's f cost value
509 fcost
[newnode
] = GetFCost(to
, newnode
, parent
[newnode
], gcost
);
511 //this loop re-orders the heap so that the lowest fcost is at the top
513 while (m
!= 1) //while this item isn't at the top of the heap already
515 //if it has a lower fcost than its parent
516 if (fcost
[openlist
[m
]] <= fcost
[openlist
[m
/2]])
518 temp
= openlist
[m
/2];
519 openlist
[m
/2] = openlist
[m
];
520 openlist
[m
] = temp
; //swap them
527 else //if this node is already on the open list
530 VectorSubtract(nodes
[newnode
].origin
, nodes
[atNode
].origin
, vec
);
531 gc
+= VectorLength(vec
); //calculate what the gcost would be if we reached this node along the current path
533 if (gc
< gcost
[newnode
]) //if the new gcost is less (ie, this path is shorter than what we had before)
535 parent
[newnode
] = atNode
; //set the new parent for this node
536 gcost
[newnode
] = gc
; //and the new g cost
538 for (i
= 1; i
< numOpen
; i
++) //loop through all the items on the open list
540 if (openlist
[i
] == newnode
) //find this node in the list
542 //calculate the new fcost and store it
543 fcost
[newnode
] = GetFCost(to
, newnode
, parent
[newnode
], gcost
);
545 //reorder the list again, with the lowest fcost item on top
549 //if the item has a lower fcost than it's parent
550 if (fcost
[openlist
[m
]] < fcost
[openlist
[m
/2]])
552 temp
= openlist
[m
/2];
553 openlist
[m
/2] = openlist
[m
];
554 openlist
[m
] = temp
; //swap them
560 break; //exit the 'for' loop because we already changed this node
563 } //if (gc < gcost[newnode])
564 } //if (list[newnode] != 1) --> else
565 } //for (loop through links)
566 } //if (numOpen != 0)
569 found
= qfalse
; //there is no path between these nodes
573 if (list
[to
] == 1) //if the destination node is on the open list, we're done
580 if (found
== qtrue
) //if we found a path
582 //G_Printf("%s - path found!n", bot->client->pers.netname);
585 temp
= to
; //start at the end point
586 while (temp
!= from
) //travel along the path (backwards) until we reach the starting point
588 pathlist
[count
++] = temp
; //add the node to the pathlist and increment the count
589 temp
= parent
[temp
]; //move to the parent of this node to continue the path
592 pathlist
[count
++] = from
; //add the beginning node to the end of the pathlist
594 #ifdef __BOT_SHORTEN_ROUTING__
595 count
= ShortenASTARRoute(pathlist
, count
); // This isn't working... Dunno why.. Unique1
596 #endif //__BOT_SHORTEN_ROUTING__
600 //G_Printf("^1*** ^4BOT DEBUG^5: (CreatePathAStar) There is no route between node ^7%i^5 and node ^7%i^5.n", from, to);
601 count
= CreateDumbRoute(from
, to
, pathlist
);
605 #ifdef __BOT_SHORTEN_ROUTING__
606 count
= ShortenASTARRoute(pathlist
, count
); // This isn't working... Dunno why.. Unique1
607 #endif //__BOT_SHORTEN_ROUTING__
612 return count
; //return the number of nodes in the path, -1 if not found
616 ===========================================================================
618 Utility function used by A* pathfinding to calculate the
619 cost to move between nodes towards a goal. Using the A*
620 algorithm F = G + H, G here is the distance along the node
621 paths the bot must travel, and H is the straight-line distance
623 Returned as an int because more precision is unnecessary and it
624 will slightly speed up heap access
625 ===========================================================================
627 int GetFCost(int to
, int num
, int parentNum
, float *gcost
)
633 if (gcost
[num
] == -1)
637 gc
= gcost
[parentNum
];
638 VectorSubtract(nodes
[num
].origin
, nodes
[parentNum
].origin
, v
);
639 gc
+= VectorLength(v
);
646 VectorSubtract(nodes
[to
].origin
, nodes
[num
].origin
, v
);
647 hc
= VectorLength(v
);
649 return (int)(gc
+ hc
);
651 #endif //__PATHFINDING__