NSTalkMonster: pathfinding fixes that benefit following players around the maps. More failsafes.

This commit is contained in:
Marco Cawthorne 2023-10-11 00:49:35 -07:00
parent 7d9de3a2cc
commit a623b1e301
Signed by: eukara
GPG key ID: CE2032F0A2882A22
5 changed files with 169 additions and 25 deletions

View file

@ -14,8 +14,8 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/ */
var float autocvar_ai_walkSpeed = 64; var float autocvar_ai_walkSpeed = 150;
var float autocvar_ai_runSpeed = 364; var float autocvar_ai_runSpeed = 320;
void void
NSMonster::NSMonster(void) NSMonster::NSMonster(void)
@ -97,6 +97,7 @@ NSMonster::NSMonster(void)
m_flRunSpeed = autocvar_ai_runSpeed; m_flRunSpeed = autocvar_ai_runSpeed;
m_flLeapDamage = 0; m_flLeapDamage = 0;
m_bLeapAttacked = false; m_bLeapAttacked = false;
maxspeed = 1024;
#endif #endif
} }
@ -781,14 +782,19 @@ NSMonster::GetYawSpeed(void)
void void
NSMonster::_LerpTurnToYaw(vector turnYaw) NSMonster::_LerpTurnToYaw(vector turnYaw)
{ {
#if 1 #if 0
angles[1] = input_angles[1] = v_angle[1] = turnYaw[1]; angles[1] = input_angles[1] = v_angle[1] = turnYaw[1];
#else #else
float turnSpeed = GetYawSpeed(); float turnSpeed = GetYawSpeed();
vector vecWishAngle = turnYaw; vector vecWishAngle = turnYaw;
float yawDiff = anglesub(turnYaw[1], v_angle[1]); float yawDiff = anglesub(turnYaw[1], v_angle[1]);
if (fabs(yawDiff) > 90) { /* anything but small turns? halt the player */
if (fabs(yawDiff) > 5) {
input_movevalues = g_vec_null;
}
if (fabs(yawDiff) > 45) {
velocity = g_vec_null; velocity = g_vec_null;
input_movevalues = g_vec_null; input_movevalues = g_vec_null;
@ -1238,13 +1244,13 @@ NSMonster::RouteEnded(void)
m_iSequenceState = SEQUENCESTATE_ENDING; m_iSequenceState = SEQUENCESTATE_ENDING;
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved; think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
nextthink = time + duration; nextthink = time + duration;
NSMonster_Log("^2%s::^3CheckRoute^7: %s overriding anim for %f seconds (modelindex %d, frame %d)", \ NSMonster_Log("^2%s::^3RouteEnded^7: %s overriding anim for %f seconds (modelindex %d, frame %d)", \
classname, this.targetname, duration, modelindex, m_flSequenceEnd); classname, this.targetname, duration, modelindex, m_flSequenceEnd);
} else { } else {
/* we still need to trigger targets */ /* we still need to trigger targets */
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved; think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
nextthink = time; nextthink = time;
NSMonster_Log("^2%s::^3CheckRoute^7: %s has no anim, finished sequence", \ NSMonster_Log("^2%s::^3RouteEnded^7: %s has no anim, finished sequence", \
classname, this.targetname); classname, this.targetname);
} }
} }
@ -1252,6 +1258,7 @@ NSMonster::RouteEnded(void)
void void
NSMonster::WalkRoute(void) NSMonster::WalkRoute(void)
{ {
/* we're busy shooting at something, don't walk */ /* we're busy shooting at something, don't walk */
if (GetState() == MONSTER_AIMING && m_eEnemy) { if (GetState() == MONSTER_AIMING && m_eEnemy) {
input_angles = vectoangles(m_eEnemy.origin - origin); input_angles = vectoangles(m_eEnemy.origin - origin);
@ -1260,6 +1267,33 @@ NSMonster::WalkRoute(void)
input_angles = GetRouteDirection(); input_angles = GetRouteDirection();
input_angles[0] = input_angles[2] = 0; input_angles[0] = input_angles[2] = 0;
input_movevalues = GetRouteMovevalues() * m_flSequenceSpeed; input_movevalues = GetRouteMovevalues() * m_flSequenceSpeed;
/* is something in our way? */
makevectors(input_angles);
tracebox(origin, mins, maxs, origin + v_forward * 256, MOVE_NORMAL, this);
/* indeed it is */
if (trace_fraction < 1.0f) {
vector testOrg = origin + (v_right * 32);
testOrg[2] += mins[2] + 18.0f; /* test at feet level */
traceline(testOrg, testOrg + v_forward * 256, MOVE_NORMAL, this);
/* is space free to the right? */
if (trace_fraction == 1.0) {
input_movevalues[1] = m_flSequenceSpeed * 0.25f;
} else {
testOrg = origin - (v_right * 32);
testOrg[2] += mins[2] + 18.0f; /* test at feet level */
traceline(testOrg, testOrg + v_forward * 256, MOVE_NORMAL, this);
/* is space free to the left? */
if (trace_fraction == 1.0)
input_movevalues[1] = -m_flSequenceSpeed * 0.25f;
}
}
} else if (GetState() == MONSTER_CHASING && m_eEnemy) { } else if (GetState() == MONSTER_CHASING && m_eEnemy) {
/* we've got 'em in our sights, just need to walk closer */ /* we've got 'em in our sights, just need to walk closer */
input_angles = vectoangles(m_eEnemy.origin - origin); input_angles = vectoangles(m_eEnemy.origin - origin);
@ -1268,6 +1302,11 @@ NSMonster::WalkRoute(void)
} else } else
return; return;
/* don't move while turning. */
if (m_bTurning == true) {
input_movevalues = [0,0,0];
}
/* yaw interpolation */ /* yaw interpolation */
_LerpTurnToYaw(input_angles); _LerpTurnToYaw(input_angles);
} }

