/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ #include "b_local.h" #include "g_navigator.h" #if !defined(RAVL_VEC_INC) #include "../Ravl/CVec.h" #endif #include "../Ratl/vector_vs.h" #define MAX_PACKS 10 #define LEAVE_PACK_DISTANCE 1000 #define JOIN_PACK_DISTANCE 800 #define WANDER_RANGE 1000 #define FRIGHTEN_DISTANCE 300 extern qboolean G_PlayerSpawned( void ); ratl::vector_vs mPacks; //////////////////////////////////////////////////////////////////////////////////////// // Update The Packs, Delete Dead Leaders, Join / Split Packs, Find MY Leader //////////////////////////////////////////////////////////////////////////////////////// gentity_t* NPC_AnimalUpdateLeader(void) { // Find The Closest Pack Leader, Not Counting Myself //--------------------------------------------------- gentity_t* closestLeader = 0; float closestDist = 0; int myLeaderNum = 0; for (int i=0; ihealth<=0) { if (mPacks[i]==NPC->client->leader) { NPC->client->leader = 0; } mPacks.erase_swap(i); if (i>=mPacks.size()) { closestLeader = 0; break; } } // Don't Count Self //------------------ if (mPacks[i]==NPC) { myLeaderNum = i; continue; } float Dist = Distance(mPacks[i]->currentOrigin, NPC->currentOrigin); if (!closestLeader || Distclient->leader==NPC) { mPacks.erase_swap(myLeaderNum); // Erase Myself From The Leader List } // Join The Pack! //---------------- NPC->client->leader = closestLeader; } // Do I Have A Leader? //--------------------- if (NPC->client->leader) { // AM I A Leader? //---------------- if (NPC->client->leader!=NPC) { // If Our Leader Is Dead, Clear Him Out if ( NPC->client->leader->health<=0 || NPC->client->leader->inuse == 0) { NPC->client->leader = 0; } // If My Leader Isn't His Own Leader, Then, Use His Leader //--------------------------------------------------------- else if (NPC->client->leader->client->leader!=NPC->client->leader) { // Eh. Can this get more confusing? NPC->client->leader = NPC->client->leader->client->leader; } // If Our Leader Is Too Far Away, Clear Him Out //------------------------------------------------------ else if ( Distance(NPC->client->leader->currentOrigin, NPC->currentOrigin)>LEAVE_PACK_DISTANCE) { NPC->client->leader = 0; } } } // If We Couldn't Find A Leader, Then Become One //----------------------------------------------- else if (!mPacks.full()) { NPC->client->leader = NPC; mPacks.push_back(NPC); } return NPC->client->leader; } /* ------------------------- NPC_BSAnimal_Default ------------------------- */ void NPC_BSAnimal_Default( void ) { if (!NPC || !NPC->client) { return; } // Update Some Positions //----------------------- CVec3 CurrentLocation(NPC->currentOrigin); // Update The Leader //------------------- gentity_t* leader = NPC_AnimalUpdateLeader(); // Select Closest Threat Location //-------------------------------- CVec3 ThreatLocation(0,0,0); qboolean PlayerSpawned = G_PlayerSpawned(); if ( PlayerSpawned ) {//player is actually in the level now ThreatLocation = player->currentOrigin; } int alertEvent = NPC_CheckAlertEvents(qtrue, qtrue, -1, qfalse, AEL_MINOR, qfalse); if ( alertEvent >= 0 ) { alertEvent_t *event = &level.alertEvents[alertEvent]; if (event->owner!=NPC && Distance(event->position, CurrentLocation.v)radius) { ThreatLocation = event->position; } } // float DistToThreat = CurrentLocation.Dist(ThreatLocation); // float DistFromHome = CurrentLocation.Dist(mHome); bool EvadeThreat = (level.timeinvestigateSoundDebounceTime); bool CharmedDocile = (level.timeconfusionTime); bool CharmedApproach = (level.timecharmedTime); // If Not Already Evading, Test To See If We Should "Know" About The Threat //-------------------------------------------------------------------------- /* if (false && !EvadeThreat && PlayerSpawned && (DistToThreatcurrentAngles); LookAim.AngToVec(); CVec3 MyPos(CurrentLocation); MyPos -= ThreatLocation; MyPos.SafeNorm(); float DirectionSimilarity = MyPos.Dot(LookAim); if (fabsf(DirectionSimilarity)<0.8f) { EvadeThreat = true; NPCInfo->investigateSoundDebounceTime = level.time + Q_irand(0, 1000); VectorCopy(ThreatLocation.v, NPCInfo->investigateGoal); } }*/ STEER::Activate(NPC); { // Charmed Approach - Walk TOWARD The Threat Location //---------------------------------------------------- if (CharmedApproach) { NAV::GoTo(NPC, NPCInfo->investigateGoal); } // Charmed Docile - Stay Put //--------------------------- else if (CharmedDocile) { NAV::ClearPath(NPC); STEER::Stop(NPC); } // Run Away From This Threat //--------------------------- else if (EvadeThreat) { NAV::ClearPath(NPC); STEER::Flee(NPC, NPCInfo->investigateGoal); } // Normal Behavior //----------------- else { // Follow Our Pack Leader! //------------------------- if (leader && leader!=NPC) { float followDist = 100.0f; float curDist = Distance(NPC->currentOrigin, leader->followPos); // Update The Leader's Follow Position //------------------------------------- STEER::FollowLeader(NPC, leader, followDist); bool inSeekRange = (curDistfollowPosWaypoint)); bool leaderStop = ((level.time - leader->lastMoveTime)>500); // If Close Enough, Dump Any Existing Path //----------------------------------------- if (inSeekRange || onNbrPoints) { NAV::ClearPath(NPC); // If The Leader Isn't Moving, Stop //---------------------------------- if (leaderStop) { STEER::Stop(NPC); } // Otherwise, Try To Get To The Follow Position //---------------------------------------------- else { STEER::Seek(NPC, leader->followPos, fabsf(followDist)/2.0f/*slowing distance*/, 1.0f/*wight*/, leader->resultspeed); } } // Otherwise, Get A Path To The Follow Position //---------------------------------------------- else { NAV::GoTo(NPC, leader->followPosWaypoint); } STEER::Separation(NPC, 4.0f); STEER::AvoidCollisions(NPC, leader); } // Leader AI - Basically Wander //------------------------------ else { // Are We Doing A Path? //---------------------- bool HasPath = NAV::HasPath(NPC); if (HasPath) { HasPath = NAV::UpdatePath(NPC); if (HasPath) { STEER::Path(NPC); // Follow The Path STEER::AvoidCollisions(NPC); } } if (!HasPath) { // If Debounce Time Has Expired, Choose A New Sub State //------------------------------------------------------ if (NPCInfo->investigateDebounceTimeaiFlags &= ~NPCAI_OFF_PATH; NPCInfo->aiFlags &= ~NPCAI_WALKING; // Pick Another Spot //------------------- int NEXTSUBSTATE = Q_irand(0, 10); bool RandomPathNode = (NEXTSUBSTATE<8); //(NEXTSUBSTATE<9); bool PathlessWander = (NEXTSUBSTATE<9); //false; // Random Path Node //------------------ if (RandomPathNode) { // Sometimes, Walk //----------------- if (Q_irand(0, 1)==0) { NPCInfo->aiFlags |= NPCAI_WALKING; } NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); NAV::FindPath(NPC, NAV::ChooseRandomNeighbor(NAV::GetNearestNode(NPC)));//, mHome.v, WANDER_RANGE)); } // Pathless Wandering //-------------------- else if (PathlessWander) { // Sometimes, Walk //----------------- if (Q_irand(0, 1)==0) { NPCInfo->aiFlags |= NPCAI_WALKING; } NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); NPCInfo->aiFlags |= NPCAI_OFF_PATH; } // Just Stand Here //----------------- else { NPCInfo->investigateDebounceTime = level.time + Q_irand(2000, 6000); //NPC_SetAnim(NPC, SETANIM_BOTH, ((Q_irand(0, 1)==0)?(BOTH_GUARD_LOOKAROUND1):(BOTH_GUARD_IDLE1)), SETANIM_FLAG_NORMAL); } } // Ok, So We Don't Have A Path, And Debounce Time Is Still Active, So We Are Either Wandering Or Looking Around //-------------------------------------------------------------------------------------------------------------- else { // if (DistFromHome>(WANDER_RANGE)) // { // STEER::Seek(NPC, mHome); // } // else { if (NPCInfo->aiFlags & NPCAI_OFF_PATH) { STEER::Wander(NPC); STEER::AvoidCollisions(NPC); } else { STEER::Stop(NPC); } } } } } } } STEER::DeActivate(NPC, &ucmd); NPC_UpdateAngles( qtrue, qtrue ); }