Improved retreat from danger - FreeSlave/halflife-updated GitHub Wiki
Some monsters in Half-Life can run to cover upon hearing the AI Danger sound. AI sounds are not actually audible, but they act as special markers that NPCs can perceive. Danger sounds are usually produced by grenades shortly before detonation, giving monsters the chance to run away once they "hear" it.
TASK_FIND_COVER_FROM_BEST_SOUND in dlls/schedule.cpp
is a task that calls FindCover
function which tries to find a node where the monster will not be visible to the threat. If found, such node is considered to be a suitable hideout for a monster and they will try to run to this spot. FindCover
is used in multiple places in Half-Life SDK, but in this particular case it's not really necessary for a monster to hide from danger behind a cover. Just running away would be enough to escape the grenade impact radius. FindCover
won't help in the open field as it won't find any cover, so we're going to write an alternative function for finding a node to run to. Let's name it FindRetreat
.
First of all, go to dlls/basemonster.h
and add a new function declaration, somewhere in CBaseMonster
class declaration, e.g. right after the FindCover
.
virtual bool FindCover(Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist);
+ bool FindRetreat(Vector vecThreat, float flMinDist, float flMaxDist);
FindRetreat
has a similar parameters to FindCover
, but it does not need view offset of a threat. We also don't make it virtual, as you most likely are not going to override it in derived classes.
Now go to dlls/monsters.cpp
and after FindCover
implementation add this:
bool CBaseMonster::FindRetreat(Vector vecThreat, float flMinDist, float flMaxDist)
{
int i;
int iMyHullIndex;
int iMyNode;
int iThreatNode;
float flDist;
if (0 == flMaxDist)
{
// user didn't supply a MaxDist, so work up a crazy one.
flMaxDist = 784;
}
if (flMinDist > 0.5 * flMaxDist)
{
#if _DEBUG
ALERT(at_console, "FindRetreat MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist);
#endif
flMinDist = 0.5 * flMaxDist;
}
if (0 == WorldGraph.m_fGraphPresent || 0 == WorldGraph.m_fGraphPointersSet)
{
ALERT(at_aiconsole, "Graph not ready for findretreat!\n");
return false;
}
iMyNode = WorldGraph.FindNearestNode(pev->origin, this);
iThreatNode = WorldGraph.FindNearestNode(vecThreat, this);
iMyHullIndex = WorldGraph.HullIndex(this);
if (iMyNode == NO_NODE)
{
ALERT(at_aiconsole, "FindRetreat() - %s has no nearest node!\n", STRING(pev->classname));
return false;
}
if (iThreatNode == NO_NODE)
{
// ALERT ( at_aiconsole, "FindRetreat() - Threat has no nearest node!\n" );
iThreatNode = iMyNode;
// return false;
}
// we'll do a rough sample to find nodes that are relatively nearby
for (i = 0; i < WorldGraph.m_cNodes; i++)
{
int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes;
CNode& node = WorldGraph.Node(nodeNumber);
// could use an optimization here!!
flDist = (pev->origin - node.m_vecOrigin).Length();
if (flDist >= flMinDist && flDist < flMaxDist)
{
// node is also closer to me than the threat, or the same distance from myself and the threat the node is good.
if ((iMyNode == iThreatNode) || WorldGraph.PathLength(iMyNode, nodeNumber, iMyHullIndex, m_afCapability) <= WorldGraph.PathLength(iThreatNode, nodeNumber, iMyHullIndex, m_afCapability))
{
if (FValidateCover(node.m_vecOrigin) && MoveToLocation(ACT_RUN, 0, node.m_vecOrigin))
{
WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here.
return true;
}
}
}
}
return false;
}
As you can see the implementation is very similar to FindCover
. However it doesn't use UTIL_TraceLine
for finding a cover, thus it's a bit simpler.
Note also how I moved the
WorldGraph.m_iLastCoverSearch = nodeNumber + 1;
line right before the return.FindCover
has this line executed every cycle, but it seems weird to change this variable in loop asnodeNumber
is already calculated from it andi
index variable. So you can apply the similar change to theFindCover
function.
Now as we have both declaration and implementation, we can use the function in code. Go to dlls/schedule.cpp
and make changes under the TASK_FIND_COVER_FROM_BEST_SOUND
:
-if (pBestSound && FindCover(pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius()))
-{
- // then try for plain ole cover
- m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
- TaskComplete();
-}
-else
-{
- // no coverwhatsoever. or no sound in list
- TaskFail();
-}
+if (pBestSound)
+{
+ if (FindCover(pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius()))
+ {
+ // then try for plain ole cover
+ m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
+ TaskComplete();
+ }
+ else if (FindRetreat(pBestSound->m_vecOrigin, pBestSound->m_iVolume, CoverRadius()))
+ {
+ m_flMoveWaitFinished = gpGlobals->time + pTask->flData;
+ TaskComplete();
+ }
+}
+// no coverwhatsoever. or no sound in list
+if (!TaskIsComplete()) {
+ TaskFail();
+}
break;
Monsters will still prefer nodes with cover, but if they can't find one they would just run to some node away from the danger.