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