1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 //===========================================================================
30 //
31 // Name: ai_cast_events.c
32 // Function: Wolfenstein AI Character Events
33 // Programmer: Ridah
34 // Tab Size: 4 (real tabs)
35 //===========================================================================
36
37 #include "g_local.h"
38 #include "../qcommon/q_shared.h"
39 #include "../botlib/botlib.h" //bot lib interface
40 #include "../botlib/be_aas.h"
41 #include "../botlib/be_ea.h"
42 #include "../botlib/be_ai_gen.h"
43 #include "../botlib/be_ai_goal.h"
44 #include "../botlib/be_ai_move.h"
45 #include "../botlib/botai.h" //bot ai interface
46
47 #include "ai_cast.h"
48
49 /*
50 Contains response functions for various events that require specific handling
51 for Cast AI's.
52 */
53
54 /*
55 ============
56 AICast_Sight
57 ============
58 */
AICast_Sight(gentity_t * ent,gentity_t * other,int lastSight)59 void AICast_Sight( gentity_t *ent, gentity_t *other, int lastSight ) {
60 cast_state_t *cs, *ocs;
61
62 cs = AICast_GetCastState( ent->s.number );
63 ocs = AICast_GetCastState( other->s.number );
64
65 //
66 // call the sightfunc for this cast, so we can play associated sounds, or do any character-specific things
67 //
68 if ( cs->sightfunc ) {
69 // factor in the reaction time
70 if ( AICast_EntityVisible( cs, other->s.number, qfalse ) ) {
71 cs->sightfunc( ent, other, lastSight );
72 }
73 }
74
75 if ( other->aiName && other->health <= 0 ) {
76
77 // they died since we last saw them
78 if ( ocs->deathTime > lastSight ) {
79 if ( !AICast_SameTeam( cs, other->s.number ) ) {
80 AICast_ScriptEvent( cs, "enemysightcorpse", other->aiName );
81 } else if ( !( cs->castScriptStatus.scriptFlags & SFL_FRIENDLYSIGHTCORPSE_TRIGGERED ) ) {
82 cs->castScriptStatus.scriptFlags |= SFL_FRIENDLYSIGHTCORPSE_TRIGGERED;
83 AICast_ScriptEvent( cs, "friendlysightcorpse", "" );
84 }
85 }
86
87 // if this is the first time, call the sight script event
88 } else if ( !lastSight && other->aiName ) {
89 if ( !AICast_SameTeam( cs, other->s.number ) ) {
90 // disabled.. triggered when entering combat mode
91 //AICast_ScriptEvent( cs, "enemysight", other->aiName );
92 } else {
93 AICast_ScriptEvent( cs, "sight", other->aiName );
94 }
95 }
96 }
97
98 /*
99 ============
100 AICast_Pain
101 ============
102 */
AICast_Pain(gentity_t * targ,gentity_t * attacker,int damage,vec3_t point)103 void AICast_Pain( gentity_t *targ, gentity_t *attacker, int damage, vec3_t point ) {
104 cast_state_t *cs;
105
106 cs = AICast_GetCastState( targ->s.number );
107
108 // print debugging message
109 if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) {
110 G_Printf( "hit %s %i\n", targ->aiName, targ->health );
111 }
112
113 // if we are below alert mode, then go there immediately
114 if ( cs->aiState < AISTATE_ALERT ) {
115 AICast_StateChange( cs, AISTATE_ALERT );
116 }
117
118 if ( cs->aiFlags & AIFL_NOPAIN ) {
119 return;
120 }
121
122 // process the event (turn to face the attacking direction? go into hide/retreat state?)
123 // need to weigh up the situation, but foremost, an inactive AI cast should always react in some way to being hurt
124 cs->lastPain = level.time;
125
126 // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way)
127 if ( attacker->client ) {
128 AICast_UpdateVisibility( targ, attacker, qtrue, qtrue );
129 }
130
131 // if either of us are neutral, then we are now enemies
132 if ( targ->aiTeam == AITEAM_NEUTRAL || attacker->aiTeam == AITEAM_NEUTRAL ) {
133 cs->vislist[attacker->s.number].flags |= AIVIS_ENEMY;
134 }
135
136 AICast_ScriptEvent( cs, "painenemy", attacker->aiName );
137
138 AICast_ScriptEvent( cs, "pain", va( "%d %d", targ->health, targ->health + damage ) );
139
140 if ( cs->aiFlags & AIFL_DENYACTION ) {
141 // dont play any sounds
142 return;
143 }
144
145 //
146 // call the painfunc for this cast, so we can play associated sounds, or do any character-specific things
147 //
148 if ( cs->painfunc ) {
149 cs->painfunc( targ, attacker, damage, point );
150 }
151 }
152
153 /*
154 ============
155 AICast_Die
156 ============
157 */
AICast_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)158 void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
159 int contents;
160 int killer;
161 cast_state_t *cs;
162 qboolean nogib = qtrue;
163 char mapname[MAX_QPATH];
164
165 // print debugging message
166 if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) {
167 G_Printf( "killed %s\n", self->aiName );
168 }
169
170 cs = AICast_GetCastState( self->s.number );
171
172 if ( attacker ) {
173 killer = attacker->s.number;
174 } else {
175 killer = ENTITYNUM_WORLD;
176 }
177
178 // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way)
179 if ( attacker && attacker->client ) {
180 AICast_UpdateVisibility( self, attacker, qtrue, qtrue );
181 }
182
183 if ( self->aiCharacter == AICHAR_HEINRICH || self->aiCharacter == AICHAR_HELGA || self->aiCharacter == AICHAR_SUPERSOLDIER || self->aiCharacter == AICHAR_PROTOSOLDIER ) {
184 if ( self->health <= GIB_HEALTH ) {
185 self->health = -1;
186 }
187 }
188
189 // the zombie should show special effect instead of gibbing
190 if ( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime ) {
191 if ( cs->secondDeadTime > 1 ) {
192 // we are already totally dead
193 self->health += damage; // don't drop below gib_health if we weren't already below it
194 return;
195 }
196 /*
197 if (!cs->rebirthTime)
198 {
199 self->health = -999;
200 damage = 999;
201 } else if ( self->health >= GIB_HEALTH ) {
202 // while waiting for rebirth, we only "die" if we drop below gib health
203 return;
204 }
205 */
206 // always gib
207 self->health = -999;
208 damage = 999;
209 }
210
211 // Zombies are very fragile against highly explosives
212 if ( self->aiCharacter == AICHAR_ZOMBIE && damage > 20 && inflictor != attacker ) {
213 self->health = -999;
214 damage = 999;
215 }
216
217 // process the event
218 if ( self->client->ps.pm_type == PM_DEAD ) {
219 // already dead
220 if ( self->health < GIB_HEALTH ) {
221 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
222 // RF, changed this so Zombies always gib now
223 GibEntity( self, killer );
224 nogib = qfalse;
225 self->takedamage = qfalse;
226 self->r.contents = 0;
227 cs->secondDeadTime = 2;
228 cs->rebirthTime = 0;
229 cs->revivingTime = 0;
230 } else {
231 body_die( self, inflictor, attacker, damage, meansOfDeath );
232 return;
233 }
234 }
235
236 } else { // this is our first death, so set everything up
237
238 if ( level.intermissiontime ) {
239 return;
240 }
241
242 self->client->ps.pm_type = PM_DEAD;
243
244 self->enemy = attacker;
245
246 // drop a weapon?
247 // if client is in a nodrop area, don't drop anything
248 contents = trap_PointContents( self->r.currentOrigin, -1 );
249 if ( !( contents & CONTENTS_NODROP ) ) {
250 TossClientItems( self );
251 }
252
253 // make sure the client doesn't forget about this entity until it's set to "dead" frame
254 // otherwise it might replay it's death animation if it goes out and into client view
255 self->r.svFlags |= SVF_BROADCAST;
256
257 self->takedamage = qtrue; // can still be gibbed
258
259 self->s.weapon = WP_NONE;
260 if ( cs->bs ) {
261 cs->weaponNum = WP_NONE;
262 }
263 self->client->ps.weapon = WP_NONE;
264
265 self->s.powerups = 0;
266 self->r.contents = CONTENTS_CORPSE;
267
268 self->s.angles[0] = 0;
269 self->s.angles[1] = self->client->ps.viewangles[1];
270 self->s.angles[2] = 0;
271
272 VectorCopy( self->s.angles, self->client->ps.viewangles );
273
274 self->s.loopSound = 0;
275
276 self->r.maxs[2] = -8;
277 self->client->ps.maxs[2] = self->r.maxs[2];
278
279 // remove powerups
280 memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) );
281
282 //cs->rebirthTime = 0;
283
284 // never gib in a nodrop
285 if ( self->health <= GIB_HEALTH ) {
286 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
287 // RF, changed this so Zombies always gib now
288 GibEntity( self, killer );
289 nogib = qfalse;
290 } else if ( !( contents & CONTENTS_NODROP ) ) {
291 body_die( self, inflictor, attacker, damage, meansOfDeath );
292 //GibEntity( self, killer );
293 nogib = qfalse;
294 }
295 }
296
297 // if we are a zombie, and lying down during our first death, then we should just die
298 if ( !( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime && cs->rebirthTime ) ) {
299
300 // set enemy weapon
301 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
302 if ( attacker && attacker->client ) {
303 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, inflictor->s.weapon, qtrue );
304 } else {
305 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
306 }
307
308 // set enemy location
309 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, 0, qfalse );
310 if ( infront( self, inflictor ) ) {
311 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue );
312 } else {
313 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue );
314 }
315
316 if ( self->takedamage ) { // only play the anim if we haven't gibbed
317 // play the animation
318 BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue );
319 }
320
321 // set gib delay
322 if ( cs->aiCharacter == AICHAR_HEINRICH || cs->aiCharacter == AICHAR_HELGA ) {
323 cs->lastLoadTime = level.time + self->client->ps.torsoTimer - 200;
324 }
325
326 // set this flag so no other anims override us
327 self->client->ps.eFlags |= EF_DEAD;
328 self->s.eFlags |= EF_DEAD;
329
330 // make sure we dont move around while on the ground
331 //self->flags |= FL_NO_HEADCHECK;
332
333 }
334
335 // if end map, sink into ground
336 cs->deadSinkStartTime = 0;
337 if ( cs->aiCharacter == AICHAR_WARZOMBIE ) {
338 trap_Cvar_VariableStringBuffer( "mapname", mapname, sizeof( mapname ) );
339 if ( !Q_strncmp( mapname, "end", 3 ) ) { // !! FIXME: post beta2, make this a spawnflag!
340 cs->deadSinkStartTime = level.time + 4000;
341 }
342 }
343 }
344
345 if ( nogib ) {
346 // set for rebirth
347 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
348 if ( !cs->secondDeadTime ) {
349 cs->rebirthTime = level.time + 5000 + rand() % 2000;
350 // RF, only set for gib at next death, if NoRevive is not set
351 if ( !( self->spawnflags & 2 ) ) {
352 cs->secondDeadTime = qtrue;
353 }
354 cs->revivingTime = 0;
355 } else if ( cs->secondDeadTime > 1 ) {
356 cs->rebirthTime = 0;
357 cs->revivingTime = 0;
358 cs->deathTime = level.time;
359 }
360 } else {
361 // the body can still be gibbed
362 self->die = body_die;
363 }
364 }
365
366 trap_LinkEntity( self );
367
368 // kill, instanly, any streaming sound the character had going
369 G_AddEvent( &g_entities[self->s.number], EV_STOPSTREAMINGSOUND, 0 );
370
371 // mark the time of death
372 cs->deathTime = level.time;
373
374 // dying ai's can trigger a target
375 if ( !cs->rebirthTime ) {
376 G_UseTargets( self, self );
377 // really dead now, so call the script
378 if ( attacker )
379 AICast_ScriptEvent( cs, "death", attacker->aiName ? attacker->aiName : "" );
380 // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
381 if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) {
382 cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod
383 }
384 } else {
385 // really dead now, so call the script
386 AICast_ScriptEvent( cs, "fakedeath", "" );
387 // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
388 if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) {
389 cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod
390 }
391 }
392 }
393
394 /*
395 ===============
396 AICast_EndChase
397 ===============
398 */
AICast_EndChase(cast_state_t * cs)399 void AICast_EndChase( cast_state_t *cs ) {
400 // anything?
401 }
402
403 /*
404 ===============
405 AICast_AIDoor_Touch
406 ===============
407 */
AICast_AIDoor_Touch(gentity_t * ent,gentity_t * aidoor_trigger,gentity_t * door)408 void AICast_AIDoor_Touch( gentity_t *ent, gentity_t *aidoor_trigger, gentity_t *door ) {
409 cast_state_t *cs, *ocs;
410 gentity_t *trav;
411 int i;
412 trace_t tr;
413 vec3_t mins, pos, dir;
414
415 cs = AICast_GetCastState( ent->s.number );
416
417 if ( !cs->bs ) {
418 return;
419 }
420
421 // does the aidoor have ai_marker's?
422 if ( !aidoor_trigger->targetname ) {
423 G_Printf( "trigger_aidoor has no ai_marker's at %s\n", vtos( ent->r.currentOrigin ) );
424 return;
425 }
426
427 // are we heading for an ai_marker?
428 if ( cs->aifunc == AIFunc_DoorMarker ) {
429 return;
430 }
431
432 // if they are moving away from the door, ignore them
433 if ( VectorLength( cs->bs->velocity ) > 1 ) {
434 VectorAdd( door->r.absmin, door->r.absmax, pos );
435 VectorScale( pos, 0.5, pos );
436 VectorSubtract( pos, cs->bs->origin, dir );
437 if ( DotProduct( cs->bs->velocity, dir ) < 0 ) {
438 return;
439 }
440 }
441
442 for ( trav = NULL; ( trav = G_Find( trav, FOFS( target ), aidoor_trigger->targetname ) ); ) {
443 // make sure the marker is vacant
444 trap_Trace( &tr, trav->r.currentOrigin, ent->r.mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
445 if ( tr.startsolid ) {
446 continue;
447 }
448 // search all other AI's, to see if they are heading for this marker
449 for ( i = 0, ocs = AICast_GetCastState( 0 ); i < aicast_maxclients; i++, ocs++ ) {
450 if ( !ocs->bs ) {
451 continue;
452 }
453 if ( ocs->aifunc != AIFunc_DoorMarker ) {
454 continue;
455 }
456 if ( ocs->doorMarker != trav->s.number ) {
457 continue;
458 }
459 // found a match
460 break;
461 }
462 if ( i < aicast_maxclients ) {
463 continue;
464 }
465 // make sure there is a clear path
466 VectorCopy( ent->r.mins, mins );
467 mins[2] += 16; // step height
468 trap_Trace( &tr, ent->r.currentOrigin, mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
469 if ( tr.fraction < 1.0 ) {
470 continue;
471 }
472 // the marker is vacant and available
473 cs->doorMarkerTime = level.time;
474 cs->doorMarkerNum = trav->s.number;
475 cs->doorMarkerDoor = door->s.number;
476 break;
477 }
478 }
479
480 /*
481 ============
482 AICast_ProcessActivate
483 ============
484 */
AICast_ProcessActivate(int entNum,int activatorNum)485 void AICast_ProcessActivate( int entNum, int activatorNum ) {
486 cast_state_t *cs;
487 gentity_t *newent, *ent;
488
489 cs = AICast_GetCastState( entNum );
490 ent = &g_entities[entNum];
491
492 if ( cs->lastActivate > level.time - 1000 ) {
493 return;
494 }
495 cs->lastActivate = level.time;
496
497 if ( !AICast_SameTeam( cs, activatorNum ) ) {
498
499 if ( ent->aiTeam == AITEAM_NEUTRAL ) {
500 AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
501 }
502
503 return;
504 }
505
506 // try running the activate event, if it denies us the request, then abort
507 cs->aiFlags &= ~AIFL_DENYACTION;
508 AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
509 if ( cs->aiFlags & AIFL_DENYACTION ) {
510 return;
511 }
512
513 // if we are doing something else
514 if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
515 if ( ent->eventTime != level.time ) {
516 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) );
517 }
518 return;
519 }
520
521 // if we are already following them, stop following
522 if ( cs->leaderNum == activatorNum ) {
523 if ( ent->eventTime != level.time ) {
524 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[STAYSOUNDSCRIPT] ) );
525 }
526
527 cs->leaderNum = -1;
528
529 // create a goal at this position
530 newent = G_Spawn();
531 newent->classname = "AI_wait_goal";
532 newent->r.ownerNum = entNum;
533 G_SetOrigin( newent, cs->bs->origin );
534 AIFunc_ChaseGoalStart( cs, newent->s.number, 128, qtrue );
535
536 //AIFunc_IdleStart( cs );
537 } else { // start following
538 int count, i;
539 cast_state_t *tcs;
540
541 // if they already have enough followers, deny
542 for ( count = 0, i = 0, tcs = caststates; i < level.maxclients; i++, tcs++ ) {
543 if ( tcs->bs && tcs != cs && tcs->entityNum != activatorNum && g_entities[tcs->entityNum].health > 0 && tcs->leaderNum == activatorNum ) {
544 count++;
545 }
546 }
547 if ( count >= 3 ) {
548 if ( ent->eventTime != level.time ) {
549 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) );
550 }
551 return;
552 }
553
554 if ( ent->eventTime != level.time ) {
555 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].soundScripts[FOLLOWSOUNDSCRIPT] ) );
556 }
557
558 // if they have a wait goal, free it
559 if ( cs->followEntity >= MAX_CLIENTS && g_entities[cs->followEntity].classname && !strcmp( g_entities[cs->followEntity].classname, "AI_wait_goal" ) ) {
560 G_FreeEntity( &g_entities[cs->followEntity] );
561 }
562
563 cs->followEntity = -1;
564 cs->leaderNum = activatorNum;
565 }
566 }
567
568 /*
569 ================
570 AICast_RecordScriptSound
571 ================
572 */
AICast_RecordScriptSound(int client)573 void AICast_RecordScriptSound( int client ) {
574 cast_state_t *cs;
575
576 cs = AICast_GetCastState( client );
577 cs->lastScriptSound = level.time;
578 }
579