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_characters.c
32 // Function:		Wolfenstein AI Characters
33 // Programmer:		Ridah
34 // Tab Size:		4 (real tabs)
35 //===========================================================================
36 
37 #include "g_local.h"
38 #include "../botlib/botlib.h"      //bot lib interface
39 #include "../botlib/be_aas.h"
40 #include "../botlib/be_ea.h"
41 #include "../botlib/be_ai_gen.h"
42 #include "../botlib/be_ai_goal.h"
43 #include "../botlib/be_ai_move.h"
44 #include "../botlib/botai.h"          //bot ai interface
45 
46 #include "ai_cast.h"
47 
48 //---------------------------------------------------------------------------
49 // Character specific attributes (defaults, these can be altered in the editor (TODO!))
50 AICharacterDefaults_t aiDefaults[NUM_CHARACTERS] = {
51 
52 	//AICHAR_NONE
53 	{0},
54 
55 	//AICHAR_SOLDIER
56 	{
57 		"Soldier",
58 		{
59 			220,        // running speed
60 			90,         // walking speed
61 			80,         // crouching speed
62 			90,         // Field of View
63 			200,        // Yaw Speed	// RF change
64 			0.0,        // leader
65 			0.5,        // aim skill
66 			0.5,        // aim accuracy
67 			0.75,       // attack skill
68 			0.5,        // reaction time
69 			0.4,        // attack crouch
70 			0.0,        // idle crouch
71 			0.5,        // aggression
72 			0.8,        // tactical
73 			0.0,        // camper
74 			16000,      // alertness
75 			100,        // starting health
76 			1.0,        // hearing scale
77 			0.9,        // not in pvs hearing scale
78 			512,        // relaxed detection radius
79 			1.0,        // pain threshold multiplier
80 		},
81 		{
82 			"infantrySightPlayer",
83 			"infantryAttackPlayer",
84 			"infantryOrders",
85 			"infantryDeath",
86 			"infantrySilentDeath",				//----(SA)	added
87 			"infantryFlameDeath",				//----(SA)	added
88 			"infantryPain",
89 			"infantryStay",						// stay - you're told to stay put
90 			"infantryFollow",					// follow - go with ordering player ("i'm with you" rather than "yes sir!")
91 			"infantryOrdersDeny",				// deny - refuse orders (doing something else)
92 		},
93 		AITEAM_NAZI,							// team
94 		"infantryss/default",					// default model/skin
95 		{WP_MP40,WP_GRENADE_LAUNCHER},			// starting weapons
96 		BBOX_SMALL, {32,48},					// bbox, crouch/stand height
97 		AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2,	// flags
98 		0, 0, 0,								// special attack routine
99 		NULL,									// looping sound
100 		AISTATE_RELAXED
101 	},
102 
103 	//AICHAR_AMERICAN
104 	{
105 		"American",
106 		{
107 			220,        // running speed
108 			90,         // walking speed
109 			80,         // crouching speed
110 			90,         // Field of View
111 			200,        // Yaw Speed	// RF change
112 			0.0,        // leader
113 			0.70,       // aim skill
114 			0.70,       // aim accuracy
115 			0.75,       // attack skill
116 			0.5,        // reaction time
117 			0.3,        // attack crouch
118 			0.0,        // idle crouch
119 			0.5,        // aggression
120 			0.8,        // tactical
121 			0.0,        // camper
122 			16000,      // alertness
123 			100,        // starting health
124 			1.0,        // hearing scale
125 			0.9,        // not in pvs hearing scale
126 			512,        // relaxed detection radius
127 			1.0,        // pain threshold multiplier
128 		},
129 		{
130 			"americanSightPlayer",
131 			"americanAttackPlayer",
132 			"americanOrders",
133 			"americanDeath",
134 			"americanSilentDeath",	//----(SA)	added
135 			"americanFlameDeath",	//----(SA)	added
136 			"americanPain",
137 			"americanStay",			// stay - you're told to stay put
138 			"americanFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
139 			"americanOrdersDeny",	// deny - refuse orders (doing something else)
140 		},
141 		AITEAM_ALLIES,
142 		"american/default",
143 		{WP_THOMPSON,WP_GRENADE_PINEAPPLE},
144 		BBOX_SMALL, {32,48},
145 		AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2,
146 		0, 0, 0,
147 		NULL,
148 		AISTATE_RELAXED
149 	},
150 
151 	//AICHAR_ZOMBIE
152 	{
153 		"Zombie",
154 		{
155 			200,        // running speed		//----(SA)	DK requested change
156 			60,         // walking speed		//----(SA)	DK requested change
157 			80,         // crouching speed
158 			90,         // Field of View
159 			350,        // Yaw Speed
160 			0.0,        // leader
161 			0.70,       // aim skill
162 			0.70,       // aim accuracy
163 			0.75,       // attack skill
164 			0.1,        // reaction time
165 			0.0,        // attack crouch
166 			0.0,        // idle crouch
167 			1.0,        // aggression
168 			0.0,        // tactical
169 			0.0,        // camper
170 			16000,      // alertness
171 			180,        // starting health
172 			1.0,        // hearing scale
173 			0.9,        // not in pvs hearing scale
174 			512,        // relaxed detection radius
175 			1.0,        // pain threshold multiplier
176 		},
177 		{
178 			"zombieSightPlayer",
179 			"zombieAttackPlayer",
180 			"zombieOrders",
181 			"zombieDeath",
182 			"zombieSilentDeath",				//----(SA)	added
183 			"zombieFlameDeath",					//----(SA)	added
184 			"zombiePain",
185 			"sound/weapons/melee/fstatck.wav",	// stay - you're told to stay put
186 			"sound/weapons/melee/fstmiss.wav",	// follow - go with ordering player ("i'm with you" rather than "yes sir!")
187 			"zombieOrdersDeny",					// deny - refuse orders (doing something else)
188 		},
189 		AITEAM_MONSTER,
190 		"zombie/default",
191 		{ /*WP_GAUNTLET,*/ WP_MONSTER_ATTACK2, WP_MONSTER_ATTACK3},
192 		BBOX_SMALL, {32,48},
193 		/*AIFL_NOPAIN|AIFL_WALKFORWARD|*/ AIFL_NO_RELOAD,
194 		AIFunc_ZombieFlameAttackStart, AIFunc_ZombieAttack2Start, AIFunc_ZombieMeleeStart,
195 		NULL,
196 		AISTATE_ALERT
197 	},
198 
199 //----(SA)	added
200 	//AICHAR_WARZOMBIE
201 	{
202 		"WarriorZombie",
203 		{
204 			250,        // running speed	(SA) upped from 200->250 per Mike/DK
205 			60,         // walking speed
206 			80,         // crouching speed
207 			90,         // Field of View
208 			350,        // Yaw Speed
209 			0.0,        // leader
210 			0.70,       // aim skill
211 			0.70,       // aim accuracy
212 			0.75,       // attack skill
213 			0.1,        // reaction time
214 			0.0,        // attack crouch
215 			0.0,        // idle crouch
216 			1.0,        // aggression
217 			0.0,        // tactical
218 			0.0,        // camper
219 			16000,      // alertness
220 			180,        // starting health
221 			1.0,        // hearing scale
222 			0.9,        // not in pvs hearing scale
223 			512,        // relaxed detection radius
224 			1.0,        // pain threshold multiplier
225 		},
226 		{
227 			"warzombieSightPlayer",
228 			"warzombieAttackPlayer",
229 			"warzombieOrders",
230 			"warzombieDeath",
231 			"warzombieSilentDeath",				//----(SA)	added
232 			"warzombieFlameDeath",				//----(SA)	added
233 			"warzombiePain",
234 //----(SA)	changed per DK
235 //			"sound/weapons/melee/fstatck.wav",	// stay - you're told to stay put
236 			"sound/weapons/melee/warz_hit.wav",
237 //			"sound/weapons/melee/fstmiss.wav",	// follow - go with ordering player ("i'm with you" rather than "yes sir!")
238 			"sound/weapons/melee/warz_miss.wav",
239 			"warzombieOrdersDeny",				// deny - refuse orders (doing something else)
240 		},
241 		AITEAM_MONSTER,
242 		"warrior/crypt2",
243 		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},
244 		BBOX_SMALL, {10,48},					// very low defense position
245 		AIFL_NO_RELOAD,
246 		AIFunc_WarriorZombieMeleeStart, /*AIFunc_WarriorZombieSightStart*/ 0, AIFunc_WarriorZombieDefenseStart,
247 		NULL,
248 		AISTATE_ALERT
249 	},
250 //----(SA)	end
251 
252 	//AICHAR_VENOM
253 	{
254 		"Venom",
255 		{
256 			110,        // running speed
257 			100,        // walking speed
258 			80,         // crouching speed
259 			90,         // Field of View
260 			200,        // Yaw Speed
261 			0.0,        // leader
262 			0.70,       // aim skill
263 			0.70,       // aim accuracy
264 			0.75,       // attack skill
265 			0.5,        // reaction time
266 			0.05,       // attack crouch
267 			0.0,        // idle crouch
268 			0.9,        // aggression
269 			0.2,        // tactical
270 			0.0,        // camper
271 			16000,      // alertness
272 			240,        // starting health
273 			1.0,        // hearing scale
274 			0.9,        // not in pvs hearing scale
275 			512,        // relaxed detection radius
276 			1.0,        // pain threshold multiplier
277 		},
278 		{
279 			"venomSightPlayer",
280 			"venomAttackPlayer",
281 			"venomOrders",
282 			"venomDeath",
283 			"venomSilentDeath",	//----(SA)	added
284 			"venomFlameDeath",	//----(SA)	added
285 			"venomPain",
286 			"venomStay",		// stay - you're told to stay put
287 			"venomFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
288 			"venomOrdersDeny",	// deny - refuse orders (doing something else)
289 		},
290 		AITEAM_NAZI,
291 		"venom/default",
292 		{WP_FLAMETHROWER},
293 		BBOX_SMALL, {32,48},
294 		AIFL_NO_FLAME_DAMAGE | AIFL_WALKFORWARD | AIFL_NO_RELOAD,   // |AIFL_NO_HEADSHOT_DMG,
295 		0, 0, 0,
296 		NULL,
297 		AISTATE_RELAXED
298 	},
299 
300 	//AICHAR_LOPER
301 	{
302 		"Loper",
303 		{
304 			220,        // running speed
305 			70,         // walking speed
306 			220,        // crouching speed
307 			90,         // Field of View
308 			200,        // Yaw Speed
309 			0.0,        // leader
310 			0.70,       // aim skill
311 			0.70,       // aim accuracy
312 			0.75,       // attack skill
313 			0.8,        // reaction time
314 			0.05,       // attack crouch
315 			0.0,        // idle crouch
316 			1.0,        // aggression
317 			0.1,        // tactical
318 			0.0,        // camper
319 			16000,      // alertness
320 			500,        // starting health
321 			1.0,        // hearing scale
322 			0.9,        // not in pvs hearing scale
323 			512,        // relaxed detection radius
324 			1.0,        // pain threshold multiplier
325 		},
326 		{
327 			"loperSightPlayer",
328 			"loperAttackPlayer",
329 			"loperOrders",
330 			"loperDeath",
331 			"loperSilentDeath",		//----(SA)	added
332 			"loperFlameDeath",		//----(SA)	added
333 			"loperPain",
334 			"loperAttack2Start",	// stay - you're told to stay put
335 			"loperAttackStart",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
336 			"loperHit1",			// deny - refuse orders (doing something else)
337 			"loperHit2",			// misc1
338 		},
339 		AITEAM_MONSTER,
340 		"loper/default",
341 		{ /*WP_MONSTER_ATTACK1,*/ WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},
342 		BBOX_LARGE, {32,32},		// large is for wide characters
343 		AIFL_NO_RELOAD,
344 		0 /*AIFunc_LoperAttack1Start*/, AIFunc_LoperAttack2Start, AIFunc_LoperAttack3Start,
345 		"sound/world/electloop.wav",
346 		AISTATE_ALERT
347 	},
348 
349 	//AICHAR_ELITEGUARD
350 	{
351 		"Elite Guard",
352 		{
353 			230,        // running speed
354 			90,         // walking speed
355 			100,        // crouching speed
356 			90,         // Field of View
357 			200,        // Yaw Speed	// RF change
358 			0.0,        // leader
359 			0.5,        // aim skill
360 			1.0,        // aim accuracy
361 			0.9,        // attack skill
362 			0.3,        // reaction time
363 			0.4,        // attack crouch
364 			0.0,        // idle crouch
365 			0.5,        // aggression
366 			1.0,        // tactical
367 			0.0,        // camper
368 			16000,      // alertness
369 			120,        // starting health
370 			1.0,        // hearing scale
371 			0.9,        // not in pvs hearing scale
372 			512,        // relaxed detection radius
373 			1.0,        // pain threshold multiplier
374 		},
375 		{
376 			"eliteGuardSightPlayer",
377 			"eliteGuardAttackPlayer",
378 			"eliteGuardOrders",
379 			"eliteGuardDeath",
380 			"eliteGuardSilentDeath",	//----(SA)	added
381 			"eliteGuardFlameDeath",		//----(SA)	added
382 			"eliteGuardPain",
383 			"eliteGuardStay",			// stay - you're told to stay put
384 			"eliteGuardFollow",			// follow - go with ordering player ("i'm with you" rather than "yes sir!")
385 			"eliteGuardOrdersDeny",		// deny - refuse orders (doing something else)
386 		},
387 		AITEAM_NAZI,
388 		"eliteguard/default",
389 		{WP_SILENCER},      //----(SA)	TODO: replace w/ "silenced luger"
390 		BBOX_SMALL, {32,48},
391 		AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2,
392 		0, 0, 0,
393 		NULL,
394 		AISTATE_RELAXED
395 	},
396 
397 	//AICHAR_STIMSOLDIER1
398 	{
399 		"Stim Soldier",
400 		{
401 			170,        // running speed
402 			100,        // walking speed
403 			90,         // crouching speed
404 			90,         // Field of View
405 			150,        // Yaw Speed
406 			0.0,        // leader
407 			0.7,        // aim skill
408 			1.0,        // aim accuracy
409 			0.9,        // attack skill
410 			0.6,        // reaction time
411 			0.05,       // attack crouch
412 			0.0,        // idle crouch
413 			0.9,        // aggression
414 			0.1,        // tactical
415 			0.0,        // camper
416 			16000,      // alertness
417 			300,        // starting health
418 			1.0,        // hearing scale
419 			0.9,        // not in pvs hearing scale
420 			512,        // relaxed detection radius
421 			1.0,        // pain threshold multiplier
422 		},
423 		{
424 			"stimSoldierSightPlayer",
425 			"stimSoldierAttackPlayer",
426 			"stimSoldierOrders",
427 			"stimSoldierDeath",
428 			"stimSoldierSilentDeath",	//----(SA)	added
429 			"stimSoldeirFlameDeath",	//----(SA)	added
430 			"stimSoldierPain",
431 			"stimSoldierStay",			// stay - you're told to stay put
432 			"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
433 			"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
434 		},
435 		AITEAM_NAZI,
436 		"stim/default",
437 		{WP_MONSTER_ATTACK2},			// TODO: dual machinegun attack
438 		BBOX_LARGE, {48,64},
439 		AIFL_NO_RELOAD,
440 		0, AIFunc_StimSoldierAttack2Start, 0,
441 		NULL,
442 		AISTATE_ALERT
443 	},
444 
445 	//AICHAR_STIMSOLDIER2
446 	{
447 		"Stim Soldier",
448 		{
449 			170,        // running speed
450 			100,        // walking speed
451 			90,         // crouching speed
452 			90,         // Field of View
453 			150,        // Yaw Speed
454 			0.0,        // leader
455 			0.7,        // aim skill
456 			1.0,        // aim accuracy
457 			0.9,        // attack skill
458 			0.6,        // reaction time
459 			0.05,       // attack crouch
460 			0.0,        // idle crouch
461 			0.9,        // aggression
462 			0.1,        // tactical
463 			0.0,        // camper
464 			16000,      // alertness
465 			300,        // starting health
466 			1.0,        // hearing scale
467 			0.9,        // not in pvs hearing scale
468 			512,        // relaxed detection radius
469 			1.0,        // pain threshold multiplier
470 		},
471 		{
472 			"stimSoldierSightPlayer",
473 			"stimSoldierAttackPlayer",
474 			"stimSoldierOrders",
475 			"stimSoldierDeath",
476 			"stimSoldierSilentDeath",	//----(SA)	added
477 			"stimSoldierFlameDeath",	//----(SA)	added
478 			"stimSoldierPain",
479 			"stimSoldierStay",			// stay - you're told to stay put
480 			"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
481 			"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
482 		},
483 		AITEAM_NAZI,
484 		"stim/default",
485 		{WP_MP40, WP_MONSTER_ATTACK1},	// attack1 is leaping rocket attack
486 		BBOX_LARGE, {48,64},
487 		AIFL_NO_RELOAD,
488 		AIFunc_StimSoldierAttack1Start, 0, 0,
489 		NULL,
490 		AISTATE_ALERT
491 	},
492 
493 	//AICHAR_STIMSOLDIER3
494 	{
495 		"Stim Soldier",
496 		{
497 			170,        // running speed
498 			100,        // walking speed
499 			90,         // crouching speed
500 			90,         // Field of View
501 			150,        // Yaw Speed
502 			0.0,        // leader
503 			0.7,        // aim skill
504 			1.0,        // aim accuracy
505 			0.9,        // attack skill
506 			0.6,        // reaction time
507 			0.05,       // attack crouch
508 			0.0,        // idle crouch
509 			0.9,        // aggression
510 			0.1,        // tactical
511 			0.0,        // camper
512 			16000,      // alertness
513 			300,        // starting health
514 			1.0,        // hearing scale
515 			0.9,        // not in pvs hearing scale
516 			512,        // relaxed detection radius
517 			1.0,        // pain threshold multiplier
518 		},
519 		{
520 			"stimSoldierSightPlayer",
521 			"stimSoldierAttackPlayer",
522 			"stimSoldierOrders",
523 			"stimSoldierDeath",
524 			"stimSoldierSilentDeath",	//----(SA)	added
525 			"stimSoldierFlameDeath",	//----(SA)	added
526 			"stimSoldierPain",
527 			"stimSoldierStay",			// stay - you're told to stay put
528 			"stimSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
529 			"stimSoldierOrdersDeny",	// deny - refuse orders (doing something else)
530 		},
531 		AITEAM_NAZI,
532 		"stim/default",
533 		{WP_MP40, WP_TESLA},			// no monster_attack1, since that's only used for the jumping rocket attack
534 		BBOX_LARGE, {48,64},
535 		AIFL_NO_RELOAD,
536 		AIFunc_StimSoldierAttack1Start, 0, 0,
537 		NULL,
538 		AISTATE_ALERT
539 	},
540 
541 	//AICHAR_SUPERSOLDIER
542 	{
543 		"Super Soldier",
544 		{
545 			170,        // running speed
546 			100,        // walking speed
547 			90,         // crouching speed
548 			90,         // Field of View
549 			150,        // Yaw Speed
550 			0.0,        // leader
551 			0.7,        // aim skill
552 			1.0,        // aim accuracy
553 			0.9,        // attack skill
554 			0.6,        // reaction time
555 			0.05,       // attack crouch
556 			0.0,        // idle crouch
557 			1.0,        // aggression
558 			0.0,        // tactical
559 			0.0,        // camper
560 			16000,      // alertness
561 			300,        // starting health
562 			1.0,        // hearing scale
563 			0.9,        // not in pvs hearing scale
564 			512,        // relaxed detection radius
565 			2.0,        // pain threshold multiplier
566 		},
567 		{
568 			"superSoldierSightPlayer",
569 			"superSoldierAttackPlayer",
570 			"superSoldierOrders",
571 			"superSoldierDeath",
572 			"superSoldierSilentDeath",	//----(SA)	added
573 			"superSoldierFlameDeath",	//----(SA)	added
574 			"superSoldierPain",
575 			"superSoldierStay",			// stay - you're told to stay put
576 			"superSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
577 			"superSoldierOrdersDeny",	// deny - refuse orders (doing something else)
578 		},
579 		AITEAM_NAZI,
580 		"supersoldier/default",
581 		{WP_VENOM},
582 		BBOX_LARGE, {48,64},
583 		AIFL_NO_RELOAD | AIFL_NO_FLAME_DAMAGE | AIFL_NO_TESLA_DAMAGE,
584 		0, 0, 0,
585 		NULL,
586 		AISTATE_ALERT
587 	},
588 
589 	//AICHAR_BLACKGUARD
590 	{
591 		"Black Guard",
592 		{
593 			220,        // running speed
594 			90,         // walking speed
595 			100,        // crouching speed
596 			90,         // Field of View
597 			300,        // Yaw Speed
598 			0.0,        // leader
599 			0.5,        // aim skill
600 			0.8,        // aim accuracy
601 			0.9,        // attack skill
602 			0.3,        // reaction time
603 			0.4,        // attack crouch
604 			0.0,        // idle crouch
605 			0.5,        // aggression
606 			1.0,        // tactical
607 			0.0,        // camper
608 			16000,      // alertness
609 			120,        // starting health
610 			1.0,        // hearing scale
611 			0.9,        // not in pvs hearing scale
612 			512,        // relaxed detection radius
613 			1.0,        // pain threshold multiplier
614 		},
615 		{
616 			"blackGuardSightPlayer",
617 			"blackGuardAttackPlayer",
618 			"blackGuardOrders",
619 			"blackGuardDeath",
620 			"blackGuardSilentDeath",	//----(SA)	added
621 			"blackGuardFlameDeath",		//----(SA)	added
622 			"blackGuardPain",
623 			"blackGuardStay",			// stay - you're told to stay put
624 			"blackGuardFollow",			// follow - go with ordering player ("i'm with you" rather than "yes sir!")
625 			"blackGuardOrdersDeny",		// deny - refuse orders (doing something else)
626 		},
627 		AITEAM_NAZI,
628 		"blackguard/default",
629 //		{WP_MP40, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1},					// attack1 is melee kick
630 		{WP_FG42, WP_FG42SCOPE, WP_GRENADE_LAUNCHER, WP_MONSTER_ATTACK1},	// attack1 is melee kick
631 		BBOX_SMALL, {32,48},
632 		AIFL_CATCH_GRENADE | AIFL_FLIP_ANIM | AIFL_STAND_IDLE2,
633 		AIFunc_BlackGuardAttack1Start, 0, 0,
634 		NULL,
635 		AISTATE_RELAXED
636 	},
637 
638 	//AICHAR_PROTOSOLDIER
639 	{
640 		"Protosoldier",
641 		{
642 			170,        // running speed
643 			100,        // walking speed
644 			90,         // crouching speed
645 			90,         // Field of View
646 			230,        // Yaw Speed
647 			0.0,        // leader
648 			0.7,        // aim skill
649 			1.0,        // aim accuracy
650 			0.9,        // attack skill
651 			0.2,        // reaction time
652 			0.05,       // attack crouch
653 			0.0,        // idle crouch
654 			0.9,        // aggression
655 			0.1,        // tactical
656 			0.0,        // camper
657 			16000,      // alertness
658 			300,        // starting health
659 			1.0,        // hearing scale
660 			0.9,        // not in pvs hearing scale
661 			512,        // relaxed detection radius
662 			2.0,        // pain threshold multiplier
663 		},
664 		{
665 			"protoSoldierSightPlayer",
666 			"protoSoldierAttackPlayer",
667 			"protoSoldierOrders",
668 			"protoSoldierDeath",
669 			"protoSoldierSilentDeath",	//----(SA)	added
670 			"protoSoldierFlameDeath",	//----(SA)	added
671 			"protoSoldierPain",
672 			"protoSoldierStay",			// stay - you're told to stay put
673 			"protoSoldierFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
674 			"protoSoldierOrdersDeny",	// deny - refuse orders (doing something else)
675 		},
676 		AITEAM_NAZI,
677 		"protosoldier/default",
678 		{WP_VENOM},
679 		BBOX_LARGE, {48,64},
680 		AIFL_NO_TESLA_DAMAGE | AIFL_NO_FLAME_DAMAGE | AIFL_WALKFORWARD | AIFL_NO_RELOAD,
681 		0, 0, 0,
682 		NULL,
683 		AISTATE_ALERT
684 	},
685 
686 	// AICHAR_FROGMAN
687 	{
688 		"Frogman",
689 		{
690 			170,        // running speed
691 			100,        // walking speed
692 			90,         // crouching speed
693 			90,         // Field of View
694 			150,        // Yaw Speed
695 			0.0,        // leader
696 			0.7,        // aim skill
697 			1.0,        // aim accuracy
698 			0.9,        // attack skill
699 			0.6,        // reaction time
700 			0.05,       // attack crouch
701 			0.0,        // idle crouch
702 			0.9,        // aggression
703 			0.1,        // tactical
704 			0.0,        // camper
705 			16000,      // alertness
706 			200,        // starting health
707 			1.0,        // hearing scale
708 			0.9,        // not in pvs hearing scale
709 			512,        // relaxed detection radius
710 			1.0,        // pain threshold multiplier
711 		},
712 		{
713 			"frogmanSightPlayer",
714 			"frogmanAttackPlayer",
715 			"frogmanOrders",
716 			"frogmanDeath",
717 			"frogmanSilentDeath",	//----(SA)	added
718 			"frogmanFlameDeath",	//----(SA)	added
719 			"frogmanPain",
720 			"frogmanStay",			// stay - you're told to stay put
721 			"frogmanFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
722 			"frogmanOrdersDeny",	// deny - refuse orders (doing something else)
723 		},
724 		AITEAM_NAZI,
725 		"frogman/default",
726 		{0},
727 		BBOX_SMALL, {32,48},    // bbox, crouch/stand height
728 		0,
729 		0, 0, 0,
730 		NULL,
731 		AISTATE_RELAXED
732 	},
733 
734 	//AICHAR_HELGA
735 	{
736 		"Helga",
737 		{
738 			140,        // running speed
739 			90,         // walking speed
740 			80,         // crouching speed
741 			90,         // Field of View
742 			200,        // Yaw Speed
743 			0.0,        // leader
744 			0.5,        // aim skill
745 			0.5,        // aim accuracy
746 			0.75,       // attack skill
747 			0.5,        // reaction time
748 			0.0,        // attack crouch
749 			0.0,        // idle crouch
750 			1.0,        // aggression
751 			0.0,        // tactical
752 			0.0,        // camper
753 			16000,      // alertness
754 			100,        // starting health
755 			1.0,        // hearing scale
756 			0.9,        // not in pvs hearing scale
757 			512,        // relaxed detection radius
758 			3.0,        // pain threshold multiplier
759 		},
760 		{
761 			"helgaAttackPlayer",
762 			"helgaAttackPlayer",
763 			"helgaOrders",
764 			"helgaDeath",
765 			"helgaSilentDeath",					//----(SA)	added
766 			"helgaFlameDeath",					//----(SA)	added
767 			"helgaAttackPlayer",
768 			"sound/weapons/melee/fstatck.wav",	// stay - you're told to stay put
769 			"helgaFollow",						// follow - go with ordering player ("i'm with you" rather than "yes sir!")
770 			"helgaOrdersDeny",					// deny - refuse orders (doing something else)
771 		},
772 		AITEAM_MONSTER,														// team
773 		"beast/default",													// default model/skin
774 		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2 /*,WP_MONSTER_ATTACK3*/},	// starting weapons
775 		BBOX_LARGE, {90,90},												// bbox, crouch/stand height
776 		AIFL_WALKFORWARD | AIFL_NO_RELOAD,
777 		AIFunc_Helga_MeleeStart, AIFunc_Helga_SpiritAttack_Start, 0,		// special attack routine
778 		NULL,
779 		AISTATE_ALERT
780 	},
781 
782 	//AICHAR_HEINRICH
783 	{
784 		"Heinrich",
785 		{
786 			170,        // running speed
787 			100,        // walking speed
788 			90,         // crouching speed
789 			90,         // Field of View
790 			130,        // Yaw Speed
791 			0.0,        // leader
792 			0.7,        // aim skill
793 			1.0,        // aim accuracy
794 			0.9,        // attack skill
795 			0.2,        // reaction time
796 			0.05,       // attack crouch
797 			0.0,        // idle crouch
798 			1.0,        // aggression
799 			0.0,        // tactical
800 			0.0,        // camper
801 			16000,      // alertness
802 			2000,       // starting health
803 			1.0,        // hearing scale
804 			0.9,        // not in pvs hearing scale
805 			512,        // relaxed detection radius
806 			5.0,        // pain threshold multiplier
807 		},
808 		{
809 			"heinrichSightPlayer",
810 			"heinrichAttackPlayer",
811 			"heinrichOrders",
812 			"heinrichDeath",
813 			"heinrichSilentDeath",
814 			"heinrichFlameDeath",	//----(SA)	added
815 			"heinrichPain",
816 			"heinrichStay",			// stay - you're told to stay put
817 			"heinrichFollow",		// follow - go with ordering player ("i'm with you" rather than "yes sir!")
818 			"heinrichStomp",		// deny - refuse orders (doing something else)
819 		},
820 		AITEAM_NAZI,
821 		"heinrich/default",
822 		{WP_MONSTER_ATTACK1,WP_MONSTER_ATTACK2,WP_MONSTER_ATTACK3},	// attack3 is given to him by scripting
823 		BBOX_LARGE, {72,72},		// (SA) height is not exact.  just eyeballed.
824 		AIFL_NO_FLAME_DAMAGE | AIFL_WALKFORWARD | AIFL_NO_RELOAD,
825 		AIFunc_Heinrich_MeleeStart, AIFunc_Heinrich_RaiseDeadStart, AIFunc_Heinrich_SpawnSpiritsStart,
826 		NULL,
827 		AISTATE_ALERT
828 	},
829 
830 	//AICHAR_PARTISAN
831 	{
832 		"Partisan",
833 		{
834 			220,        // running speed
835 			90,         // walking speed
836 			80,         // crouching speed
837 			90,         // Field of View
838 			300,        // Yaw Speed
839 			0.0,        // leader
840 			0.70,       // aim skill
841 			0.70,       // aim accuracy
842 			0.75,       // attack skill
843 			0.5,        // reaction time
844 			0.3,        // attack crouch
845 			0.0,        // idle crouch
846 			0.5,        // aggression
847 			0.8,        // tactical
848 			0.0,        // camper
849 			16000,      // alertness
850 			100,        // starting health
851 			1.0,        // hearing scale
852 			0.9,        // not in pvs hearing scale
853 			512,        // relaxed detection radius
854 			1.0,        // pain threshold multiplier
855 		},
856 		{
857 			"partisanSightPlayer",
858 			"partisanAttackPlayer",
859 			"partisanOrders",
860 			"partisanDeath",
861 			"partisanSilentDeath",	//----(SA)	added
862 			"partisanFlameDeath",	//----(SA)	added
863 			"partisanPain",
864 			"partisanStay",
865 			"partisanFollow",
866 			"partisanOrdersDeny",
867 		},
868 		AITEAM_ALLIES,  //----(SA)	changed affiliation for DK
869 		"partisan/default",
870 		{WP_THOMPSON},
871 		BBOX_SMALL, {32,48},
872 		AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2,
873 		0, 0, 0,
874 		NULL,
875 		AISTATE_RELAXED
876 	},
877 
878 	//AICHAR_CIVILIAN
879 	{
880 		"Civilian",
881 		{
882 			220,        // running speed
883 			90,         // walking speed
884 			80,         // crouching speed
885 			90,         // Field of View
886 			300,        // Yaw Speed
887 			0.0,        // leader
888 			0.70,       // aim skill
889 			0.70,       // aim accuracy
890 			0.75,       // attack skill
891 			0.5,        // reaction time
892 			0.3,        // attack crouch
893 			0.0,        // idle crouch
894 			0.5,        // aggression
895 			0.8,        // tactical
896 			0.0,        // camper
897 			16000,      // alertness
898 			100,        // starting health
899 			1.0,        // hearing scale
900 			0.9,        // not in pvs hearing scale
901 			512,        // relaxed detection radius
902 			1.0,        // pain threshold multiplier
903 		},
904 		{
905 			"civilianSightPlayer",
906 			"civilianAttackPlayer",
907 			"civilianOrders",
908 			"civilianDeath",
909 			"civilianSilentDeath",	//----(SA)	added
910 			"civilianFlameDeath",	//----(SA)	added
911 			"civilianPain",
912 			"civilianStay",
913 			"civilianFollow",
914 			"civilianOrdersDeny",
915 		},
916 		AITEAM_NEUTRAL,			//----(SA)	changed affiliation for DK
917 		"civilian/default",
918 		{0},
919 		BBOX_SMALL, {32,48},
920 		AIFL_CATCH_GRENADE | AIFL_STAND_IDLE2,
921 		0, 0, 0,
922 		NULL,
923 		AISTATE_RELAXED
924 	},
925 
926 };
927 //---------------------------------------------------------------------------
928 
929 //---------------------------------------------------------------------------
930 // Bounding boxes
931 static vec3_t bbmins[2] = {{-18, -18, -24},{-32,-32,-24}};
932 static vec3_t bbmaxs[2] = {{ 18,  18,  48},{ 32, 32, 68}};
933 //static float crouchMaxZ[2] = {32,48};	// same as player, will head be ok?
934 //---------------------------------------------------------------------------
935 
936 //---------------------------------------------------------------------------
937 // Weapon info
938 cast_weapon_info_t weaponInfo;
939 //---------------------------------------------------------------------------
940 
941 /*
942 ============
943 AIChar_SetBBox
944 
945   FIXME: pass a maxZ into this so we can tailor the height for each character,
946   since height isn't important for the AAS routing (whereas width is very important)
947 ============
948 */
AIChar_SetBBox(gentity_t * ent,cast_state_t * cs,qboolean useHeadTag)949 void AIChar_SetBBox( gentity_t *ent, cast_state_t *cs, qboolean useHeadTag ) {
950 	vec3_t bbox[2];
951 	trace_t tr;
952 	orientation_t or;
953 
954 	if ( !useHeadTag ) {
955 		VectorCopy( bbmins[cs->aasWorldIndex], ent->client->ps.mins );
956 		VectorCopy( bbmaxs[cs->aasWorldIndex], ent->client->ps.maxs );
957 		ent->client->ps.maxs[2] = aiDefaults[cs->aiCharacter].crouchstandZ[1];
958 		VectorCopy( ent->client->ps.mins, ent->r.mins );
959 		VectorCopy( ent->client->ps.maxs, ent->r.maxs );
960 		ent->client->ps.crouchMaxZ = aiDefaults[cs->aiCharacter].crouchstandZ[0];
961 		ent->s.density = cs->aasWorldIndex;
962 	} else if ( trap_GetTag( ent->s.number, "tag_head", &or ) ) {  // if not found, then just leave it
963 		or.origin[2] -= ent->client->ps.origin[2];  // convert to local coordinates
964 		or.origin[2] += 11;
965 		if ( or.origin[2] < 0 ) {
966 			or.origin[2] = 0;
967 		}
968 		if ( or.origin[2] > aiDefaults[cs->aiCharacter].crouchstandZ[1] + 30 ) {
969 			or.origin[2] = aiDefaults[cs->aiCharacter].crouchstandZ[1] + 30;
970 		}
971 
972 		memset( &tr, 0, sizeof( tr ) );
973 
974 		// check that the new height is ok first, otherwise leave it alone
975 		VectorCopy( bbmins[cs->aasWorldIndex], bbox[0] );
976 		VectorCopy( bbmaxs[cs->aasWorldIndex], bbox[1] );
977 		// set the head tag height
978 		bbox[1][2] = or.origin[2];
979 
980 		if ( bbox[1][2] > ent->client->ps.maxs[2] ) {
981 			// check this area is clear
982 			trap_TraceCapsule( &tr, ent->client->ps.origin, bbox[0], bbox[1], ent->client->ps.origin, ent->s.number, ent->clipmask );
983 		}
984 
985 		if ( !tr.startsolid && !tr.allsolid ) {
986 			VectorCopy( bbox[0], ent->client->ps.mins );
987 			VectorCopy( bbox[1], ent->client->ps.maxs );
988 			VectorCopy( ent->client->ps.mins, ent->r.mins );
989 			VectorCopy( ent->client->ps.maxs, ent->r.maxs );
990 			ent->client->ps.crouchMaxZ = aiDefaults[cs->aiCharacter].crouchstandZ[0];
991 			ent->s.density = cs->aasWorldIndex;
992 		}
993 	}
994 
995 	// if they are linked, then relink to update bbox
996 	if ( ent->r.linked ) {
997 		trap_LinkEntity( ent );
998 	}
999 }
1000 
1001 /*
1002 ============
1003 AIChar_Death
1004 ============
1005 */
AIChar_Death(gentity_t * ent,gentity_t * attacker,int damage,int mod)1006 void AIChar_Death( gentity_t *ent, gentity_t *attacker, int damage, int mod ) { //----(SA)	added mod
1007 	// need this check otherwise sound will overwrite gib message
1008 	if ( ent->health > GIB_HEALTH  ) {
1009 		if ( ent->client->ps.eFlags & EF_HEADSHOT ) {
1010 			G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[QUIETDEATHSOUNDSCRIPT] ) );
1011 		} else {
1012 			switch ( mod ) {               //----(SA)	modified to add 'quiet' deaths
1013 			case MOD_KNIFE_STEALTH:
1014 			case MOD_SNIPERRIFLE:
1015 			case MOD_SNOOPERSCOPE:
1016 				G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[QUIETDEATHSOUNDSCRIPT] ) );
1017 				break;
1018 			case MOD_FLAMETHROWER:
1019 				G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[FLAMEDEATHSOUNDSCRIPT] ) );      //----(SA)	added
1020 				break;
1021 			default:
1022 				G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[DEATHSOUNDSCRIPT] ) );
1023 				break;
1024 			}
1025 		}
1026 	}
1027 }
1028 
1029 /*
1030 =============
1031 AIChar_GetPainLocation
1032 =============
1033 */
AIChar_GetPainLocation(gentity_t * ent,vec3_t point)1034 int AIChar_GetPainLocation( gentity_t *ent, vec3_t point ) {
1035 	static char *painTagNames[] = {
1036 		"tag_head",
1037 		"tag_chest",
1038 		"tag_torso",
1039 		"tag_groin",
1040 		"tag_armright",
1041 		"tag_armleft",
1042 		"tag_legright",
1043 		"tag_legleft",
1044 		NULL,
1045 	};
1046 
1047 	int tagIndex, bestTag;
1048 	float bestDist, dist;
1049 	orientation_t or;
1050 
1051 	// first make sure the client is able to retrieve tag information
1052 	if ( !trap_GetTag( ent->s.number, painTagNames[0], &or ) ) {
1053 		return 0;
1054 	}
1055 
1056 	// find a correct animation to play, based on the body orientation at previous frame
1057 	for ( tagIndex = 0, bestDist = 0, bestTag = -1; painTagNames[tagIndex]; tagIndex++ ) {
1058 		// grab the tag with this name
1059 		if ( trap_GetTag( ent->s.number, painTagNames[tagIndex], &or ) ) {
1060 			dist = VectorDistance( or.origin, point );
1061 			if ( !bestDist || dist < bestDist ) {
1062 				bestTag = tagIndex;
1063 				bestDist = dist;
1064 			}
1065 		}
1066 	}
1067 
1068 	if ( bestTag >= 0 ) {
1069 		return bestTag + 1;
1070 	}
1071 
1072 	return 0;
1073 }
1074 
1075 /*
1076 ============
1077 AIChar_Pain
1078 ============
1079 */
AIChar_Pain(gentity_t * ent,gentity_t * attacker,int damage,vec3_t point)1080 void AIChar_Pain( gentity_t *ent, gentity_t *attacker, int damage, vec3_t point ) {
1081 	#define PAIN_THRESHOLD      25
1082 	#define STUNNED_THRESHOLD   30
1083 	cast_state_t    *cs;
1084 	float dist;
1085 	qboolean forceStun = qfalse;
1086 	float painThreshold, stunnedThreshold;
1087 
1088 	cs = AICast_GetCastState( ent->s.number );
1089 
1090 	if ( g_testPain.integer == 1 ) {
1091 		ent->health = ent->client->pers.maxHealth;  // debugging
1092 	}
1093 
1094 	if ( g_testPain.integer != 2 ) {
1095 		if ( level.time < cs->painSoundTime ) {
1096 			return;
1097 		}
1098 	}
1099 
1100 	painThreshold = PAIN_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE];
1101 	stunnedThreshold = STUNNED_THRESHOLD * cs->attributes[PAIN_THRESHOLD_SCALE];
1102 
1103 	// if they are already playing another animation, we might get confused and cut it off, so don't play a pain
1104 	if ( ent->client->ps.torsoTimer || ent->client->ps.legsTimer ) {
1105 		return;
1106 	}
1107 
1108 	// if we are waiting for our weapon to fire (throwing a grenade)
1109 	if ( ent->client->ps.weaponDelay ) {
1110 		return;
1111 	}
1112 
1113 	if ( attacker->s.weapon == WP_FLAMETHROWER && !( cs->aiFlags & AIFL_NO_FLAME_DAMAGE ) ) {   // flames should be recognized more often, since they stay onfire until they're dead anyway
1114 		painThreshold = 1;
1115 		stunnedThreshold = 99999;   // dont be stunned
1116 	}
1117 
1118 	// HACK: if the attacker is using the flamethrower, don't do any special pain anim or sound
1119 	// FIXME: we should pass in the MOD here, since they could have fired a grenade, then switched weapons
1120 //	if ( attacker->s.weapon == WP_FLAMETHROWER ) {
1121 //		return;
1122 //	}
1123 
1124 	if ( !Q_stricmp( attacker->classname, "props_statue" ) ) {
1125 		damage = 99999; // try and force a stun
1126 		forceStun = qtrue;
1127 	}
1128 
1129 	if ( attacker->s.weapon == WP_TESLA ) {
1130 		damage *= 2;
1131 		if ( cs->attributes[PAIN_THRESHOLD_SCALE] <= 1.0 ) {
1132 			damage = 99999;
1133 		}
1134 	}
1135 
1136 	// now check the damageQuota to see if we should play a pain animation
1137 	// first reduce the current damageQuota with time
1138 	if ( cs->damageQuotaTime && cs->damageQuota > 0 ) {
1139 		cs->damageQuota -= (int)( ( 1.0 + ( g_gameskill.value / GSKILL_MAX ) ) * ( (float)( level.time - cs->damageQuotaTime ) / 1000 ) * ( 7.5 + cs->attributes[ATTACK_SKILL] * 10.0 ) );
1140 		if ( cs->damageQuota < 0 ) {
1141 			cs->damageQuota = 0;
1142 		}
1143 	}
1144 
1145 	// if it's been a long time since our last pain, scale it up
1146 	if ( cs->painSoundTime < level.time - 1000 ) {
1147 		float scale;
1148 		scale = (float)( level.time - cs->painSoundTime - 1000 ) / 1000.0;
1149 		if ( scale > 4.0 ) {
1150 			scale = 4.0;
1151 		}
1152 		damage = (int)( (float)damage * ( 1.0 + ( scale * ( 1.0 - 0.5 * g_gameskill.value / GSKILL_MAX ) ) ) );
1153 	}
1154 
1155 	// adjust the new damage with distance, if they are really close, scale it down, to make it
1156 	// harder to get through the game by continually rushing the enemies
1157 	if ( ( attacker->s.weapon != WP_TESLA ) && ( ( dist = VectorDistance( ent->r.currentOrigin, attacker->r.currentAngles ) ) < 384 ) ) {
1158 		damage -= (int)( (float)damage * ( 1.0 - ( dist / 384.0 ) ) * ( 0.5 + 0.5 * g_gameskill.value / GSKILL_MAX ) );
1159 	}
1160 
1161 	// add the new damage
1162 	cs->damageQuota += damage;
1163 	cs->damageQuotaTime = level.time;
1164 
1165 	if ( forceStun ) {
1166 		damage = 99999; // try and force a stun
1167 		cs->damageQuota = painThreshold + 1;
1168 	}
1169 
1170 	// if it's over the threshold, play a pain
1171 
1172 	// don't do this if crouching, or we might clip through the world
1173 
1174 	if ( g_testPain.integer == 2 || ( cs->damageQuota > painThreshold ) ) {
1175 		int delay;
1176 
1177 		// stunned?
1178 		if ( damage > stunnedThreshold && ( forceStun || ( rand() % 2 ) ) ) {   // stunned
1179 			BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, qtrue, qfalse );
1180 		}
1181 		// enemy weapon
1182 		if ( attacker->client ) {
1183 			BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, attacker->s.weapon, qtrue );
1184 		}
1185 		if ( point ) {
1186 			// location
1187 			BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, AIChar_GetPainLocation( ent, point ), qtrue );
1188 		} else {
1189 			BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse );
1190 		}
1191 
1192 		// pause while we play a pain
1193 		delay = BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_PAIN, qfalse, qtrue );
1194 
1195 		// turn off temporary conditions
1196 		BG_UpdateConditionValue( ent->s.number, ANIM_COND_STUNNED, 0, qfalse );
1197 		BG_UpdateConditionValue( ent->s.number, ANIM_COND_ENEMY_WEAPON, 0, qfalse );
1198 		BG_UpdateConditionValue( ent->s.number, ANIM_COND_IMPACT_POINT, 0, qfalse );
1199 
1200 		if ( delay >= 0 ) {
1201 			// setup game stuff to handle the character movements, etc
1202 			cs->pauseTime = level.time + delay + 250;
1203 			cs->lockViewAnglesTime = cs->pauseTime;
1204 			// make sure we stop crouching
1205 			cs->attackcrouch_time = 0;
1206 			// don't fire while in pain?
1207 			cs->triggerReleaseTime = cs->pauseTime;
1208 			// stay crouching if we were before the pain
1209 			if ( cs->bs->cur_ps.viewheight == cs->bs->cur_ps.crouchViewHeight ) {
1210 				cs->attackcrouch_time = level.time + (float)( cs->pauseTime - level.time ) + 500;
1211 			}
1212 		}
1213 
1214 		// if we didn't just play a scripted sound, then play one of the default sounds
1215 		if ( cs->lastScriptSound < level.time ) {
1216 			G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[PAINSOUNDSCRIPT] ) );
1217 		}
1218 
1219 		// reset the quota
1220 		cs->damageQuota = 0;
1221 		cs->damageQuotaTime = 0;
1222 		//
1223 		cs->painSoundTime = cs->pauseTime + (int)( 1000 * ( g_gameskill.value / GSKILL_MAX ) );     // add a bit more of a buffer before the next one
1224 	}
1225 
1226 }
1227 
1228 /*
1229 ============
1230 AIChar_Sight
1231 ============
1232 */
AIChar_Sight(gentity_t * ent,gentity_t * other,int lastSight)1233 void AIChar_Sight( gentity_t *ent, gentity_t *other, int lastSight ) {
1234 	cast_state_t    *cs;
1235 
1236 	cs = AICast_GetCastState( ent->s.number );
1237 
1238 	// if we are in noattack mode, don't make sounds
1239 	if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) {
1240 		return;
1241 	}
1242 	if ( cs->noAttackTime >= level.time ) {
1243 		return;
1244 	}
1245 
1246 	// if they have recently played a script sound, then ignore this
1247 	if ( cs->lastScriptSound > level.time - 4000 ) {
1248 		return;
1249 	}
1250 
1251 	if ( !AICast_SameTeam( cs, other->s.number ) ) {
1252 		if ( !cs->firstSightTime || cs->firstSightTime < ( level.time - 15000 ) ) {
1253 			//G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].sightSoundScript ) );
1254 		}
1255 		cs->firstSightTime = level.time;
1256 	}
1257 
1258 }
1259 
1260 /*
1261 =====================
1262 AIChar_AttackSND
1263 
1264   NOTE: this should just lookup a sound script for this character/weapon combo
1265 =====================
1266 */
AIChar_AttackSound(cast_state_t * cs)1267 void AIChar_AttackSound( cast_state_t *cs ) {
1268 
1269 	gentity_t *ent;
1270 
1271 	ent = &g_entities [cs->entityNum];
1272 
1273 	if ( cs->attackSNDtime > level.time ) {
1274 		return;
1275 	}
1276 
1277 	// if we are in noattack mode, don't make sounds
1278 	if ( cs->castScriptStatus.scriptNoAttackTime >= level.time ) {
1279 		return;
1280 	}
1281 	if ( cs->noAttackTime >= level.time ) {
1282 		return;
1283 	}
1284 
1285 	// Ridah, only yell when throwing grenades every now and then, since it's not very "stealthy"
1286 	if ( cs->weaponNum == WP_GRENADE_LAUNCHER && rand() % 5 ) {
1287 		return;
1288 	}
1289 
1290 	cs->attackSNDtime = level.time + 5000 + ( 1000 * rand() % 10 );
1291 
1292 	AICast_ScriptEvent( cs, "attacksound", ent->aiName );
1293 	if ( cs->aiFlags & AIFL_DENYACTION ) {
1294 		return;
1295 	}
1296 
1297 	if ( cs->weaponNum == WP_LUGER ) {
1298 		G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSSOUNDSCRIPT] ) );
1299 	} else {
1300 		G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ATTACKSOUNDSCRIPT] ) );
1301 	}
1302 
1303 }
1304 
1305 /*
1306 ============
1307 AIChar_spawn
1308 ============
1309 */
AIChar_spawn(gentity_t * ent)1310 void AIChar_spawn( gentity_t *ent ) {
1311 	gentity_t       *newent;
1312 	cast_state_t    *cs;
1313 	AICharacterDefaults_t *aiCharDefaults;
1314 	int i;
1315 	static int lastCall;
1316 	static int numCalls;
1317 
1318 	// if there are other cast's waiting to spawn before us, wait for them
1319 	for ( i = MAX_CLIENTS, newent = &g_entities[MAX_CLIENTS]; i < MAX_GENTITIES; i++, newent++ ) {
1320 		if ( !newent->inuse ) {
1321 			continue;
1322 		}
1323 		if ( newent->think != AIChar_spawn ) {
1324 			continue;
1325 		}
1326 		if ( newent == ent ) {
1327 			break;      // we are the first in line
1328 		}
1329 		// still waiting for someone else
1330 		ent->nextthink = level.time + FRAMETIME;
1331 		return;
1332 	}
1333 
1334 	// if the client hasn't connected yet, wait around
1335 	if ( !AICast_FindEntityForName( "player" ) ) {
1336 		ent->nextthink = level.time + FRAMETIME;
1337 		return;
1338 	}
1339 
1340 	if ( lastCall == level.time ) {
1341 		if ( numCalls++ > 2 ) {
1342 			ent->nextthink = level.time + FRAMETIME;
1343 			return;     // spawned enough this frame already
1344 		}
1345 	} else {
1346 		numCalls = 0;
1347 	}
1348 	lastCall = level.time;
1349 
1350 	aiCharDefaults = &aiDefaults[ent->aiCharacter];
1351 
1352 	// ............................
1353 	// setup weapon info
1354 	//
1355 	// starting weapons/ammo
1356 	memset( &weaponInfo, 0, sizeof( weaponInfo ) );
1357 	for ( i = 0; aiCharDefaults->weapons[i]; i++ ) {
1358 		//weaponInfo.startingWeapons[(aiCharDefaults->weapons[i] / 32)] |= ( 1 << aiCharDefaults->weapons[i] );
1359 		//weaponInfo.startingWeapons[0] |= ( 1 << aiCharDefaults->weapons[i] );
1360 
1361 		COM_BitSet( weaponInfo.startingWeapons, aiCharDefaults->weapons[i] );
1362 		if ( aiCharDefaults->weapons[i] == WP_GRENADE_LAUNCHER ) { // give them a bunch of grenades, but not an unlimited supply
1363 			weaponInfo.startingAmmo[BG_FindAmmoForWeapon( aiCharDefaults->weapons[i] )] = 6;
1364 		} else {
1365 			weaponInfo.startingAmmo[BG_FindAmmoForWeapon( aiCharDefaults->weapons[i] )] = 999;
1366 		}
1367 	}
1368 	//
1369 	// use the default skin if nothing specified
1370 	if ( !ent->aiSkin || !strlen( ent->aiSkin ) ) {
1371 		ent->aiSkin = aiCharDefaults->skin;
1372 	}
1373 	// ............................
1374 	//
1375 	// create the character
1376 
1377 	// (there will always be an ent->aiSkin (SA))
1378 	newent = AICast_CreateCharacter( ent, aiCharDefaults->attributes, &weaponInfo, aiCharDefaults->name, ent->aiSkin, ent->aihSkin, "m", "7", "100" );
1379 
1380 	if ( !newent ) {
1381 		G_FreeEntity( ent );
1382 		return;
1383 	}
1384 	// copy any character-specific information to the new entity (like editor fields, etc)
1385 	//
1386 	// copy this across so killing ai can trigger a target
1387 	newent->target = ent->target;
1388 	//
1389 	newent->classname = ent->classname;
1390 	newent->r.svFlags |= ( ent->r.svFlags & SVF_NOFOOTSTEPS );
1391 	newent->aiCharacter = ent->aiCharacter;
1392 	newent->client->ps.aiChar = ent->aiCharacter;
1393 	newent->spawnflags = ent->spawnflags;
1394 	newent->aiTeam = ent->aiTeam;
1395 	if ( newent->aiTeam < 0 ) {
1396 		newent->aiTeam = aiCharDefaults->aiTeam;
1397 	}
1398 	newent->client->ps.teamNum = newent->aiTeam;
1399 	//
1400 	// kill the old entity
1401 	G_FreeEntity( ent );
1402 	// attach to the new entity
1403 	ent = newent;
1404 	//
1405 	// precache any specific sounds
1406 	//
1407 	// ...
1408 	//
1409 	// get the cast state
1410 	cs = AICast_GetCastState( ent->s.number );
1411 	//
1412 	// setup any character specific cast_state variables
1413 	cs->deathfunc = AIChar_Death;
1414 	cs->painfunc = AIChar_Pain;
1415 	cs->aiFlags |= aiCharDefaults->aiFlags;
1416 	cs->aiState = aiCharDefaults->aiState;
1417 	//
1418 	cs->queryCountValidTime = -1;
1419 	//
1420 	// randomly choose idle animation
1421 	if ( cs->aiFlags & AIFL_STAND_IDLE2 ) {
1422 		newent->client->ps.eFlags |= EF_STAND_IDLE2;
1423 	}
1424 	//
1425 	// attach any event specific functions (pain, death, etc)
1426 	//
1427 	//cs->getDeathAnim = AIChar_getDeathAnim;
1428 	cs->sightfunc = AIChar_Sight;
1429 	if ( ent->aiTeam == AITEAM_ALLIES || ent->aiTeam == AITEAM_NEUTRAL ) { // friendly
1430 		cs->activate = AICast_ProcessActivate;
1431 	} else {
1432 		cs->activate = 0;
1433 	}
1434 	cs->aifuncAttack1 = aiCharDefaults->aifuncAttack1;
1435 	cs->aifuncAttack2 = aiCharDefaults->aifuncAttack2;
1436 	cs->aifuncAttack3 = aiCharDefaults->aifuncAttack3;
1437 	//
1438 	// looping sound?
1439 	if ( aiCharDefaults->loopingSound ) {
1440 		ent->s.loopSound = G_SoundIndex( aiCharDefaults->loopingSound );
1441 	}
1442 	//
1443 	// precache sounds for this character
1444 	for ( i = 0; i < MAX_AI_EVENT_SOUNDS; i++ ) {
1445 		if ( aiDefaults[ent->aiCharacter].soundScripts[i] ) {
1446 			G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[i] );
1447 		}
1448 	}
1449 	//
1450 	if ( ent->aiCharacter == AICHAR_HEINRICH ) {
1451 		AICast_Heinrich_SoundPrecache();
1452 	}
1453 	//
1454 	// special spawnflag stuff
1455 	if ( ent->spawnflags & 2 ) {
1456 		cs->secondDeadTime = qtrue;
1457 	}
1458 	//
1459 	// init scripting
1460 	cs->castScriptStatus.castScriptEventIndex = -1;
1461 	cs->castScriptStatus.scriptAttackEnt = -1;
1462 	//
1463 	// set crouch move speed
1464 	ent->client->ps.crouchSpeedScale = cs->attributes[CROUCHING_SPEED] / cs->attributes[RUNNING_SPEED];
1465 	//
1466 	// check for some anims which we can use for special behaviours
1467 	if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_ROLL ) >= 0 ) {
1468 		cs->aiFlags |= AIFL_ROLL_ANIM;
1469 	}
1470 	if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_FLIP ) >= 0 ) {
1471 		cs->aiFlags |= AIFL_FLIP_ANIM;
1472 	}
1473 	if ( BG_GetAnimScriptEvent( &ent->client->ps, ANIM_ET_DIVE ) >= 0 ) {
1474 		cs->aiFlags |= AIFL_DIVE_ANIM;
1475 	}
1476 	// HACK
1477 	if ( ent->aiName && !Q_stricmp( ent->aiName, "deathshead" ) ) {
1478 		cs->aiFlags |= AIFL_NO_FLAME_DAMAGE;
1479 	}
1480 	//
1481 	// check for no headshot damage
1482 	if ( cs->aiFlags & AIFL_NO_HEADSHOT_DMG ) {
1483 		ent->headshotDamageScale = 0.0;
1484 	}
1485 	// set these values now so scripting system isn't relying on a Think having been run prior to running a script
1486 	//origin of the cast
1487 	VectorCopy( ent->client->ps.origin, cs->bs->origin );
1488 	//velocity of the cast
1489 	VectorCopy( ent->client->ps.velocity, cs->bs->velocity );
1490 	//playerstate
1491 	cs->bs->cur_ps = ent->client->ps;
1492 	//
1493 	if ( !ent->aiInactive ) {
1494 		// trigger a spawn script event
1495 		AICast_ScriptEvent( cs, "spawn", "" );
1496 	} else {
1497 		trap_UnlinkEntity( ent );
1498 	}
1499 
1500 }
1501 
1502 //----------------------------------------------------------------------------------------------------------------------------
1503 /*QUAKED ai_soldier (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1504 soldier entity
1505 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'infantryss/default' is used)
1506 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1507 "ainame" name of AI
1508 */
1509 /*
1510 -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY --------
1511 model="models\mapobjects\characters\test\nazi.md3"
1512 */
1513 /*
1514 ============
1515 SP_ai_soldier
1516 ============
1517 */
SP_ai_soldier(gentity_t * ent)1518 void SP_ai_soldier( gentity_t *ent ) {
1519 	AICast_DelayedSpawnCast( ent, AICHAR_SOLDIER );
1520 }
1521 
1522 //----------------------------------------------------------------------------------------------------------------------------
1523 /*QUAKED ai_american (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1524 american entity
1525 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'american/default' is used)
1526 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1527 "ainame" name of AI
1528 */
1529 
1530 /*
1531 ============
1532 SP_ai_american
1533 ============
1534 */
SP_ai_american(gentity_t * ent)1535 void SP_ai_american( gentity_t *ent ) {
1536 	AICast_DelayedSpawnCast( ent, AICHAR_AMERICAN );
1537 }
1538 
1539 //----------------------------------------------------------------------------------------------------------------------------
1540 /*QUAKED ai_zombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie
1541 zombie entity
1542 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'zombie/default' is used)
1543 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1544 "ainame" name of AI
1545 */
1546 
1547 /*
1548 ============
1549 SP_ai_zombie
1550 ============
1551 */
SP_ai_zombie(gentity_t * ent)1552 void SP_ai_zombie( gentity_t *ent ) {
1553 	ent->r.svFlags |= SVF_NOFOOTSTEPS;
1554 	AICast_DelayedSpawnCast( ent, AICHAR_ZOMBIE );
1555 }
1556 
1557 
1558 //----(SA)	added
1559 //----------------------------------------------------------------------------------------------------------------------------
1560 /*QUAKED ai_warzombie (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive PortalZombie
1561 warrior zombie entity
1562 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'warrior/default' is used)
1563 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1564 "ainame" name of AI
1565 */
1566 
1567 /*
1568 ============
1569 SP_ai_warzombie
1570 ============
1571 */
SP_ai_warzombie(gentity_t * ent)1572 void SP_ai_warzombie( gentity_t *ent ) {
1573 	AICast_DelayedSpawnCast( ent, AICHAR_WARZOMBIE );
1574 }
1575 //----(SA)	end
1576 
1577 //----------------------------------------------------------------------------------------------------------------------------
1578 /*QUAKED ai_venom (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1579 venom entity
1580 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'venom/default' is used)
1581 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1582 "ainame" name of AI
1583 */
1584 
1585 /*
1586 ============
1587 SP_ai_venom
1588 ============
1589 */
SP_ai_venom(gentity_t * ent)1590 void SP_ai_venom( gentity_t *ent ) {
1591 	ent->r.svFlags |= SVF_NOFOOTSTEPS;
1592 	AICast_DelayedSpawnCast( ent, AICHAR_VENOM );
1593 }
1594 
1595 
1596 //----------------------------------------------------------------------------------------------------------------------------
1597 /*QUAKED ai_loper (1 0.25 0) (-32 -32 -24) (32 32 48) TriggerSpawn NoRevive
1598 loper entity
1599 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'loper/default' is used)
1600 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1601 "ainame" name of AI
1602 */
1603 
1604 /*
1605 ============
1606 SP_ai_loper
1607 ============
1608 */
SP_ai_loper(gentity_t * ent)1609 void SP_ai_loper( gentity_t *ent ) {
1610 	ent->r.svFlags |= SVF_NOFOOTSTEPS;
1611 	AICast_DelayedSpawnCast( ent, AICHAR_LOPER );
1612 	//
1613 	level.loperZapSound = G_SoundIndex( "loperZap" );
1614 }
1615 
1616 //----------------------------------------------------------------------------------------------------------------------------
1617 /*QUAKED ai_boss_helga (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1618 helga entity
1619 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used)
1620 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1621 "ainame" name of AI
1622 */
1623 
1624 /*
1625 ============
1626 SP_ai_boss_helga
1627 ============
1628 */
SP_ai_boss_helga(gentity_t * ent)1629 void SP_ai_boss_helga( gentity_t *ent ) {
1630 	AICast_DelayedSpawnCast( ent, AICHAR_HELGA );
1631 }
1632 
1633 //----------------------------------------------------------------------------------------------------------------------------
1634 /*QUAKED ai_boss_heinrich (1 0.25 0) (-32 -32 -24) (32 32 156) TriggerSpawn NoRevive
1635 heinrich entity
1636 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'helga/default' is used)
1637 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1638 "ainame" name of AI
1639 */
1640 
1641 /*
1642 ============
1643 SP_ai_boss_heinrich
1644 ============
1645 */
SP_ai_boss_heinrich(gentity_t * ent)1646 void SP_ai_boss_heinrich( gentity_t *ent ) {
1647 	AICast_DelayedSpawnCast( ent, AICHAR_HEINRICH );
1648 }
1649 
1650 //----------------------------------------------------------------------------------------------------------------------------
1651 /*QUAKED ai_partisan (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1652 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'partisan/default' is used)
1653 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1654 "ainame" name of AI
1655 */
1656 
1657 /*
1658 ============
1659 SP_ai_partisan
1660 ============
1661 */
SP_ai_partisan(gentity_t * ent)1662 void SP_ai_partisan( gentity_t *ent ) {
1663 	AICast_DelayedSpawnCast( ent, AICHAR_PARTISAN );
1664 }
1665 
1666 //----------------------------------------------------------------------------------------------------------------------------
1667 /*QUAKED ai_civilian (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1668 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'civilian/default' is used)
1669 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1670 "ainame" name of AI
1671 */
1672 
1673 /*
1674 ============
1675 SP_ai_civilian
1676 ============
1677 */
SP_ai_civilian(gentity_t * ent)1678 void SP_ai_civilian( gentity_t *ent ) {
1679 	AICast_DelayedSpawnCast( ent, AICHAR_CIVILIAN );
1680 }
1681 
1682 //----------------------------------------------------------------------------------------------------------------------------
1683 /*QUAKED ai_eliteguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1684 elite guard entity
1685 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'eliteguard/default' is used)
1686 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1687 "ainame" name of AI
1688 */
1689 
1690 /*
1691 ============
1692 SP_ai_eliteguard
1693 ============
1694 */
SP_ai_eliteguard(gentity_t * ent)1695 void SP_ai_eliteguard( gentity_t *ent ) {
1696 	AICast_DelayedSpawnCast( ent, AICHAR_ELITEGUARD );
1697 }
1698 
1699 
1700 //----------------------------------------------------------------------------------------------------------------------------
1701 /*QUAKED ai_frogman (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1702 elite guard entity
1703 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'frogman/default' is used)
1704 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1705 "ainame" name of AI
1706 */
1707 
1708 /*
1709 ============
1710 SP_ai_frogman
1711 ============
1712 */
SP_ai_frogman(gentity_t * ent)1713 void SP_ai_frogman( gentity_t *ent ) {
1714 	ent->r.svFlags |= SVF_NOFOOTSTEPS;
1715 	AICast_DelayedSpawnCast( ent, AICHAR_FROGMAN );
1716 }
1717 
1718 
1719 //----------------------------------------------------------------------------------------------------------------------------
1720 /*QUAKED ai_stimsoldier_dual (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
1721 stim soldier entity
1722 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
1723 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1724 "ainame" name of AI
1725 */
1726 
1727 /*
1728 ============
1729 SP_ai_stimsoldier_dual
1730 ============
1731 */
SP_ai_stimsoldier_dual(gentity_t * ent)1732 void SP_ai_stimsoldier_dual( gentity_t *ent ) {
1733 	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER1 );
1734 	//
1735 	level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" );
1736 }
1737 
1738 //----------------------------------------------------------------------------------------------------------------------------
1739 /*QUAKED ai_stimsoldier_rocket (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
1740 stim soldier entity
1741 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
1742 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1743 "ainame" name of AI
1744 */
1745 
1746 /*
1747 ============
1748 SP_ai_stimsoldier_rocket
1749 ============
1750 */
SP_ai_stimsoldier_rocket(gentity_t * ent)1751 void SP_ai_stimsoldier_rocket( gentity_t *ent ) {
1752 	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER2 );
1753 	//
1754 	level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" );
1755 }
1756 
1757 //----------------------------------------------------------------------------------------------------------------------------
1758 /*QUAKED ai_stimsoldier_tesla (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
1759 stim soldier entity
1760 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'stim/default' is used)
1761 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1762 "ainame" name of AI
1763 */
1764 
1765 /*
1766 ============
1767 SP_ai_stimsoldier_tesla
1768 ============
1769 */
SP_ai_stimsoldier_tesla(gentity_t * ent)1770 void SP_ai_stimsoldier_tesla( gentity_t *ent ) {
1771 	AICast_DelayedSpawnCast( ent, AICHAR_STIMSOLDIER3 );
1772 	//
1773 	level.stimSoldierFlySound = G_SoundIndex( "sound/stimsoldier/flyloop.wav" );
1774 }
1775 
1776 //----------------------------------------------------------------------------------------------------------------------------
1777 /*QUAKED ai_supersoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
1778 supersoldier entity
1779 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'supersoldier/default' is used)
1780 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1781 "ainame" name of AI
1782 */
1783 
1784 /*
1785 ============
1786 SP_ai_supersoldier
1787 ============
1788 */
SP_ai_supersoldier(gentity_t * ent)1789 void SP_ai_supersoldier( gentity_t *ent ) {
1790 	AICast_DelayedSpawnCast( ent, AICHAR_SUPERSOLDIER );
1791 }
1792 
1793 //----------------------------------------------------------------------------------------------------------------------------
1794 /*QUAKED ai_protosoldier (1 0.25 0) (-32 -32 -24) (32 32 64) TriggerSpawn NoRevive
1795 protosoldier entity
1796 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'protosoldier/default' is used)
1797 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1798 "ainame" name of AI
1799 */
1800 
1801 /*
1802 ============
1803 SP_ai_protosoldier
1804 ============
1805 */
SP_ai_protosoldier(gentity_t * ent)1806 void SP_ai_protosoldier( gentity_t *ent ) {
1807 	AICast_DelayedSpawnCast( ent, AICHAR_PROTOSOLDIER );
1808 }
1809 
1810 //----------------------------------------------------------------------------------------------------------------------------
1811 /*QUAKED ai_blackguard (1 0.25 0) (-16 -16 -24) (16 16 64) TriggerSpawn NoRevive
1812 black guard entity
1813 "skin" the .skin file to use for this character (must exist in the player characters directory, otherwise 'blackguard/default' is used)
1814 "head" the .skin file to use for his head (must exist in the pc's dir, otherwise 'default' is used)
1815 "ainame" name of AI
1816 */
1817 
1818 /*
1819 ============
1820 SP_ai_blackguard
1821 ============
1822 */
SP_ai_blackguard(gentity_t * ent)1823 void SP_ai_blackguard( gentity_t *ent ) {
1824 	AICast_DelayedSpawnCast( ent, AICHAR_BLACKGUARD );
1825 }
1826