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.
*/
var float autocvar_ai_walkSpeed = 64;
var float autocvar_ai_runSpeed = 364;
var float autocvar_ai_walkSpeed = 150;
var float autocvar_ai_runSpeed = 320;
void
NSMonster::NSMonster(void)
@ -97,6 +97,7 @@ NSMonster::NSMonster(void)
m_flRunSpeed = autocvar_ai_runSpeed;
m_flLeapDamage = 0;
m_bLeapAttacked = false;
maxspeed = 1024;
#endif
}
@ -781,14 +782,19 @@ NSMonster::GetYawSpeed(void)
void
NSMonster::_LerpTurnToYaw(vector turnYaw)
{
#if 1
#if 0
angles[1] = input_angles[1] = v_angle[1] = turnYaw[1];
#else
float turnSpeed = GetYawSpeed();
vector vecWishAngle = turnYaw;
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;
input_movevalues = g_vec_null;
@ -1238,13 +1244,13 @@ NSMonster::RouteEnded(void)
m_iSequenceState = SEQUENCESTATE_ENDING;
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
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);
} else {
/* we still need to trigger targets */
think = (m_iSequenceFlags & SSFL_NOSCRIPTMOVE) ? FreeState : FreeStateMoved;
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);
}
}
@ -1252,6 +1258,7 @@ NSMonster::RouteEnded(void)
void
NSMonster::WalkRoute(void)
{
/* we're busy shooting at something, don't walk */
if (GetState() == MONSTER_AIMING && m_eEnemy) {
input_angles = vectoangles(m_eEnemy.origin - origin);
@ -1260,6 +1267,33 @@ NSMonster::WalkRoute(void)
input_angles = GetRouteDirection();
input_angles[0] = input_angles[2] = 0;
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) {
/* we've got 'em in our sights, just need to walk closer */
input_angles = vectoangles(m_eEnemy.origin - origin);
@ -1268,6 +1302,11 @@ NSMonster::WalkRoute(void)
} else
return;
/* don't move while turning. */
if (m_bTurning == true) {
input_movevalues = [0,0,0];
}
/* yaw interpolation */
_LerpTurnToYaw(input_angles);
}

View file

@ -44,6 +44,10 @@ private:
vector m_vecTurnAngle;
string m_pathTarget;
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 */
int m_iAmmoTypes[MAX_AMMO_TYPES];

View file

