1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer 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 multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP 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 MP 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 MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP 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 MP 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, "pain", va( "%d %d", targ->health, targ->health + damage ) );
137
138 if ( cs->aiFlags & AIFL_DENYACTION ) {
139 // dont play any sounds
140 return;
141 }
142
143 //
144 // call the painfunc for this cast, so we can play associated sounds, or do any character-specific things
145 //
146 if ( cs->painfunc ) {
147 cs->painfunc( targ, attacker, damage, point );
148 }
149 }
150
151 /*
152 ============
153 AICast_Die
154 ============
155 */
AICast_Die(gentity_t * self,gentity_t * inflictor,gentity_t * attacker,int damage,int meansOfDeath)156 void AICast_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) {
157 int contents;
158 int killer;
159 cast_state_t *cs;
160 qboolean nogib = qtrue;
161
162 // print debugging message
163 if ( aicast_debug.integer == 2 && attacker->s.number == 0 ) {
164 G_Printf( "killed %s\n", self->aiName );
165 }
166
167 cs = AICast_GetCastState( self->s.number );
168
169 if ( attacker ) {
170 killer = attacker->s.number;
171 } else {
172 killer = ENTITYNUM_WORLD;
173 }
174
175 // record the sighting (FIXME: silent weapons shouldn't do this, but the AI should react in some way)
176 if ( attacker && attacker->client ) {
177 AICast_UpdateVisibility( self, attacker, qtrue, qtrue );
178 }
179
180 // the zombie should show special effect instead of gibbing
181 if ( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime ) {
182 if ( cs->secondDeadTime > 1 ) {
183 // we are already totally dead
184 self->health += damage; // don't drop below gib_health if we weren't already below it
185 return;
186 }
187 /*
188 if (!cs->rebirthTime)
189 {
190 self->health = -999;
191 damage = 999;
192 } else if ( self->health >= GIB_HEALTH ) {
193 // while waiting for rebirth, we only "die" if we drop below gib health
194 return;
195 }
196 */
197 // always gib
198 self->health = -999;
199 damage = 999;
200 }
201
202 // Zombies are very fragile against highly explosives
203 if ( self->aiCharacter == AICHAR_ZOMBIE && damage > 20 && inflictor != attacker ) {
204 self->health = -999;
205 damage = 999;
206 }
207
208 // process the event
209 if ( self->client->ps.pm_type == PM_DEAD ) {
210 // already dead
211 if ( self->health < GIB_HEALTH ) {
212 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
213 // RF, changed this so Zombies always gib now
214 GibEntity( self, killer );
215 nogib = qfalse;
216 self->takedamage = qfalse;
217 self->r.contents = 0;
218 cs->secondDeadTime = 2;
219 cs->rebirthTime = 0;
220 cs->revivingTime = 0;
221 } else {
222 body_die( self, inflictor, attacker, damage, meansOfDeath );
223 return;
224 }
225 }
226
227 } else { // this is our first death, so set everything up
228
229 if ( level.intermissiontime ) {
230 return;
231 }
232
233 self->client->ps.pm_type = PM_DEAD;
234
235 self->enemy = attacker;
236
237 // drop a weapon?
238 // if client is in a nodrop area, don't drop anything
239 contents = trap_PointContents( self->r.currentOrigin, -1 );
240 if ( !( contents & CONTENTS_NODROP ) ) {
241 TossClientItems( self );
242 }
243
244 // make sure the client doesn't forget about this entity until it's set to "dead" frame
245 // otherwise it might replay it's death animation if it goes out and into client view
246 self->r.svFlags |= SVF_BROADCAST;
247
248 self->takedamage = qtrue; // can still be gibbed
249
250 self->s.weapon = WP_NONE;
251 self->s.powerups = 0;
252 self->r.contents = CONTENTS_CORPSE;
253
254 self->s.angles[0] = 0;
255 self->s.angles[1] = self->client->ps.viewangles[1];
256 self->s.angles[2] = 0;
257
258 VectorCopy( self->s.angles, self->client->ps.viewangles );
259
260 self->s.loopSound = 0;
261
262 self->r.maxs[2] = -8;
263 self->client->ps.maxs[2] = self->r.maxs[2];
264
265 // remove powerups
266 memset( self->client->ps.powerups, 0, sizeof( self->client->ps.powerups ) );
267
268 //cs->rebirthTime = 0;
269
270 // never gib in a nodrop
271 if ( self->health <= GIB_HEALTH ) {
272 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
273 // RF, changed this so Zombies always gib now
274 GibEntity( self, killer );
275 nogib = qfalse;
276 } else if ( !( contents & CONTENTS_NODROP ) ) {
277 body_die( self, inflictor, attacker, damage, meansOfDeath );
278 //GibEntity( self, killer );
279 nogib = qfalse;
280 }
281 }
282
283 // if we are a zombie, and lying down during our first death, then we should just die
284 if ( !( self->aiCharacter == AICHAR_ZOMBIE && cs->secondDeadTime && cs->rebirthTime ) ) {
285
286 // set enemy weapon
287 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
288 if ( attacker && attacker->client ) {
289 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, inflictor->s.weapon, qtrue );
290 } else {
291 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
292 }
293
294 // set enemy location
295 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, 0, qfalse );
296 if ( infront( self, inflictor ) ) {
297 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_INFRONT, qtrue );
298 } else {
299 BG_UpdateConditionValue( self->s.number, ANIM_COND_ENEMY_POSITION, POSITION_BEHIND, qtrue );
300 }
301
302 // play the animation
303 BG_AnimScriptEvent( &self->client->ps, ANIM_ET_DEATH, qfalse, qtrue );
304
305 // set this flag so no other anims override us
306 self->client->ps.eFlags |= EF_DEAD;
307 self->s.eFlags |= EF_DEAD;
308
309 }
310 }
311
312 if ( nogib ) {
313 // set for rebirth
314 if ( self->aiCharacter == AICHAR_ZOMBIE ) {
315 if ( !cs->secondDeadTime ) {
316 cs->rebirthTime = level.time + 5000 + rand() % 2000;
317 cs->secondDeadTime = qtrue;
318 cs->revivingTime = 0;
319 } else if ( cs->secondDeadTime > 1 ) {
320 cs->rebirthTime = 0;
321 cs->revivingTime = 0;
322 cs->deathTime = level.time;
323 }
324 } else {
325 // the body can still be gibbed
326 self->die = body_die;
327 }
328 }
329
330 trap_LinkEntity( self );
331
332 // mark the time of death
333 cs->deathTime = level.time;
334
335 // dying ai's can trigger a target
336 if ( !cs->rebirthTime ) {
337 G_UseTargets( self, self );
338 // really dead now, so call the script
339 AICast_ScriptEvent( cs, "death", "" );
340 // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
341 if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) {
342 cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod
343 }
344 } else {
345 // really dead now, so call the script
346 AICast_ScriptEvent( cs, "fakedeath", "" );
347 // call the deathfunc for this cast, so we can play associated sounds, or do any character-specific things
348 if ( !( cs->aiFlags & AIFL_DENYACTION ) && cs->deathfunc ) {
349 cs->deathfunc( self, attacker, damage, meansOfDeath ); //----(SA) added mod
350 }
351 }
352 }
353
354 /*
355 ===============
356 AICast_EndChase
357 ===============
358 */
AICast_EndChase(cast_state_t * cs)359 void AICast_EndChase( cast_state_t *cs ) {
360 // anything?
361 }
362
363 /*
364 ===============
365 AICast_AIDoor_Touch
366 ===============
367 */
AICast_AIDoor_Touch(gentity_t * ent,gentity_t * aidoor_trigger,gentity_t * door)368 void AICast_AIDoor_Touch( gentity_t *ent, gentity_t *aidoor_trigger, gentity_t *door ) {
369 cast_state_t *cs, *ocs;
370 gentity_t *trav;
371 int i;
372 trace_t tr;
373 vec3_t mins, pos, dir;
374
375 cs = AICast_GetCastState( ent->s.number );
376
377 if ( !cs->bs ) {
378 return;
379 }
380
381 // does the aidoor have ai_marker's?
382 if ( !aidoor_trigger->targetname ) {
383 G_Printf( "trigger_aidoor has no ai_marker's at %s\n", vtos( ent->r.currentOrigin ) );
384 return;
385 }
386
387 // are we heading for an ai_marker?
388 if ( cs->aifunc == AIFunc_DoorMarker ) {
389 return;
390 }
391
392 // if they are moving away from the door, ignore them
393 if ( VectorLength( cs->bs->velocity ) > 1 ) {
394 VectorAdd( door->r.absmin, door->r.absmax, pos );
395 VectorScale( pos, 0.5, pos );
396 VectorSubtract( pos, cs->bs->origin, dir );
397 if ( DotProduct( cs->bs->velocity, dir ) < 0 ) {
398 return;
399 }
400 }
401
402 for ( trav = NULL; ( trav = G_Find( trav, FOFS( target ), aidoor_trigger->targetname ) ); ) {
403 // make sure the marker is vacant
404 trap_Trace( &tr, trav->r.currentOrigin, ent->r.mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
405 if ( tr.startsolid ) {
406 continue;
407 }
408 // search all other AI's, to see if they are heading for this marker
409 for ( i = 0, ocs = AICast_GetCastState( 0 ); i < aicast_maxclients; i++, ocs++ ) {
410 if ( !ocs->bs ) {
411 continue;
412 }
413 if ( ocs->aifunc != AIFunc_DoorMarker ) {
414 continue;
415 }
416 if ( ocs->doorMarker != trav->s.number ) {
417 continue;
418 }
419 // found a match
420 break;
421 }
422 if ( i < aicast_maxclients ) {
423 continue;
424 }
425 // make sure there is a clear path
426 VectorCopy( ent->r.mins, mins );
427 mins[2] += 16; // step height
428 trap_Trace( &tr, ent->r.currentOrigin, mins, ent->r.maxs, trav->r.currentOrigin, ent->s.number, ent->clipmask );
429 if ( tr.fraction < 1.0 ) {
430 continue;
431 }
432 // the marker is vacant and available
433 cs->doorMarkerTime = level.time;
434 cs->doorMarkerNum = trav->s.number;
435 cs->doorMarkerDoor = door->s.number;
436 break;
437 }
438 }
439
440 /*
441 ============
442 AICast_ProcessActivate
443 ============
444 */
AICast_ProcessActivate(int entNum,int activatorNum)445 void AICast_ProcessActivate( int entNum, int activatorNum ) {
446 cast_state_t *cs;
447 gentity_t *newent, *ent;
448
449 cs = AICast_GetCastState( entNum );
450 ent = &g_entities[entNum];
451
452 if ( !AICast_SameTeam( cs, activatorNum ) ) {
453
454 if ( ent->aiTeam == AITEAM_NEUTRAL ) {
455 AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
456 }
457
458 return;
459 }
460
461 // try running the activate event, if it denies us the request, then abort
462 cs->aiFlags &= ~AIFL_DENYACTION;
463 AICast_ScriptEvent( cs, "activate", g_entities[activatorNum].aiName );
464 if ( cs->aiFlags & AIFL_DENYACTION ) {
465 return;
466 }
467
468 // if we are doing something else
469 if ( cs->castScriptStatus.castScriptEventIndex >= 0 ) {
470 if ( ent->eventTime != level.time ) {
471 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) );
472 }
473 return;
474 }
475
476 // if we are already following them, stop following
477 if ( cs->leaderNum == activatorNum ) {
478 if ( ent->eventTime != level.time ) {
479 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].staySoundScript ) );
480 }
481
482 cs->leaderNum = -1;
483
484 // create a goal at this position
485 newent = G_Spawn();
486 newent->classname = "AI_wait_goal";
487 newent->r.ownerNum = entNum;
488 G_SetOrigin( newent, cs->bs->origin );
489 AIFunc_ChaseGoalStart( cs, newent->s.number, 128, qtrue );
490
491 //AIFunc_IdleStart( cs );
492 } else { // start following
493 int count, i;
494 cast_state_t *tcs;
495
496 // if they already have enough followers, deny
497 for ( count = 0, i = 0, tcs = caststates; i < level.maxclients; i++, tcs++ ) {
498 if ( tcs->bs && tcs != cs && tcs->entityNum != activatorNum && g_entities[tcs->entityNum].health > 0 && tcs->leaderNum == activatorNum ) {
499 count++;
500 }
501 }
502 if ( count >= 3 ) {
503 if ( ent->eventTime != level.time ) {
504 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].ordersDenySoundScript ) );
505 }
506 return;
507 }
508
509 if ( ent->eventTime != level.time ) {
510 G_AddEvent( &g_entities[entNum], EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[cs->aiCharacter].followSoundScript ) );
511 }
512
513 // if they have a wait goal, free it
514 if ( cs->followEntity >= MAX_CLIENTS && g_entities[cs->followEntity].classname && !strcmp( g_entities[cs->followEntity].classname, "AI_wait_goal" ) ) {
515 G_FreeEntity( &g_entities[cs->followEntity] );
516 }
517
518 cs->followEntity = -1;
519 cs->leaderNum = activatorNum;
520 }
521 }
522
523 /*
524 ================
525 AICast_RecordScriptSound
526 ================
527 */
AICast_RecordScriptSound(int client)528 void AICast_RecordScriptSound( int client ) {
529 cast_state_t *cs;
530
531 cs = AICast_GetCastState( client );
532 cs->lastScriptSound = level.time;
533 }
534