kingpin-sdk/gamesrc/g_ai_memory.c
2000-03-27 00:00:00 +00:00

1476 lines
36 KiB
C

//==============================================================
// g_ai_memory.c
//
// Character memory related AI routines
//==============================================================
#include "g_local.h"
#include "voice_punk.h"
#include "voice_bitch.h"
//============================================================================
/*
=================
AddCharacterToGame
Called whenever a character enters the game (whether AI or client)
returns FALSE if the character is not allowed to enter the game
=================
*/
qboolean AddCharacterToGame(edict_t *self)
{
int i;
if (level.num_characters == MAX_CHARACTERS)
{
gi.dprintf("\nMAX_CHARACTERS exceeded, new character rejected.\n\n");
return false;
}
// client is always first
if ((level.characters[0] != &g_edicts[1]) || self->client)
{
level.characters[0] = &g_edicts[1];
g_edicts[1].character_index = 0;
if (self->client)
return true;
}
// look for them already in the list
for (i=0; i<level.num_characters; i++)
{
if (level.characters[i] == self)
{
self->character_index = i;
return true;
}
}
if (level.num_characters < 1)
level.num_characters = 1;
// add them to the list
self->character_index = level.num_characters++;
level.characters[self->character_index] = self;
return true;
}
/*
==============
AI_RelaseCastMemory
Release all memory associated to this memory block
==============
*/
void AI_ReleaseCastMemory(edict_t *self, cast_memory_t *cast_memory)
{
cast_memory_t *next_memory, *this_memory;
this_memory = cast_memory;
while (this_memory)
{
next_memory = this_memory->next;
level.global_cast_memory[self->character_index][g_edicts[cast_memory->cast_ent].character_index] = NULL;
memset( this_memory, 0, sizeof(cast_memory_t) );
this_memory = next_memory;
}
}
/*
==============
AI_InitMemory
==============
*/
void AI_InitMemory( edict_t *self )
{
self->cast_info.friend_memory = NULL;
self->cast_info.enemy_memory = NULL;
self->cast_info.neutral_memory = NULL;
}
/*
==============
AI_UnloadCastMemory
Clears all memory associated with the cast member
==============
*/
void AI_UnloadCastMemory (edict_t *self)
{
int i;
edict_t *trav;
cast_memory_t *other_memory;
if (deathmatch->value)
return;
if (!(self->svflags & SVF_MONSTER || self->client)) // <- Note to Rafael: the lack of this line was causing memory problems (hopefully all of them..)
return;
// delete all our memories
AI_ReleaseCastMemory( self, self->cast_info.friend_memory );
self->cast_info.friend_memory = NULL;
AI_ReleaseCastMemory( self, self->cast_info.neutral_memory );
self->cast_info.neutral_memory = NULL;
AI_ReleaseCastMemory( self, self->cast_info.enemy_memory );
self->cast_info.enemy_memory = NULL;
// initialize the global memory list, and delete all other memories of us
for ( i=0; i<level.num_characters; i++ )
{
if (!level.characters[i])
continue;
level.global_cast_memory[self->character_index][i] = NULL; // since this doesn't exist anymore..
// unload this other character's memory of us
trav = level.characters[i];
if (trav->client)
continue;
if (!(other_memory = level.global_cast_memory[i][self->character_index]))
continue;
AI_RemoveFromMemory ( trav, other_memory );
level.global_cast_memory[i][self->character_index] = NULL;
}
}
/*
===========
AI_RemoveFromMemory
===========
*/
void AI_RemoveFromMemory ( edict_t *self, cast_memory_t *memory )
{
if (memory->prev)
memory->prev->next = memory->next;
if (memory->next)
memory->next->prev = memory->prev;
if (self->cast_info.friend_memory == memory)
self->cast_info.friend_memory = memory->next;
else if (self->cast_info.neutral_memory == memory)
self->cast_info.neutral_memory = memory->next;
else if (self->cast_info.enemy_memory == memory)
self->cast_info.enemy_memory = memory->next;
}
/*
===========
AI_AddToMemory
===========
*/
void AI_AddToMemory ( edict_t *self, cast_memory_t *memory, int memory_type )
{
cast_memory_t **head=NULL;
switch (memory_type)
{
case MEMORY_TYPE_FRIEND :
head = &self->cast_info.friend_memory;
break;
case MEMORY_TYPE_NEUTRAL :
head = &self->cast_info.neutral_memory;
break;
case MEMORY_TYPE_ENEMY :
head = &self->cast_info.enemy_memory;
break;
}
if (*head)
{
(*head)->prev = memory;
memory->next = *head;
}
else
{
memory->next = NULL;
}
*head = memory;
memory->prev = NULL;
memory->memory_type = memory_type;
memory->timestamp = level.time;
}
/*
===========
AI_CreateCharacterMemory
Creates a Character Memory association between 2 characters
===========
*/
void AI_CreateCharacterMemory(edict_t *src, edict_t *dest)
{
cast_memory_t *new_memory, **head;
int memory_type = MEMORY_TYPE_NEUTRAL; // default to neutral
if (ai_debug_memory->value)
{
gi.dprintf("ai_debug_memory: #%i sighted #%i", src->character_index, dest->character_index);
}
head = &src->cast_info.neutral_memory;
// first, create the cast_memory structure
// new_memory = gi.TagMalloc(sizeof(cast_memory_t), TAG_LEVEL);
new_memory = &(g_cast_memory[src->character_index * MAX_CHARACTERS + dest->character_index]);
memset( new_memory, 0, sizeof(cast_memory_t));
// set variables
new_memory->cast_ent = (int) (dest - g_edicts);
new_memory->flags = 0;
new_memory->timestamp = -1;
// determine relationship between us and this character
if (dest->cast_group)
{
if (dest->cast_group == src->cast_group)
{ // we're in the same group, so we're mates
memory_type = MEMORY_TYPE_FRIEND;
head = &src->cast_info.friend_memory;
if (ai_debug_memory->value)
gi.dprintf(" - FRIEND");
}
else if (src->cast_group)
{ // rival groups
// FIXME: check for inter-group relationships? (some groups may have an agreement?)
memory_type = MEMORY_TYPE_ENEMY;
head = &src->cast_info.enemy_memory;
if (ai_debug_memory->value)
gi.dprintf(" - ENEMY");
}
}
if (ai_debug_memory->value)
gi.dprintf("\n");
// never start off as enemies, for now
// place this memory chunk into the SRC's memory
if (!(*head))
{
*head = new_memory;
}
else // add to the start of the current list
{
(*head)->prev = new_memory;
new_memory->next = *head;
*head = new_memory;
}
new_memory->memory_type = memory_type;
new_memory->prev = NULL;
// set the global memory
level.global_cast_memory[src->character_index][dest->character_index] = new_memory;
}
/*
===========
AI_CreateCopyMemory
creates a memory slot, and copies the given memory information into it
===========
*/
cast_memory_t *AI_CreateCopyMemory ( edict_t *src, edict_t *dest, cast_memory_t *cast_memory )
{
cast_memory_t *new_memory;
// first, create the cast_memory structure
// new_memory = gi.TagMalloc(sizeof(cast_memory_t), TAG_LEVEL);
new_memory = &(g_cast_memory[src->character_index * MAX_CHARACTERS + dest->character_index]);
// copy the information across
memcpy( new_memory, cast_memory, sizeof(cast_memory_t) );
new_memory->prev = new_memory->next = NULL;
new_memory->cast_ent = (int) (dest - g_edicts);
return new_memory;
}
/*
===========
AI_ShareEnemies
spreads our list of enemies with other
===========
*/
void AI_ShareEnemies ( edict_t *self, edict_t *other )
{
cast_memory_t *self_memory, *other_memory;
// Ridah, 18-may-99, cast_group 1 guys shouldn't help other group 1's if they haven't been hired yet
if ((other->cast_group == 1) && !other->leader)
return;
if ((self->cast_group == 1) && !self->leader)
return;
// see if any of self's enemies aren't an enemy of other
self_memory = self->cast_info.enemy_memory;
while (self_memory)
{
// is this character an enemy to other?
if ( (self_memory->cast_ent != (int) (other - g_edicts))
&& !(self_memory->flags & MEMORY_PERSONAL_OPINION))
{
if ( !(other_memory = level.global_cast_memory[other->character_index][g_edicts[self_memory->cast_ent].character_index])
|| (other_memory->memory_type != MEMORY_TYPE_ENEMY))
{
if (!other_memory)
{ // create the memory of them
other_memory = AI_CreateCopyMemory( other, &g_edicts[self_memory->cast_ent], self_memory);
level.global_cast_memory[other->character_index][g_edicts[self_memory->cast_ent].character_index] = other_memory;
}
else // remove them from whereever they are
{
AI_RemoveFromMemory(other, other_memory);
}
// make an enemy
AI_AddToMemory(other, other_memory, MEMORY_TYPE_ENEMY);
if (ai_debug_memory->value)
{
gi.dprintf("ai_debug_memory: #%i is now an enemy of #%i (copied from #%i)\n",
g_edicts[self_memory->cast_ent].character_index, other->character_index, self->character_index);
}
}
// make sure we share any flags necessary
other_memory->flags |= (self_memory->flags & MEMORY_HOSTILE_ENEMY);
}
self_memory = self_memory->next;
}
}
// JOSEPH 28-DEC-98
void AI_Think_MakeEnemy_Timer (edict_t *ent)
{
cast_memory_t *cast_memory;
ent->reactdelay -= 0.1;
if (ent->reactdelay <= 0)
{
if ((ent->handle) && (ent->handle2) && (!ent->handle->deadflag) && (!ent->handle2->deadflag))
{
if ((!(cast_memory = level.global_cast_memory[ent->handle->character_index][ent->handle2->character_index])) ||
(!(cast_memory->flags & MEMORY_HOSTILE_ENEMY)))
{
AI_MakeEnemy(ent->handle, ent->handle2, ent->avelflag);
}
}
ent->nextthink = 0;
G_FreeEdict(ent);
}
else
{
ent->nextthink = level.time + 0.1;
}
}
void AI_MakeEnemy_Timer (edict_t *self, edict_t *other, int flags, float delay)
{
edict_t *timer;
timer = G_Spawn();
timer->think = AI_Think_MakeEnemy_Timer;
timer->nextthink = level.time + 0.1;
timer->reactdelay = delay;
timer->handle = self;
timer->handle2 = other;
timer->avelflag = flags;
gi.linkentity (timer);
return;
}
// END JOSEPH
/*
===========
AI_MakeEnemy
other becomes an enemy of self, and we get hostile at them
memory_flags get carried through to the cast_memory->flags variable
===========
*/
void AI_MakeEnemy ( edict_t *self, edict_t *other, int memory_flags )
{
cast_memory_t *cast_memory;
// JOSEPH 28-DEC-98
edict_t *e;
int i;
//if (self->name_index == NAME_LISA)
//self = self;
if (other->client)// && other->client->gun_noise)
// Find all members of the local team and hostilize them
if (self->localteam)
{
for (i=1, e=g_edicts+i ; i < globals.num_edicts ; i++,e++)
{
if ((!e->deadflag) && (e->localteam) && (e->localteam != self->localteam) &&
(!strcmp(e->localteam, self->localteam)))
{
if ((!(cast_memory = level.global_cast_memory[self->character_index][other->character_index])) ||
(!(cast_memory->flags & MEMORY_HOSTILE_ENEMY)))
{
e->localteam = NULL;
if (e->reactdelay)
{
AI_MakeEnemy_Timer(e, other, memory_flags, e->reactdelay);
}
else
{
AI_MakeEnemy(e, other, memory_flags);
}
}
}
}
self->localteam = NULL;
}
// END JOSEPH
if ( ! (cast_memory = level.global_cast_memory[self->character_index][other->character_index] ) )
{ // we are not aware of this person, so create the memory
AI_RecordSighting ( self, other, VectorDistance(self->s.origin, other->s.origin) );
cast_memory = level.global_cast_memory[self->character_index][other->character_index];
}
if (cast_memory->memory_type != MEMORY_TYPE_ENEMY)
{ // make them one
AI_RemoveFromMemory( self, cast_memory );
AI_AddToMemory ( self, cast_memory, MEMORY_TYPE_ENEMY );
}
cast_memory->flags |= memory_flags;
cast_memory->flags |= MEMORY_HOSTILE_ENEMY;
}
/*
===========
AI_RecordSighting
Updates SRC's memory of DEST
===========
*/
void AI_RecordSighting(edict_t *src, edict_t *dest, float dist)
{
cast_memory_t *cast_memory, *dest_enemy_memory, *dest_to_dest_enemy_memory;
qboolean processed=false;
if (ai_debug_memory->value)
{
// draw a line to show that we can see them
NAV_DrawLine( src->s.origin, dest->s.origin );
}
cast_memory = level.global_cast_memory[src->character_index][dest->character_index];
if (!cast_memory)
{ // we need to create the memory of this character
AI_CreateCharacterMemory(src, dest);
cast_memory = level.global_cast_memory[src->character_index][dest->character_index];
}
// do we have a sight target?
if ( (cast_memory->memory_type == MEMORY_TYPE_ENEMY)
&& (src->sight_target)
&& (!src->goal_ent)
&& (cast_memory->timestamp < (level.time - 10))) // we haven't seen them in a while
{
edict_t *targ;
targ = NULL;
if (targ = G_Find (NULL, FOFS(targetname), src->sight_target))
{
src->goal_ent = targ;
src->cast_info.aiflags |= AI_GOAL_IGNOREENEMY;
gi.dprintf( "AI: Going for sight_target\n");
}
}
// Check for special events
processed = EP_CastSight ( src, dest, cast_memory );
// update the memory of this character
VectorCopy(dest->s.origin, cast_memory->last_known_origin);
cast_memory->timestamp = level.time;
cast_memory->timestamp_dist = dist;
cast_memory->last_known_closest_node = dest->nav_data.cache_node;
// if they're a friend, share enemy information
if ( (cast_memory->memory_type == MEMORY_TYPE_FRIEND)
&& ( (src->cast_group != 1)
|| (!dest->client && dest->leader) // if not a client, only help if they've been hired
|| (cast_memory->flags & MEMORY_HIRED)))
{
AI_ShareEnemies( src, dest );
}
else if (processed) // EP_CastSight() has processed the reactions of this AI character
{
return;
}
/*
else if (cast_memory->flags & MEMORY_TAUNT)
{
src->cast_info.currentmove = src->cast_info.move_stand;
}
*/
// Below checks for making them HOSTILE
else if (cast_memory->flags & MEMORY_HOSTILE_ENEMY)
{
// already hostile
// if currently hiding from someone else, then abort hiding
if ( (src->cast_info.aiflags & AI_TAKE_COVER)
&& (dest != src->cover_ent))
{
src->cast_info.aiflags &= ~AI_TAKE_COVER;
src->cast_info.aiflags &= ~AI_TAKECOVER_IGNOREHEALTH;
}
// if we've been told to hold position, and we're melee, then hide
else if ( !(src->cast_info.aiflags & AI_TAKE_COVER)
&& (src->cast_info.aiflags & AI_HOLD_POSITION)
&& (src->cast_info.aiflags & AI_MELEE))
{
AI_ForceTakeCover( src, dest, true );
}
}
// If our leader has just issued a Moveout order, then we should attack it
else if ( (src->leader)
&& (src->cast_info.aiflags & AI_MOVEOUT)
&& (src->leader->order == ORDER_MOVE)
&& (src->leader->moveout_ent == dest)
&& (dest->health > 0))
{
AI_MakeEnemy( src, dest, 0 );
src->enemy = dest;
// Ridah, changed this, they should keep taking moveout orders until we tell them otherwise
// src->cast_info.aiflags &= ~AI_MOVEOUT;
}
// Are they in our territory?
else if ( (dest->last_territory_touched)
&& (dest->last_territory_touched->cast_group == src->cast_group)
&& (dest->last_territory_touched->moral > MORAL_HAPPY))
{
// the player can have some time to get out of the
// territory if his weapon is holstered
if ( ( (dest->noise_time > (level.time - 1))
&& (dest->noise_type == PNOISE_WEAPON))
|| ( VectorDistance(dest->s.origin, dest->last_territory_touched->pos1) > dest->last_territory_touched->dmg_radius )
// problem with the ai_territory
// || (dest->client && !(dest->client->pers.holsteredweapon) && dest->client->pers.weapon)
// && (dist < AI_NOT_HOLSTERED_RANGE_2)
|| ( (dist < AI_NOT_HOLSTERED_RANGE_1)
&& (!(cast_memory->flags & MEMORY_HOSTILE_ENEMY))
&& ( (!dest->client)
|| ( (!dest->client->pers.holsteredweapon)
&& (dest->client->pers.weapon)))))
{ // always attack
AI_MakeEnemy( src, dest, 0 );
if (dest->client && !(dest->client->pers.holsteredweapon) && dest->client->pers.weapon)
{
if (src->gender == GENDER_FEMALE)
Voice_Random( src, dest, f_profanity_level3, F_NUM_PROFANITY_LEVEL3 );
else
Voice_Random( src, dest, fightsounds, 10 );
}
else
{
if (src->gender == GENDER_FEMALE)
Voice_Specific( src, dest, f_fightsounds, 1 ); // FUCKER!!
else
Voice_Specific( src, dest, fightsounds, 9 ); // FUCKER!!
}
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, attacking!\n");
}
}
else if (dist < AI_NOT_HOLSTERED_RANGE_3)
{ // let them know we're aware of them
#define TIME_TO_COMPLY 4.0
if (!(cast_memory->flags & MEMORY_HOSTILE_ENEMY))
{
if ( (level.time > cast_memory->not_holstered_attack_time)
&& (level.time < (cast_memory->not_holstered_attack_time+5)))
{ // attack, we've already warned them
AI_MakeEnemy( src, dest, 0 );
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, attacking!\n");
}
}
else if (level.time > cast_memory->not_holstered_attack_time)
{
//gi.dprintf( "SOUND TODO: GET OUT OF OUR TERRITORY PUNK!\n" );
// FIXME: we need a list of voices to choose from here
// there sounds should only play is dest can see src
if (visible (src, dest) && infront (src, dest))
{
if (src->gender == GENDER_FEMALE)
{
//extern voice_table_t f_backoff[];
Voice_Random(src, dest, f_backoff, 3);
}
else
{
//extern voice_table_t m_backoff[];
// JOSEPH 26-MAY-99
if (src->name_index == NAME_NICKIBLANCO)
{
Voice_Specific(src, src->cast_info.talk_ent, nickiblanco, 10);
}
else if (src->name_index == NAME_TYRONE)
{
Voice_Specific(src, src->cast_info.talk_ent, ty_tyrone, 10);
}
else if (src->name_index == NAME_MOKER)
{
Voice_Specific(src, src->cast_info.talk_ent, steeltown_moker, 10);
}
else if (src->name_index == NAME_JESUS)
{
Voice_Specific(src, src->cast_info.talk_ent, sr_jesus, 10);
}
else if (src->name_index == NAME_HEILMAN)
{
Voice_Specific(src, src->cast_info.talk_ent, heilman, 10);
}
else if (src->name_index == NAME_BLUNT)
{
Voice_Specific(src, src->cast_info.talk_ent, blunt, 10);
}
else if (src->name_index == NAME_KINGPIN)
{
Voice_Specific(src, src->cast_info.talk_ent, kingpin, 10);
}
else
Voice_Random(src, dest, specific, 2);
// END JOSEPH
}
}
// give them some "time to comply"
cast_memory->not_holstered_attack_time = level.time + TIME_TO_COMPLY;
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, you have %i seconds to comply\n", (int) TIME_TO_COMPLY);
}
// if we're standing around, turn to face them
if ( (src->cast_info.currentmove->frame->aifunc == ai_stand)
&& (src->cast_info.move_avoid_walk && src->cast_info.move_avoid_crwalk))
{
vec3_t vec;
VectorSubtract( dest->s.origin, src->s.origin, vec );
VectorNormalize( vec );
src->ideal_yaw = vectoyaw( vec );
if (src->maxs[2] < src->cast_info.standing_max_z)
src->cast_info.currentmove = src->cast_info.move_avoid_crwalk;
else
src->cast_info.currentmove = src->cast_info.move_avoid_walk;
}
}
}
// if we're standing around, turn to face them
if (src->cast_info.currentmove->frame->aifunc == ai_stand)
{
vec3_t vec;
VectorSubtract( dest->s.origin, src->s.origin, vec );
VectorNormalize( vec );
src->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( src );
}
}
/*
else if (dist < AI_NOT_HOLSTERED_RANGE_3)
{ // if walking, ignore, if running, turn to face them, ready for attack if they get within range
if (VectorLength( dest->velocity ) > 210)
{ // running
// if we're standing around, turn to face them
if (src->cast_info.currentmove->frame->aifunc == ai_stand)
{
vec3_t vec;
VectorSubtract( dest->s.origin, src->s.origin, vec );
VectorNormalize( vec );
src->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( src );
}
}
}
*/
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Get the FUCK off my street!\n");
}
}
// are they attacking one of our friends (client only)?
else if ( (dest->enemy)
&& (dest->enemy->client)
&& (dest->enemy->health > 0)
&& (dest_to_dest_enemy_memory = level.global_cast_memory[dest->character_index][dest->enemy->character_index])
&& (dest_to_dest_enemy_memory->flags & MEMORY_STARTED_ATTACK)
&& (dest_enemy_memory = level.global_cast_memory[src->character_index][dest->enemy->character_index])
&& ( (src->cast_group != 1)
|| (!dest->enemy->client && dest->enemy->leader) // if not a client, only help if they've been hired
|| (dest_enemy_memory->flags & MEMORY_HIRED))
&& (dest_enemy_memory->memory_type == MEMORY_TYPE_FRIEND))
{ // hey, leave our homie alone!
AI_MakeEnemy( src, dest, 0 );
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Hey, leave our homie alone!\n");
}
}
// if they have recently fired a weapon, we should get outta here (since we don't want to get involved)
else if ( !src->enemy
&& (!src->cast_group || (dest->current_territory != src->cast_group))
&& (src->moral < MORAL_PSYCOTIC)
&& (dest->noise_time > (level.time - 3) && dest->noise_type == PNOISE_WEAPON)
&& (!src->leader || (src->cast_info.aiflags & AI_HOLD_POSITION))
&& !src->goal_ent
&& (!(src->cast_info.aiflags & AI_TAKE_COVER) || (VectorDistance(src->s.origin, src->combat_goalent->s.origin) < 256))
&& (!directly_infront_angle(dest->noise_angles, dest, src)))
// && (AI_ClearSight(src, dest, false)))
{
if (AI_ForceTakeCover( src, dest, true ))
{
}
else if (src->maxs[2] > DUCKING_MAX_Z && src->cast_info.move_evade && src->cast_info.currentmove != src->cast_info.move_evade
&& (src->cast_info.currentmove->endfunc != AI_CheckEvade)) // evade
{
src->cast_info.currentmove = src->cast_info.move_evade;
src->cast_info.avoid_ent = dest;
src->cast_info.last_avoid = level.time;
if (src->cast_info.backoff)
src->cast_info.backoff( src, dest );
}
}
// if they just fired in our direction, attack
else if ( (dest->noise_time > (level.time - 1))
&& (src->cast_info.currentmove->frame->aifunc == ai_stand)
&& (dest->noise_type == PNOISE_WEAPON)
&& (dest->client && dest->client->pers.weapon && (dest->client->pers.weapon->ammo)) // ignore if melee
&& (cast_memory->memory_type != MEMORY_TYPE_FRIEND)
&& (directly_infront_angle( dest->noise_angles, dest, src ))
&& (AI_ClearSight(dest, src, false)))
{
AI_MakeEnemy( src, dest, 0 );
}
else if ( (dest->client)
&& !(dest->client->pers.holsteredweapon)
&& (cast_memory->memory_type != MEMORY_TYPE_FRIEND)
&& (dest->client->pers.weapon))
{ // get mad, they have a weapon raised
if ( (dist < AI_NOT_HOLSTERED_RANGE_1)
&& !(cast_memory->flags & MEMORY_HOSTILE_ENEMY))
{ // always attack
AI_MakeEnemy( src, dest, 0 );
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, attacking!\n");
}
}
else if (dist < AI_NOT_HOLSTERED_RANGE_2)
{ // let them know we're aware of them
#define TIME_TO_COMPLY 4.0
if (!(cast_memory->flags & MEMORY_HOSTILE_ENEMY))
{
if ( (level.time > cast_memory->not_holstered_attack_time)
&& (level.time < (cast_memory->not_holstered_attack_time+5)))
{ // attack, we've already warned them
AI_MakeEnemy( src, dest, 0 );
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, attacking!\n");
}
}
else if (level.time > cast_memory->not_holstered_attack_time)
{
if ( !(cast_memory->flags & MEMORY_WARNED_BACKOFF)
&& (infront(src, dest) && visible (src, dest)))
{
cast_memory->flags |= MEMORY_WARNED_BACKOFF;
if (src->gender == GENDER_FEMALE)
{
extern voice_table_t f_backoff[];
Voice_Random(src, src->cast_info.talk_ent, f_backoff, 3);
}
else
{
extern voice_table_t m_backoff[];
// JOSEPH 26-MAY-99
if (src->name_index == NAME_JESUS)
Voice_Random(src, src->cast_info.talk_ent, &sr_jesus[6], 10);
else if (src->name_index == NAME_KINGPIN)
Voice_Random(src, src->cast_info.talk_ent, &kingpin[6], 10);
else if (src->name_index == NAME_HEILMAN)
Voice_Specific (src, src->cast_info.talk_ent, heilman, 11);
else if (src->name_index == NAME_NICKIBLANCO)
Voice_Specific (src, src->cast_info.talk_ent, nickiblanco, 11);
else
Voice_Random(src, src->cast_info.talk_ent, m_backoff, 3);
// END JOSEPH
}
}
// gi.sound( src, CHAN_VOICE, gi.soundindex( "actors/profanity/level2/cuss2-3.wav" ), 1, 2, 0 );
// give them some "time to comply"
cast_memory->not_holstered_attack_time = level.time + TIME_TO_COMPLY;
// gi.dprintf("SOUND TODO: Drop your weapon biatch!\n");
if (ai_debug_memory->value)
{
gi.dprintf("AI_RecordSighting: Weapon not holstered, you have %i seconds to comply\n", (int) TIME_TO_COMPLY);
}
// if we're standing around, turn to face them
if ( (src->cast_info.currentmove->frame->aifunc == ai_stand)
&& (src->cast_info.move_avoid_walk && src->cast_info.move_avoid_crwalk))
{
src->cast_info.avoid( src, dest, true );
}
}
}
// if we're standing around, turn to face them
if (src->cast_info.currentmove->frame->aifunc == ai_stand)
{
vec3_t vec;
VectorSubtract( dest->s.origin, src->s.origin, vec );
VectorNormalize( vec );
src->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( src );
}
// evade?
if (src->cast_info.currentmove->frame->aifunc == ai_stand && src->cast_info.move_stand_evade && (src->moral < MORAL_AGGRESSIVE))
{
src->cast_info.currentmove = src->cast_info.move_stand_evade;
src->last_stand_evade = level.time;
}
}
else if (dist < AI_NOT_HOLSTERED_RANGE_3)
{ // if walking, ignore, if running, turn to face them, ready for attack if they get within range
if (VectorLength( dest->velocity ) > 210)
{ // running
// if we're standing around, turn to face them
if (src->cast_info.currentmove->frame->aifunc == ai_stand)
{
vec3_t vec;
VectorSubtract( dest->s.origin, src->s.origin, vec );
VectorNormalize( vec );
src->ideal_yaw = vectoyaw( vec );
M_ChangeYaw( src );
}
}
}
}
}
void AI_CheckRecordMemory( edict_t *src, edict_t *dest )
{
vec3_t dest_vec;
float length;
int i;
cast_memory_t *memory;
vec3_t src_org, dest_org;
if (dest->health <= 0)
return;
if (!dest->solid)
return;
if (dest->flags & FL_NOTARGET)
return;
memory = NULL;
// check for sight by noise
if ( (dest->noise_time > level.time)
//&& (level.global_cast_memory[src->character_index][dest->character_index])
&& (gi.inPVS( src->s.origin, dest->noise_pos )))
{
// update the memory of this character
AI_RecordSighting(src, dest, VectorDistance(src->s.origin, dest->s.origin) );
return;
}
if (dest->client && (dest->light_level < 5))
return;
// do a faster distance check, without sqrt'ing
VectorSubtract(dest->s.origin, src->s.origin, dest_vec);
length = 0;
for (i=0 ; i< 3 ; i++)
length += dest_vec[i]*dest_vec[i];
if (length > (src->cast_info.max_sighting_distance * src->cast_info.max_sighting_distance))
return;
memory = level.global_cast_memory[src->character_index][dest->character_index];
if (memory && (memory->ignore_time > level.time))
return; // we are currently ignoring this entity
if ( ( (length > 65535) // 256^2, since length is the squared distance
|| ( (dest->client)
&& (VectorLength(dest->velocity) < 210))) // out of "close" range
&& ( ( !memory
|| (memory->timestamp < (level.time - 10))) // not aware of this person
&& (!infront(src, dest))))
{
return;
}
VectorCopy( src->s.origin, src_org );
src_org[2] += src->viewheight;
VectorCopy( dest->s.origin, dest_org );
dest_org[2] += dest->viewheight;
if (!gi.inPVS(src_org, dest_org) /*&& !gi.inPHS(src_org, dest_org)*/)
return;
if ( !memory
|| (memory->timestamp < (level.time - 5)) // haven't seen them for a while
|| (memory->ignore_time > level.time))
{ // do a thorough test
if (!AI_ClearSight(src, dest, false))
return;
// move the box around a bit
}
// SRC can see DEST
// update the memory of this character
AI_RecordSighting(src, dest, sqrt(length));
}
/*
===========
AI_UpdateCharacterMemories
Called once per frame, this handles all AI character sightings, updating their memories
as we go.
The individual characters can then evaluate their memory when idle, to see if there's
something they can do.
===========
*/
void AI_UpdateCharacterMemories( int max_iterations )
{
// For each character, check all other characters to see if we can see and recognise them
// Reasons for failing a sighting (in order for speed purposes):
//
// 1. Enemy is too dark (can't see or recognise)
// 2. Enemy is a NOTARGET
// 3. Enemy is too far away to recognise
// 4. Enemy is not infront of us
// 5. We can't physically see them
static int src_index, dest_index;
int num_iterations=0;
edict_t *src, *dest;
int i;
if (deathmatch->value)
return;
if (level.cut_scene_time)
return;
// first check client sightings
dest = level.characters[0];
if (dest && !(dest->flags & FL_NOTARGET))
{
for (i=1; i<level.num_characters; i++)
{
src = level.characters[i];
if (!src)
continue;
if (src->health <= 0)
continue;
if (src->client)
continue;
if (src->cast_group < 2)
continue;
AI_CheckRecordMemory( src, dest );
}
}
if (src_index >= level.num_characters)
src_index = 0;
for ( ; src_index < level.num_characters; src_index++)
{
if (!level.characters[src_index])
continue;
src = level.characters[src_index];
if (src->health <= 0)
continue;
if (src->client)
continue;
for ( ; dest_index < level.num_characters; dest_index++ )
{
if (!level.characters[dest_index])
continue;
dest = level.characters[dest_index];
if (src == dest)
continue;
if (dest->client && (src->cast_group >= 2))
continue; // already processed above
if (num_iterations++ > max_iterations)
return;
AI_CheckRecordMemory( src, dest );
}
dest_index = 0;
}
src_index = 0;
}
void AI_ReactDelayThink (edict_t *ent)
{
if (ent->enemy && ent->owner)
{
AI_MakeEnemy (ent->owner, ent->enemy, 0);
ent->cast_info.aiflags &= ~AI_HEARD_GUN_SHOT;
}
G_FreeEdict (ent);
}
// RAFAEL 28-dec-98
void AI_ReactDelay (edict_t *self, edict_t *player)
{
edict_t *ent;
ent = G_Spawn();
ent->enemy = player;
ent->owner = self;
ent->nextthink = level.time + self->gun_noise_delay;
ent->think = AI_ReactDelayThink;
gi.linkentity (self);
}
// RAFAEL 28-dec-98
/*
AI_HearPlayer
Returns TRUE if the player and self are in the same PVS and the player has made a hostile sound
*/
qboolean AI_HearPlayer (edict_t *self)
{
edict_t *player;
float dist;
vec3_t vec;
if (self->cast_info.aiflags & AI_IMMORTAL)
return false;
if (self->cast_group == 1)
return false;
if (self->cast_group == 0) // neutral will try to take cover
return false;
if (self->cast_info.aiflags & AI_HEARD_GUN_SHOT)
return false;
// this will cause a problem with scripted characters so
// if (!EP_GetCharacter ( self->name_index ))
// return false;
player = &g_edicts[1];
if (!player->client)
return false;
if (player->client->gun_noise)
{
if ( (self->cast_group || directly_infront(player, self))
&& gi.inPVS (player->s.origin, self->s.origin))
{
VectorSubtract (player->s.origin, self->s.origin, vec);
dist = VectorLength (vec);
// if (self->moral > MORAL_HAPPY)
if (dist < 1538)
{
if (self->gun_noise_delay)
{
self->cast_info.aiflags |= AI_HEARD_GUN_SHOT;
AI_ReactDelay (self, player);
}
else
AI_MakeEnemy (self, player, 0);
return true;
}
/*
else
{
gi.dprintf ("Player fired a shot but my moral is %d\n", self->moral);
return false;
}
*/
}
}
return false;
}
/*
===========
AI_HasLeaderButGoForEnemy
===========
*/
qboolean AI_HasLeaderButGoForEnemy( edict_t *self, edict_t *enemy )
{
float leader_dist;
if (!( (self->leader)
&& (self->leader->order == ORDER_FOLLOWME)))
{
return true;
}
leader_dist = VectorDistance( self->s.origin, self->leader->s.origin );
// if we're out of range, go to them
if (leader_dist > 512)
{
return false;
}
/*
// if they've just ordered it, go to them at all costs
if (enemy->leader->order_timestamp > (level.time - 5))
{
return false;
}
*/
// if we're a Melee and the enemy is out of a reasonable range, don't bother
if (self->cast_info.aiflags & AI_MELEE)
{
if (VectorDistance( self->leader->s.origin, enemy->s.origin ) > 256)
{
return false;
}
}
// if we're shooting, and they're not clearly visible (shootable from here), then fail
else
{
if (!AI_ClearSight( self, enemy, false ))
{
return false;
}
}
return true;
}
/*
===========
AI_FindTarget
Self is currently not attacking anything, so try to find a target
Returns TRUE if an enemy is available (has been sighted, and we should engage)
============
*/
qboolean AI_FindTarget (edict_t *self)
{
cast_memory_t *cast_memory;
edict_t *enemy, *best;
float best_dist;
int i;
if (self->cast_info.aiflags & AI_IMMORTAL)
return false;
// Ridah, Changed this 28-Mar-99, so if even a slight memory bug won't cause them to ignore you if under attack
// traverse our enemy list for something to attack
//cast_memory = self->cast_info.enemy_memory;
best = NULL;
i = 0;
for (i = 0; i < level.num_characters; i++)
{
cast_memory = level.global_cast_memory[self->character_index][i];
if (!cast_memory || cast_memory->memory_type != MEMORY_TYPE_ENEMY || g_edicts[cast_memory->cast_ent].health <= 0)
continue;
if ( (cast_memory->timestamp > (level.time - ENEMY_SIGHT_DURATION))
&& (cast_memory->flags & MEMORY_HOSTILE_ENEMY)
&& (cast_memory->ignore_time < level.time))
{
enemy = &g_edicts[cast_memory->cast_ent];
if (enemy->flags & FL_NOTARGET)
goto failed;
if (!enemy->inuse)
goto failed;
if (enemy->health <= 0)
goto failed;
if (!AI_HasLeaderButGoForEnemy( self, enemy ))
goto failed;
if (self->cast_info.aiflags & AI_TAKE_COVER)
{ // make sure we can see them before we attack
if (!AI_ClearSight( self, enemy, false ))
{
goto failed;
}
else if (cast_memory->timestamp_dist < 128) // allow some time to attack
{
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->combat_goalent = NULL;
self->dont_takecover_time = 99999; // attack for a few second
}
else // too far away, just go for another hiding pos
{
self->cast_info.aiflags &= ~AI_TAKE_COVER;
self->combat_goalent = NULL;
// goto failed;
}
}
// found an enemy
if (!best || (cast_memory->timestamp_dist < best_dist))
{
best = enemy;
best_dist = cast_memory->timestamp_dist;
}
}
failed:
cast_memory = cast_memory->next;
}
if (best)
{
enemy = best;
if ( (enemy->client)
&& (self->cast_info.move_evade)
&& (!self->cast_group)
&& (self->moral < MORAL_AGGRESSIVE)
&& (!self->pain_debounce_time)) // if we've been hurt, go bezerk (don't evade)
{
if ( (!enemy->client->pers.holsteredweapon)
&& (enemy->client->pers.weapon))
{
vec3_t vec;
float len;
VectorSubtract (self->s.origin, enemy->s.origin, vec);
len = VectorLength (vec);
self->enemy = enemy;
if (len < AI_NOT_HOLSTERED_RANGE_1 && self->moral > MORAL_HAPPY)
{
AI_StartAttack( self, enemy );
if (self->cast_info.sight)
self->cast_info.sight ( self, self->enemy );
}
else if (len > AI_NOT_HOLSTERED_RANGE_3)
{
self->cast_info.currentmove = self->cast_info.move_stand;
}
else if (self->maxs[2] > DUCKING_MAX_Z)
self->cast_info.currentmove = self->cast_info.move_evade;
else if (self->cast_info.move_stand_up)
self->cast_info.currentmove = self->cast_info.move_stand_up;
self->maxs[2] = self->cast_info.standing_max_z;
}
else
{
return false;
}
}
else
{
AI_StartAttack( self, enemy );
if (self->cast_info.sight)
self->cast_info.sight ( self, self->enemy );
// Ridah, 7-5-99, If they've been ordered to attack us by a client, get mad at them also
if (enemy->leader)
AI_MakeEnemy( self, enemy->leader, 0 );
}
return true;
}
return false;
}