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