diff --git a/src/shared/NSMonster.qc b/src/shared/NSMonster.qc index 1c4a45e4..cffbd86a 100644 --- a/src/shared/NSMonster.qc +++ b/src/shared/NSMonster.qc @@ -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); } diff --git a/src/shared/NSNavAI.h b/src/shared/NSNavAI.h index f502b455..3954bfcd 100644 --- a/src/shared/NSNavAI.h +++ b/src/shared/NSNavAI.h @@ -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]; diff --git a/src/shared/NSNavAI.qc b/src/shared/NSNavAI.qc index 49bfadbb..5b0e6727 100644 --- a/src/shared/NSNavAI.qc +++ b/src/shared/NSNavAI.qc @@ -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) { diff --git a/src/shared/NSTalkMonster.h b/src/shared/NSTalkMonster.h index 81d604aa..d70a40d5 100644 --- a/src/shared/NSTalkMonster.h +++ b/src/shared/NSTalkMonster.h @@ -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 */ diff --git a/src/shared/NSTalkMonster.qc b/src/shared/NSTalkMonster.qc index 2c715ff1..4f88cb1c 100644 --- a/src/shared/NSTalkMonster.qc +++ b/src/shared/NSTalkMonster.qc @@ -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;