@ -23,6 +23,8 @@ NSNavAI::NSNavAI(void)
m_pRoute = __NULL__;
m_vecLastNode = [0,0,0];
m_vecTurnAngle = [0,0,0];
_m_flRouteGiveUp = 0.0f;
for (int i = 0; i < MAX_AMMO_TYPES; i++)
m_iAmmoTypes[i] = 0;
@ -108,6 +110,7 @@ void
NSNavAI::CheckRoute(void)
{
float flDist;
float flNodeRadius;
vector evenpos;
if (m_pathTarget) {
@ -118,18 +121,41 @@ NSNavAI::CheckRoute(void)
if (!m_iNodes)
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 */
if (m_iCurNode < 0) {
evenpos = m_vecLastNode;
evenpos[2] = origin[2];
flNodeRadius = 8.0f;
} else {
evenpos = m_pRoute[m_iCurNode].dest;
evenpos[2] = origin[2];
flNodeRadius = m_pRoute[m_iCurNode].radius;
if (flNodeRadius <= 0.0)
flNodeRadius = 8.0f;
}
flDist = floor(vlen(evenpos - origin));
if (flDist < 8) {
if (flDist < flNodeRadius) {
NSNavAI_Log("^2%s::^3CheckRoute^7: " \
"%s reached node", classname, targetname);
m_iCurNode--;
@ -149,12 +175,14 @@ NSNavAI::CheckRoute(void)
}
}
#if 1
/* 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 */
int iNextNode = (m_iCurNode - 1);
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 */
if (!trace_startsolid && trace_fraction == 1.0f) {
@ -162,8 +190,10 @@ NSNavAI::CheckRoute(void)
m_iCurNode = iNextNode;
NSNavAI_Log("^2%s::^3CheckRoute^7: skipping to next node %i at '%v'", \
classname, iNextNode, vecNextNode);
return;
}
}
#endif
/* reached the end of the line */
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_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_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.
*/
@ -102,6 +104,7 @@ public:
virtual float SendEntity(entity,float);
virtual void Save(float);
virtual void Restore(string,string);
virtual void Touch(entity);
/*virtual void(void) TalkAnswer;
virtual void(void) TalkAsk;
@ -158,7 +161,6 @@ private:
float m_flNextSentence;
int m_iFlags;
entity m_eFollowing;
entity m_eFollowingChain;
vector m_vecLastUserPos;
float m_flChangePath;
@ -167,6 +169,10 @@ private:
float m_flFollowSpeed;
bool m_bFollowOnUse;
float m_flFollowDistance;
float m_flMaxFollowDistance;
bool m_bFollowGrouping;
/* sentences identifiers */
string m_talkAnswer; /* random answer to whenever a question is asked */
string m_talkAsk; /* asks a random generic question */

View file

@ -51,6 +51,9 @@ NSTalkMonster::NSTalkMonster(void)
m_talkStopFollow = __NULL__;
m_talkDenyFollow = __NULL__;
m_bFollowOnUse = false;
m_flFollowDistance = 128.0f;
m_flMaxFollowDistance = 2048.0f;
m_bFollowGrouping = false;
#else
m_flSentenceTime = 0.0f;
m_pSentenceQue = __NULL__;
@ -102,6 +105,8 @@ NSTalkMonster::Save(float handle)
SaveFloat(handle, "m_flFollowSpeedChanged", m_flFollowSpeedChanged);
SaveFloat(handle, "m_flFollowSpeed", m_flFollowSpeed);
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_talkAsk", m_talkAsk);
@ -164,6 +169,12 @@ NSTalkMonster::Restore(string strKey, string strValue)
case "m_bFollowOnUse":
m_bFollowOnUse = ReadBool(strValue);
break;
case "m_flFollowDistance":
m_flFollowDistance = ReadFloat(strValue);
break;
case "m_flMaxFollowDistance":
m_flMaxFollowDistance = ReadFloat(strValue);
break;
case "m_talkAnswer":
m_talkAnswer = ReadString(strValue);
break;
@ -505,6 +516,22 @@ NSTalkMonster::TalkDenyFollow(void)
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
NSTalkMonster::FollowPlayer(void)
@ -525,41 +552,71 @@ NSTalkMonster::FollowPlayer(void)
flPlayerDist = vlen(vecParent - origin);
/* Give up after 1024 units */
if (flPlayerDist > 1024) {
if (flPlayerDist > m_flMaxFollowDistance) {
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 */
if (m_flFollowSpeedChanged < time) {
float flNextSpeed = GetChaseSpeed();
/* if we're close enough, we ought to walk */
if (flPlayerDist < 256)
if (flPlayerDist < (m_flFollowDistance * 1.5f))
flNextSpeed = GetWalkSpeed();
/* only update the timer when speed changed */
if (flNextSpeed != m_flFollowSpeed) {
m_flFollowSpeed = flNextSpeed;
m_flFollowSpeedChanged = time + 1.0f;
m_flFollowSpeedChanged = time + 5.0f;
}
}
if (DistanceFromYaw(vecParent) > 0.9f)
input_movevalues[0] = m_flFollowSpeed;
other = world;
traceline(origin, m_eFollowingChain.origin, MOVE_OTHERONLY, this);
/* when we're in a chain... can we see the user any more? */
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 */
if (trace_fraction < 1.0f) {
input_angles = vectoangles(m_vecLastUserPos - origin);
input_angles[0] = 0;
input_angles[1] = Math_FixDelta(input_angles[1]);
input_angles[2] = 0;
_LerpTurnToYaw(input_angles);
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[0] = 0;
input_angles[1] = Math_FixDelta(input_angles[1]);
input_angles[2] = 0;
_LerpTurnToYaw(input_angles);
}
} else {
m_vecLastUserPos = m_eFollowingChain.origin;
m_vecLastUserPos = vecParent;
}
if (m_bFollowGrouping == false)
return;
/* Trace again to see if another hostage is in our path and if so
* follow them instead, this makes pathing easier */
traceline(origin, /*mins, maxs,*/ m_vecLastUserPos, FALSE, this);
@ -578,8 +635,7 @@ void
NSTalkMonster::PanicFrame(void)
{
m_iFlags |= MONSTER_METPLAYER;
maxspeed = 240;
input_movevalues = [maxspeed, 0, 0];
input_movevalues = [240, 0, 0];
if (m_flTraceTime < time) {
traceline(origin, origin + (v_forward * 64), FALSE, this);
@ -625,7 +681,7 @@ NSTalkMonster::RunAI(void)
TalkPlayerGreet();
FollowChain();
if (m_eFollowing != world) {
if (m_eFollowing != world && m_iNodes <= 0) {
m_eLookAt = m_eFollowing;
FollowPlayer();
} else if (m_iFlags & MONSTER_FEAR) {
@ -644,6 +700,7 @@ NSTalkMonster::Respawn(void)
{
super::Respawn();
RouteClear();
m_eFollowing = world;
m_eFollowingChain = world;
PlayerUse = OnPlayerUse;
@ -677,6 +734,7 @@ NSTalkMonster::OnPlayerUse(void)
} else {
TalkUnfollow();
m_eFollowing = world;
RouteClear();
}
}
@ -761,6 +819,13 @@ NSTalkMonster::SpawnKey(string strKey, string strValue)
case "follow_on_use":
m_bFollowOnUse = ReadBool(strValue);
break;
case "follow_dist":
m_flFollowDistance = ReadFloat(strValue);
break;
case "follow_maxdist":
m_flMaxFollowDistance = ReadFloat(strValue);
break;
default:
super::SpawnKey(strKey, strValue);
break;