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_funcs.c
32 // Function: Wolfenstein AI Character Decision Making
33 // Programmer: Ridah
34 // Tab Size: 4 (real tabs)
35 //===========================================================================
36
37 #include "g_local.h"
38 #include "../qcommon/q_shared.h"
39 #include "../botlib/botlib.h" //bot lib interface
40 #include "../botlib/be_aas.h"
41 #include "../botlib/be_ea.h"
42 #include "../botlib/be_ai_gen.h"
43 #include "../botlib/be_ai_goal.h"
44 #include "../botlib/be_ai_move.h"
45 #include "../botlib/botai.h" //bot ai interface
46
47 #include "ai_cast.h"
48
49 //=================================================================================
50 //
51 // Helga, the first boss
52 //
53 //=================================================================================
54
55 #define HELGA_SPIRIT_BUILDUP_TIME 8000 // last for this long
56 #define HELGA_SPIRIT_FADEOUT_TIME 1000
57 #define HELGA_SPIRIT_DLIGHT_RADIUS_MAX 384
58 #define HELGA_SPIRIT_FIRE_INTERVAL 1000
59
60 extern int lastZombieSpiritAttack;
61
AIFunc_Helga_SpiritAttack(cast_state_t * cs)62 char *AIFunc_Helga_SpiritAttack( cast_state_t *cs ) {
63 gentity_t *ent;
64 //
65 cs->aiFlags |= AIFL_SPECIAL_FUNC;
66 ent = &g_entities[cs->entityNum];
67 // make sure we're still playing the right anim
68 if ( ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack1", cs->entityNum ) ) {
69 return AIFunc_DefaultStart( cs );
70 }
71 //
72 if ( cs->enemyNum < 0 ) {
73 ent->client->ps.torsoTimer = 0;
74 ent->client->ps.legsTimer = 0;
75 return AIFunc_DefaultStart( cs );
76 }
77 //
78 // if we can't see them anymore, abort immediately
79 if ( cs->vislist[cs->enemyNum].real_visible_timestamp != cs->vislist[cs->enemyNum].real_update_timestamp ) {
80 ent->client->ps.torsoTimer = 0;
81 ent->client->ps.legsTimer = 0;
82 return AIFunc_DefaultStart( cs );
83 }
84 // we are firing this weapon, so record it
85 cs->weaponFireTimes[WP_MONSTER_ATTACK2] = level.time;
86 //
87 // once an attack has started, only abort once the player leaves our view, or time runs out
88 if ( cs->thinkFuncChangeTime < level.time - HELGA_SPIRIT_BUILDUP_TIME ) {
89 // if enough time has elapsed, finish this attack
90 if ( level.time > cs->thinkFuncChangeTime + HELGA_SPIRIT_BUILDUP_TIME + HELGA_SPIRIT_FADEOUT_TIME ) {
91 ent->client->ps.torsoTimer = 0;
92 ent->client->ps.legsTimer = 0;
93 return AIFunc_DefaultStart( cs );
94 }
95 } else {
96
97 // set timers
98 ent->client->ps.torsoTimer = 1000;
99 ent->client->ps.legsTimer = 1000;
100
101 // draw the client-side effect
102 ent->client->ps.eFlags |= EF_MONSTER_EFFECT;
103
104 // inform the client of our enemies position
105 VectorCopy( g_entities[cs->enemyNum].client->ps.origin, ent->s.origin2 );
106 ent->s.origin2[2] += g_entities[cs->enemyNum].client->ps.viewheight;
107 }
108 //
109 //
110 return NULL;
111 }
112
AIFunc_Helga_SpiritAttack_Start(cast_state_t * cs)113 char *AIFunc_Helga_SpiritAttack_Start( cast_state_t *cs ) {
114 gentity_t *ent;
115 //
116 ent = &g_entities[cs->entityNum];
117 ent->s.otherEntityNum2 = cs->enemyNum;
118 ent->s.effect1Time = level.time;
119 cs->aiFlags |= AIFL_SPECIAL_FUNC;
120 //
121 // dont turn
122 cs->ideal_viewangles[YAW] = cs->viewangles[YAW];
123 // play an anim
124 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_WEAPON, WP_MONSTER_ATTACK2, qtrue );
125 BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue );
126 //
127 cs->aifunc = AIFunc_Helga_SpiritAttack;
128 return "AIFunc_Helga_SpiritAttack";
129 }
130
131 //=================================================================================
132 //
133 // Standing melee attacks
134 //
135 //=================================================================================
136
137 #define NUM_HELGA_ANIMS 3
138 #define MAX_HELGA_IMPACTS 3
139 int helgaHitTimes[NUM_HELGA_ANIMS][MAX_HELGA_IMPACTS] = { // up to three hits per attack
140 {ANIMLENGTH( 16,20 ),-1},
141 {ANIMLENGTH( 11,20 ),ANIMLENGTH( 19,20 ),-1},
142 {ANIMLENGTH( 10,20 ),ANIMLENGTH( 17,20 ),ANIMLENGTH( 26,20 )},
143 };
144 int helgaHitDamage[NUM_HELGA_ANIMS] = {
145 20,
146 14,
147 12
148 };
149
150 /*
151 ================
152 AIFunc_Helga_Melee
153 ================
154 */
AIFunc_Helga_Melee(cast_state_t * cs)155 char *AIFunc_Helga_Melee( cast_state_t *cs ) {
156 gentity_t *ent = &g_entities[cs->entityNum];
157 gentity_t *enemy;
158 cast_state_t *ecs;
159 int hitDelay = -1, anim;
160 trace_t tr;
161 float enemyDist;
162 aicast_predictmove_t move;
163 vec3_t vec;
164
165 cs->aiFlags |= AIFL_SPECIAL_FUNC;
166
167 if ( !ent->client->ps.torsoTimer || !ent->client->ps.legsTimer ) {
168 cs->aiFlags &= ~AIFL_SPECIAL_FUNC;
169 return AIFunc_DefaultStart( cs );
170 }
171
172 if ( cs->enemyNum < 0 ) {
173 ent->client->ps.legsTimer = 0; // allow legs us to move
174 ent->client->ps.torsoTimer = 0; // allow legs us to move
175 cs->aiFlags &= ~AIFL_SPECIAL_FUNC;
176 return AIFunc_DefaultStart( cs );
177 }
178
179 ecs = AICast_GetCastState( cs->enemyNum );
180 enemy = &g_entities[cs->enemyNum];
181
182 anim = ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) - BG_AnimationIndexForString( "attack3", cs->entityNum );
183 if ( anim < 0 || anim >= NUM_HELGA_ANIMS ) {
184 // animation interupted
185 cs->aiFlags &= ~AIFL_SPECIAL_FUNC;
186 return AIFunc_DefaultStart( cs );
187 //G_Error( "AIFunc_HelgaZombieMelee: helgaBoss using invalid or unknown attack anim" );
188 }
189 if ( cs->animHitCount < MAX_HELGA_IMPACTS && helgaHitTimes[anim][cs->animHitCount] >= 0 ) {
190
191 // face them
192 VectorCopy( cs->bs->origin, vec );
193 vec[2] += ent->client->ps.viewheight;
194 VectorSubtract( enemy->client->ps.origin, vec, vec );
195 VectorNormalize( vec );
196 vectoangles( vec, cs->ideal_viewangles );
197 cs->ideal_viewangles[PITCH] = AngleNormalize180( cs->ideal_viewangles[PITCH] );
198
199 // get hitDelay
200 if ( !cs->animHitCount ) {
201 hitDelay = helgaHitTimes[anim][cs->animHitCount];
202 } else {
203 hitDelay = helgaHitTimes[anim][cs->animHitCount] - helgaHitTimes[anim][cs->animHitCount - 1];
204 }
205
206 // check for inflicting damage
207 if ( level.time - cs->weaponFireTimes[cs->weaponNum] > hitDelay ) {
208 // do melee damage
209 enemyDist = VectorDistance( enemy->r.currentOrigin, ent->r.currentOrigin );
210 enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
211 enemyDist -= ent->r.maxs[0];
212 if ( enemyDist < 10 + AICast_WeaponRange( cs, cs->weaponNum ) ) {
213 trap_Trace( &tr, ent->r.currentOrigin, NULL, NULL, enemy->r.currentOrigin, ent->s.number, MASK_SHOT );
214 if ( tr.entityNum == cs->enemyNum ) {
215 G_Damage( &g_entities[tr.entityNum], ent, ent, vec3_origin, tr.endpos,
216 helgaHitDamage[anim], 0, MOD_GAUNTLET );
217 G_AddEvent( enemy, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[STAYSOUNDSCRIPT] ) );
218 }
219 }
220 cs->weaponFireTimes[cs->weaponNum] = level.time;
221 cs->animHitCount++;
222 }
223 }
224
225 // if they are outside range, move forward
226 AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 );
227 VectorSubtract( move.endpos, cs->bs->origin, vec );
228 vec[2] = 0;
229 enemyDist = VectorLength( vec );
230 enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
231 enemyDist -= ent->r.maxs[0];
232 if ( enemyDist > 8 ) { // we can get closer
233 //if (!ent->client->ps.legsTimer) {
234 // cs->castScriptStatus.scriptNoMoveTime = 0;
235 trap_EA_MoveForward( cs->entityNum );
236 //}
237 //ent->client->ps.legsTimer = 0; // allow legs us to move
238 }
239
240 return NULL;
241 }
242
243 /*
244 ================
245 AIFunc_Helga_MeleeStart
246 ================
247 */
AIFunc_Helga_MeleeStart(cast_state_t * cs)248 char *AIFunc_Helga_MeleeStart( cast_state_t *cs ) {
249 gentity_t *ent;
250
251 ent = &g_entities[cs->entityNum];
252 ent->s.effect1Time = level.time;
253 cs->ideal_viewangles[YAW] = cs->viewangles[YAW];
254 cs->weaponFireTimes[cs->weaponNum] = level.time;
255 cs->animHitCount = 0;
256 cs->aiFlags |= AIFL_SPECIAL_FUNC;
257
258 // face them
259 AICast_AimAtEnemy( cs );
260
261 // play an anim
262 BG_UpdateConditionValue( cs->entityNum, ANIM_COND_WEAPON, cs->weaponNum, qtrue );
263 BG_AnimScriptEvent( &ent->client->ps, ANIM_ET_FIREWEAPON, qfalse, qtrue );
264
265 // play a sound
266 G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ATTACKSOUNDSCRIPT] ) );
267
268 cs->aifunc = AIFunc_Helga_Melee;
269 cs->aifunc( cs ); // think once now, to prevent a delay
270 return "AIFunc_Helga_Melee";
271 }
272
273
274 //===================================================================
275
276 /*
277 ==============
278 AIFunc_FlameZombie_Portal
279 ==============
280 */
AIFunc_FlameZombie_Portal(cast_state_t * cs)281 char *AIFunc_FlameZombie_Portal( cast_state_t *cs ) {
282 gentity_t *ent = &g_entities[cs->entityNum];
283 //
284 if ( cs->thinkFuncChangeTime < level.time - PORTAL_ZOMBIE_SPAWNTIME ) {
285 // HACK, make them aware of the player
286 AICast_UpdateVisibility( &g_entities[cs->entityNum], AICast_FindEntityForName( "player" ), qfalse, qtrue );
287 ent->s.time2 = 0; // turn spawning effect off
288 return AIFunc_DefaultStart( cs );
289 }
290 //
291 return NULL;
292 }
293
294 /*
295 ==============
296 AIFunc_FlameZombie_PortalStart
297 ==============
298 */
AIFunc_FlameZombie_PortalStart(cast_state_t * cs)299 char *AIFunc_FlameZombie_PortalStart( cast_state_t *cs ) {
300 gentity_t *ent = &g_entities[cs->entityNum];
301 //
302 ent->s.time2 = level.time + 200; // hijacking this for portal spawning effect
303 //
304 // play a special animation
305 ent->client->ps.torsoAnim =
306 ( ( ent->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_EXTRA1;
307 ent->client->ps.legsAnim =
308 ( ( ent->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | BOTH_EXTRA1;
309 ent->client->ps.torsoTimer = PORTAL_ZOMBIE_SPAWNTIME - 200;
310 ent->client->ps.legsTimer = PORTAL_ZOMBIE_SPAWNTIME - 200;
311 //
312 cs->thinkFuncChangeTime = level.time;
313 //
314 cs->aifunc = AIFunc_FlameZombie_Portal;
315 return "AIFunc_FlameZombie_Portal";
316 }
317
318
319 //=================================================================================
320 //
321 // Heinrich, the LAST boss
322 //
323 //=================================================================================
324
325 //
326 // Special Sound Precache
327 //
328
329 typedef enum
330 {
331 HEINRICH_SWORDIMPACT,
332 HEINRICH_SWORDLUNGE_START,
333 HEINRICH_SWORDKNOCKBACK_START,
334 HEINRICH_SWORDKNOCKBACK_WEAPON,
335 HEINRICH_SWORDSIDESLASH_START,
336 HEINRICH_SWORDSIDESLASH_WEAPON,
337 HEINRICH_EARTHQUAKE_START,
338 HEINRICH_RAISEDEAD_START,
339 HEINRICH_TAUNT_GOODHEALTH,
340 HEINRICH_TAUNT_LOWHEALTH,
341
342 MAX_HEINRICH_SOUNDS
343 } heinrichSounds_t;
344
345 char *heinrichSounds[MAX_HEINRICH_SOUNDS] = {
346 "heinrichSwordImpact",
347 "heinrichSwordLungeStart",
348 "heinrichSwordKnockbackStart",
349 "heinrichSwordKnockbackWeapon",
350 "heinrichSwordSideSlashStart",
351 "heinrichSwordSideSlashWeapon",
352 "heinrichSwordEarthquakeStart",
353 "heinrichRaiseWarriorStart",
354 "heinrichTauntGoodHealth",
355 "heinrichTauntLowHealth",
356 };
357
358 int heinrichSoundIndex[MAX_HEINRICH_SOUNDS];
359
AICast_Heinrich_SoundPrecache(void)360 void AICast_Heinrich_SoundPrecache( void ) {
361 int i;
362 for ( i = 0; i < MAX_HEINRICH_SOUNDS; i++ ) {
363 heinrichSoundIndex[i] = G_SoundIndex( heinrichSounds[i] );
364 }
365 }
366
AICast_Heinrich_Taunt(cast_state_t * cs)367 void AICast_Heinrich_Taunt( cast_state_t *cs ) {
368 gentity_t *ent = &g_entities[cs->entityNum];
369 static int lastTaunt;
370 // sound
371 if ( ent->health > cs->attributes[STARTING_HEALTH] * 0.25 ) {
372 if ( lastTaunt > level.time || lastTaunt < level.time - 20000 ) {
373 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_TAUNT_GOODHEALTH] );
374 lastTaunt = level.time;
375 }
376 } else {
377 if ( lastTaunt > level.time || lastTaunt < level.time - 40000 ) {
378 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_TAUNT_LOWHEALTH] );
379 lastTaunt = level.time;
380 }
381 }
382 }
383
384 #define HEINRICH_LUNGE_DELAY ANIMLENGTH( 15,20 )
385 #define HEINRICH_LUNGE_RANGE 170
386 #define HEINRICH_LUNGE_DAMAGE ( 50 + rand() % 20 )
387
AIFunc_Heinrich_SwordLunge(cast_state_t * cs)388 char *AIFunc_Heinrich_SwordLunge( cast_state_t *cs ) {
389 gentity_t *ent = &g_entities[cs->entityNum];
390 trace_t *tr;
391 vec3_t left;
392 float enemyDist;
393 aicast_predictmove_t move;
394 vec3_t vec;
395 cast_state_t *ecs;
396
397 cs->aiFlags |= AIFL_SPECIAL_FUNC;
398
399 if ( cs->enemyNum < 0 ) {
400 if ( ent->client->ps.torsoTimer ) {
401 return NULL;
402 }
403 return AIFunc_DefaultStart( cs );
404 }
405
406 ecs = AICast_GetCastState( cs->enemyNum );
407
408
409 if ( ent->client->ps.torsoTimer < 500 ) {
410 if ( !ent->client->ps.legsTimer ) {
411 trap_EA_MoveForward( cs->entityNum );
412 }
413 ent->client->ps.legsTimer = 0;
414 ent->client->ps.torsoTimer = 0;
415 cs->castScriptStatus.scriptNoMoveTime = 0;
416 AICast_Heinrich_Taunt( cs );
417 return AIFunc_BattleChaseStart( cs );
418 }
419
420 // time for the melee?
421 if ( cs->enemyNum >= 0 && !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
422 // face them
423 AICast_AimAtEnemy( cs );
424 // keep checking for impact status
425 tr = CheckMeleeAttack( ent, HEINRICH_LUNGE_RANGE, qfalse );
426 /* // do we need to move?
427 if (!(tr && (tr->entityNum == cs->enemyNum))) {
428 ent->client->ps.legsTimer = 0;
429 cs->castScriptStatus.scriptNoMoveTime = 0;
430 trap_EA_MoveForward( cs->entityNum );
431 }
432 */ // ready for damage?
433 if ( cs->thinkFuncChangeTime < level.time - HEINRICH_LUNGE_DELAY ) {
434 cs->aiFlags |= AIFL_MISCFLAG1;
435 // do melee damage
436 if ( tr && ( tr->entityNum == cs->enemyNum ) ) {
437 G_Damage( &g_entities[tr->entityNum], ent, ent, left, tr->endpos, HEINRICH_LUNGE_DAMAGE, 0, MOD_GAUNTLET );
438 // sound
439 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT] );
440 }
441 }
442 }
443
444 // if they are outside range, move forward
445 AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 );
446 VectorSubtract( move.endpos, cs->bs->origin, vec );
447 vec[2] = 0;
448 enemyDist = VectorLength( vec );
449 enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
450 enemyDist -= ent->r.maxs[0];
451 if ( enemyDist > 30 ) { // we can get closer
452 if ( ent->client->ps.legsTimer ) {
453 cs->castScriptStatus.scriptNoMoveTime = level.time + 100;
454 ent->client->ps.legsTimer = 0; // allow legs to move us
455 }
456 if ( cs->castScriptStatus.scriptNoMoveTime < level.time ) {
457 trap_EA_MoveForward( cs->entityNum );
458 }
459 }
460
461 return NULL;
462 }
463
AIFunc_Heinrich_SwordLungeStart(cast_state_t * cs)464 char *AIFunc_Heinrich_SwordLungeStart( cast_state_t *cs ) {
465 gentity_t *ent = &g_entities[cs->entityNum];
466 // gentity_t *enemy = &g_entities[cs->enemyNum];
467
468 cs->aiFlags |= AIFL_SPECIAL_FUNC;
469 // sound
470 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDLUNGE_START] );
471 // face them
472 AICast_AimAtEnemy( cs );
473 // clear flags
474 cs->aiFlags &= ~( AIFL_MISCFLAG1 | AIFL_MISCFLAG2 );
475 // play the anim
476 BG_PlayAnimName( &ent->client->ps, "attack9", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
477 // start the func
478 cs->aifunc = AIFunc_Heinrich_SwordLunge;
479 return "AIFunc_Heinrich_SwordLunge";
480 }
481
482 #define HEINRICH_KNOCKBACK_DELAY ANIMLENGTH( 26,20 )
483 #define HEINRICH_KNOCKBACK_RANGE 150
484 #define HEINRICH_KNOCKBACK_DAMAGE ( 60 + rand() % 20 )
485
AIFunc_Heinrich_SwordKnockback(cast_state_t * cs)486 char *AIFunc_Heinrich_SwordKnockback( cast_state_t *cs ) {
487 gentity_t *ent = &g_entities[cs->entityNum];
488 trace_t *tr;
489 vec3_t right, left;
490
491 cs->aiFlags |= AIFL_SPECIAL_FUNC;
492
493 if ( cs->enemyNum < 0 ) {
494 if ( ent->client->ps.torsoTimer ) {
495 return NULL;
496 }
497 return AIFunc_DefaultStart( cs );
498 }
499
500 AICast_GetCastState( cs->enemyNum );
501
502 if ( ent->client->ps.torsoTimer < 500 ) {
503 if ( !ent->client->ps.legsTimer ) {
504 trap_EA_MoveForward( cs->entityNum );
505 }
506 ent->client->ps.legsTimer = 0;
507 ent->client->ps.torsoTimer = 0;
508 cs->castScriptStatus.scriptNoMoveTime = 0;
509 AICast_Heinrich_Taunt( cs );
510 return AIFunc_BattleChaseStart( cs );
511 }
512
513 // time for the melee?
514 if ( cs->enemyNum >= 0 && !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
515 // face them
516 AICast_AimAtEnemy( cs );
517 // keep checking for impact status
518 tr = CheckMeleeAttack( ent, HEINRICH_KNOCKBACK_RANGE, qfalse );
519 /* // do we need to move?
520 if (!(tr && (tr->entityNum == cs->enemyNum))) {
521 ent->client->ps.legsTimer = 0;
522 cs->castScriptStatus.scriptNoMoveTime = 0;
523 trap_EA_MoveForward( cs->entityNum );
524 }
525 */ // ready for damage?
526 if ( cs->thinkFuncChangeTime < level.time - HEINRICH_KNOCKBACK_DELAY ) {
527 cs->aiFlags |= AIFL_MISCFLAG1;
528 // do melee damage
529 if ( tr && ( tr->entityNum == cs->enemyNum ) ) {
530 AngleVectors( cs->viewangles, NULL, right, NULL );
531 VectorNegate( right, left );
532 G_Damage( &g_entities[tr->entityNum], ent, ent, left, tr->endpos, HEINRICH_KNOCKBACK_DAMAGE, 0, MOD_GAUNTLET );
533 // sound
534 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT] );
535 // throw them in direction of impact
536 if ( ( ent->client->ps.torsoAnim & ~ANIM_TOGGLEBIT ) == BG_AnimationIndexForString( "attack2", cs->entityNum ) ) {
537 // right
538 right[2] = 0.5;
539 VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 400, right, g_entities[cs->enemyNum].client->ps.velocity );
540 } else {
541 // left
542 left[2] = 0.5;
543 VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 400, left, g_entities[cs->enemyNum].client->ps.velocity );
544 }
545 }
546 }
547 }
548 return NULL;
549 }
550
AIFunc_Heinrich_SwordKnockbackStart(cast_state_t * cs)551 char *AIFunc_Heinrich_SwordKnockbackStart( cast_state_t *cs ) {
552 gentity_t *ent = &g_entities[cs->entityNum];
553 // gentity_t *enemy = &g_entities[cs->enemyNum];
554
555 cs->aiFlags |= AIFL_SPECIAL_FUNC;
556 // sound
557 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDKNOCKBACK_START] );
558 // weapon sound
559 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDKNOCKBACK_WEAPON] );
560 // face them
561 AICast_AimAtEnemy( cs );
562 // clear flags
563 cs->aiFlags &= ~( AIFL_MISCFLAG1 | AIFL_MISCFLAG2 );
564 // play the anim
565 if ( rand() % 2 ) {
566 BG_PlayAnimName( &ent->client->ps, "attack2", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
567 } else {
568 BG_PlayAnimName( &ent->client->ps, "attack3", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
569 }
570 // start the func
571 cs->aifunc = AIFunc_Heinrich_SwordKnockback;
572 return "AIFunc_Heinrich_SwordKnockback";
573 }
574
575 #define HEINRICH_SLASH_DELAY ANIMLENGTH( 17,25 )
576 #define HEINRICH_SLASH_RANGE 140
577 #define HEINRICH_SLASH_DAMAGE ( 30 + rand() % 15 )
578
AIFunc_Heinrich_SwordSideSlash(cast_state_t * cs)579 char *AIFunc_Heinrich_SwordSideSlash( cast_state_t *cs ) {
580 gentity_t *ent = &g_entities[cs->entityNum];
581 trace_t *tr;
582 vec3_t right, left;
583 float enemyDist;
584 aicast_predictmove_t move;
585 vec3_t vec;
586 cast_state_t *ecs;
587
588 cs->aiFlags |= AIFL_SPECIAL_FUNC;
589
590 if ( cs->enemyNum < 0 ) {
591 if ( ent->client->ps.torsoTimer ) {
592 return NULL;
593 }
594 return AIFunc_DefaultStart( cs );
595 }
596
597 ecs = AICast_GetCastState( cs->enemyNum );
598
599 if ( ent->client->ps.torsoTimer < 500 ) {
600 if ( !ent->client->ps.legsTimer ) {
601 trap_EA_MoveForward( cs->entityNum );
602 }
603 ent->client->ps.legsTimer = 0;
604 ent->client->ps.torsoTimer = 0;
605 cs->castScriptStatus.scriptNoMoveTime = 0;
606 AICast_Heinrich_Taunt( cs );
607 return AIFunc_BattleChaseStart( cs );
608 }
609
610 // time for the melee?
611 if ( cs->enemyNum >= 0 && !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
612 // face them
613 AICast_AimAtEnemy( cs );
614 // keep checking for impact status
615 tr = CheckMeleeAttack( ent, HEINRICH_SLASH_RANGE, qfalse );
616 // ready for damage?
617 if ( cs->thinkFuncChangeTime < level.time - HEINRICH_SLASH_DELAY ) {
618 cs->aiFlags |= AIFL_MISCFLAG1;
619 // do melee damage
620 if ( tr && ( tr->entityNum == cs->enemyNum ) ) {
621 AngleVectors( cs->viewangles, NULL, right, NULL );
622 VectorNegate( right, left );
623 G_Damage( &g_entities[tr->entityNum], ent, ent, left, tr->endpos, HEINRICH_SLASH_DAMAGE, 0, MOD_GAUNTLET );
624 // sound
625 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDIMPACT] );
626 // throw them in direction of impact
627 left[2] = 0.5;
628 VectorMA( g_entities[cs->enemyNum].client->ps.velocity, 400, left, g_entities[cs->enemyNum].client->ps.velocity );
629 }
630 }
631 }
632
633 // if they are outside range, move forward
634 AICast_PredictMovement( ecs, 2, 0.3, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 );
635 VectorSubtract( move.endpos, cs->bs->origin, vec );
636 vec[2] = 0;
637 enemyDist = VectorLength( vec );
638 enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
639 enemyDist -= ent->r.maxs[0];
640 if ( enemyDist > 30 ) { // we can get closer
641 if ( ent->client->ps.legsTimer ) {
642 cs->castScriptStatus.scriptNoMoveTime = level.time + 100;
643 ent->client->ps.legsTimer = 0; // allow legs to move us
644 }
645 if ( cs->castScriptStatus.scriptNoMoveTime < level.time ) {
646 trap_EA_MoveForward( cs->entityNum );
647 }
648 }
649
650 return NULL;
651 }
652
AIFunc_Heinrich_SwordSideSlashStart(cast_state_t * cs)653 char *AIFunc_Heinrich_SwordSideSlashStart( cast_state_t *cs ) {
654 gentity_t *ent = &g_entities[cs->entityNum];
655
656 cs->aiFlags |= AIFL_SPECIAL_FUNC;
657 // sound
658 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDSIDESLASH_START] );
659 // weapon sound
660 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_SWORDSIDESLASH_WEAPON] );
661 // face them
662 AICast_AimAtEnemy( cs );
663 // clear flags
664 cs->aiFlags &= ~( AIFL_MISCFLAG1 | AIFL_MISCFLAG2 );
665 // play the anim
666 BG_PlayAnimName( &ent->client->ps, "attack8", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
667 // start the func
668 cs->aifunc = AIFunc_Heinrich_SwordSideSlash;
669 return "AIFunc_Heinrich_SwordSideSlash";
670 }
671
672 #define HEINRICH_STOMP_DELAY 900
673 #define HEINRICH_STOMP_RANGE 1024.0
674 #define HEINRICH_STOMP_VELOCITY_Z 420
675 #define HEINRICH_STOMP_DAMAGE 35
676
AIFunc_Heinrich_Earthquake(cast_state_t * cs)677 char *AIFunc_Heinrich_Earthquake( cast_state_t *cs ) {
678 gentity_t *ent = &g_entities[cs->entityNum];
679 gentity_t *enemy;
680 cast_state_t *ecs;
681 vec3_t enemyVec;
682 float enemyDist, scale;
683 trace_t *tr;
684
685 cs->aiFlags |= AIFL_SPECIAL_FUNC;
686
687 if ( cs->enemyNum < 0 ) {
688 if ( !ent->client->ps.torsoTimer ) {
689 return AIFunc_DefaultStart( cs );
690 }
691 return NULL;
692 }
693
694 enemy = &g_entities[cs->enemyNum];
695 ecs = AICast_GetCastState( cs->enemyNum );
696
697 VectorMA( enemy->r.currentOrigin, HEINRICH_STOMP_DELAY, enemy->client->ps.velocity, enemyVec );
698 VectorDistance( ent->r.currentOrigin, enemyVec );
699
700 if ( ent->client->ps.torsoTimer < 500 ) {
701 int rnd;
702 aicast_predictmove_t move;
703 vec3_t vec;
704
705 AICast_PredictMovement( ecs, 2, 0.5, &move, &g_entities[cs->enemyNum].client->pers.cmd, -1 );
706 VectorSubtract( move.endpos, cs->bs->origin, vec );
707 vec[2] = 0;
708 enemyDist = VectorLength( vec );
709 enemyDist -= g_entities[cs->enemyNum].r.maxs[0];
710 enemyDist -= ent->r.maxs[0];
711 //
712 if ( enemyDist < 140 ) {
713 // combo attack
714 rnd = rand() % 3;
715 switch ( rnd ) {
716 case 0:
717 return AIFunc_Heinrich_SwordSideSlashStart( cs );
718 case 1:
719 return AIFunc_Heinrich_SwordKnockbackStart( cs );
720 case 2:
721 return AIFunc_Heinrich_SwordLungeStart( cs );
722 }
723 } else { // back to roaming
724 ent->client->ps.legsTimer = 0;
725 ent->client->ps.torsoTimer = 0;
726 cs->castScriptStatus.scriptNoMoveTime = 0;
727 AICast_Heinrich_Taunt( cs );
728 return AIFunc_DefaultStart( cs );
729 }
730 }
731
732 // time for the thump?
733 if ( !( cs->aiFlags & AIFL_MISCFLAG1 ) ) {
734 // face them
735 AICast_AimAtEnemy( cs );
736 // ready for damage?
737 if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) {
738 cs->aiFlags |= AIFL_MISCFLAG1;
739 // play the stomp sound
740 G_AddEvent( ent, EV_GENERAL_SOUND, G_SoundIndex( aiDefaults[ent->aiCharacter].soundScripts[ORDERSDENYSOUNDSCRIPT] ) );
741 // check for striking the player
742 tr = CheckMeleeAttack( ent, 70, qfalse );
743 // do melee damage
744 if ( tr && ( tr->entityNum == cs->enemyNum ) ) {
745 G_Damage( &g_entities[tr->entityNum], ent, ent, vec3_origin, tr->endpos, HEINRICH_STOMP_DAMAGE, 0, MOD_GAUNTLET );
746 }
747 // call the debris trigger
748 AICast_ScriptEvent( cs, "trigger", "quake" );
749 }
750 }
751
752 enemyDist = Distance( enemy->s.pos.trBase, ent->s.pos.trBase );
753
754 // do the earthquake effects
755 if ( cs->thinkFuncChangeTime < level.time - HEINRICH_STOMP_DELAY ) {
756 // throw the player into the air, if they are on the ground
757 if ( ( enemy->s.groundEntityNum != ENTITYNUM_NONE ) && enemyDist < HEINRICH_STOMP_RANGE ) {
758 scale = 0.5 + 0.5 * ( (float)ent->client->ps.torsoTimer / 1000.0 );
759 if ( scale > 1.0 ) {
760 scale = 1.0;
761 }
762 VectorSubtract( ent->s.pos.trBase, enemy->s.pos.trBase, enemyVec );
763 VectorScale( enemyVec, 2.0 * ( 0.6 + 0.5 * random() ) * scale * ( 0.6 + 0.6 * ( 1.0 - ( enemyDist / HEINRICH_STOMP_RANGE ) ) ), enemyVec );
764 enemyVec[2] = scale * HEINRICH_STOMP_VELOCITY_Z * ( 1.0 - 0.5 * ( enemyDist / HEINRICH_STOMP_RANGE ) );
765 // bounce the player using this velocity
766 VectorAdd( enemy->client->ps.velocity, enemyVec, enemy->client->ps.velocity );
767 }
768 }
769
770 return NULL;
771 }
772
AIFunc_Heinrich_MeleeStart(cast_state_t * cs)773 char *AIFunc_Heinrich_MeleeStart( cast_state_t *cs ) {
774 gentity_t *ent = &g_entities[cs->entityNum];
775 gentity_t *enemy = &g_entities[cs->enemyNum];
776 int rnd;
777 static int lastStomp;
778
779 if ( cs->enemyNum < 0 ) {
780 return NULL;
781 }
782
783 // record weapon fire
784 cs->weaponFireTimes[cs->weaponNum] = level.time;
785 // face them
786 AICast_AimAtEnemy( cs );
787 // clear flags
788 cs->aiFlags &= ~( AIFL_MISCFLAG1 | AIFL_MISCFLAG2 );
789 // decide which attack to use
790 if ( VectorDistance( ent->r.currentOrigin, enemy->r.currentOrigin ) < 60 ) {
791 rnd = 0; // sword slash up close
792 } else if ( VectorDistance( ent->r.currentOrigin, enemy->r.currentOrigin ) >= HEINRICH_SLASH_RANGE ) {
793 rnd = 1; // too far away, stomp
794 } else {
795 // pick at random
796 rnd = rand() % 2;
797 }
798 //
799 switch ( rnd ) {
800 case 0:
801 {
802 int rnd = rand() % 3;
803 switch ( rnd ) {
804 case 0:
805 return AIFunc_Heinrich_SwordSideSlashStart( cs );
806 case 1:
807 return AIFunc_Heinrich_SwordKnockbackStart( cs );
808 case 2:
809 return AIFunc_Heinrich_SwordLungeStart( cs );
810 }
811 }
812 case 1:
813 // dont do stomp too often
814 if ( lastStomp > level.time - 12000 ) { // plenty of time to let debris disappear
815 return NULL;
816 }
817 lastStomp = level.time;
818 cs->aiFlags |= AIFL_SPECIAL_FUNC;
819 // sound
820 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_EARTHQUAKE_START] );
821 // play the anim
822 BG_PlayAnimName( &ent->client->ps, "attack7", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
823 // start the func
824 cs->aifunc = AIFunc_Heinrich_Earthquake;
825 return "AIFunc_Heinrich_Earthquake";
826 }
827 // shutup compiler
828 return NULL;
829 }
830
831 #define HEINRICH_RAISEDEAD_DELAY 1200
832 #define HEINRICH_RAISEDEAD_COUNT 3
833 int lastRaise;
834
AIFunc_Heinrich_RaiseDead(cast_state_t * cs)835 char *AIFunc_Heinrich_RaiseDead( cast_state_t *cs ) {
836 int i;
837 gentity_t *ent = &g_entities[cs->entityNum];
838 gentity_t *enemy = &g_entities[cs->enemyNum];
839 gentity_t *trav, *closest;
840 float closestDist, dist;
841 //
842 cs->aiFlags |= AIFL_SPECIAL_FUNC;
843 if ( cs->enemyNum < 0 ) {
844 if ( !ent->client->ps.torsoTimer ) {
845 return AIFunc_DefaultStart( cs );
846 }
847 return NULL;
848 }
849 //
850 // record weapon fire
851 cs->weaponFireTimes[cs->weaponNum] = level.time;
852 //
853 if ( !ent->client->ps.torsoTimer ) {
854 return AIFunc_DefaultStart( cs );
855 }
856 if ( ent->count2 && lastRaise < level.time - HEINRICH_RAISEDEAD_DELAY ) {
857 lastRaise = level.time;
858 // summons the closest warrior
859 closest = NULL;
860 closestDist = 0; // shutup the compiler
861 for ( i = 0, trav = g_entities; i < level.maxclients; i++, trav++ ) {
862 if ( !trav->inuse ) {
863 continue;
864 }
865 if ( !trav->aiInactive ) {
866 continue;
867 }
868 if ( trav->aiCharacter != AICHAR_WARZOMBIE ) {
869 continue;
870 }
871 dist = VectorDistance( trav->s.pos.trBase, enemy->r.currentOrigin );
872 if ( !closest || dist < closestDist ) {
873 closest = trav;
874 closestDist = dist;
875 }
876 }
877 //
878 if ( closest ) {
879 closest->AIScript_AlertEntity( closest );
880 // make them aware of the player
881 AICast_UpdateVisibility( closest, enemy, qtrue, qtrue );
882 // reduce the count
883 ent->count2--;
884 }
885 }
886 //
887 return NULL;
888 }
889
AIFunc_Heinrich_RaiseDeadStart(cast_state_t * cs)890 char *AIFunc_Heinrich_RaiseDeadStart( cast_state_t *cs ) {
891 int i, cnt, free;
892 gentity_t *ent = &g_entities[cs->entityNum];
893 gentity_t *trav;
894 float circleDist;
895 //
896 // count the number of active warriors
897 cnt = 0;
898 free = 0;
899 for ( i = 0, trav = g_entities; i < level.maxclients; i++, trav++ ) {
900 if ( !trav->inuse ) {
901 continue;
902 }
903 if ( trav->aiCharacter != AICHAR_WARZOMBIE ) {
904 continue;
905 }
906 if ( trav->aiInactive ) {
907 free++;
908 continue;
909 }
910 if ( trav->health <= 0 ) {
911 continue;
912 }
913 cnt++;
914 }
915 //
916 if ( cnt < HEINRICH_RAISEDEAD_COUNT && free ) { // need a new one
917 cs->aiFlags &= ~AIFL_MISCFLAG1;
918 ent->count2 = HEINRICH_RAISEDEAD_COUNT - cnt;
919 lastRaise = level.time;
920 cs->aiFlags |= AIFL_SPECIAL_FUNC;
921 // start the animation
922 BG_PlayAnimName( &ent->client->ps, "attack4", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
923 // play the sound
924 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_RAISEDEAD_START] );
925 // start the func
926 cs->aifunc = AIFunc_Heinrich_RaiseDead;
927 return "AIFunc_Heinrich_RaiseDead";
928 }
929 // enable all the spirit spawners
930 trav = NULL;
931 // TTimo: gcc: suggest () around assignment used as truth value
932 while ( ( trav = G_Find( trav, FOFS( classname ), "func_bats" ) ) ) {
933 if ( !trav->active && trav->spawnflags & 4 ) {
934 trav->active = 1; // let them release spirits now
935 }
936 }
937 // is the player outside the circle?
938 trav = NULL;
939 // TTimo: gcc: suggest () around assignment used as truth value
940 while ( ( trav = G_Find( trav, FOFS( classname ), "func_bats" ) ) ) {
941 if ( trav->spawnflags & 4 ) {
942 circleDist = trav->radius;
943 trav = G_Find( NULL, FOFS( targetname ), trav->target );
944 if ( trav ) {
945 if ( VectorDistance( g_entities[0].s.pos.trBase, trav->s.origin ) > circleDist ) {
946 cs->aiFlags &= ~AIFL_MISCFLAG1;
947 ent->count2 = 0;
948 cs->aiFlags |= AIFL_SPECIAL_FUNC;
949 // start the animation
950 BG_PlayAnimName( &ent->client->ps, "attack4", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
951 // play the sound
952 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_RAISEDEAD_START] );
953 // start the func
954 cs->aifunc = AIFunc_Heinrich_RaiseDead;
955 return "AIFunc_Heinrich_RaiseDead";
956 }
957 }
958 break;
959 }
960 }
961 //
962 return NULL;
963 }
964
AIFunc_Heinrich_SpawnSpiritsStart(cast_state_t * cs)965 char *AIFunc_Heinrich_SpawnSpiritsStart( cast_state_t *cs ) {
966 gentity_t *ent = &g_entities[cs->entityNum];
967 gentity_t *trav;
968 float circleDist;
969 //
970 // enable all the spirit spawners
971 trav = NULL;
972 // TTimo: gcc: suggest () around assignment used as truth value
973 while ( ( trav = G_Find( trav, FOFS( classname ), "func_bats" ) ) ) {
974 if ( !trav->active && trav->spawnflags & 4 ) {
975 trav->active = 1; // let them release spirits now
976 }
977 }
978 // is the player outside the circle?
979 trav = NULL;
980 // TTimo: gcc: suggest () around assignment used as truth value
981 while ( ( trav = G_Find( trav, FOFS( classname ), "func_bats" ) ) ) {
982 if ( trav->spawnflags & 4 ) {
983 circleDist = trav->radius;
984 trav = G_Find( NULL, FOFS( targetname ), trav->target );
985 if ( trav ) {
986 if ( VectorDistance( g_entities[0].s.pos.trBase, trav->s.origin ) > circleDist ) {
987 cs->aiFlags &= ~AIFL_MISCFLAG1;
988 ent->count2 = 0;
989 cs->aiFlags |= AIFL_SPECIAL_FUNC;
990 // start the animation
991 BG_PlayAnimName( &ent->client->ps, "attack4", ANIM_BP_BOTH, qtrue, qfalse, qtrue );
992 // play the sound
993 G_AddEvent( ent, EV_GENERAL_SOUND, heinrichSoundIndex[HEINRICH_RAISEDEAD_START] );
994 // start the func
995 cs->aifunc = AIFunc_Heinrich_RaiseDead; // just do raise dead, without raising any warriors
996 return "AIFunc_Heinrich_RaiseDead";
997 }
998 }
999 break;
1000 }
1001 }
1002 //
1003 return NULL;
1004 }
1005