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