2022-12-17 13:32:43 +01:00
|
|
|
/***
|
2013-08-30 13:34:05 -07:00
|
|
|
*
|
|
|
|
* Copyright (c) 1996-2001, Valve LLC. All rights reserved.
|
|
|
|
*
|
|
|
|
* This product contains software technology licensed from Id
|
|
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
|
|
|
* This source code contains proprietary and confidential information of
|
|
|
|
* Valve LLC and its suppliers. Access to this code is restricted to
|
|
|
|
* persons who have executed a written SDK license with Valve. Any access,
|
|
|
|
* use or distribution of this code by or to any unlicensed person is illegal.
|
|
|
|
*
|
|
|
|
****/
|
|
|
|
//=========================================================
|
|
|
|
// nodes.cpp - AI node tree stuff.
|
|
|
|
//=========================================================
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
#include <cassert>
|
|
|
|
#include <limits>
|
|
|
|
#include <string>
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
#include "extdll.h"
|
|
|
|
#include "util.h"
|
|
|
|
#include "cbase.h"
|
|
|
|
#include "monsters.h"
|
|
|
|
#include "nodes.h"
|
|
|
|
#include "animation.h"
|
|
|
|
#include "doors.h"
|
2021-11-28 19:34:20 +01:00
|
|
|
#include "filesystem_utils.h"
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
#define HULL_STEP_SIZE 16 // how far the test hull moves on each step
|
|
|
|
#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier)
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// to help eliminate node clutter by level designers, this is used to cap how many other nodes
|
|
|
|
// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()".
|
2021-11-28 16:54:48 +01:00
|
|
|
#define MAX_NODE_INITIAL_LINKS 128
|
|
|
|
#define MAX_NODES 1024
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Vector VecBModelOrigin(entvars_t* pevBModel);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
CGraph WorldGraph;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(info_node, CNodeEnt);
|
|
|
|
LINK_ENTITY_TO_CLASS(info_node_air, CNodeEnt);
|
2021-11-28 19:34:20 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
|
|
|
// CGraph - InitGraph - prepares the graph for use. Frees any
|
2021-11-28 16:54:48 +01:00
|
|
|
// memory currently in use by the world graph, NULLs
|
2013-08-30 13:34:05 -07:00
|
|
|
// all pointers, and zeros the node count.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CGraph::InitGraph()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
|
|
|
// Make the graph unavailable
|
|
|
|
//
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fGraphPresent = 0;
|
|
|
|
m_fGraphPointersSet = 0;
|
|
|
|
m_fRoutingComplete = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Free the link pool
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_pLinkPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(m_pLinkPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_pLinkPool = NULL;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// Free the node info
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_pNodes)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(m_pNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_pNodes = NULL;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_di)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(m_di);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_di = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Free the routing info.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_pRouteInfo)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(m_pRouteInfo);
|
2013-08-30 13:34:05 -07:00
|
|
|
m_pRouteInfo = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_pHashLinks)
|
|
|
|
{
|
|
|
|
free(m_pHashLinks);
|
|
|
|
m_pHashLinks = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Zero node and link counts
|
|
|
|
//
|
|
|
|
m_cNodes = 0;
|
|
|
|
m_cLinks = 0;
|
|
|
|
m_nRouteInfo = 0;
|
|
|
|
|
|
|
|
m_iLastActiveIdleSearch = 0;
|
|
|
|
m_iLastCoverSearch = 0;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
|
|
|
// CGraph - AllocNodes - temporary function that mallocs a
|
|
|
|
// reasonable number of nodes so we can build the path which
|
|
|
|
// will be saved to disk.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::AllocNodes()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// malloc all of the nodes
|
|
|
|
m_pNodes = (CNode*)calloc(sizeof(CNode), MAX_NODES);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// could not malloc space for all the nodes!
|
|
|
|
if (!m_pNodes)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes);
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - LinkEntForLink - sometimes the ent that blocks
|
|
|
|
// a path is a usable door, in which case the monster just
|
|
|
|
// needs to face the door and fire it. In other cases, the
|
2021-11-28 16:54:48 +01:00
|
|
|
// monster needs to operate a button or lever to get the
|
2013-08-30 13:34:05 -07:00
|
|
|
// door to open. This function will return a pointer to the
|
2021-11-28 16:54:48 +01:00
|
|
|
// button if the monster needs to hit a button to open the
|
|
|
|
// door, or returns a pointer to the door if the monster
|
2013-08-30 13:34:05 -07:00
|
|
|
// need only use the door.
|
|
|
|
//
|
|
|
|
// pNode is the node the monster will be standing on when it
|
|
|
|
// will need to stop and trigger the ent.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
entvars_t* CGraph::LinkEntForLink(CLink* pLink, CNode* pNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
edict_t* pentSearch;
|
|
|
|
edict_t* pentTrigger;
|
|
|
|
entvars_t* pevTrigger;
|
|
|
|
entvars_t* pevLinkEnt;
|
|
|
|
TraceResult tr;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
pevLinkEnt = pLink->m_pLinkEnt;
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!pevLinkEnt)
|
2013-08-30 13:34:05 -07:00
|
|
|
return NULL;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pentSearch = NULL; // start search at the top of the ent list.
|
|
|
|
|
|
|
|
if (FClassnameIs(pevLinkEnt, "func_door") || FClassnameIs(pevLinkEnt, "func_door_rotating"))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is
|
2013-08-30 13:34:05 -07:00
|
|
|
// TOGGLE or STAY OPEN, even monsters that can't open doors can go that way.
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY) != 0)
|
|
|
|
{ // door is use only, so the door is all the monster has to worry about
|
2013-08-30 13:34:05 -07:00
|
|
|
return pevLinkEnt;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
while (true)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pentTrigger = FIND_ENTITY_BY_TARGET(pentSearch, STRING(pevLinkEnt->targetname)); // find the button or trigger
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (FNullEnt(pentTrigger))
|
|
|
|
{ // no trigger found
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// right now this is a problem among auto-open doors, or any door that opens through the use
|
2013-08-30 13:34:05 -07:00
|
|
|
// of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow
|
2021-11-28 16:54:48 +01:00
|
|
|
// monsters to open these sorts of doors for now.
|
2013-08-30 13:34:05 -07:00
|
|
|
return pevLinkEnt;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
pentSearch = pentTrigger;
|
2021-11-28 16:54:48 +01:00
|
|
|
pevTrigger = VARS(pentTrigger);
|
|
|
|
|
|
|
|
if (FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button"))
|
|
|
|
{ // only buttons are handled right now.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// trace from the node to the trigger, make sure it's one we can see from the node.
|
|
|
|
// !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore!
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_TraceLine(pNode->m_vecOrigin, VecBModelOrigin(pevTrigger), ignore_monsters, g_pBodyQueueHead, &tr);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (VARS(tr.pHit) == pevTrigger)
|
|
|
|
{ // good to go!
|
|
|
|
return VARS(tr.pHit);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING(pevLinkEnt->classname));
|
2013-08-30 13:34:05 -07:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - HandleLinkEnt - a brush ent is between two
|
2021-11-28 16:54:48 +01:00
|
|
|
// nodes that would otherwise be able to see each other.
|
2013-08-30 13:34:05 -07:00
|
|
|
// Given the monster's capability, determine whether
|
2021-11-28 16:54:48 +01:00
|
|
|
// or not the monster can go this way.
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::HandleLinkEnt(int iNode, entvars_t* pevLinkEnt, int afCapMask, NODEQUERY queryType)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
edict_t* pentWorld;
|
|
|
|
CBaseEntity* pDoor;
|
|
|
|
TraceResult tr;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 == m_fGraphPresent || 0 == m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (FNullEnt(pevLinkEnt))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "dead path ent!\n");
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
pentWorld = NULL;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// func_door
|
|
|
|
if (FClassnameIs(pevLinkEnt, "func_door") || FClassnameIs(pevLinkEnt, "func_door_rotating"))
|
|
|
|
{ // ent is a door.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pDoor = (CBaseEntity::Instance(pevLinkEnt));
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY) != 0)
|
|
|
|
{ // door is use only.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((afCapMask & bits_CAP_OPEN_DOORS) != 0)
|
|
|
|
{ // let monster right through if he can open doors
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// monster should try for it if the door is open and looks as if it will stay that way
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pDoor->GetToggleState() == TS_AT_TOP && (pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else
|
|
|
|
{ // door must be opened with a button or trigger field.
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// monster should try for it if the door is open and looks as if it will stay that way
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pDoor->GetToggleState() == TS_AT_TOP && (pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((afCapMask & bits_CAP_OPEN_DOORS) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS) == 0 || queryType == NODEGRAPH_STATIC)
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
// func_breakable
|
|
|
|
else if (FClassnameIs(pevLinkEnt, "func_breakable") && queryType == NODEGRAPH_STATIC)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "Unhandled Ent in Path %s\n", STRING(pevLinkEnt->classname));
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
//=========================================================
|
|
|
|
// FindNearestLink - finds the connection (line) nearest
|
2021-11-19 13:45:16 +01:00
|
|
|
// the given point. Returns false if fails, or true if it
|
2013-08-30 13:34:05 -07:00
|
|
|
// has stuffed the index into the nearest link pool connection
|
2021-11-19 14:40:35 +01:00
|
|
|
// into the passed int pointer, and a bool telling whether or
|
|
|
|
// not the point is along the line into the passed bool pointer.
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph:: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, bool *pfAlongLine )
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int i, j;// loops
|
|
|
|
|
|
|
|
int iNearestLink;// index into the link pool, this is the nearest node at any time.
|
|
|
|
float flMinDist;// the distance of of the nearest case so far
|
|
|
|
float flDistToLine;// the distance of the current test case
|
|
|
|
|
2021-11-19 14:40:35 +01:00
|
|
|
bool fCurrentAlongLine;
|
|
|
|
bool fSuccess;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
//float flConstant;// line constant
|
|
|
|
Vector vecSpot1, vecSpot2;
|
|
|
|
Vector2D vec2Spot1, vec2Spot2, vec2TestPoint;
|
|
|
|
Vector2D vec2Normal;// line normal
|
|
|
|
Vector2D vec2Line;
|
|
|
|
|
|
|
|
TraceResult tr;
|
|
|
|
|
|
|
|
iNearestLink = -1;// prepare for failure
|
2021-11-19 13:43:33 +01:00
|
|
|
fSuccess = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
flMinDist = 9999;// anything will be closer than this
|
|
|
|
|
|
|
|
// go through all of the nodes, and each node's connections
|
|
|
|
int cSkip = 0;// how many links proper pairing allowed us to skip
|
|
|
|
int cChecked = 0;// how many links were checked
|
|
|
|
|
|
|
|
for ( i = 0 ; i < m_cNodes ; i++ )
|
|
|
|
{
|
|
|
|
vecSpot1 = m_pNodes[ i ].m_vecOrigin;
|
|
|
|
|
|
|
|
if ( m_pNodes[ i ].m_cNumLinks <= 0 )
|
|
|
|
{// this shouldn't happen!
|
|
|
|
ALERT ( at_aiconsole, "**Node %d has no links\n", i );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
!!!This optimization only works when the node graph consists of properly linked pairs.
|
|
|
|
if ( INodeLink ( i, j ) <= i )
|
|
|
|
{
|
|
|
|
// since we're going through the nodes in order, don't check
|
|
|
|
// any connections whose second node is lower in the list
|
|
|
|
// than the node we're currently working with. This eliminates
|
|
|
|
// redundant checks.
|
|
|
|
cSkip++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
vecSpot2 = PNodeLink ( i, j )->m_vecOrigin;
|
|
|
|
|
|
|
|
// these values need a little attention now and then, or sometimes ramps cause trouble.
|
|
|
|
if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 )
|
|
|
|
{
|
|
|
|
// if both endpoints of the line are 32 units or more above or below the monster,
|
|
|
|
// the monster won't be able to get to them, so we do a bit of trivial rejection here.
|
|
|
|
// this may change if monsters are allowed to jump down.
|
|
|
|
//
|
|
|
|
// !!!LATER: some kind of clever X/Y hashing should be used here, too
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// now we have two endpoints for a line segment that we've not already checked.
|
|
|
|
// since all lines that make it this far are within -/+ 32 units of the test point's
|
|
|
|
// Z Plane, we can get away with doing the point->line check in 2d.
|
|
|
|
|
|
|
|
cChecked++;
|
|
|
|
|
|
|
|
vec2Spot1 = vecSpot1.Make2D();
|
|
|
|
vec2Spot2 = vecSpot2.Make2D();
|
|
|
|
vec2TestPoint = vecTestPoint.Make2D();
|
|
|
|
|
|
|
|
// get the line normal.
|
|
|
|
vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize();
|
|
|
|
vec2Normal.x = -vec2Line.y;
|
|
|
|
vec2Normal.y = vec2Line.x;
|
|
|
|
|
|
|
|
if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 )
|
|
|
|
{// point outside of line
|
|
|
|
flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length();
|
2021-11-19 13:43:33 +01:00
|
|
|
fCurrentAlongLine = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 )
|
|
|
|
{// point outside of line
|
|
|
|
flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length();
|
2021-11-19 13:43:33 +01:00
|
|
|
fCurrentAlongLine = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{// point inside line
|
|
|
|
flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) );
|
2021-11-19 13:45:16 +01:00
|
|
|
fCurrentAlongLine = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( flDistToLine < flMinDist )
|
|
|
|
{// just found a line nearer than any other so far
|
|
|
|
|
|
|
|
UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr );
|
|
|
|
|
|
|
|
if ( tr.flFraction != 1.0 )
|
|
|
|
{// crap. can't see the first node of this link, try to see the other
|
|
|
|
|
|
|
|
UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr );
|
|
|
|
if ( tr.flFraction != 1.0 )
|
|
|
|
{// can't use this link, cause can't see either node!
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
fSuccess = true;// we know there will be something to return.
|
2013-08-30 13:34:05 -07:00
|
|
|
flMinDist = flDistToLine;
|
|
|
|
iNearestLink = m_pNodes [ i ].m_iFirstLink + j;
|
|
|
|
*piNearestLink = m_pNodes[ i ].m_iFirstLink + j;
|
|
|
|
*pfAlongLine = fCurrentAlongLine;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
if ( fSuccess )
|
|
|
|
{
|
|
|
|
WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY);
|
|
|
|
WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE);
|
|
|
|
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT);
|
|
|
|
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT);
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
ALERT ( at_aiconsole, "%d Checked\n", cChecked );
|
|
|
|
return fSuccess;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int CGraph::HullIndex(const CBaseEntity* pEntity)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pEntity->pev->movetype == MOVETYPE_FLY)
|
2013-08-30 13:34:05 -07:00
|
|
|
return NODE_FLY_HULL;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pEntity->pev->mins == Vector(-12, -12, 0))
|
2013-08-30 13:34:05 -07:00
|
|
|
return NODE_SMALL_HULL;
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (pEntity->pev->mins == VEC_HUMAN_HULL_MIN)
|
2013-08-30 13:34:05 -07:00
|
|
|
return NODE_HUMAN_HULL;
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (pEntity->pev->mins == Vector(-32, -32, 0))
|
2013-08-30 13:34:05 -07:00
|
|
|
return NODE_LARGE_HULL;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" );
|
2013-08-30 13:34:05 -07:00
|
|
|
return NODE_HUMAN_HULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int CGraph::NodeType(const CBaseEntity* pEntity)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pEntity->pev->movetype == MOVETYPE_FLY)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (pEntity->pev->waterlevel != 0)
|
|
|
|
{
|
|
|
|
return bits_NODE_WATER;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return bits_NODE_AIR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return bits_NODE_LAND;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Sum up graph weights on the path from iStart to iDest to determine path length
|
2021-11-28 16:54:48 +01:00
|
|
|
float CGraph::PathLength(int iStart, int iDest, int iHull, int afCapMask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
float distance = 0;
|
|
|
|
int iNext;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int iMaxLoop = m_cNodes;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int iCurrentNode = iStart;
|
2021-11-28 16:54:48 +01:00
|
|
|
int iCap = CapIndex(afCapMask);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
while (iCurrentNode != iDest)
|
|
|
|
{
|
|
|
|
if (iMaxLoop-- <= 0)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_console, "Route Failure\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
iNext = NextNodeInRoute(iCurrentNode, iDest, iHull, iCap);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (iCurrentNode == iNext)
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "SVD: Can't get there from here..\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int iLink;
|
|
|
|
HashSearch(iCurrentNode, iNext, iLink);
|
|
|
|
if (iLink < 0)
|
|
|
|
{
|
|
|
|
ALERT(at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest);
|
|
|
|
return 0;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
CLink& link = Link(iLink);
|
2013-08-30 13:34:05 -07:00
|
|
|
distance += link.m_flWeight;
|
|
|
|
|
|
|
|
iCurrentNode = iNext;
|
|
|
|
}
|
|
|
|
|
|
|
|
return distance;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest
|
2021-11-28 16:54:48 +01:00
|
|
|
int CGraph::NextNodeInRoute(int iCurrentNode, int iDest, int iHull, int iCap)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int iNext = iCurrentNode;
|
2021-11-28 16:54:48 +01:00
|
|
|
int nCount = iDest + 1;
|
|
|
|
char* pRoute = m_pRouteInfo + m_pNodes[iCurrentNode].m_pNextBestNode[iHull][iCap];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Until we decode the next best node
|
|
|
|
//
|
|
|
|
while (nCount > 0)
|
|
|
|
{
|
|
|
|
char ch = *pRoute++;
|
|
|
|
//ALERT(at_aiconsole, "C(%d)", ch);
|
|
|
|
if (ch < 0)
|
|
|
|
{
|
|
|
|
// Sequence phrase
|
|
|
|
//
|
|
|
|
ch = -ch;
|
|
|
|
if (nCount <= ch)
|
|
|
|
{
|
|
|
|
iNext = iDest;
|
|
|
|
nCount = 0;
|
|
|
|
//ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch);
|
|
|
|
nCount = nCount - ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "C(%d)", *pRoute);
|
|
|
|
|
|
|
|
// Repeat phrase
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (nCount <= ch + 1)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
iNext = iCurrentNode + *pRoute;
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iNext >= m_cNodes)
|
|
|
|
iNext -= m_cNodes;
|
|
|
|
else if (iNext < 0)
|
|
|
|
iNext += m_cNodes;
|
2013-08-30 13:34:05 -07:00
|
|
|
nCount = 0;
|
|
|
|
//ALERT(at_aiconsole, "REP: iNext=%d\n", iNext);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch);
|
|
|
|
nCount = nCount - ch - 1;
|
|
|
|
}
|
|
|
|
pRoute++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return iNext;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// CGraph - FindShortestPath
|
2013-08-30 13:34:05 -07:00
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
// accepts a capability mask (afCapMask), and will only
|
2013-08-30 13:34:05 -07:00
|
|
|
// find a path usable by a monster with those capabilities
|
|
|
|
// returns the number of nodes copied into supplied array
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph::FindShortestPath(int* piPath, int iStart, int iDest, int iHull, int afCapMask)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int iVisitNode;
|
|
|
|
int iCurrentNode;
|
|
|
|
int iNumPathNodes;
|
|
|
|
int iHullMask;
|
|
|
|
|
|
|
|
if (0 == m_fGraphPresent || 0 == m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available or built
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2021-11-28 15:32:26 +01:00
|
|
|
return 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
if (iStart < 0 || iStart > m_cNodes)
|
|
|
|
{ // The start node is bad?
|
|
|
|
ALERT(at_aiconsole, "Can't build a path, iStart is %d!\n", iStart);
|
2021-11-28 15:32:26 +01:00
|
|
|
return 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (iStart == iDest)
|
|
|
|
{
|
|
|
|
piPath[0] = iStart;
|
|
|
|
piPath[1] = iDest;
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is routing information present.
|
|
|
|
//
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != m_fRoutingComplete)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int iCap = CapIndex(afCapMask);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
iNumPathNodes = 0;
|
|
|
|
piPath[iNumPathNodes++] = iStart;
|
|
|
|
iCurrentNode = iStart;
|
|
|
|
int iNext;
|
|
|
|
|
|
|
|
//ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest);
|
|
|
|
|
|
|
|
// Until we arrive at the destination
|
|
|
|
//
|
|
|
|
while (iCurrentNode != iDest)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
iNext = NextNodeInRoute(iCurrentNode, iDest, iHull, iCap);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (iCurrentNode == iNext)
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "SVD: Can't get there from here..\n");
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iNumPathNodes >= MAX_PATH_SIZE)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "SVD: Don't return the entire path.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
piPath[iNumPathNodes++] = iNext;
|
|
|
|
iCurrentNode = iNext;
|
|
|
|
}
|
|
|
|
//ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CQueuePriority queue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (iHull)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case NODE_SMALL_HULL:
|
|
|
|
iHullMask = bits_LINK_SMALL_HULL;
|
|
|
|
break;
|
|
|
|
case NODE_HUMAN_HULL:
|
|
|
|
iHullMask = bits_LINK_HUMAN_HULL;
|
|
|
|
break;
|
|
|
|
case NODE_LARGE_HULL:
|
|
|
|
iHullMask = bits_LINK_LARGE_HULL;
|
|
|
|
break;
|
|
|
|
case NODE_FLY_HULL:
|
|
|
|
iHullMask = bits_LINK_FLY_HULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark all the nodes as unvisited.
|
|
|
|
//
|
|
|
|
int i;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[i].m_flClosestSoFar = -1.0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[iStart].m_flClosestSoFar = 0.0;
|
|
|
|
m_pNodes[iStart].m_iPreviousNode = iStart; // tag this as the origin node
|
|
|
|
queue.Insert(iStart, 0.0); // insert start node
|
|
|
|
|
|
|
|
while (!queue.Empty())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// now pull a node out of the queue
|
|
|
|
float flCurrentDistance;
|
|
|
|
iCurrentNode = queue.Remove(flCurrentDistance);
|
|
|
|
|
|
|
|
// For straight-line weights, the following Shortcut works. For arbitrary weights,
|
|
|
|
// it doesn't.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iCurrentNode == iDest)
|
|
|
|
break;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
CNode* pCurrentNode = &m_pNodes[iCurrentNode];
|
|
|
|
|
|
|
|
for (i = 0; i < pCurrentNode->m_cNumLinks; i++)
|
|
|
|
{ // run through all of this node's neighbors
|
|
|
|
|
|
|
|
iVisitNode = INodeLink(iCurrentNode, i);
|
|
|
|
if ((m_pLinkPool[m_pNodes[iCurrentNode].m_iFirstLink + i].m_afLinkInfo & iHullMask) != iHullMask)
|
|
|
|
{ // monster is too large to walk this connection
|
2013-08-30 13:34:05 -07:00
|
|
|
//ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull );
|
|
|
|
continue;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
// check the connection from the current node to the node we're about to mark visited and push into the queue
|
|
|
|
if (m_pLinkPool[m_pNodes[iCurrentNode].m_iFirstLink + i].m_pLinkEnt != NULL)
|
|
|
|
{ // there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it
|
|
|
|
|
|
|
|
if (!HandleLinkEnt(iCurrentNode, m_pLinkPool[m_pNodes[iCurrentNode].m_iFirstLink + i].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC))
|
|
|
|
{ // monster should not try to go this way.
|
2013-08-30 13:34:05 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
float flOurDistance = flCurrentDistance + m_pLinkPool[m_pNodes[iCurrentNode].m_iFirstLink + i].m_flWeight;
|
|
|
|
if (m_pNodes[iVisitNode].m_flClosestSoFar < -0.5 || flOurDistance < m_pNodes[iVisitNode].m_flClosestSoFar - 0.001)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance;
|
|
|
|
m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
queue.Insert(iVisitNode, flOurDistance);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_pNodes[iDest].m_flClosestSoFar < -0.5)
|
|
|
|
{ // Destination is unreachable, no path found.
|
2013-08-30 13:34:05 -07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// the queue is not empty
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path
|
|
|
|
iCurrentNode = iDest;
|
2021-11-28 16:54:48 +01:00
|
|
|
iNumPathNodes = 1; // count the dest
|
|
|
|
|
|
|
|
while (iCurrentNode != iStart)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
iNumPathNodes++;
|
2021-11-28 16:54:48 +01:00
|
|
|
iCurrentNode = m_pNodes[iCurrentNode].m_iPreviousNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
iCurrentNode = iDest;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = iNumPathNodes - 1; i >= 0; i--)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
piPath[i] = iCurrentNode;
|
|
|
|
iCurrentNode = m_pNodes[iCurrentNode].m_iPreviousNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
|
|
if (m_fRoutingComplete)
|
|
|
|
{
|
|
|
|
// This will draw the entire path that was generated for the monster.
|
|
|
|
|
|
|
|
for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ )
|
|
|
|
{
|
|
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
|
|
WRITE_BYTE( TE_SHOWLINE);
|
|
|
|
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT );
|
|
|
|
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT );
|
|
|
|
MESSAGE_END();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
#if 0 // MAZE map
|
|
|
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY );
|
|
|
|
WRITE_BYTE( TE_SHOWLINE);
|
|
|
|
|
|
|
|
WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT );
|
|
|
|
|
|
|
|
WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x );
|
|
|
|
WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y );
|
|
|
|
WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT );
|
|
|
|
MESSAGE_END();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return iNumPathNodes;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
inline unsigned int Hash(void* p, int len)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
CRC32_t ulCrc;
|
|
|
|
CRC32_INIT(&ulCrc);
|
|
|
|
CRC32_PROCESS_BUFFER(&ulCrc, p, len);
|
|
|
|
return CRC32_FINAL(ulCrc);
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void inline CalcBounds(int& Lower, int& Upper, int Goal, int Best)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int Temp = 2 * Goal - Best;
|
|
|
|
if (Best > Goal)
|
|
|
|
{
|
|
|
|
Lower = V_max(0, Temp);
|
|
|
|
Upper = Best;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Upper = V_min(255, Temp);
|
|
|
|
Lower = Best;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert from [-8192,8192] to [0, 255]
|
|
|
|
//
|
|
|
|
inline int CALC_RANGE(int x, int lower, int upper)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return NUM_RANGES * (x - lower) / ((upper - lower + 1));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void inline UpdateRange(int& minValue, int& maxValue, int Goal, int Best)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int Lower, Upper;
|
|
|
|
CalcBounds(Lower, Upper, Goal, Best);
|
|
|
|
if (Upper < maxValue)
|
|
|
|
maxValue = Upper;
|
|
|
|
if (minValue < Lower)
|
|
|
|
minValue = Lower;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CGraph::CheckNode(Vector vecOrigin, int iNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// Have we already seen this point before?.
|
|
|
|
//
|
|
|
|
if (m_di[iNode].m_CheckedEvent == m_CheckedCounter)
|
|
|
|
return;
|
|
|
|
m_di[iNode].m_CheckedEvent = m_CheckedCounter;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
float flDist = (vecOrigin - m_pNodes[iNode].m_vecOriginPeek).Length();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (flDist < m_flShortest)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
TraceResult tr;
|
|
|
|
|
|
|
|
// make sure that vecOrigin can trace to this node!
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_TraceLine(vecOrigin, m_pNodes[iNode].m_vecOriginPeek, ignore_monsters, 0, &tr);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (tr.flFraction == 1.0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_iNearest = iNode;
|
|
|
|
m_flShortest = flDist;
|
|
|
|
|
|
|
|
UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]);
|
|
|
|
UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]);
|
|
|
|
UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]);
|
|
|
|
|
|
|
|
// From maxCircle, calculate maximum bounds box. All points must be
|
|
|
|
// simultaneously inside all bounds of the box.
|
|
|
|
//
|
|
|
|
m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]);
|
|
|
|
m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]);
|
|
|
|
m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]);
|
|
|
|
m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]);
|
|
|
|
m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]);
|
|
|
|
m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - FindNearestNode - returns the index of the node nearest
|
|
|
|
// the given vector -1 is failure (couldn't find a valid
|
|
|
|
// near node )
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph::FindNearestNode(const Vector& vecOrigin, CBaseEntity* pEntity)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return FindNearestNode(vecOrigin, NodeType(pEntity));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph::FindNearestNode(const Vector& vecOrigin, int afNodeTypes)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i;
|
2013-08-30 13:34:05 -07:00
|
|
|
TraceResult tr;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 == m_fGraphPresent || 0 == m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check with the cache
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
unsigned int iHash = (CACHE_SIZE - 1) & Hash((void*)(const float*)vecOrigin, sizeof(vecOrigin));
|
2013-08-30 13:34:05 -07:00
|
|
|
if (m_Cache[iHash].v == vecOrigin)
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "Cache Hit.\n");
|
|
|
|
return m_Cache[iHash].n;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//ALERT(at_aiconsole, "Cache Miss.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark all points as unchecked.
|
|
|
|
//
|
|
|
|
m_CheckedCounter++;
|
|
|
|
if (m_CheckedCounter == 0)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
m_di[i].m_CheckedEvent = 0;
|
|
|
|
}
|
|
|
|
m_CheckedCounter++;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_iNearest = -1;
|
|
|
|
m_flShortest = 999999.0; // just a big number.
|
|
|
|
|
|
|
|
// If we can find a visible point, then let CalcBounds set the limits, but if
|
|
|
|
// we have no visible point at all to start with, then don't restrict the limits.
|
|
|
|
//
|
|
|
|
#if 1
|
2021-11-28 16:54:48 +01:00
|
|
|
m_minX = 0;
|
|
|
|
m_maxX = 255;
|
|
|
|
m_minY = 0;
|
|
|
|
m_maxY = 255;
|
|
|
|
m_minZ = 0;
|
|
|
|
m_maxZ = 255;
|
|
|
|
m_minBoxX = 0;
|
|
|
|
m_maxBoxX = 255;
|
|
|
|
m_minBoxY = 0;
|
|
|
|
m_maxBoxY = 255;
|
|
|
|
m_minBoxZ = 0;
|
|
|
|
m_maxBoxZ = 255;
|
2013-08-30 13:34:05 -07:00
|
|
|
#else
|
|
|
|
m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]);
|
|
|
|
m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]);
|
|
|
|
m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]);
|
|
|
|
m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]);
|
|
|
|
m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]);
|
|
|
|
m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2])
|
2021-11-28 16:54:48 +01:00
|
|
|
CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]);
|
|
|
|
CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]);
|
|
|
|
CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]);
|
2013-08-30 13:34:05 -07:00
|
|
|
#endif
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int halfX = (m_minX + m_maxX) / 2;
|
|
|
|
int halfY = (m_minY + m_maxY) / 2;
|
|
|
|
int halfZ = (m_minZ + m_maxZ) / 2;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int j;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = halfX; i >= m_minX; i--)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgY > m_maxBoxY)
|
|
|
|
break;
|
|
|
|
if (rgY < m_minBoxY)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgZ < m_minBoxZ)
|
|
|
|
continue;
|
|
|
|
if (rgZ > m_maxBoxZ)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = V_max(m_minY, halfY + 1); i <= m_maxY; i++)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgZ > m_maxBoxZ)
|
|
|
|
break;
|
|
|
|
if (rgZ < m_minBoxZ)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgX < m_minBoxX)
|
|
|
|
continue;
|
|
|
|
if (rgX > m_maxBoxX)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = V_min(m_maxZ, halfZ); i >= m_minZ; i--)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgX > m_maxBoxX)
|
|
|
|
break;
|
|
|
|
if (rgX < m_minBoxX)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgY < m_minBoxY)
|
|
|
|
continue;
|
|
|
|
if (rgY > m_maxBoxY)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[2]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = V_max(m_minX, halfX + 1); i <= m_maxX; i++)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgY > m_maxBoxY)
|
|
|
|
break;
|
|
|
|
if (rgY < m_minBoxY)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgZ < m_minBoxZ)
|
|
|
|
continue;
|
|
|
|
if (rgZ > m_maxBoxZ)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = V_min(m_maxY, halfY); i >= m_minY; i--)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgZ > m_maxBoxZ)
|
|
|
|
break;
|
|
|
|
if (rgZ < m_minBoxZ)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgX < m_minBoxX)
|
|
|
|
continue;
|
|
|
|
if (rgX > m_maxBoxX)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = V_max(m_minZ, halfZ + 1); i <= m_maxZ; i++)
|
|
|
|
{
|
|
|
|
for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++)
|
|
|
|
{
|
|
|
|
if ((m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes) == 0)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgX > m_maxBoxX)
|
|
|
|
break;
|
|
|
|
if (rgX < m_minBoxX)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1];
|
2021-11-28 16:54:48 +01:00
|
|
|
if (rgY < m_minBoxY)
|
|
|
|
continue;
|
|
|
|
if (rgY > m_maxBoxY)
|
|
|
|
continue;
|
|
|
|
CheckNode(vecOrigin, m_di[j].m_SortedBy[2]);
|
|
|
|
}
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
// Verify our answers.
|
|
|
|
//
|
|
|
|
int iNearestCheck = -1;
|
|
|
|
m_flShortest = 8192;// find nodes within this radius
|
|
|
|
|
|
|
|
for ( i = 0 ; i < m_cNodes ; i++ )
|
|
|
|
{
|
|
|
|
float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length();
|
|
|
|
|
|
|
|
if ( flDist < m_flShortest )
|
|
|
|
{
|
|
|
|
// make sure that vecOrigin can trace to this node!
|
|
|
|
UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr );
|
|
|
|
|
|
|
|
if ( tr.flFraction == 1.0 )
|
|
|
|
{
|
|
|
|
iNearestCheck = i;
|
|
|
|
m_flShortest = flDist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iNearestCheck != m_iNearest)
|
|
|
|
{
|
|
|
|
ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n",
|
|
|
|
iNearestCheck,
|
|
|
|
m_pNodes[iNearestCheck].m_vecOriginPeek.x,
|
|
|
|
m_pNodes[iNearestCheck].m_vecOriginPeek.y,
|
|
|
|
m_pNodes[iNearestCheck].m_vecOriginPeek.z,
|
|
|
|
m_iNearest,
|
|
|
|
(m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x),
|
|
|
|
(m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y),
|
|
|
|
(m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z));
|
|
|
|
}
|
|
|
|
if (m_iNearest == -1)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "All that work for nothing.\n");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
m_Cache[iHash].v = vecOrigin;
|
|
|
|
m_Cache[iHash].n = m_iNearest;
|
|
|
|
return m_iNearest;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - ShowNodeConnections - draws a line from the given node
|
|
|
|
// to all connected nodes
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CGraph::ShowNodeConnections(int iNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
Vector vecSpot;
|
|
|
|
CNode* pNode;
|
|
|
|
CNode* pLinkNode;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (0 == m_fGraphPresent || 0 == m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available or built
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iNode < 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "Can't show connections for node %d\n", iNode);
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pNode = &m_pNodes[iNode];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_ParticleEffect(pNode->m_vecOrigin, g_vecZero, 255, 20); // show node position
|
|
|
|
|
|
|
|
if (pNode->m_cNumLinks <= 0)
|
|
|
|
{ // no connections!
|
|
|
|
ALERT(at_aiconsole, "**No Connections!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < pNode->m_cNumLinks; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
pLinkNode = &Node(NodeLink(iNode, i).m_iDestNode);
|
2013-08-30 13:34:05 -07:00
|
|
|
vecSpot = pLinkNode->m_vecOrigin;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
|
|
|
|
WRITE_BYTE(TE_SHOWLINE);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WRITE_COORD(m_pNodes[iNode].m_vecOrigin.x);
|
|
|
|
WRITE_COORD(m_pNodes[iNode].m_vecOrigin.y);
|
|
|
|
WRITE_COORD(m_pNodes[iNode].m_vecOrigin.z + NODE_HEIGHT);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WRITE_COORD(vecSpot.x);
|
|
|
|
WRITE_COORD(vecSpot.y);
|
|
|
|
WRITE_COORD(vecSpot.z + NODE_HEIGHT);
|
|
|
|
MESSAGE_END();
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - LinkVisibleNodes - the first, most basic
|
|
|
|
// function of node graph creation, this connects every
|
2021-11-28 16:54:48 +01:00
|
|
|
// node to every other node that it can see. Expects a
|
|
|
|
// pointer to an empty connection pool and a file pointer
|
2013-08-30 13:34:05 -07:00
|
|
|
// to write progress to. Returns the total number of initial
|
|
|
|
// links.
|
|
|
|
//
|
|
|
|
// If there's a problem with this process, the index
|
|
|
|
// of the offending node will be written to piBadNode
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph::LinkVisibleNodes(CLink* pLinkPool, FSFile& file, int* piBadNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i, j, z;
|
|
|
|
edict_t* pTraceEnt;
|
|
|
|
int cTotalLinks, cLinksThisNode, cMaxInitialLinks;
|
|
|
|
TraceResult tr;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph
|
|
|
|
// it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read
|
|
|
|
// by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random
|
|
|
|
// number back.
|
|
|
|
*piBadNode = 0;
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_cNodes <= 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "No Nodes!\n");
|
2021-11-28 15:32:26 +01:00
|
|
|
return 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// if the file pointer is bad, don't blow up, just don't write the
|
|
|
|
// file.
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**LinkVisibleNodes:\ncan't write to file.");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("LinkVisibleNodes - Initial Connections\n");
|
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
cTotalLinks = 0; // start with no connections
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// to keep track of the maximum number of initial links any node had so far.
|
|
|
|
// this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are
|
|
|
|
// being generous enough.
|
|
|
|
cMaxInitialLinks = 0;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
cLinksThisNode = 0; // reset this count for each node.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Node #%4d:\n\n", i);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (z = 0; z < MAX_NODE_INITIAL_LINKS; z++)
|
|
|
|
{ // clear out the important fields in the link pool for this node
|
|
|
|
pLinkPool[cTotalLinks + z].m_iSrcNode = i; // so each link knows which node it originates from
|
|
|
|
pLinkPool[cTotalLinks + z].m_iDestNode = 0;
|
|
|
|
pLinkPool[cTotalLinks + z].m_pLinkEnt = NULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[i].m_iFirstLink = cTotalLinks;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// now build a list of every other node that this node can see
|
2021-11-28 16:54:48 +01:00
|
|
|
for (j = 0; j < m_cNodes; j++)
|
|
|
|
{
|
|
|
|
if (j == i)
|
|
|
|
{ // don't connect to self!
|
2013-08-30 13:34:05 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
|
|
|
if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) )
|
|
|
|
{
|
|
|
|
// don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#else
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((m_pNodes[i].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[j].m_afNodeInfo & bits_NODE_GROUP_REALM))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture.
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
tr.pHit = NULL; // clear every time so we don't get stuck with last trace's hit ent
|
2013-08-30 13:34:05 -07:00
|
|
|
pTraceEnt = 0;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_TraceLine(m_pNodes[i].m_vecOrigin,
|
|
|
|
m_pNodes[j].m_vecOrigin,
|
|
|
|
ignore_monsters,
|
|
|
|
g_pBodyQueueHead, //!!!HACKHACK no real ent to supply here, using a global we don't care about
|
|
|
|
&tr);
|
|
|
|
|
|
|
|
|
|
|
|
if (0 != tr.fStartSolid)
|
2013-08-30 13:34:05 -07:00
|
|
|
continue;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (tr.flFraction != 1.0)
|
|
|
|
{ // trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pTraceEnt = tr.pHit; // store the ent that the trace hit, for comparison
|
|
|
|
|
|
|
|
UTIL_TraceLine(m_pNodes[j].m_vecOrigin,
|
|
|
|
m_pNodes[i].m_vecOrigin,
|
|
|
|
ignore_monsters,
|
|
|
|
g_pBodyQueueHead, //!!!HACKHACK no real ent to supply here, using a global we don't care about
|
|
|
|
&tr);
|
|
|
|
|
|
|
|
|
|
|
|
// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep
|
|
|
|
// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated
|
|
|
|
// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded
|
|
|
|
// graphs are prepared for use.
|
|
|
|
if (tr.pHit == pTraceEnt && !FClassnameIs(tr.pHit, "worldspawn"))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// get a pointer
|
2021-11-28 16:54:48 +01:00
|
|
|
pLinkPool[cTotalLinks].m_pLinkEnt = VARS(tr.pHit);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// record the modelname, so that we can save/load node trees
|
2021-11-28 16:54:48 +01:00
|
|
|
memcpy(pLinkPool[cTotalLinks].m_szLinkEntModelname, STRING(VARS(tr.pHit)->model), 4);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// set the flag for this ent that indicates that it is attached to the world graph
|
|
|
|
// if this ent is removed from the world, it must also be removed from the connections
|
|
|
|
// that it formerly blocked.
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!FBitSet(VARS(tr.pHit)->flags, FL_GRAPHED))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
VARS(tr.pHit)->flags += FL_GRAPHED;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2021-11-28 16:54:48 +01:00
|
|
|
{ // even if the ent wasn't there, these nodes couldn't be connected. Skip.
|
2013-08-30 13:34:05 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("%4d", j);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!FNullEnt(pLinkPool[cTotalLinks].m_pLinkEnt))
|
|
|
|
{ // record info about the ent in the way, if any.
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf(" Entity on connection: %s, name: %s Model: %s", STRING(VARS(pTraceEnt)->classname), STRING(VARS(pTraceEnt)->targetname), STRING(VARS(tr.pHit)->model));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pLinkPool[cTotalLinks].m_iDestNode = j;
|
2013-08-30 13:34:05 -07:00
|
|
|
cLinksThisNode++;
|
|
|
|
cTotalLinks++;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// If we hit this, either a level designer is placing too many nodes in the same area, or
|
2013-08-30 13:34:05 -07:00
|
|
|
// we need to allow for a larger initial link pool.
|
2021-11-28 16:54:48 +01:00
|
|
|
if (cLinksThisNode == MAX_NODE_INITIAL_LINKS)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i);
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i);
|
2013-08-30 13:34:05 -07:00
|
|
|
*piBadNode = i;
|
2021-11-28 16:54:48 +01:00
|
|
|
return 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes)
|
|
|
|
{ // this is paranoia
|
|
|
|
ALERT(at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES");
|
2013-08-30 13:34:05 -07:00
|
|
|
*piBadNode = i;
|
2021-11-28 16:54:48 +01:00
|
|
|
return 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (cLinksThisNode == 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("**NO INITIAL LINKS**\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// record the connection info in the link pool
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[i].m_cNumLinks = cLinksThisNode;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// keep track of the most initial links ANY node had, so we can figure out
|
|
|
|
// if we have a large enough default link pool
|
2021-11-28 16:54:48 +01:00
|
|
|
if (cLinksThisNode > cMaxInitialLinks)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
cMaxInitialLinks = cLinksThisNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks);
|
|
|
|
file.Printf("----------------------------------------------------------------------------\n\n\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
return cTotalLinks;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - RejectInlineLinks - expects a pointer to a link
|
|
|
|
// pool, and a pointer to and already-open file ( if you
|
|
|
|
// want status reports written to disk ). RETURNS the number
|
|
|
|
// of connections that were rejected
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CGraph::RejectInlineLinks(CLink* pLinkPool, FSFile& file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i, j, k;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int cRejectedLinks;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
bool fRestartLoop; // have to restart the J loop if we eliminate a link.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
CNode* pSrcNode;
|
|
|
|
CNode* pCheckNode; // the node we are testing for (one of pSrcNode's connections)
|
|
|
|
CNode* pTestNode; // the node we are checking against ( also one of pSrcNode's connections)
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
float flDistToTestNode, flDistToCheckNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Vector2D vec2DirToTestNode, vec2DirToCheckNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("InLine Rejection:\n");
|
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
cRejectedLinks = 0;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pSrcNode = &m_pNodes[i];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Node %3d:\n", i);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (j = 0; j < pSrcNode->m_cNumLinks; j++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pCheckNode = &m_pNodes[pLinkPool[pSrcNode->m_iFirstLink + j].m_iDestNode];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
vec2DirToCheckNode = (pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin).Make2D();
|
2013-08-30 13:34:05 -07:00
|
|
|
flDistToCheckNode = vec2DirToCheckNode.Length();
|
|
|
|
vec2DirToCheckNode = vec2DirToCheckNode.Normalize();
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pLinkPool[pSrcNode->m_iFirstLink + j].m_flWeight = flDistToCheckNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-19 13:43:33 +01:00
|
|
|
fRestartLoop = false;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (k = 0; k < pSrcNode->m_cNumLinks && !fRestartLoop; k++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (k == j)
|
|
|
|
{ // don't check against same node
|
2013-08-30 13:34:05 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pTestNode = &m_pNodes[pLinkPool[pSrcNode->m_iFirstLink + k].m_iDestNode];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
vec2DirToTestNode = (pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin).Make2D();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
flDistToTestNode = vec2DirToTestNode.Length();
|
|
|
|
vec2DirToTestNode = vec2DirToTestNode.Normalize();
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (DotProduct(vec2DirToCheckNode, vec2DirToTestNode) >= 0.998)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode.
|
|
|
|
if (flDistToTestNode < flDistToCheckNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[pSrcNode->m_iFirstLink + j].m_iDestNode, pLinkPool[pSrcNode->m_iFirstLink + k].m_iDestNode, DotProduct(vec2DirToCheckNode, vec2DirToTestNode));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pLinkPool[pSrcNode->m_iFirstLink + j] = pLinkPool[pSrcNode->m_iFirstLink + (pSrcNode->m_cNumLinks - 1)];
|
2013-08-30 13:34:05 -07:00
|
|
|
pSrcNode->m_cNumLinks--;
|
|
|
|
j--;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
cRejectedLinks++; // keeping track of how many links are cut, so that we can return that value.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
fRestartLoop = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (file)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("----------------------------------------------------------------------------\n\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cRejectedLinks;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// TestHull is a modelless clip hull that verifies reachable
|
|
|
|
// nodes by walking from every node to each of it's connections
|
|
|
|
//=========================================================
|
|
|
|
class CTestHull : public CBaseMonster
|
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
2021-11-28 16:54:48 +01:00
|
|
|
void Spawn(entvars_t* pevMasterNode);
|
2021-11-29 20:31:17 +01:00
|
|
|
int ObjectCaps() override { return CBaseMonster::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
|
2021-11-28 16:54:48 +01:00
|
|
|
void EXPORT CallBuildNodeGraph();
|
|
|
|
void BuildNodeGraph();
|
|
|
|
void EXPORT ShowBadNode();
|
|
|
|
void EXPORT DropDelay();
|
|
|
|
void EXPORT PathFind();
|
|
|
|
|
|
|
|
Vector vecBadNodeOrigin;
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(testhull, CTestHull);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CTestHull::Spawn
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CTestHull::Spawn(entvars_t* pevMasterNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
SET_MODEL(ENT(pev), "models/player.mdl");
|
|
|
|
UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->solid = SOLID_SLIDEBOX;
|
|
|
|
pev->movetype = MOVETYPE_STEP;
|
|
|
|
pev->effects = 0;
|
|
|
|
pev->health = 50;
|
|
|
|
pev->yaw_speed = 8;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 != WorldGraph.m_fGraphPresent)
|
|
|
|
{ // graph loaded from disk, so we don't need the test hull
|
|
|
|
SetThink(&CTestHull::SUB_Remove);
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->nextthink = gpGlobals->time;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
SetThink(&CTestHull::DropDelay);
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->nextthink = gpGlobals->time + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make this invisible
|
|
|
|
// UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client.
|
|
|
|
pev->rendermode = kRenderTransTexture;
|
|
|
|
pev->renderamt = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// TestHull::DropDelay - spawns TestHull on top of
|
2013-08-30 13:34:05 -07:00
|
|
|
// the 0th node and drops it to the ground.
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
void CTestHull::DropDelay()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." );
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2022-10-07 16:31:04 +02:00
|
|
|
UTIL_SetOrigin(pev, WorldGraph.m_pNodes[0].m_vecOrigin);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
SetThink(&CTestHull::CallBuildNodeGraph);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
pev->nextthink = gpGlobals->time + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// nodes start out as ents in the world. As they are spawned,
|
|
|
|
// the node info is recorded then the ents are discarded.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CNodeEnt::KeyValue(KeyValueData* pkvd)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (FStrEq(pkvd->szKeyName, "hinttype"))
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_sHintType = (short)atoi(pkvd->szValue);
|
2021-11-28 15:32:26 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (FStrEq(pkvd->szKeyName, "activity"))
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_sHintActivity = (short)atoi(pkvd->szValue);
|
2021-11-28 15:32:26 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 15:32:26 +01:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
return CBaseEntity::KeyValue(pkvd);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CNodeEnt::Spawn()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
pev->movetype = MOVETYPE_NONE;
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->solid = SOLID_NOT; // always solid_not
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 != WorldGraph.m_fGraphPresent)
|
|
|
|
{ // graph loaded from disk, so discard all these node ents as soon as they spawn
|
|
|
|
REMOVE_ENTITY(edict());
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (WorldGraph.m_cNodes == 0)
|
|
|
|
{ // this is the first node to spawn, spawn the test hull entity that builds and walks the node tree
|
|
|
|
CTestHull* pHull = GetClassPtr((CTestHull*)NULL);
|
|
|
|
pHull->Spawn(pev);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (WorldGraph.m_cNodes >= MAX_NODES)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "cNodes > MAX_NODES\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_vecOriginPeek =
|
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_vecOrigin = pev->origin;
|
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_flHintYaw = pev->angles.y;
|
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_sHintType = m_sHintType;
|
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_sHintActivity = m_sHintActivity;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (FClassnameIs(pev, "info_node_air"))
|
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_afNodeInfo = bits_NODE_AIR;
|
2013-08-30 13:34:05 -07:00
|
|
|
else
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[WorldGraph.m_cNodes].m_afNodeInfo = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
WorldGraph.m_cNodes++;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
REMOVE_ENTITY(edict());
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CTestHull - ShowBadNode - makes a bad node fizzle. When
|
2021-11-28 16:54:48 +01:00
|
|
|
// there's a problem with node graph generation, the test
|
2013-08-30 13:34:05 -07:00
|
|
|
// hull will be placed up the bad node's location and will generate
|
|
|
|
// particles
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CTestHull::ShowBadNode()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
pev->movetype = MOVETYPE_FLY;
|
|
|
|
pev->angles.y = pev->angles.y + 4;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_MakeVectors(pev->angles);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_ParticleEffect(pev->origin, g_vecZero, 255, 25);
|
|
|
|
UTIL_ParticleEffect(pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25);
|
|
|
|
UTIL_ParticleEffect(pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25);
|
|
|
|
UTIL_ParticleEffect(pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25);
|
|
|
|
UTIL_ParticleEffect(pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
pev->nextthink = gpGlobals->time + 0.1;
|
|
|
|
}
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CTestHull::CallBuildNodeGraph()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// TOUCH HACK -- Don't allow this entity to call anyone's "touch" function
|
2021-11-19 13:45:16 +01:00
|
|
|
gTouchDisabled = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
BuildNodeGraph();
|
2021-11-19 13:43:33 +01:00
|
|
|
gTouchDisabled = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
// Undo TOUCH HACK
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// BuildNodeGraph - think function called by the empty walk
|
|
|
|
// hull that is spawned by the first node to spawn. This
|
|
|
|
// function links all nodes that can see each other, then
|
2021-11-28 16:54:48 +01:00
|
|
|
// eliminates all inline links, then uses a monster-sized
|
2013-08-30 13:34:05 -07:00
|
|
|
// hull that walks between each node and each of its links
|
|
|
|
// to ensure that a monster can actually fit through the space
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CTestHull::BuildNodeGraph()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
TraceResult tr;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
CLink* pTempPool; // temporary link pool
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
CNode* pSrcNode; // node we're currently working with
|
|
|
|
CNode* pDestNode; // the other node in comparison operations
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
bool fSkipRemainingHulls; //if smallest hull can't fit, don't check any others
|
|
|
|
bool fPairsValid; // are all links in the graph evenly paired?
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int i, j, hull;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int iBadNode; // this is the node that caused graph generation to fail
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int cMaxInitialLinks = 0;
|
|
|
|
int cMaxValidLinks = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int iPoolIndex = 0;
|
|
|
|
int cPoolLinks; // number of links in the pool.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Vector vecDirToCheckNode;
|
|
|
|
Vector vecDirToTestNode;
|
|
|
|
Vector vecStepCheckDir;
|
|
|
|
Vector vecTraceSpot;
|
|
|
|
Vector vecSpot;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Vector2D vec2DirToCheckNode;
|
|
|
|
Vector2D vec2DirToTestNode;
|
|
|
|
Vector2D vec2StepCheckDir;
|
|
|
|
Vector2D vec2TraceSpot;
|
|
|
|
Vector2D vec2Spot;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
float flYaw; // use this stuff to walk the hull between nodes
|
|
|
|
float flDist;
|
|
|
|
int step;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
SetThink(&CTestHull::SUB_Remove); // no matter what happens, the hull gets rid of itself.
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->nextthink = gpGlobals->time;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are.
|
|
|
|
pTempPool = (CLink*)calloc(sizeof(CLink), (WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS));
|
|
|
|
if (!pTempPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**Could not malloc TempPool!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make sure directories have been made
|
2021-11-28 19:34:20 +01:00
|
|
|
g_pFileSystem->CreateDirHierarchy("maps/graphs", "GAMECONFIG");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
const std::string nrpFileName{std::string{"maps/graphs/"} + STRING(gpGlobals->mapname) + ".nrp"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
FSFile file{nrpFileName.c_str(), "w+", "GAMECONFIG"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!file)
|
|
|
|
{ // file error
|
2021-11-28 19:34:20 +01:00
|
|
|
ALERT(at_aiconsole, "Couldn't create %s!\n", nrpFileName.c_str());
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pTempPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(pTempPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname));
|
|
|
|
file.Printf("%d Total Nodes\n\n", WorldGraph.m_cNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
|
|
|
{ // print all node numbers and their locations to the file.
|
|
|
|
WorldGraph.m_pNodes[i].m_cNumLinks = 0;
|
|
|
|
WorldGraph.m_pNodes[i].m_iFirstLink = 0;
|
|
|
|
memset(WorldGraph.m_pNodes[i].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[i].m_pNextBestNode));
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Node# %4d\n", i);
|
|
|
|
file.Printf("Location %4d,%4d,%4d\n", (int)WorldGraph.m_pNodes[i].m_vecOrigin.x, (int)WorldGraph.m_pNodes[i].m_vecOrigin.y, (int)WorldGraph.m_pNodes[i].m_vecOrigin.z);
|
|
|
|
file.Printf("HintType: %4d\n", WorldGraph.m_pNodes[i].m_sHintType);
|
|
|
|
file.Printf("HintActivity: %4d\n", WorldGraph.m_pNodes[i].m_sHintActivity);
|
|
|
|
file.Printf("HintYaw: %4f\n", WorldGraph.m_pNodes[i].m_flHintYaw);
|
|
|
|
file.Printf("-------------------------------------------------------------------------------\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("\n\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
// Automatically recognize WATER nodes and drop the LAND nodes to the floor.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((WorldGraph.m_pNodes[i].m_afNodeInfo & bits_NODE_AIR) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// do nothing
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (UTIL_PointContents(WorldGraph.m_pNodes[i].m_vecOrigin) == CONTENTS_WATER)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[i].m_afNodeInfo |= bits_NODE_WATER;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[i].m_afNodeInfo |= bits_NODE_LAND;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// trace to the ground, then pop up 8 units and place node there to make it
|
|
|
|
// easier for them to connect (think stairs, chairs, and bumps in the floor).
|
|
|
|
// After the routing is done, push them back down.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
TraceResult tr;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_TraceLine(WorldGraph.m_pNodes[i].m_vecOrigin,
|
|
|
|
WorldGraph.m_pNodes[i].m_vecOrigin - Vector(0, 0, 384),
|
|
|
|
ignore_monsters,
|
|
|
|
g_pBodyQueueHead, //!!!HACKHACK no real ent to supply here, using a global we don't care about
|
|
|
|
&tr);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH
|
2021-11-28 16:54:48 +01:00
|
|
|
TraceResult trEnt;
|
|
|
|
UTIL_TraceLine(WorldGraph.m_pNodes[i].m_vecOrigin,
|
|
|
|
WorldGraph.m_pNodes[i].m_vecOrigin - Vector(0, 0, 384),
|
|
|
|
dont_ignore_monsters,
|
|
|
|
g_pBodyQueueHead, //!!!HACKHACK no real ent to supply here, using a global we don't care about
|
|
|
|
&trEnt);
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Did we hit something closer than the floor?
|
2021-11-28 16:54:48 +01:00
|
|
|
if (trEnt.flFraction < tr.flFraction)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// If it was a world brush entity, copy the node location
|
2021-11-28 16:54:48 +01:00
|
|
|
if (trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
tr.vecEndPos = trEnt.vecEndPos;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[i].m_vecOriginPeek.z =
|
2013-08-30 13:34:05 -07:00
|
|
|
WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
cPoolLinks = WorldGraph.LinkVisibleNodes(pTempPool, file, &iBadNode);
|
|
|
|
|
|
|
|
if (0 == cPoolLinks)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**ConnectVisibleNodes FAILED!\n");
|
|
|
|
|
|
|
|
SetThink(&CTestHull::ShowBadNode); // send the hull off to show the offending node.
|
2013-08-30 13:34:05 -07:00
|
|
|
//pev->solid = SOLID_NOT;
|
2021-11-28 16:54:48 +01:00
|
|
|
pev->origin = WorldGraph.m_pNodes[iBadNode].m_vecOrigin;
|
|
|
|
|
|
|
|
if (pTempPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(pTempPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// send the walkhull to all of this node's connections now. We'll do this here since
|
|
|
|
// so much of it relies on being able to control the test hull.
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("----------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("Walk Rejection:\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pSrcNode = &WorldGraph.m_pNodes[i];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("-------------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("Node %4d:\n\n", i);
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
for (j = 0; j < pSrcNode->m_cNumLinks; j++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// assume that all hulls can walk this link, then eliminate the ones that can't.
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
// do a check for each hull size.
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
// if we can't fit a tiny hull through a connection, no other hulls with fit either, so we
|
2013-08-30 13:34:05 -07:00
|
|
|
// should just fall out of the loop. Do so by setting the SkipRemainingHulls flag.
|
2021-11-19 13:43:33 +01:00
|
|
|
fSkipRemainingHulls = false;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (hull = 0; hull < MAX_NODE_HULLS; hull++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls
|
|
|
|
continue;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (hull)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
case NODE_SMALL_HULL:
|
|
|
|
UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24));
|
|
|
|
break;
|
|
|
|
case NODE_HUMAN_HULL:
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX);
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
case NODE_LARGE_HULL:
|
|
|
|
UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64));
|
|
|
|
break;
|
|
|
|
case NODE_FLY_HULL:
|
|
|
|
UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64));
|
|
|
|
// UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_SetOrigin(pev, pSrcNode->m_vecOrigin); // place the hull on the node
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!FBitSet(pev->flags, FL_ONGROUND))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "OFFGROUND!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// now build a yaw that points to the dest node, and get the distance.
|
2021-11-28 16:54:48 +01:00
|
|
|
if (j < 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "**** j = %d ****\n", j);
|
|
|
|
if (pTempPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(pTempPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
|
|
|
pDestNode = &WorldGraph.m_pNodes[pTempPool[pSrcNode->m_iFirstLink + j].m_iDestNode];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
vecSpot = pDestNode->m_vecOrigin;
|
|
|
|
//vecSpot.z = pev->origin.z;
|
|
|
|
|
|
|
|
if (hull < NODE_FLY_HULL)
|
|
|
|
{
|
|
|
|
int SaveFlags = pev->flags;
|
|
|
|
int MoveMode = WALKMOVE_WORLDONLY;
|
2021-11-28 15:32:26 +01:00
|
|
|
if ((pSrcNode->m_afNodeInfo & bits_NODE_WATER) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
pev->flags |= FL_SWIM;
|
|
|
|
MoveMode = WALKMOVE_NORMAL;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
flYaw = UTIL_VecToYaw(pDestNode->m_vecOrigin - pev->origin);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
flDist = (vecSpot - pev->origin).Length2D();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 15:32:26 +01:00
|
|
|
bool fWalkFailed = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// in this loop we take tiny steps from the current node to the nodes that it links to, one at a time.
|
|
|
|
// pev->angles.y = flYaw;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (step = 0; step < flDist && !fWalkFailed; step += HULL_STEP_SIZE)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
float stepSize = HULL_STEP_SIZE;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((step + stepSize) >= (flDist - 1))
|
2013-08-30 13:34:05 -07:00
|
|
|
stepSize = (flDist - step) - 1;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!WALK_MOVE(ENT(pev), flYaw, stepSize, MoveMode))
|
|
|
|
{ // can't take the next step
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
fWalkFailed = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64)
|
|
|
|
{
|
|
|
|
// ALERT( at_console, "bogus walk\n");
|
2021-11-28 16:54:48 +01:00
|
|
|
// we thought we
|
2021-11-19 13:45:16 +01:00
|
|
|
fWalkFailed = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (fWalkFailed)
|
|
|
|
{
|
|
|
|
|
|
|
|
//pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ];
|
|
|
|
|
|
|
|
// now me must eliminate the hull that couldn't walk this connection
|
2021-11-28 16:54:48 +01:00
|
|
|
switch (hull)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("NODE_SMALL_HULL step %d\n", step);
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL);
|
|
|
|
fSkipRemainingHulls = true; // don't bother checking larger hulls
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
case NODE_HUMAN_HULL:
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("NODE_HUMAN_HULL step %d\n", step);
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL);
|
|
|
|
fSkipRemainingHulls = true; // don't bother checking larger hulls
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
case NODE_LARGE_HULL:
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("NODE_LARGE_HULL step %d\n", step);
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo &= ~bits_LINK_LARGE_HULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pev->flags = SaveFlags;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TraceResult tr;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_TraceHull(pSrcNode->m_vecOrigin + Vector(0, 0, 32), pDestNode->m_vecOriginPeek + Vector(0, 0, 32), ignore_monsters, large_hull, ENT(pev), &tr);
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != tr.fStartSolid || tr.flFraction < 1.0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo &= ~bits_LINK_FLY_HULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pTempPool[pSrcNode->m_iFirstLink + j].m_afLinkInfo == 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Rejected Node %3d - Unreachable by ", pTempPool[pSrcNode->m_iFirstLink + j].m_iDestNode);
|
2021-11-28 16:54:48 +01:00
|
|
|
pTempPool[pSrcNode->m_iFirstLink + j] = pTempPool[pSrcNode->m_iFirstLink + (pSrcNode->m_cNumLinks - 1)];
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("Any Hull\n");
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
pSrcNode->m_cNumLinks--;
|
2021-11-28 16:54:48 +01:00
|
|
|
cPoolLinks--; // we just removed a link, so decrement the total number of links in the pool.
|
2013-08-30 13:34:05 -07:00
|
|
|
j--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("-------------------------------------------------------------------------------\n\n\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
cPoolLinks -= WorldGraph.RejectInlineLinks(pTempPool, file);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// now malloc a pool just large enough to hold the links that are actually used
|
|
|
|
WorldGraph.m_pLinkPool = (CLink*)calloc(sizeof(CLink), cPoolLinks);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!WorldGraph.m_pLinkPool)
|
|
|
|
{ // couldn't make the link pool!
|
|
|
|
ALERT(at_aiconsole, "Couldn't malloc LinkPool!\n");
|
|
|
|
if (pTempPool)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
free(pTempPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
WorldGraph.m_cLinks = cPoolLinks;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
//copy only the used portions of the TempPool into the graph's link pool
|
2013-08-30 13:34:05 -07:00
|
|
|
int iFinalPoolIndex = 0;
|
|
|
|
int iOldFirstLink;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
iOldFirstLink = WorldGraph.m_pNodes[i].m_iFirstLink; // store this, because we have to re-assign it before entering the copy loop
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[i].m_iFirstLink = iFinalPoolIndex;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (j = 0; j < WorldGraph.m_pNodes[i].m_cNumLinks; j++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pLinkPool[iFinalPoolIndex++] = pTempPool[iOldFirstLink + j];
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// Node sorting numbers linked nodes close to each other
|
|
|
|
//
|
|
|
|
WorldGraph.SortNodes();
|
|
|
|
|
|
|
|
// This is used for HashSearch
|
|
|
|
//
|
|
|
|
WorldGraph.BuildLinkLookups();
|
|
|
|
|
2021-11-19 13:45:16 +01:00
|
|
|
fPairsValid = true; // assume that the connection pairs are all valid to start
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("\n\n-------------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("Link Pairings:\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// link integrity check. The idea here is that if Node A links to Node B, node B should
|
|
|
|
// link to node A. If not, we have a situation that prevents us from using a basic
|
|
|
|
// optimization in the FindNearestLink function.
|
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
for (j = 0; j < WorldGraph.m_pNodes[i].m_cNumLinks; j++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int iLink;
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.HashSearch(WorldGraph.INodeLink(i, j), i, iLink);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (iLink < 0)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
fPairsValid = false; // unmatched link pair.
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code
|
|
|
|
// (in the find nearest line function)
|
2021-11-28 16:54:48 +01:00
|
|
|
if (fPairsValid)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("\nAll Connections are Paired!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Printf("-------------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("\n\n-------------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("Total Number of Connections in Pool: %d\n", cPoolLinks);
|
|
|
|
file.Printf("-------------------------------------------------------------------------------\n");
|
|
|
|
file.Printf("Connection Pool: %d bytes\n", sizeof(CLink) * cPoolLinks);
|
|
|
|
file.Printf("-------------------------------------------------------------------------------\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks);
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
// This is used for FindNearestNode
|
|
|
|
//
|
|
|
|
WorldGraph.BuildRegionTables();
|
|
|
|
|
|
|
|
|
|
|
|
// Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < WorldGraph.m_cNodes; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if ((WorldGraph.m_pNodes[i].m_afNodeInfo & bits_NODE_LAND) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_pNodes[i].m_vecOrigin.z -= NODE_HEIGHT;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pTempPool)
|
|
|
|
{ // free the temp pool
|
|
|
|
free(pTempPool);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Close();
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// We now have some graphing capabilities.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
WorldGraph.m_fGraphPresent = 1; //graph is in memory.
|
|
|
|
WorldGraph.m_fGraphPointersSet = 1; // since the graph was generated, the pointers are ready
|
|
|
|
WorldGraph.m_fRoutingComplete = 0; // Optimal routes aren't computed, yet.
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Compute and compress the routing information.
|
|
|
|
//
|
|
|
|
WorldGraph.ComputeStaticRoutingTables();
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// save the node graph for this level
|
2021-11-28 19:34:20 +01:00
|
|
|
WorldGraph.FSaveGraph(STRING(gpGlobals->mapname));
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_console, "Done.\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// returns a hardcoded path.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CTestHull::PathFind()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int iPath[50];
|
|
|
|
int iPathSize;
|
|
|
|
int i;
|
|
|
|
CNode *pNode, *pNextNode;
|
|
|
|
|
|
|
|
if (0 == WorldGraph.m_fGraphPresent || 0 == WorldGraph.m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
iPathSize = WorldGraph.FindShortestPath(iPath, 0, 19, 0, 0); // UNDONE use hull constant
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 == iPathSize)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "No Path!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "%d\n", iPathSize);
|
|
|
|
|
|
|
|
pNode = &WorldGraph.m_pNodes[iPath[0]];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < iPathSize - 1; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
pNextNode = &WorldGraph.m_pNodes[iPath[i + 1]];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
|
|
|
|
WRITE_BYTE(TE_SHOWLINE);
|
|
|
|
|
|
|
|
WRITE_COORD(pNode->m_vecOrigin.x);
|
|
|
|
WRITE_COORD(pNode->m_vecOrigin.y);
|
|
|
|
WRITE_COORD(pNode->m_vecOrigin.z + NODE_HEIGHT);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
WRITE_COORD(pNextNode->m_vecOrigin.x);
|
|
|
|
WRITE_COORD(pNextNode->m_vecOrigin.y);
|
|
|
|
WRITE_COORD(pNextNode->m_vecOrigin.z + NODE_HEIGHT);
|
2013-08-30 13:34:05 -07:00
|
|
|
MESSAGE_END();
|
|
|
|
|
|
|
|
pNode = pNextNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CStack Constructor
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
CStack::CStack()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_level = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// pushes a value onto the stack
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CStack::Push(int value)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_level >= MAX_STACK_NODES)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
printf("Error!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_stack[m_level] = value;
|
|
|
|
m_level++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// pops a value off of the stack
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CStack::Pop()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_level <= 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
m_level--;
|
2021-11-28 16:54:48 +01:00
|
|
|
return m_stack[m_level];
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// returns the value on the top of the stack
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CStack::Top()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
return m_stack[m_level - 1];
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// copies every element on the stack into an array LIFO
|
2013-08-30 13:34:05 -07:00
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CStack::CopyToArray(int* piArray)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < m_level; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
piArray[i] = m_stack[i];
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CQueue constructor
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
CQueue::CQueue()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_cSize = 0;
|
|
|
|
m_head = 0;
|
|
|
|
m_tail = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// inserts a value into the queue
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CQueue::Insert(int iValue, float fPriority)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (Full())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
printf("Queue is full!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_tail++;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_tail == MAX_STACK_NODES)
|
|
|
|
{ //wrap around
|
2013-08-30 13:34:05 -07:00
|
|
|
m_tail = 0;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_queue[m_tail].Id = iValue;
|
|
|
|
m_queue[m_tail].Priority = fPriority;
|
2013-08-30 13:34:05 -07:00
|
|
|
m_cSize++;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// removes a value from the queue (FIFO)
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CQueue::Remove(float& fPriority)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_head == MAX_STACK_NODES)
|
|
|
|
{ // wrap
|
2013-08-30 13:34:05 -07:00
|
|
|
m_head = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_cSize--;
|
2021-11-28 16:54:48 +01:00
|
|
|
fPriority = m_queue[m_head].Priority;
|
|
|
|
return m_queue[m_head++].Id;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CQueue constructor
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
CQueuePriority::CQueuePriority()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_cSize = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// inserts a value into the priority queue
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
void CQueuePriority::Insert(int iValue, float fPriority)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (Full())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
printf("Queue is full!\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_heap[m_cSize].Priority = fPriority;
|
|
|
|
m_heap[m_cSize].Id = iValue;
|
|
|
|
m_cSize++;
|
|
|
|
Heap_SiftUp();
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// removes the smallest item from the priority queue
|
|
|
|
//
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
int CQueuePriority::Remove(float& fPriority)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int iReturn = m_heap[0].Id;
|
|
|
|
fPriority = m_heap[0].Priority;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
m_cSize--;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_heap[0] = m_heap[m_cSize];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
Heap_SiftDown(0);
|
|
|
|
return iReturn;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
#define HEAP_LEFT_CHILD(x) (2 * (x) + 1)
|
|
|
|
#define HEAP_RIGHT_CHILD(x) (2 * (x) + 2)
|
|
|
|
#define HEAP_PARENT(x) (((x)-1) / 2)
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
void CQueuePriority::Heap_SiftDown(int iSubRoot)
|
|
|
|
{
|
|
|
|
int parent = iSubRoot;
|
|
|
|
int child = HEAP_LEFT_CHILD(parent);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
struct tag_HEAP_NODE Ref = m_heap[parent];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
while (child < m_cSize)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int rightchild = HEAP_RIGHT_CHILD(parent);
|
|
|
|
if (rightchild < m_cSize)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_heap[rightchild].Priority < m_heap[child].Priority)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
child = rightchild;
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
if (Ref.Priority <= m_heap[child].Priority)
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_heap[parent] = m_heap[child];
|
2013-08-30 13:34:05 -07:00
|
|
|
parent = child;
|
|
|
|
child = HEAP_LEFT_CHILD(parent);
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
m_heap[parent] = Ref;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CQueuePriority::Heap_SiftUp()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int child = m_cSize - 1;
|
2021-11-28 15:32:26 +01:00
|
|
|
while (0 != child)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int parent = HEAP_PARENT(child);
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_heap[parent].Priority <= m_heap[child].Priority)
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
|
|
|
|
struct tag_HEAP_NODE Tmp;
|
2021-11-28 16:54:48 +01:00
|
|
|
Tmp = m_heap[child];
|
|
|
|
m_heap[child] = m_heap[parent];
|
|
|
|
m_heap[parent] = Tmp;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
child = parent;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - FLoadGraph - attempts to load a node graph from disk.
|
|
|
|
// if the current level is maps/snar.bsp, maps/graphs/snar.nod
|
|
|
|
// will be loaded. If file cannot be loaded, the node tree
|
|
|
|
// will be created and saved to disk.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::FLoadGraph(const char* szMapName)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// make sure the directories have been made
|
2021-11-28 19:34:20 +01:00
|
|
|
g_pFileSystem->CreateDirHierarchy("maps/graphs", "GAMECONFIG");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
const std::string fileName{std::string{"maps/graphs/"} + szMapName + ".nod"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2022-04-04 15:56:53 +02:00
|
|
|
//Note: Allow loading graphs only from the mod directory itself.
|
2022-03-28 13:07:09 +02:00
|
|
|
//Do not allow loading from other games since they may have a different graph format.
|
2022-04-04 15:56:53 +02:00
|
|
|
const auto buffer = FileSystem_LoadFileIntoBuffer(fileName.c_str(), FileContentFormat::Binary, "GAMECONFIG");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
if (buffer.empty())
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
auto pMemFile = reinterpret_cast<const byte*>(buffer.data());
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
assert(buffer.size() <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
|
|
|
|
int length = static_cast<int>(buffer.size());
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read the graph version number
|
|
|
|
//
|
|
|
|
length -= sizeof(int);
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
int iVersion;
|
|
|
|
memcpy(&iVersion, pMemFile, sizeof(int));
|
|
|
|
pMemFile += sizeof(int);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
if (iVersion != GRAPH_VERSION)
|
|
|
|
{
|
|
|
|
// This file was written by a different build of the dll!
|
2013-08-30 13:34:05 -07:00
|
|
|
//
|
2021-11-28 19:34:20 +01:00
|
|
|
ALERT(at_aiconsole, "**ERROR** Graph version is %d, expected %d\n", iVersion, GRAPH_VERSION);
|
|
|
|
return false;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read the graph class
|
|
|
|
//
|
|
|
|
length -= sizeof(CGraph);
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(this, pMemFile, sizeof(CGraph));
|
|
|
|
pMemFile += sizeof(CGraph);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Set the pointers to zero, just in case we run out of memory.
|
|
|
|
//
|
|
|
|
m_pNodes = NULL;
|
|
|
|
m_pLinkPool = NULL;
|
|
|
|
m_di = NULL;
|
|
|
|
m_pRouteInfo = NULL;
|
|
|
|
m_pHashLinks = NULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Malloc for the nodes
|
|
|
|
//
|
|
|
|
m_pNodes = (CNode*)calloc(sizeof(CNode), m_cNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
if (!m_pNodes)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes);
|
|
|
|
return false;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read in all the nodes
|
|
|
|
//
|
|
|
|
length -= sizeof(CNode) * m_cNodes;
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(m_pNodes, pMemFile, sizeof(CNode) * m_cNodes);
|
|
|
|
pMemFile += sizeof(CNode) * m_cNodes;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Malloc for the link pool
|
|
|
|
//
|
|
|
|
m_pLinkPool = (CLink*)calloc(sizeof(CLink), m_cLinks);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
if (!m_pLinkPool)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks);
|
|
|
|
return false;
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read in all the links
|
|
|
|
//
|
|
|
|
length -= sizeof(CLink) * m_cLinks;
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(m_pLinkPool, pMemFile, sizeof(CLink) * m_cLinks);
|
|
|
|
pMemFile += sizeof(CLink) * m_cLinks;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Malloc for the sorting info.
|
|
|
|
//
|
|
|
|
m_di = (DIST_INFO*)calloc(sizeof(DIST_INFO), m_cNodes);
|
|
|
|
if (!m_di)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes);
|
|
|
|
return false;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read it in.
|
|
|
|
//
|
|
|
|
length -= sizeof(DIST_INFO) * m_cNodes;
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(m_di, pMemFile, sizeof(DIST_INFO) * m_cNodes);
|
|
|
|
pMemFile += sizeof(DIST_INFO) * m_cNodes;
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Malloc for the routing info.
|
|
|
|
//
|
|
|
|
m_fRoutingComplete = 0;
|
|
|
|
m_pRouteInfo = (char*)calloc(sizeof(char), m_nRouteInfo);
|
|
|
|
if (!m_pRouteInfo)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_CheckedCounter = 0;
|
|
|
|
for (int i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
m_di[i].m_CheckedEvent = 0;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read in the route information.
|
|
|
|
//
|
|
|
|
length -= sizeof(char) * m_nRouteInfo;
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(m_pRouteInfo, pMemFile, sizeof(char) * m_nRouteInfo);
|
|
|
|
pMemFile += sizeof(char) * m_nRouteInfo;
|
|
|
|
m_fRoutingComplete = 1;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// malloc for the hash links
|
|
|
|
//
|
|
|
|
m_pHashLinks = (short*)calloc(sizeof(short), m_nHashLinks);
|
|
|
|
if (!m_pHashLinks)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks);
|
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Read in the hash link information
|
|
|
|
//
|
|
|
|
length -= sizeof(short) * m_nHashLinks;
|
|
|
|
if (length < 0)
|
|
|
|
return false;
|
|
|
|
memcpy(m_pHashLinks, pMemFile, sizeof(short) * m_nHashLinks);
|
|
|
|
pMemFile += sizeof(short) * m_nHashLinks;
|
|
|
|
|
|
|
|
// Set the graph present flag, clear the pointers set flag
|
|
|
|
//
|
|
|
|
m_fGraphPresent = 1;
|
|
|
|
m_fGraphPointersSet = 0;
|
|
|
|
|
|
|
|
if (length != 0)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CGraph - FSaveGraph - It's not rocket science.
|
|
|
|
// this WILL overwrite existing files.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::FSaveGraph(const char* szMapName)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 == m_fGraphPresent || 0 == m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available or built
|
|
|
|
ALERT(at_aiconsole, "Graph not ready!\n");
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// make sure directories have been made
|
2021-11-28 19:34:20 +01:00
|
|
|
g_pFileSystem->CreateDirHierarchy("maps/graphs", "GAMECONFIG");
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
const std::string fileName{std::string{"maps/graphs/"} + szMapName + ".nod"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
FSFile file{fileName.c_str(), "wb", "GAMECONFIG"};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
ALERT(at_aiconsole, "Created: %s\n", fileName.c_str());
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!file)
|
|
|
|
{ // couldn't create
|
2021-11-28 19:34:20 +01:00
|
|
|
ALERT(at_aiconsole, "Couldn't Create: %s\n", fileName.c_str());
|
2021-11-19 13:43:33 +01:00
|
|
|
return false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// write the version
|
|
|
|
const int iVersion = GRAPH_VERSION;
|
|
|
|
file.Write(&iVersion, sizeof(int));
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// write the CGraph class
|
|
|
|
file.Write(this, sizeof(CGraph));
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// write the nodes
|
|
|
|
file.Write(m_pNodes, sizeof(CNode) * m_cNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// write the links
|
|
|
|
file.Write(m_pLinkPool, sizeof(CLink) * m_cLinks);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
file.Write(m_di, sizeof(DIST_INFO) * m_cNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
// Write the route info.
|
|
|
|
//
|
|
|
|
if (m_pRouteInfo && 0 != m_nRouteInfo)
|
|
|
|
{
|
|
|
|
file.Write(m_pRouteInfo, sizeof(char) * m_nRouteInfo);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 19:34:20 +01:00
|
|
|
|
|
|
|
if (m_pHashLinks && 0 != m_nHashLinks)
|
|
|
|
{
|
|
|
|
file.Write(m_pHashLinks, sizeof(short) * m_nHashLinks);
|
|
|
|
}
|
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// CGraph - FSetGraphPointers - Takes the modelnames of
|
2013-08-30 13:34:05 -07:00
|
|
|
// all of the brush ents that block connections in the node
|
|
|
|
// graph and resolves them into pointers to those entities.
|
|
|
|
// this is done after loading the graph from disk, whereupon
|
|
|
|
// the pointers are not valid.
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::FSetGraphPointers()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int i;
|
|
|
|
edict_t* pentLinkEnt;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (i = 0; i < m_cLinks; i++)
|
|
|
|
{ // go through all of the links
|
|
|
|
|
|
|
|
if (m_pLinkPool[i].m_pLinkEnt != NULL)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
char name[5];
|
|
|
|
// when graphs are saved, any valid pointers are will be non-zero, signifying that we should
|
|
|
|
// reset those pointers upon reloading. Any pointers that were NULL when the graph was saved
|
|
|
|
// will be NULL when reloaded, and will ignored by this function.
|
|
|
|
|
|
|
|
// m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes)
|
2021-11-28 16:54:48 +01:00
|
|
|
memcpy(name, m_pLinkPool[i].m_szLinkEntModelname, 4);
|
2013-08-30 13:34:05 -07:00
|
|
|
name[4] = 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
pentLinkEnt = FIND_ENTITY_BY_STRING(NULL, "model", name);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (FNullEnt(pentLinkEnt))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
// the ent isn't around anymore? Either there is a major problem, or it was removed from the world
|
|
|
|
// ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null.
|
|
|
|
ALERT(at_aiconsole, "**Could not find model %s\n", name);
|
|
|
|
m_pLinkPool[i].m_pLinkEnt = NULL;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pLinkPool[i].m_pLinkEnt = VARS(pentLinkEnt);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (!FBitSet(m_pLinkPool[i].m_pLinkEnt->flags, FL_GRAPHED))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pLinkPool[i].m_pLinkEnt->flags += FL_GRAPHED;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// the pointers are now set.
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fGraphPointersSet = 1;
|
2021-11-19 13:45:16 +01:00
|
|
|
return true;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================
|
2021-11-28 16:54:48 +01:00
|
|
|
// CGraph - CheckNODFile - this function checks the date of
|
2013-08-30 13:34:05 -07:00
|
|
|
// the BSP file that was just loaded and the date of the a
|
2021-11-28 16:54:48 +01:00
|
|
|
// ssociated .NOD file. If the NOD file is not present, or
|
2013-08-30 13:34:05 -07:00
|
|
|
// is older than the BSP file, we rebuild it.
|
|
|
|
//
|
2021-11-19 13:43:33 +01:00
|
|
|
// returns false if the .NOD file doesn't qualify and needs
|
2013-08-30 13:34:05 -07:00
|
|
|
// to be rebuilt.
|
|
|
|
//
|
|
|
|
// !!!BUGBUG - the file times we get back are 20 hours ahead!
|
2021-11-28 16:54:48 +01:00
|
|
|
// since this happens consistantly, we can still correctly
|
2013-08-30 13:34:05 -07:00
|
|
|
// determine which of the 2 files is newer. This needs fixed,
|
|
|
|
// though. ( I now suspect that we are getting GMT back from
|
|
|
|
// these functions and must compensate for local time ) (sjb)
|
|
|
|
//=========================================================
|
2021-11-29 20:31:17 +01:00
|
|
|
bool CGraph::CheckNODFile(const char* szMapName)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 19:34:20 +01:00
|
|
|
const std::string bspFileName{std::string{"maps/"} + szMapName + ".bsp"};
|
|
|
|
const std::string graphFileName{std::string{"maps/graphs/"} + szMapName + ".nod"};
|
2021-11-28 16:54:48 +01:00
|
|
|
|
2021-11-28 19:34:20 +01:00
|
|
|
bool retValue = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int iCompare;
|
2023-06-25 13:52:56 +02:00
|
|
|
if (FileSystem_CompareFileTime(bspFileName.c_str(), graphFileName.c_str(), &iCompare))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (iCompare > 0)
|
|
|
|
{ // BSP file is newer.
|
|
|
|
ALERT(at_aiconsole, ".NOD File will be updated\n\n");
|
2021-11-19 13:43:33 +01:00
|
|
|
retValue = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-19 13:43:33 +01:00
|
|
|
retValue = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return retValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define ENTRY_STATE_EMPTY -1
|
|
|
|
|
|
|
|
struct tagNodePair
|
|
|
|
{
|
|
|
|
short iSrc;
|
|
|
|
short iDest;
|
|
|
|
};
|
|
|
|
|
|
|
|
void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey)
|
|
|
|
{
|
|
|
|
struct tagNodePair np;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
np.iSrc = iSrcNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
np.iDest = iDestNode;
|
|
|
|
CRC32_t dwHash;
|
|
|
|
CRC32_INIT(&dwHash);
|
|
|
|
CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np));
|
|
|
|
dwHash = CRC32_FINAL(dwHash);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int di = m_HashPrimes[dwHash & 15];
|
|
|
|
int i = (dwHash >> 4) % m_nHashLinks;
|
|
|
|
while (m_pHashLinks[i] != ENTRY_STATE_EMPTY)
|
|
|
|
{
|
|
|
|
i += di;
|
|
|
|
if (i >= m_nHashLinks)
|
|
|
|
i -= m_nHashLinks;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
m_pHashLinks[i] = iKey;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void CGraph::HashSearch(int iSrcNode, int iDestNode, int& iKey)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
struct tagNodePair np;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
np.iSrc = iSrcNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
np.iDest = iDestNode;
|
|
|
|
CRC32_t dwHash;
|
|
|
|
CRC32_INIT(&dwHash);
|
|
|
|
CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np));
|
|
|
|
dwHash = CRC32_FINAL(dwHash);
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int di = m_HashPrimes[dwHash & 15];
|
|
|
|
int i = (dwHash >> 4) % m_nHashLinks;
|
|
|
|
while (m_pHashLinks[i] != ENTRY_STATE_EMPTY)
|
|
|
|
{
|
|
|
|
CLink& link = Link(m_pHashLinks[i]);
|
|
|
|
if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode)
|
|
|
|
{
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
i += di;
|
|
|
|
if (i >= m_nHashLinks)
|
|
|
|
i -= m_nHashLinks;
|
|
|
|
}
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
iKey = m_pHashLinks[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
#define NUMBER_OF_PRIMES 177
|
|
|
|
|
|
|
|
int Primes[NUMBER_OF_PRIMES] =
|
2021-11-28 16:54:48 +01:00
|
|
|
{1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
|
|
|
|
71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
|
|
|
|
157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239,
|
|
|
|
241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337,
|
|
|
|
347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433,
|
|
|
|
439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
|
|
|
|
547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641,
|
|
|
|
643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743,
|
|
|
|
751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857,
|
|
|
|
859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971,
|
|
|
|
977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0};
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
void CGraph::HashChoosePrimes(int TableSize)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int LargestPrime = TableSize / 2;
|
|
|
|
if (LargestPrime > Primes[NUMBER_OF_PRIMES - 2])
|
|
|
|
{
|
|
|
|
LargestPrime = Primes[NUMBER_OF_PRIMES - 2];
|
|
|
|
}
|
|
|
|
int Spacing = LargestPrime / 16;
|
|
|
|
|
|
|
|
// Pick a set primes that are evenly spaced from (0 to LargestPrime)
|
|
|
|
// We divide this interval into 16 equal sized zones. We want to find
|
|
|
|
// one prime number that best represents that zone.
|
|
|
|
//
|
|
|
|
int iPrime, iZone;
|
|
|
|
for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing)
|
|
|
|
{
|
|
|
|
// Search for a prime number that is less than the target zone
|
|
|
|
// number given by iZone.
|
|
|
|
//
|
|
|
|
int Lower = Primes[0];
|
|
|
|
for (int jPrime = 0; Primes[jPrime] != 0; jPrime++)
|
|
|
|
{
|
|
|
|
if (jPrime != 0 && TableSize % Primes[jPrime] == 0)
|
|
|
|
continue;
|
|
|
|
int Upper = Primes[jPrime];
|
|
|
|
if (Lower <= iZone && iZone <= Upper)
|
|
|
|
{
|
|
|
|
// Choose the closest lower prime number.
|
|
|
|
//
|
|
|
|
if (iZone - Lower <= Upper - iZone)
|
|
|
|
{
|
|
|
|
m_HashPrimes[iPrime++] = Lower;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_HashPrimes[iPrime++] = Upper;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Lower = Upper;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alternate negative and positive numbers
|
|
|
|
//
|
|
|
|
for (iPrime = 0; iPrime < 16; iPrime += 2)
|
|
|
|
{
|
|
|
|
m_HashPrimes[iPrime] = TableSize - m_HashPrimes[iPrime];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shuffle the set of primes to reduce correlation with bits in
|
|
|
|
// hash key.
|
|
|
|
//
|
|
|
|
for (iPrime = 0; iPrime < 16 - 1; iPrime++)
|
|
|
|
{
|
|
|
|
int Pick = RANDOM_LONG(0, 15 - iPrime);
|
|
|
|
int Temp = m_HashPrimes[Pick];
|
|
|
|
m_HashPrimes[Pick] = m_HashPrimes[15 - iPrime];
|
|
|
|
m_HashPrimes[15 - iPrime] = Temp;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Renumber nodes so that nodes that link together are together.
|
|
|
|
//
|
|
|
|
#define UNNUMBERED_NODE -1
|
2021-03-05 20:54:33 +01:00
|
|
|
void CGraph::SortNodes()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// We are using m_iPreviousNode to be the new node number.
|
|
|
|
// After assigning new node numbers to everything, we move
|
|
|
|
// things and patchup the links.
|
|
|
|
//
|
|
|
|
int iNodeCnt = 0;
|
|
|
|
int i;
|
|
|
|
m_pNodes[0].m_iPreviousNode = iNodeCnt++;
|
|
|
|
|
|
|
|
for (i = 1; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
// Run through all of this node's neighbors
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int j = 0; j < m_pNodes[i].m_cNumLinks; j++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int iDestNode = INodeLink(i, j);
|
|
|
|
if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE)
|
|
|
|
{
|
|
|
|
m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign remaining node numbers to unlinked nodes.
|
|
|
|
//
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE)
|
|
|
|
{
|
|
|
|
m_pNodes[i].m_iPreviousNode = iNodeCnt++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Alter links to reflect new node numbers.
|
|
|
|
//
|
|
|
|
for (i = 0; i < m_cLinks; i++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode;
|
2013-08-30 13:34:05 -07:00
|
|
|
m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rearrange nodes to reflect new node numbering.
|
|
|
|
//
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
while (m_pNodes[i].m_iPreviousNode != i)
|
|
|
|
{
|
|
|
|
// Move current node off to where it should be, and bring
|
|
|
|
// that other node back into the current slot.
|
|
|
|
//
|
|
|
|
int iDestNode = m_pNodes[i].m_iPreviousNode;
|
|
|
|
CNode TempNode = m_pNodes[iDestNode];
|
|
|
|
m_pNodes[iDestNode] = m_pNodes[i];
|
|
|
|
m_pNodes[i] = TempNode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CGraph::BuildLinkLookups()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_nHashLinks = 3 * m_cLinks / 2 + 3;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
HashChoosePrimes(m_nHashLinks);
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pHashLinks = (short*)calloc(sizeof(short), m_nHashLinks);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (!m_pHashLinks)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < m_nHashLinks; i++)
|
|
|
|
{
|
|
|
|
m_pHashLinks[i] = ENTRY_STATE_EMPTY;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < m_cLinks; i++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CLink& link = Link(i);
|
2013-08-30 13:34:05 -07:00
|
|
|
HashInsert(link.m_iSrcNode, link.m_iDestNode, i);
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
for (i = 0; i < m_cLinks; i++)
|
|
|
|
{
|
|
|
|
CLink &link = Link(i);
|
|
|
|
int iKey;
|
|
|
|
HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey);
|
|
|
|
if (iKey != i)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2021-03-05 20:54:33 +01:00
|
|
|
void CGraph::BuildRegionTables()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_di)
|
|
|
|
free(m_di);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
// Go ahead and setup for range searching the nodes for FindNearestNodes
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
m_di = (DIST_INFO*)calloc(sizeof(DIST_INFO), m_cNodes);
|
2013-08-30 13:34:05 -07:00
|
|
|
if (!m_di)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate regions for all the nodes.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_RegionMin[i] = 999999999.0; // just a big number out there;
|
2013-08-30 13:34:05 -07:00
|
|
|
m_RegionMax[i] = -999999999.0; // just a big number out there;
|
|
|
|
}
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0])
|
|
|
|
m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x;
|
|
|
|
if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1])
|
|
|
|
m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y;
|
|
|
|
if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2])
|
|
|
|
m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z;
|
|
|
|
|
|
|
|
if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0])
|
|
|
|
m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x;
|
|
|
|
if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1])
|
|
|
|
m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y;
|
|
|
|
if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2])
|
|
|
|
m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z;
|
|
|
|
}
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]);
|
|
|
|
m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]);
|
|
|
|
m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
int j;
|
|
|
|
for (j = 0; j < NUM_RANGES; j++)
|
|
|
|
{
|
|
|
|
m_RangeStart[i][j] = 255;
|
|
|
|
m_RangeEnd[i][j] = 0;
|
|
|
|
}
|
|
|
|
for (j = 0; j < m_cNodes; j++)
|
|
|
|
{
|
|
|
|
m_di[j].m_SortedBy[i] = j;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 0; j < m_cNodes - 1; j++)
|
|
|
|
{
|
|
|
|
int jNode = m_di[j].m_SortedBy[i];
|
|
|
|
int jCodeX = m_pNodes[jNode].m_Region[0];
|
|
|
|
int jCodeY = m_pNodes[jNode].m_Region[1];
|
|
|
|
int jCodeZ = m_pNodes[jNode].m_Region[2];
|
|
|
|
int jCode;
|
|
|
|
switch (i)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int k = j + 1; k < m_cNodes; k++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int kNode = m_di[k].m_SortedBy[i];
|
|
|
|
int kCodeX = m_pNodes[kNode].m_Region[0];
|
|
|
|
int kCodeY = m_pNodes[kNode].m_Region[1];
|
|
|
|
int kCodeZ = m_pNodes[kNode].m_Region[2];
|
|
|
|
int kCode;
|
|
|
|
switch (i)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (kCode < jCode)
|
|
|
|
{
|
|
|
|
// Swap j and k entries.
|
|
|
|
//
|
|
|
|
int Tmp = m_di[j].m_SortedBy[i];
|
|
|
|
m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i];
|
|
|
|
m_di[k].m_SortedBy[i] = Tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate lookup tables.
|
|
|
|
//
|
|
|
|
for (i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
|
|
|
int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0];
|
|
|
|
int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1];
|
|
|
|
int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2];
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (i < m_RangeStart[0][CodeX])
|
|
|
|
{
|
|
|
|
m_RangeStart[0][CodeX] = i;
|
|
|
|
}
|
|
|
|
if (i < m_RangeStart[1][CodeY])
|
|
|
|
{
|
|
|
|
m_RangeStart[1][CodeY] = i;
|
|
|
|
}
|
|
|
|
if (i < m_RangeStart[2][CodeZ])
|
|
|
|
{
|
|
|
|
m_RangeStart[2][CodeZ] = i;
|
|
|
|
}
|
|
|
|
if (m_RangeEnd[0][CodeX] < i)
|
|
|
|
{
|
|
|
|
m_RangeEnd[0][CodeX] = i;
|
|
|
|
}
|
|
|
|
if (m_RangeEnd[1][CodeY] < i)
|
|
|
|
{
|
|
|
|
m_RangeEnd[1][CodeY] = i;
|
|
|
|
}
|
|
|
|
if (m_RangeEnd[2][CodeZ] < i)
|
|
|
|
{
|
|
|
|
m_RangeEnd[2][CodeZ] = i;
|
|
|
|
}
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize the cache.
|
|
|
|
//
|
|
|
|
memset(m_Cache, 0, sizeof(m_Cache));
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CGraph::ComputeStaticRoutingTables()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int nRoutes = m_cNodes * m_cNodes;
|
|
|
|
#define FROM_TO(x, y) ((x)*m_cNodes + (y))
|
|
|
|
short* Routes = new short[nRoutes];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
int* pMyPath = new int[m_cNodes];
|
|
|
|
unsigned short* BestNextNodes = new unsigned short[m_cNodes];
|
|
|
|
char* pRoute = new char[m_cNodes * 2];
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
if (Routes && pMyPath && BestNextNodes && pRoute)
|
|
|
|
{
|
|
|
|
int nTotalCompressedSize = 0;
|
|
|
|
for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++)
|
|
|
|
{
|
|
|
|
for (int iCap = 0; iCap < 2; iCap++)
|
|
|
|
{
|
|
|
|
int iCapMask;
|
|
|
|
switch (iCap)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
iCapMask = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize Routing table to uncalculated.
|
|
|
|
//
|
|
|
|
int iFrom;
|
|
|
|
for (iFrom = 0; iFrom < m_cNodes; iFrom++)
|
|
|
|
{
|
|
|
|
for (int iTo = 0; iTo < m_cNodes; iTo++)
|
|
|
|
{
|
|
|
|
Routes[FROM_TO(iFrom, iTo)] = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (iFrom = 0; iFrom < m_cNodes; iFrom++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int iTo = m_cNodes - 1; iTo >= 0; iTo--)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (Routes[FROM_TO(iFrom, iTo)] != -1)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask);
|
|
|
|
|
|
|
|
// Use the computed path to update the routing table.
|
|
|
|
//
|
|
|
|
if (cPathSize > 1)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int iNode = 0; iNode < cPathSize - 1; iNode++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int iStart = pMyPath[iNode];
|
2021-11-28 16:54:48 +01:00
|
|
|
int iNext = pMyPath[iNode + 1];
|
|
|
|
for (int iNode1 = iNode + 1; iNode1 < cPathSize; iNode1++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
int iEnd = pMyPath[iNode1];
|
|
|
|
Routes[FROM_TO(iStart, iEnd)] = iNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if 0
|
|
|
|
// Well, at first glance, this should work, but actually it's safer
|
|
|
|
// to be told explictly that you can take a series of node in a
|
|
|
|
// particular direction. Some links don't appear to have links in
|
|
|
|
// the opposite direction.
|
|
|
|
//
|
|
|
|
for (iNode = cPathSize-1; iNode >= 1; iNode--)
|
|
|
|
{
|
|
|
|
int iStart = pMyPath[iNode];
|
|
|
|
int iNext = pMyPath[iNode-1];
|
|
|
|
for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--)
|
|
|
|
{
|
|
|
|
int iEnd = pMyPath[iNode1];
|
|
|
|
Routes[FROM_TO(iStart, iEnd)] = iNext;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Routes[FROM_TO(iFrom, iTo)] = iFrom;
|
|
|
|
Routes[FROM_TO(iTo, iFrom)] = iTo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (iFrom = 0; iFrom < m_cNodes; iFrom++)
|
|
|
|
{
|
|
|
|
for (int iTo = 0; iTo < m_cNodes; iTo++)
|
|
|
|
{
|
|
|
|
BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compress this node's routing table.
|
|
|
|
//
|
|
|
|
int iLastNode = 9999999; // just really big.
|
|
|
|
int cSequence = 0;
|
|
|
|
int cRepeats = 0;
|
|
|
|
int CompressedSize = 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
char* p = pRoute;
|
2013-08-30 13:34:05 -07:00
|
|
|
for (int i = 0; i < m_cNodes; i++)
|
|
|
|
{
|
2021-11-19 14:40:35 +01:00
|
|
|
bool CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127);
|
|
|
|
bool CanSequence = (BestNextNodes[i] == i && cSequence < 128);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != cRepeats)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (CanRepeat)
|
|
|
|
{
|
|
|
|
cRepeats++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Emit the repeat phrase.
|
|
|
|
//
|
|
|
|
CompressedSize += 2; // (count-1, iLastNode-i)
|
|
|
|
*p++ = cRepeats - 1;
|
|
|
|
int a = iLastNode - iFrom;
|
|
|
|
int b = iLastNode - iFrom + m_cNodes;
|
|
|
|
int c = iLastNode - iFrom - m_cNodes;
|
|
|
|
if (-128 <= a && a <= 127)
|
|
|
|
{
|
|
|
|
*p++ = a;
|
|
|
|
}
|
|
|
|
else if (-128 <= b && b <= 127)
|
|
|
|
{
|
|
|
|
*p++ = b;
|
|
|
|
}
|
|
|
|
else if (-128 <= c && c <= 127)
|
|
|
|
{
|
|
|
|
*p++ = c;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
cRepeats = 0;
|
|
|
|
|
|
|
|
if (CanSequence)
|
|
|
|
{
|
|
|
|
// Start a sequence.
|
|
|
|
//
|
|
|
|
cSequence++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Start another repeat.
|
|
|
|
//
|
|
|
|
cRepeats++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 15:32:26 +01:00
|
|
|
else if (0 != cSequence)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (CanSequence)
|
|
|
|
{
|
|
|
|
cSequence++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// It may be advantageous to combine
|
|
|
|
// a single-entry sequence phrase with the
|
|
|
|
// next repeat phrase.
|
|
|
|
//
|
|
|
|
if (cSequence == 1 && CanRepeat)
|
|
|
|
{
|
|
|
|
// Combine with repeat phrase.
|
|
|
|
//
|
|
|
|
cRepeats = 2;
|
|
|
|
cSequence = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Emit the sequence phrase.
|
|
|
|
//
|
|
|
|
CompressedSize += 1; // (-count)
|
|
|
|
*p++ = -cSequence;
|
|
|
|
cSequence = 0;
|
|
|
|
|
|
|
|
// Start a repeat sequence.
|
|
|
|
//
|
|
|
|
cRepeats++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (CanSequence)
|
|
|
|
{
|
|
|
|
// Start a sequence phrase.
|
|
|
|
//
|
|
|
|
cSequence++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Start a repeat sequence.
|
|
|
|
//
|
|
|
|
cRepeats++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
iLastNode = BestNextNodes[i];
|
|
|
|
}
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != cRepeats)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Emit the repeat phrase.
|
|
|
|
//
|
|
|
|
CompressedSize += 2;
|
|
|
|
*p++ = cRepeats - 1;
|
|
|
|
#if 0
|
|
|
|
iLastNode = iFrom + *pRoute;
|
|
|
|
if (iLastNode >= m_cNodes) iLastNode -= m_cNodes;
|
|
|
|
else if (iLastNode < 0) iLastNode += m_cNodes;
|
|
|
|
#endif
|
|
|
|
int a = iLastNode - iFrom;
|
|
|
|
int b = iLastNode - iFrom + m_cNodes;
|
|
|
|
int c = iLastNode - iFrom - m_cNodes;
|
|
|
|
if (-128 <= a && a <= 127)
|
|
|
|
{
|
|
|
|
*p++ = a;
|
|
|
|
}
|
|
|
|
else if (-128 <= b && b <= 127)
|
|
|
|
{
|
|
|
|
*p++ = b;
|
|
|
|
}
|
|
|
|
else if (-128 <= c && c <= 127)
|
|
|
|
{
|
|
|
|
*p++ = c;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
2021-11-28 15:32:26 +01:00
|
|
|
if (0 != cSequence)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Emit the Sequence phrase.
|
|
|
|
//
|
|
|
|
CompressedSize += 1;
|
|
|
|
*p++ = -cSequence;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Go find a place to store this thing and point to it.
|
|
|
|
//
|
|
|
|
int nRoute = p - pRoute;
|
|
|
|
if (m_pRouteInfo)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < m_nRouteInfo - nRoute; i++)
|
|
|
|
{
|
|
|
|
if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i < m_nRouteInfo - nRoute)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[iFrom].m_pNextBestNode[iHull][iCap] = i;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
char* Tmp = (char*)calloc(sizeof(char), (m_nRouteInfo + nRoute));
|
2013-08-30 13:34:05 -07:00
|
|
|
memcpy(Tmp, m_pRouteInfo, m_nRouteInfo);
|
|
|
|
free(m_pRouteInfo);
|
|
|
|
m_pRouteInfo = Tmp;
|
|
|
|
memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute);
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[iFrom].m_pNextBestNode[iHull][iCap] = m_nRouteInfo;
|
2013-08-30 13:34:05 -07:00
|
|
|
m_nRouteInfo += nRoute;
|
|
|
|
nTotalCompressedSize += CompressedSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_nRouteInfo = nRoute;
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pRouteInfo = (char*)calloc(sizeof(char), nRoute);
|
2013-08-30 13:34:05 -07:00
|
|
|
memcpy(m_pRouteInfo, pRoute, nRoute);
|
2021-11-28 16:54:48 +01:00
|
|
|
m_pNodes[iFrom].m_pNextBestNode[iHull][iCap] = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
nTotalCompressedSize += CompressedSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
}
|
|
|
|
ALERT(at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 22:06:41 +01:00
|
|
|
|
|
|
|
delete[] Routes;
|
|
|
|
delete[] BestNextNodes;
|
|
|
|
delete[] pRoute;
|
|
|
|
delete[] pMyPath;
|
|
|
|
|
2013-08-30 13:34:05 -07:00
|
|
|
Routes = 0;
|
|
|
|
BestNextNodes = 0;
|
|
|
|
pRoute = 0;
|
|
|
|
pMyPath = 0;
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
TestRoutingTables();
|
|
|
|
#endif
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fRoutingComplete = 1;
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test those routing tables. Doesn't really work, yet.
|
|
|
|
//
|
2021-11-29 20:31:17 +01:00
|
|
|
void CGraph::TestRoutingTables()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
int* pMyPath = new int[m_cNodes];
|
|
|
|
int* pMyPath2 = new int[m_cNodes];
|
2013-08-30 13:34:05 -07:00
|
|
|
if (pMyPath && pMyPath2)
|
|
|
|
{
|
|
|
|
for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++)
|
|
|
|
{
|
|
|
|
for (int iCap = 0; iCap < 2; iCap++)
|
|
|
|
{
|
|
|
|
int iCapMask;
|
|
|
|
switch (iCap)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
iCapMask = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int iFrom = 0; iFrom < m_cNodes; iFrom++)
|
|
|
|
{
|
|
|
|
for (int iTo = 0; iTo < m_cNodes; iTo++)
|
|
|
|
{
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fRoutingComplete = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask);
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fRoutingComplete = 1;
|
2013-08-30 13:34:05 -07:00
|
|
|
int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask);
|
|
|
|
|
|
|
|
// Unless we can look at the entire path, we can verify that it's correct.
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (cPathSize2 == MAX_PATH_SIZE)
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
// Compare distances.
|
|
|
|
//
|
2013-08-30 13:34:05 -07:00
|
|
|
#if 1
|
|
|
|
float flDistance1 = 0.0;
|
2020-02-13 17:50:56 +01:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int i = 0; i < cPathSize1 - 1; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Find the link from pMyPath[i] to pMyPath[i+1]
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pMyPath[i] == pMyPath[i + 1])
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int iVisitNode;
|
2021-11-19 14:40:35 +01:00
|
|
|
bool bFound = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
iVisitNode = INodeLink(pMyPath[i], iLink);
|
|
|
|
if (iVisitNode == pMyPath[i + 1])
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
flDistance1 += m_pLinkPool[m_pNodes[pMyPath[i]].m_iFirstLink + iLink].m_flWeight;
|
2021-11-19 13:45:16 +01:00
|
|
|
bFound = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!bFound)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "No link.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
float flDistance2 = 0.0;
|
2021-11-28 16:54:48 +01:00
|
|
|
for (int i = 0; i < cPathSize2 - 1; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
// Find the link from pMyPath2[i] to pMyPath2[i+1]
|
|
|
|
//
|
2021-11-28 16:54:48 +01:00
|
|
|
if (pMyPath2[i] == pMyPath2[i + 1])
|
|
|
|
continue;
|
2013-08-30 13:34:05 -07:00
|
|
|
int iVisitNode;
|
2021-11-19 14:40:35 +01:00
|
|
|
bool bFound = false;
|
2013-08-30 13:34:05 -07:00
|
|
|
for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
iVisitNode = INodeLink(pMyPath2[i], iLink);
|
|
|
|
if (iVisitNode == pMyPath2[i + 1])
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
flDistance2 += m_pLinkPool[m_pNodes[pMyPath2[i]].m_iFirstLink + iLink].m_flWeight;
|
2021-11-19 13:45:16 +01:00
|
|
|
bFound = true;
|
2013-08-30 13:34:05 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!bFound)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "No link.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fabs(flDistance1 - flDistance2) > 0.10)
|
|
|
|
{
|
|
|
|
#else
|
2021-11-28 16:54:48 +01:00
|
|
|
if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int) * cPathSize1) != 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
#endif
|
|
|
|
ALERT(at_aiconsole, "Routing is inconsistent!!!\n");
|
|
|
|
ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap);
|
|
|
|
for (int i = 0; i < cPathSize1; i++)
|
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "%d ", pMyPath[i]);
|
|
|
|
}
|
|
|
|
ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap);
|
2020-02-13 17:50:56 +01:00
|
|
|
for (int i = 0; i < cPathSize2; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
ALERT(at_aiconsole, "%d ", pMyPath2[i]);
|
|
|
|
}
|
|
|
|
ALERT(at_aiconsole, "\n");
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fRoutingComplete = 0;
|
2013-08-30 13:34:05 -07:00
|
|
|
cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask);
|
2021-11-28 15:32:26 +01:00
|
|
|
m_fRoutingComplete = 1;
|
2013-08-30 13:34:05 -07:00
|
|
|
cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask);
|
|
|
|
goto EnoughSaid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EnoughSaid:
|
|
|
|
|
2021-11-28 22:06:41 +01:00
|
|
|
delete[] pMyPath;
|
|
|
|
delete[] pMyPath2;
|
2013-08-30 13:34:05 -07:00
|
|
|
pMyPath = 0;
|
|
|
|
pMyPath2 = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//=========================================================
|
|
|
|
// CNodeViewer - Draws a graph of the shorted path from all nodes
|
|
|
|
// to current location (typically the player). It then draws
|
|
|
|
// as many connects as it can per frame, trying not to overflow the buffer
|
|
|
|
//=========================================================
|
|
|
|
class CNodeViewer : public CBaseEntity
|
|
|
|
{
|
|
|
|
public:
|
2021-03-05 23:07:22 +01:00
|
|
|
void Spawn() override;
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int m_iBaseNode;
|
|
|
|
int m_iDraw;
|
2021-11-28 16:54:48 +01:00
|
|
|
int m_nVisited;
|
2013-08-30 13:34:05 -07:00
|
|
|
int m_aFrom[128];
|
|
|
|
int m_aTo[128];
|
|
|
|
int m_iHull;
|
|
|
|
int m_afNodeType;
|
|
|
|
Vector m_vecColor;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void FindNodeConnections(int iNode);
|
|
|
|
void AddNode(int iFrom, int iTo);
|
2021-03-05 20:54:33 +01:00
|
|
|
void EXPORT DrawThink();
|
2013-08-30 13:34:05 -07:00
|
|
|
};
|
2021-11-28 16:54:48 +01:00
|
|
|
LINK_ENTITY_TO_CLASS(node_viewer, CNodeViewer);
|
|
|
|
LINK_ENTITY_TO_CLASS(node_viewer_human, CNodeViewer);
|
|
|
|
LINK_ENTITY_TO_CLASS(node_viewer_fly, CNodeViewer);
|
|
|
|
LINK_ENTITY_TO_CLASS(node_viewer_large, CNodeViewer);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void CNodeViewer::Spawn()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
if (0 == WorldGraph.m_fGraphPresent || 0 == WorldGraph.m_fGraphPointersSet)
|
|
|
|
{ // protect us in the case that the node graph isn't available or built
|
|
|
|
ALERT(at_console, "Graph not ready!\n");
|
|
|
|
UTIL_Remove(this);
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (FClassnameIs(pev, "node_viewer_fly"))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_iHull = NODE_FLY_HULL;
|
|
|
|
m_afNodeType = bits_NODE_AIR;
|
2021-11-28 16:54:48 +01:00
|
|
|
m_vecColor = Vector(160, 100, 255);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
2021-11-28 16:54:48 +01:00
|
|
|
else if (FClassnameIs(pev, "node_viewer_large"))
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
m_iHull = NODE_LARGE_HULL;
|
|
|
|
m_afNodeType = bits_NODE_LAND | bits_NODE_WATER;
|
2021-11-28 16:54:48 +01:00
|
|
|
m_vecColor = Vector(100, 255, 160);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_iHull = NODE_HUMAN_HULL;
|
|
|
|
m_afNodeType = bits_NODE_LAND | bits_NODE_WATER;
|
2021-11-28 16:54:48 +01:00
|
|
|
m_vecColor = Vector(255, 160, 100);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
m_iBaseNode = WorldGraph.FindNearestNode(pev->origin, m_afNodeType);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
if (m_iBaseNode < 0)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_console, "No nearby node\n");
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_nVisited = 0;
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "basenode %d\n", m_iBaseNode);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
if (WorldGraph.m_cNodes < 128)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < WorldGraph.m_cNodes; i++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
AddNode(i, WorldGraph.NextNodeInRoute(i, m_iBaseNode, m_iHull, 0));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// do a depth traversal
|
2021-11-28 16:54:48 +01:00
|
|
|
FindNodeConnections(m_iBaseNode);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
int start = 0;
|
|
|
|
int end;
|
2021-11-28 16:54:48 +01:00
|
|
|
do
|
|
|
|
{
|
2013-08-30 13:34:05 -07:00
|
|
|
end = m_nVisited;
|
|
|
|
// ALERT( at_console, "%d :", m_nVisited );
|
|
|
|
for (end = m_nVisited; start < end; start++)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
FindNodeConnections(m_aFrom[start]);
|
|
|
|
FindNodeConnections(m_aTo[start]);
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
} while (end != m_nVisited);
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
ALERT(at_aiconsole, "%d nodes\n", m_nVisited);
|
2013-08-30 13:34:05 -07:00
|
|
|
|
|
|
|
m_iDraw = 0;
|
2021-11-28 16:54:48 +01:00
|
|
|
SetThink(&CNodeViewer::DrawThink);
|
2013-08-30 13:34:05 -07:00
|
|
|
pev->nextthink = gpGlobals->time;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CNodeViewer::FindNodeConnections(int iNode)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
AddNode(iNode, WorldGraph.NextNodeInRoute(iNode, m_iBaseNode, m_iHull, 0));
|
|
|
|
for (int i = 0; i < WorldGraph.m_pNodes[iNode].m_cNumLinks; i++)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
CLink* pToLink = &WorldGraph.NodeLink(iNode, i);
|
|
|
|
AddNode(pToLink->m_iDestNode, WorldGraph.NextNodeInRoute(pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0));
|
2013-08-30 13:34:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-28 16:54:48 +01:00
|
|
|
void CNodeViewer::AddNode(int iFrom, int iTo)
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
if (m_nVisited >= 128)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (iFrom == iTo)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (int i = 0; i < m_nVisited; i++)
|
|
|
|
{
|
|
|
|
if (m_aFrom[i] == iFrom && m_aTo[i] == iTo)
|
|
|
|
return;
|
|
|
|
if (m_aFrom[i] == iTo && m_aTo[i] == iFrom)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_aFrom[m_nVisited] = iFrom;
|
|
|
|
m_aTo[m_nVisited] = iTo;
|
|
|
|
m_nVisited++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-29 20:31:17 +01:00
|
|
|
void CNodeViewer::DrawThink()
|
2013-08-30 13:34:05 -07:00
|
|
|
{
|
|
|
|
pev->nextthink = gpGlobals->time;
|
|
|
|
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
|
|
{
|
|
|
|
if (m_iDraw == m_nVisited)
|
|
|
|
{
|
2021-11-28 16:54:48 +01:00
|
|
|
UTIL_Remove(this);
|
2013-08-30 13:34:05 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
extern short g_sModelIndexLaser;
|
2021-11-28 16:54:48 +01:00
|
|
|
MESSAGE_BEGIN(MSG_BROADCAST, SVC_TEMPENTITY);
|
|
|
|
WRITE_BYTE(TE_BEAMPOINTS);
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aFrom[m_iDraw]].m_vecOrigin.x);
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aFrom[m_iDraw]].m_vecOrigin.y);
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aFrom[m_iDraw]].m_vecOrigin.z + NODE_HEIGHT);
|
|
|
|
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aTo[m_iDraw]].m_vecOrigin.x);
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aTo[m_iDraw]].m_vecOrigin.y);
|
|
|
|
WRITE_COORD(WorldGraph.m_pNodes[m_aTo[m_iDraw]].m_vecOrigin.z + NODE_HEIGHT);
|
|
|
|
WRITE_SHORT(g_sModelIndexLaser);
|
|
|
|
WRITE_BYTE(0); // framerate
|
|
|
|
WRITE_BYTE(0); // framerate
|
|
|
|
WRITE_BYTE(250); // life
|
|
|
|
WRITE_BYTE(40); // width
|
|
|
|
WRITE_BYTE(0); // noise
|
|
|
|
WRITE_BYTE(m_vecColor.x); // r, g, b
|
|
|
|
WRITE_BYTE(m_vecColor.y); // r, g, b
|
|
|
|
WRITE_BYTE(m_vecColor.z); // r, g, b
|
|
|
|
WRITE_BYTE(128); // brightness
|
|
|
|
WRITE_BYTE(0); // speed
|
2013-08-30 13:34:05 -07:00
|
|
|
MESSAGE_END();
|
|
|
|
|
|
|
|
m_iDraw++;
|
|
|
|
}
|
|
|
|
}
|