mirror of
https://github.com/mangosfour/server.git
synced 2025-12-14 16:37:01 +00:00
New field script_id in creature_movement can be filled for each waypoint point and run the corresponding script from creature_movement_scripts. Script_id can be any value and several points/creatures can share the same script (with limits of course, such as script depending on location and other factors). DB projects are advised to move current text, spells and emotes data in creature_movement fields as these fields are strictly no longer needed and may be fully removed in the future. Signed-off-by: NoFantasy <nofantasy@nf.no>
743 lines
29 KiB
C++
743 lines
29 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 <cassert>
|
|
|
|
//-----------------------------------------------//
|
|
void WaypointMovementGenerator<Creature>::LoadPath(Creature &c)
|
|
{
|
|
DETAIL_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "LoadPath: loading waypoint path for creature %u, %u", c.GetGUIDLow(), c.GetDBTableGUIDLow());
|
|
|
|
i_path = sWaypointMgr.GetPath(c.GetDBTableGUIDLow());
|
|
|
|
if (!i_path)
|
|
{
|
|
sLog.outErrorDb("WaypointMovementGenerator::LoadPath: creature %s (Entry: %u GUID: %u) doesn't have waypoint path",
|
|
c.GetName(), c.GetEntry(), c.GetDBTableGUIDLow());
|
|
return;
|
|
}
|
|
|
|
uint32 node_count = i_path->size();
|
|
i_hasDone.resize(node_count);
|
|
|
|
for(uint32 i = 0; i < node_count-1; ++i)
|
|
i_hasDone[i] = false;
|
|
|
|
// to prevent a misbehavior inside "update"
|
|
// update is always called with the next wp - but the wpSys needs the current
|
|
// so when the routine is called the first time, wpSys gets the last waypoint
|
|
// and this prevents the system from performing text/emote, etc
|
|
i_hasDone[node_count - 1] = true;
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Initialize( Creature &u )
|
|
{
|
|
i_nextMoveTime.Reset(0); // TODO: check the lower bound (0 is probably too small)
|
|
u.StopMoving();
|
|
LoadPath(u);
|
|
u.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Finalize( Creature &u )
|
|
{
|
|
u.clearUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Interrupt( Creature &u )
|
|
{
|
|
u.addUnitState(UNIT_STAT_ROAMING|UNIT_STAT_ROAMING_MOVE);
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::Reset( Creature &u )
|
|
{
|
|
b_StoppedByPlayer = false;
|
|
i_nextMoveTime.Reset(0);
|
|
u.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;
|
|
}
|
|
|
|
// i_path was modified by chat commands for example
|
|
if (i_path->size() != i_hasDone.size())
|
|
i_hasDone.resize(i_path->size());
|
|
|
|
if (i_currentNode >= i_path->size())
|
|
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())
|
|
{
|
|
uint32 idx = i_currentNode > 0 ? i_currentNode-1 : i_path->size()-1;
|
|
|
|
if (!i_hasDone[idx])
|
|
{
|
|
if (i_path->at(idx).orientation != 100)
|
|
creature.SetOrientation(i_path->at(idx).orientation);
|
|
|
|
if (i_path->at(idx).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(idx).script_id, idx, creature.GetDBTableGUIDLow(), creature.GetEntry());
|
|
creature.GetMap()->ScriptsStart(sCreatureMovementScripts, i_path->at(idx).script_id, &creature, &creature);
|
|
}
|
|
|
|
if (WaypointBehavior *behavior = i_path->at(idx).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
|
|
|
|
i_hasDone[idx] = true;
|
|
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;
|
|
}
|
|
} // HasDone == false
|
|
} // i_creature.IsStopped()
|
|
|
|
// This is at the end of waypoint segment or has been stopped by player
|
|
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);
|
|
|
|
const WaypointNode &node = i_path->at(i_currentNode);
|
|
i_destinationHolder.SetDestination(traveller, node.x, node.y, node.z);
|
|
i_nextMoveTime.Reset(i_destinationHolder.GetTotalTravelTime());
|
|
|
|
uint32 idx = i_currentNode > 0 ? i_currentNode-1 : i_path->size()-1;
|
|
|
|
if (i_path->at(idx).orientation != 100)
|
|
creature.SetOrientation(i_path->at(idx).orientation);
|
|
|
|
if (WaypointBehavior *behavior = i_path->at(idx).behavior)
|
|
{
|
|
i_hasDone[idx] = false;
|
|
|
|
if (behavior->model2 != 0)
|
|
creature.SetDisplayId(behavior->model2);
|
|
|
|
creature.SetUInt32Value(UNIT_NPC_EMOTESTATE, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If not stopped then stop it and set the reset of TimeTracker to waittime
|
|
creature.StopMoving();
|
|
SetStoppedByPlayer(false);
|
|
|
|
i_nextMoveTime.Reset(i_path->at(i_currentNode).delay);
|
|
++i_currentNode;
|
|
|
|
if (i_currentNode >= i_path->size())
|
|
i_currentNode = 0;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WaypointMovementGenerator<Creature>::MovementInform(Creature &unit)
|
|
{
|
|
if (unit.AI())
|
|
unit.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_IN_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_IN_FLIGHT);
|
|
}
|
|
|
|
void FlightPathMovementGenerator::Reset(Player & player)
|
|
{
|
|
player.getHostileRefManager().setOnlineOfflineState(false);
|
|
player.addUnitState(UNIT_STAT_IN_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());
|
|
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__
|