/*
===========================================================================
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 );
}