View file

@ -44,6 +44,10 @@ private:
vector m_vecTurnAngle; vector m_vecTurnAngle;
string m_pathTarget; string m_pathTarget;
NSEntity m_pathEntity; NSEntity m_pathEntity;
float _m_flRouteGiveUp;
vector _m_vecRoutePrev;
vector m_vecRouteEntity;
entity m_eFollowing;
/* These are defined in side defs\*.def, ammo_types and ammo_names */ /* These are defined in side defs\*.def, ammo_types and ammo_names */
int m_iAmmoTypes[MAX_AMMO_TYPES]; int m_iAmmoTypes[MAX_AMMO_TYPES];

View file

@ -24,6 +24,8 @@ NSNavAI::NSNavAI(void)
m_vecLastNode = [0,0,0]; m_vecLastNode = [0,0,0];
m_vecTurnAngle = [0,0,0]; m_vecTurnAngle = [0,0,0];
_m_flRouteGiveUp = 0.0f;
for (int i = 0; i < MAX_AMMO_TYPES; i++) for (int i = 0; i < MAX_AMMO_TYPES; i++)
m_iAmmoTypes[i] = 0; m_iAmmoTypes[i] = 0;
#endif #endif
@ -108,6 +110,7 @@ void
NSNavAI::CheckRoute(void) NSNavAI::CheckRoute(void)
{ {
float flDist; float flDist;
float flNodeRadius;
vector evenpos; vector evenpos;
if (m_pathTarget) { if (m_pathTarget) {
@ -118,18 +121,41 @@ NSNavAI::CheckRoute(void)
if (!m_iNodes) if (!m_iNodes)
return; return;
if (_m_flRouteGiveUp < time) {
/* 50 units in 2 seconds is not good. */
if (vlen(_m_vecRoutePrev - origin) < 50.0f) {
/* HACK: for followers */
if (m_eFollowing) {
RouteToPosition(m_eFollowing.origin);
NSNavAI_Log("^2%s::^3CheckRoute^7: " \
"Giving up current route to follower, re-calculating", classname);
} else {
RouteToPosition(m_vecLastNode);
NSNavAI_Log("^2%s::^3CheckRoute^7: " \
"Giving up current route, re-calculating", classname);
}
}
_m_flRouteGiveUp = time + 2.0f;
_m_vecRoutePrev = origin;
}
/* level out position/node stuff */ /* level out position/node stuff */
if (m_iCurNode < 0) { if (m_iCurNode < 0) {
evenpos = m_vecLastNode; evenpos = m_vecLastNode;
evenpos[2] = origin[2]; evenpos[2] = origin[2];
flNodeRadius = 8.0f;
} else { } else {
evenpos = m_pRoute[m_iCurNode].dest; evenpos = m_pRoute[m_iCurNode].dest;
evenpos[2] = origin[2]; evenpos[2] = origin[2];
flNodeRadius = m_pRoute[m_iCurNode].radius;
if (flNodeRadius <= 0.0)
flNodeRadius = 8.0f;
} }
flDist = floor(vlen(evenpos - origin)); flDist = floor(vlen(evenpos - origin));
if (flDist < 8) { if (flDist < flNodeRadius) {
NSNavAI_Log("^2%s::^3CheckRoute^7: " \ NSNavAI_Log("^2%s::^3CheckRoute^7: " \
"%s reached node", classname, targetname); "%s reached node", classname, targetname);
m_iCurNode--; m_iCurNode--;
@ -149,12 +175,14 @@ NSNavAI::CheckRoute(void)
} }
} }
#if 1
/* check if we can reach the node after the current one */ /* check if we can reach the node after the current one */
if (m_iCurNode > 0 && m_iNodes > 3) { /* HACK: only bother when we have more than 3 nodes in the path... this works around an issue in c1a0d I'm unsure about */ if (m_iCurNode > 0 && m_iNodes > 3) { /* HACK: only bother when we have more than 3 nodes in the path... this works around an issue in c1a0d I'm unsure about */
int iNextNode = (m_iCurNode - 1); int iNextNode = (m_iCurNode - 1);
vector vecNextNode = m_pRoute[iNextNode].dest; vector vecNextNode = m_pRoute[iNextNode].dest;
tracebox(origin, mins, maxs, vecNextNode, MOVE_NORMAL, this); other = world;
tracebox(origin, mins, maxs, vecNextNode, MOVE_OTHERONLY, this);
/* it's accessible */ /* it's accessible */
if (!trace_startsolid && trace_fraction == 1.0f) { if (!trace_startsolid && trace_fraction == 1.0f) {
@ -162,8 +190,10 @@ NSNavAI::CheckRoute(void)
m_iCurNode = iNextNode; m_iCurNode = iNextNode;
NSNavAI_Log("^2%s::^3CheckRoute^7: skipping to next node %i at '%v'", \ NSNavAI_Log("^2%s::^3CheckRoute^7: skipping to next node %i at '%v'", \
classname, iNextNode, vecNextNode); classname, iNextNode, vecNextNode);
return;
} }
} }
#endif
/* reached the end of the line */ /* reached the end of the line */
if (m_iCurNode < -1) { if (m_iCurNode < -1) {

View file

@ -67,6 +67,8 @@ They also can communicate with other NSTalkMonster based entities.
- "talk_stop_follow" : SentenceDef to play for when they're being asked to unfollow someone. - "talk_stop_follow" : SentenceDef to play for when they're being asked to unfollow someone.
- "talk_deny_follow" : SentenceDef to play for when they're denying the follow request. - "talk_deny_follow" : SentenceDef to play for when they're denying the follow request.
- "follow_on_use" : Can be either 0 or 1, will decide if they can follow someone. - "follow_on_use" : Can be either 0 or 1, will decide if they can follow someone.
- "follow_dist" : Distance between the it and the player its following.
- "follow_maxdist" : Maximum distance between it and the player before giving up following them.
For more keys, see NSMonster. For more keys, see NSMonster.
*/ */
@ -102,6 +104,7 @@ public:
virtual float SendEntity(entity,float); virtual float SendEntity(entity,float);
virtual void Save(float); virtual void Save(float);
virtual void Restore(string,string); virtual void Restore(string,string);
virtual void Touch(entity);
/*virtual void(void) TalkAnswer; /*virtual void(void) TalkAnswer;
virtual void(void) TalkAsk; virtual void(void) TalkAsk;
@ -158,7 +161,6 @@ private:
float m_flNextSentence; float m_flNextSentence;
int m_iFlags; int m_iFlags;
entity m_eFollowing;
entity m_eFollowingChain; entity m_eFollowingChain;
vector m_vecLastUserPos; vector m_vecLastUserPos;
float m_flChangePath; float m_flChangePath;
@ -167,6 +169,10 @@ private:
float m_flFollowSpeed; float m_flFollowSpeed;
bool m_bFollowOnUse; bool m_bFollowOnUse;
float m_flFollowDistance;
float m_flMaxFollowDistance;
bool m_bFollowGrouping;
/* sentences identifiers */ /* sentences identifiers */
string m_talkAnswer; /* random answer to whenever a question is asked */ string m_talkAnswer; /* random answer to whenever a question is asked */
string m_talkAsk; /* asks a random generic question */ string m_talkAsk; /* asks a random generic question */

View file

@ -51,6 +51,9 @@ NSTalkMonster::NSTalkMonster(void)
m_talkStopFollow = __NULL__; m_talkStopFollow = __NULL__;
m_talkDenyFollow = __NULL__; m_talkDenyFollow = __NULL__;
m_bFollowOnUse = false; m_bFollowOnUse = false;
m_flFollowDistance = 128.0f;
m_flMaxFollowDistance = 2048.0f;
m_bFollowGrouping = false;
#else #else
m_flSentenceTime = 0.0f; m_flSentenceTime = 0.0f;
m_pSentenceQue = __NULL__; m_pSentenceQue = __NULL__;
@ -102,6 +105,8 @@ NSTalkMonster::Save(float handle)
SaveFloat(handle, "m_flFollowSpeedChanged", m_flFollowSpeedChanged); SaveFloat(handle, "m_flFollowSpeedChanged", m_flFollowSpeedChanged);
SaveFloat(handle, "m_flFollowSpeed", m_flFollowSpeed); SaveFloat(handle, "m_flFollowSpeed", m_flFollowSpeed);
SaveBool(handle, "m_bFollowOnUse", m_bFollowOnUse); SaveBool(handle, "m_bFollowOnUse", m_bFollowOnUse);
SaveFloat(handle, "m_flFollowDistance", m_flFollowDistance);
SaveFloat(handle, "m_flMaxFollowDistance", m_flMaxFollowDistance);
SaveString(handle, "m_talkAnswer", m_talkAnswer); SaveString(handle, "m_talkAnswer", m_talkAnswer);
SaveString(handle, "m_talkAsk", m_talkAsk); SaveString(handle, "m_talkAsk", m_talkAsk);
@ -164,6 +169,12 @@ NSTalkMonster::Restore(string strKey, string strValue)
case "m_bFollowOnUse": case "m_bFollowOnUse":
m_bFollowOnUse = ReadBool(strValue); m_bFollowOnUse = ReadBool(strValue);
break; break;
case "m_flFollowDistance":
m_flFollowDistance = ReadFloat(strValue);
break;
case "m_flMaxFollowDistance":
m_flMaxFollowDistance = ReadFloat(strValue);
break;
case "m_talkAnswer": case "m_talkAnswer":
m_talkAnswer = ReadString(strValue); m_talkAnswer = ReadString(strValue);
break; break;
@ -505,6 +516,22 @@ NSTalkMonster::TalkDenyFollow(void)
m_flNextSentence = time + 10.0; m_flNextSentence = time + 10.0;
} }
void
NSTalkMonster::Touch(entity eToucher)
{
if (eToucher == m_eFollowing) {
makevectors(eToucher.angles);
velocity = v_forward * 250.0f;
return;
}
/* ugly hack */
if (eToucher.classname == "func_door_rotating") {
owner = eToucher;
}
super::Touch(eToucher);
}
void void
NSTalkMonster::FollowPlayer(void) NSTalkMonster::FollowPlayer(void)
@ -525,40 +552,70 @@ NSTalkMonster::FollowPlayer(void)
flPlayerDist = vlen(vecParent - origin); flPlayerDist = vlen(vecParent - origin);
/* Give up after 1024 units */ /* Give up after 1024 units */
if (flPlayerDist > 1024) { if (flPlayerDist > m_flMaxFollowDistance) {
m_eFollowing = world; m_eFollowing = world;
} else if (flPlayerDist > 128) { NSMonster_Log("Maximum follow distance reached. Will stop following.");
} else if (flPlayerDist >= m_flFollowDistance) {
/* we only allow speed changes every second, avoid jitter */ /* we only allow speed changes every second, avoid jitter */
if (m_flFollowSpeedChanged < time) { if (m_flFollowSpeedChanged < time) {
float flNextSpeed = GetChaseSpeed(); float flNextSpeed = GetChaseSpeed();
/* if we're close enough, we ought to walk */ /* if we're close enough, we ought to walk */
if (flPlayerDist < 256) if (flPlayerDist < (m_flFollowDistance * 1.5f))
flNextSpeed = GetWalkSpeed(); flNextSpeed = GetWalkSpeed();
/* only update the timer when speed changed */ /* only update the timer when speed changed */
if (flNextSpeed != m_flFollowSpeed) { if (flNextSpeed != m_flFollowSpeed) {
m_flFollowSpeed = flNextSpeed; m_flFollowSpeed = flNextSpeed;
m_flFollowSpeedChanged = time + 1.0f; m_flFollowSpeedChanged = time + 5.0f;
} }
} }
if (DistanceFromYaw(vecParent) > 0.9f) if (DistanceFromYaw(vecParent) > 0.9f)
input_movevalues[0] = m_flFollowSpeed; input_movevalues[0] = m_flFollowSpeed;
other = world; /* when we're in a chain... can we see the user any more? */
traceline(origin, m_eFollowingChain.origin, MOVE_OTHERONLY, this); if (m_eFollowingChain != m_eFollowing) {
traceline(GetOrigin(), m_eFollowing.origin, MOVE_NORMAL, this);
/* the answer is no. */
if (trace_fraction < 1.0 || trace_ent != m_eFollowing) {
/* go straight to our user */
NSEntity zamn = (NSEntity)m_eFollowing;
vector endPos = zamn.GetNearbySpot();
if (endPos != g_vec_null) {
RouteToPosition(endPos);
m_flSequenceSpeed = GetChaseSpeed();
}
return;
}
}
traceline(origin, m_eFollowingChain.origin, MOVE_NORMAL, this);
/* Tracing failed, there's world geometry in the way */ /* Tracing failed, there's world geometry in the way */
if (trace_fraction < 1.0f) { if (trace_fraction < 1.0f && trace_ent != m_eFollowingChain) {
/* are they still generally accessible? */
traceline(m_vecLastUserPos, vecParent, MOVE_NORMAL, this);
if (trace_fraction < 1.0f && trace_ent != m_eFollowing) {
RouteToPosition(m_eFollowing.origin); /* go directly to the source */
m_flSequenceSpeed = GetChaseSpeed();
return;
} else {
input_angles = vectoangles(m_vecLastUserPos - origin); input_angles = vectoangles(m_vecLastUserPos - origin);
input_angles[0] = 0; input_angles[0] = 0;
input_angles[1] = Math_FixDelta(input_angles[1]); input_angles[1] = Math_FixDelta(input_angles[1]);
input_angles[2] = 0; input_angles[2] = 0;
_LerpTurnToYaw(input_angles); _LerpTurnToYaw(input_angles);
} else {
m_vecLastUserPos = m_eFollowingChain.origin;
} }
} else {
m_vecLastUserPos = vecParent;
}
if (m_bFollowGrouping == false)
return;
/* Trace again to see if another hostage is in our path and if so /* Trace again to see if another hostage is in our path and if so
* follow them instead, this makes pathing easier */ * follow them instead, this makes pathing easier */
@ -578,8 +635,7 @@ void
NSTalkMonster::PanicFrame(void) NSTalkMonster::PanicFrame(void)
{ {
m_iFlags |= MONSTER_METPLAYER; m_iFlags |= MONSTER_METPLAYER;
maxspeed = 240; input_movevalues = [240, 0, 0];
input_movevalues = [maxspeed, 0, 0];
if (m_flTraceTime < time) { if (m_flTraceTime < time) {
traceline(origin, origin + (v_forward * 64), FALSE, this); traceline(origin, origin + (v_forward * 64), FALSE, this);
@ -625,7 +681,7 @@ NSTalkMonster::RunAI(void)
TalkPlayerGreet(); TalkPlayerGreet();
FollowChain(); FollowChain();
if (m_eFollowing != world) { if (m_eFollowing != world && m_iNodes <= 0) {
m_eLookAt = m_eFollowing; m_eLookAt = m_eFollowing;
FollowPlayer(); FollowPlayer();
} else if (m_iFlags & MONSTER_FEAR) { } else if (m_iFlags & MONSTER_FEAR) {
@ -644,6 +700,7 @@ NSTalkMonster::Respawn(void)
{ {
super::Respawn(); super::Respawn();
RouteClear();
m_eFollowing = world; m_eFollowing = world;
m_eFollowingChain = world; m_eFollowingChain = world;
PlayerUse = OnPlayerUse; PlayerUse = OnPlayerUse;
@ -677,6 +734,7 @@ NSTalkMonster::OnPlayerUse(void)
} else { } else {
TalkUnfollow(); TalkUnfollow();
m_eFollowing = world; m_eFollowing = world;
RouteClear();
} }
} }
@ -761,6 +819,13 @@ NSTalkMonster::SpawnKey(string strKey, string strValue)
case "follow_on_use": case "follow_on_use":
m_bFollowOnUse = ReadBool(strValue); m_bFollowOnUse = ReadBool(strValue);
break; break;
case "follow_dist":
m_flFollowDistance = ReadFloat(strValue);
break;
case "follow_maxdist":
m_flMaxFollowDistance = ReadFloat(strValue);
break;
default: default:
super::SpawnKey(strKey, strValue); super::SpawnKey(strKey, strValue);
break; break;