mirror of
https://github.com/mangosfour/server.git
synced 2025-12-15 10:37:02 +00:00
Also other classes have been affected, due to the use of search&replace. This will probably break some patches and 3rd party libraries, so make sure to update them if required. Thanks to Phille for the original idea and patch!
767 lines
30 KiB
C++
767 lines
30 KiB
C++
/*
|
|
* Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* 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 <ctime>
|
|
|
|
#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 <cassert>
|
|
|
|
//-----------------------------------------------//
|
|
void WaypointMovementGenerator<Creature>::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<Creature>::Initialize(Creature &creature)
|
|
{
|
|
LoadPath(creature);
|
|
creature.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Finalize(Creature &creature)
|
|
{
|
|
creature.clearUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Interrupt(Creature &creature)
|
|
{
|
|
creature.clearUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Reset(Creature &creature)
|
|
{
|
|
SetStoppedByPlayer(false);
|
|
i_nextMoveTime.Reset(0);
|
|
creature.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
bool WaypointMovementGenerator<Creature>::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<Creature>::MovementInform(Creature &creature)
|
|
{
|
|
if (creature.AI())
|
|
creature.AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode);
|
|
}
|
|
|
|
bool WaypointMovementGenerator<Creature>::GetResetPosition(Creature&, float& x, float& y, float& z)
|
|
{
|
|
return PathMovementBase<Creature, WaypointPath const*>::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<Player> 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<Player> 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__
|