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

753 lines
17 KiB
C

// g_ai_ents.c
//
// Contains AI related entities
#include "g_local.h"
#include "g_func.h"
void AI_Ent_droptofloor ( edict_t *self )
{
trace_t tr;
vec3_t start, dest;
VectorCopy( self->s.origin, start );
start[2] += 0.1;
VectorCopy( start, dest );
dest[2] -= 4000;
tr = gi.trace( start, self->mins, self->maxs, dest, self, MASK_PLAYERSOLID );
if (tr.startsolid || tr.allsolid)
{
gi.dprintf( "Warning: %s in solid at (%s)\n", self->classname, vtos(self->s.origin) );
return;
}
if (tr.fraction == 1)
{
gi.dprintf( "Warning: %s above ground at (%s)\n", self->classname, vtos(self->s.origin) );
return;
}
VectorCopy( tr.endpos, self->s.origin );
}
//======================================================================================
/*QUAKED ai_boundary (.5 .5 0) ?
Character will abort pursuing player when touching this brush. Will
take cover until the player is out of view, then return to guarding position (if
character has been assigned one).
"moral" range from 1 (Coward) to 7 (Psycotic). Only characters with an equal or lower moral level will be effected by this brush.
*/
void boundary_takecover_think ( edict_t *self )
{
if (!self->owner->combat_goalent)
{
if ((self->owner->cast_info.aiflags & (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH)) != (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH))
{
AI_ForceTakeCover( self->owner, self->owner->enemy, true );
}
G_FreeEdict( self );
return;
}
if (self->owner->health <= 0)
{
G_FreeEdict( self );
return;
}
self->nextthink = level.time + 1.0;
}
void ai_boundary_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
#define DEBUG_AI_BOUNDARY 0
edict_t *thinker;
if (!(other->svflags & SVF_MONSTER))
return;
if (other->cast_group != self->cast_group)
return;
else if (other->moral > self->moral)
return;
// abort taking cover
other->cast_info.aiflags &= ~AI_TAKE_COVER;
other->combat_goalent = NULL;
// we should go into a Take Cover mode for a bit, and then return to our guarding
// position, if possible.
if (other->enemy)
{
cast_memory_t *mem;
if ((other->cast_info.aiflags & (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH)) != (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH))
{
// Just go back to our start position
if (!AI_ForceTakeCover( other, other->enemy, true ))
{
if (mem = level.global_cast_memory[other->character_index][other->enemy->character_index])
{
// pretend that we haven't seen them in a while
mem->timestamp = level.time - 5;
mem->flags |= MEMORY_TAUNT;
other->enemy = NULL;
if (DEBUG_AI_BOUNDARY)
gi.dprintf( "Sending %s to start position\n", other->name );
// go back to our start position
other->combat_goalent = other->goal_ent = other->start_ent;
other->combat_goalent->cast_info.aiflags |= AI_GOAL_RUN;
thinker = G_Spawn();
thinker->owner = other;
thinker->nextthink = level.time + 3;
thinker->think = boundary_takecover_think;
mem->ignore_time = level.time + 2;
}
}
// else
// {
// if (DEBUG_AI_BOUNDARY)
// gi.dprintf( "%s taking cover\n", other->name );
// }
}
/*
// tell all our friends in our vacinity to do the same
mem = other->cast_info.friend_memory;
while (mem)
{
if (g_edicts[mem->cast_ent].health > 0 && VectorDistance( other->s.origin, g_edicts[mem->cast_ent].s.origin ) < 256)
{
edict_t *dude;
cast_memory_t *dude_mem;
dude = &g_edicts[mem->cast_ent];
if (dude->enemy)
{
if ((dude->cast_info.aiflags & (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH)) != (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH))
{
if (!AI_ForceTakeCover( dude ))
{
dude_mem = level.global_cast_memory[dude->character_index][dude->enemy->character_index];
// pretend that we haven't seen them in a while
dude_mem->timestamp = level.time - 5;
dude_mem->flags |= MEMORY_TAUNT;
dude_mem->ignore_time = level.time + 2;
// dude->enemy = NULL;
}
else
{
if (DEBUG_AI_BOUNDARY)
gi.dprintf( "%s taking cover\n", dude->name );
}
}
}
if ((dude->cast_info.aiflags & (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH)) != (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH))
{
if (DEBUG_AI_BOUNDARY)
gi.dprintf( "Sending %s to start position\n", dude->name );
// go back to our start position
dude->goal_ent = dude->start_ent;
dude->goal_ent->cast_info.aiflags |= AI_GOAL_RUN;
thinker = G_Spawn();
thinker->owner = dude;
thinker->nextthink = level.time + 1;
thinker->think = boundary_takecover_think;
}
}
mem = mem->next;
}
*/
}
else if ((other->cast_info.aiflags & (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH)) != (AI_TAKE_COVER|AI_TAKECOVER_IGNOREHEALTH))
{
if (DEBUG_AI_BOUNDARY)
gi.dprintf( "Sending %s to start position\n", other->name );
// start walking back to our start position
other->goal_ent = other->start_ent;
other->goal_ent->cast_info.aiflags |= AI_GOAL_RUN;
thinker = G_Spawn();
thinker->owner = other;
thinker->nextthink = level.time + 1;
thinker->think = boundary_takecover_think;
}
}
void SP_ai_boundary (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_boundary_touch;
// set the center pos
VectorAdd( ent->absmin, ent->absmax, ent->pos1 );
VectorScale( ent->pos1, 0.5, ent->pos1 );
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
//======================================================================================
/*QUAKED ai_event_hostile (.5 .5 0) ?
Character touching this brush will become a hostile enemy to all other characters
in LOS, that have the same "cast_group" as the brush.
"cast_group" must be > 0 for this brush to have any effect.
*/
void ai_event_hostile_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int i;
cast_memory_t *mem;
edict_t *icast;
if (self->last_talk_time > (level.time - 0.5))
return;
self->last_talk_time = level.time;
if (!(other->svflags & SVF_MONSTER) && !(other->client))
return;
if (other->cast_group == self->cast_group)
return;
// for all characters that belong to this entity, make them hostile towards us
for (i=0; i<level.num_characters; i++)
{
if (!level.characters[i])
continue;
icast = level.characters[i];
if (icast->cast_group != self->cast_group)
continue;
if (icast->health <= 0 || !icast->inuse)
continue;
mem = level.global_cast_memory[i][other->character_index];
if (!mem)
continue;
if (mem->timestamp < (level.time - ENEMY_SIGHT_DURATION*2))
continue;
if (mem->flags & MEMORY_HOSTILE_ENEMY)
continue;
AI_MakeEnemy( icast, other, 0 );
// now send them to us, if they can see/hear us
if ( (mem->timestamp > (level.time - ENEMY_SIGHT_DURATION))
// || (gi.inPHS( other->s.origin, icast->s.origin ))
|| (gi.inPVS( other->s.origin, icast->s.origin )))
{
AI_RecordSighting( icast, other, VectorDistance(icast->s.origin, other->s.origin) );
}
}
}
void SP_ai_event_hostile (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
if (ent->cast_group < 1)
{
gi.dprintf("Warning: ai_event_hostile without a valid cast_group\n");
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_event_hostile_touch;
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
//======================================================================================
/*QUAKED ai_event_follow (.5 .5 0) ?
Client touching this brush will become a leader to all other characters
in LOS, that have the same "cast_group" as the brush.
"cast_group" must be > 0 for this brush to have any effect.
*/
void ai_event_follow_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
int i;
edict_t *icast;
if (self->last_talk_time > (level.time - 0.5))
return;
self->last_talk_time = level.time;
if (!(other->client))
return;
if (other->cast_group != self->cast_group)
return;
// for all characters that belong to this entity, make them follow us
for (i=0; i<level.num_characters; i++)
{
if (!level.characters[i])
continue;
icast = level.characters[i];
if (icast->cast_group != self->cast_group)
continue;
if (icast->health <= 0 || !icast->inuse)
continue;
if (icast->leader)
continue;
icast->leader = other;
}
}
void SP_ai_event_follow (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
if (ent->cast_group < 1)
{
gi.dprintf("Warning: ai_event_follow without a valid cast_group\n");
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_event_follow_touch;
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
//======================================================================================
/*QUAKED ai_guard (.5 .5 1) (-16 -16 -24) (16 16 48)
Set a cast's "guard_target" to the "targetname" of this entity.
That character will then guard this location.
"targetname" links to "guard_target" for the cast entity(s)
"guard_radius" is the max guarding radius (default = 512)
*/
void SP_ai_guard (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 48);
if (!self->guard_radius)
self->guard_radius = 512;
AI_Ent_droptofloor( self );
}
//======================================================================================
/*QUAKED ai_territory (.5 .5 0) ?
Marks the boundary of a gang's territory.
A character touching this will be deemed inside the gang's
territory. This means war if sighted.
!!NOTE!!: Point the "angles" in the direction of the territory. This lets
the AI know if the character is walking into or out of the territory.
"cast_group" is the group that owns this territory
"angles" points to the direction of the territory
"radius" distance from brush before the player will be attacked (default 512)
*/
void ai_territory_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t vec;
if (!(other->svflags & SVF_MONSTER) && !(other->client))
return;
// are we going into, or out of the territory?
VectorSubtract( other->s.origin, self->pos1, vec );
vec[2] = 0;
VectorNormalize( vec );
if (DotProduct( vec, self->movedir ) > 0)
{ // going into
// if (nav_dynamic->value && other->client && (!other->last_territory_touched || (other->last_territory_touched->cast_group != self->cast_group)))
// gi.dprintf("%s going into %s territory\n", other->classname, EP_GetGangName(self->cast_group) );
if ((!other->last_territory_touched || (other->last_territory_touched->cast_group != self->cast_group)))
{
other->last_territory_touched = self;
other->current_territory = self->cast_group;
}
}
else // going out of
{
// if (nav_dynamic->value && other->client && other->last_territory_touched)
// gi.dprintf("%s leaving %s territory\n", other->classname, EP_GetGangName(self->cast_group) );
if (other->last_territory_touched)
{
other->last_territory_touched = NULL;
other->current_territory = self->cast_group;
}
}
other->time_territory_touched = level.time;
}
void SP_ai_territory ( edict_t *ent )
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_territory_touch;
if (!ent->cast_group)
ent->cast_group = 2;
if (!ent->dmg_radius)
ent->dmg_radius = 512;
if (!ent->moral)
{
gi.dprintf( "\n\nWARNING: ai_territory without a 'moral' (defaulting to 7)\nUse a 'moral' = 1 to PREVENT the gang members getting hostile when you enter their turf\n\n" );
ent->moral = MORAL_AGGRESSIVE;
}
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
// set the center pos
VectorAdd( ent->absmin, ent->absmax, ent->pos1 );
VectorScale( ent->pos1, 0.5, ent->pos1 );
AngleVectors( ent->s.angles, ent->movedir, NULL, NULL );
}
/*QUAKED ai_safespot (.5 .5 1) (-16 -16 -24) (16 16 48)
Set a cast's "flee_target" to the "targetname" of this entity.
That character will then flee to this location.
"targetname" links to "flee_target" for the cast entity(s)
*/
void SP_ai_safespot (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 48);
AI_Ent_droptofloor( self );
}
/*QUAKED ai_reset (.5 .5 0) ?
This is a brush that will reset a cast location to his
startup location
FIXME: Is this implemented yet?
*/
void ai_reset_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
EP_Reset (self, other);
}
void SP_ai_reset (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_reset_touch;
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
/*QUAKED ai_combat_spot (.5 .5 1) (-16 -16 -24) (16 16 48)
A good place to go to get a vantage point during fighting.
*/
void SP_ai_combat_spot (edict_t *self)
{
if (deathmatch->value)
{
G_FreeEdict(self);
return;
}
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
VectorSet (self->mins, -16, -16, -24);
VectorSet (self->maxs, 16, 16, 48);
AI_Ent_droptofloor( self );
}
/*QUAKED ai_trigger_character (.5 .5 0) ?
When the player touches this brush, the targetted character will start
following it's path_corner.
"target" link this with the "targetname" of the character to be triggered
*/
void ai_trigger_character_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void SP_ai_trigger_character (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_trigger_character_touch;
if (!ent->cast_group)
ent->cast_group = 2;
ent->svflags |= SVF_NOCLIENT;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
void ai_trigger_character_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
edict_t *trav=NULL;
if (!(other->client))
return;
while (trav = G_Find(trav, FOFS(targetname), self->target))
{
trav->spawnflags |= SPAWNFLAG_IMMEDIATE_FOLLOW_PATH;
}
}
/*QUAKED ai_locked_door (.5 .5 0) ?
A character touching this brush will check the targetted door to see if it is closed.
If so, the character will head towards the specified path_corner_cast.
Example use: guiding AI characters away from a locked door
"target" link this with the "targetname" of the door to check
"pathtarget" linked with "targetname" of path_corner_cast to head for
*/
void ai_locked_door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
void locked_door_think( edict_t *ent )
{
if (ent->pathtarget)
ent->target_ent = G_Find( NULL, FOFS(targetname), ent->pathtarget );
if (!ent->target_ent)
{
gi.dprintf( "ai_locked_door has invalid pathtarget (should point to a path_corner_cast)\n" );
return;
}
if (ent->target)
ent->goal_ent = G_Find( NULL, FOFS(targetname), ent->target );
if (!ent->goal_ent)
{
gi.dprintf( "ai_locked_door has invalid target (should point to a door)\n" );
return;
}
}
void SP_ai_locked_door (edict_t *ent)
{
if (deathmatch->value)
{
G_FreeEdict(ent);
return;
}
ent->solid = SOLID_TRIGGER;
ent->touch = ai_locked_door_touch;
ent->svflags |= SVF_NOCLIENT;
ent->think = locked_door_think;
ent->nextthink = level.time + 0.1;
gi.setmodel (ent, ent->model);
gi.linkentity (ent);
}
void ai_locked_door_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
if (!self->goal_ent)
return;
// JOSEPH 14-MAY-99
if (!(other->svflags & SVF_MONSTER))
return;
// END JOSEPH
if (other->goal_ent == self->goal_ent)
return;
// if it's not closed, ignore
if (!(self->goal_ent->moveinfo.state == STATE_BOTTOM || self->goal_ent->moveinfo.state == STATE_DOWN))
return;
other->goal_ent = self->goal_ent;
other->cast_info.aiflags |= AI_GOAL_IGNOREENEMY;
}
//======================================================================================
void ai_button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
// JOSEPH 14-MAY-99
if (!(other->svflags & SVF_MONSTER))
return;
// END JOSEPH
self->owner->use( self->owner, other, other );
}
void ai_button_think (edict_t *self)
{
edict_t *trav;
route_t r;
// disabled this, doesn't work too well, need a better approach
// eg. they'll activate lifts when they shouldn't
return;
// check characters to see if any are within range
while ( (self->count < level.num_characters)
&& (level.characters[self->count])
&& ( (level.characters[self->count]->client)
|| (level.characters[self->count]->health < 0)))
{
self->count++;
}
if (self->count < level.num_characters)
{ // check this character
if (!level.characters[self->count])
goto fail;
trav = level.characters[self->count];
if (!trav->inuse || trav->health <= 0)
goto fail;
if (trav->activator)
goto fail;
if (trav->cast_info.currentmove->frame->aifunc != ai_run)
goto fail;
if (VectorDistance( trav->s.origin, self->s.origin ) > 200)
goto fail;
if (!AI_ClearSight( self, trav, false ))
goto fail;
if (!NAV_Route_EntityToEntity( trav, NULL, self, VIS_PARTIAL, false, &r ))
goto fail;
// good enough..
trav->activator = self->owner;
}
fail:
self->count++;
if (self->count > level.num_characters)
self->count = 0;
self->nextthink = level.time + 0.1;
}