1 /*
2 ==============================================================================
3 
4 black widow
5 
6 ==============================================================================
7 */
8 
9 // self->timestamp used to prevent rapid fire of railgun
10 // self->plat2flags used for fire count (flashes)
11 // self->monsterinfo.pausetime used for timing of blaster shots
12 
13 #include "g_local.h"
14 #include "m_widow.h"
15 
16 #define	NUM_STALKERS_SPAWNED		6		// max # of stalkers she can spawn
17 
18 #define	RAIL_TIME					3
19 #define	BLASTER_TIME				2
20 #define	BLASTER2_DAMAGE				10
21 #define	WIDOW_RAIL_DAMAGE			50
22 
23 #define	DRAWBBOX					NULL
24 #define	SHOWME						NULL	// showme
25 
26 void BossExplode (edict_t *self);
27 
28 qboolean infront (edict_t *self, edict_t *other);
29 
30 static int	sound_pain1;
31 static int	sound_pain2;
32 static int	sound_pain3;
33 static int	sound_search1;
34 static int	sound_rail;
35 static int	sound_sight;
36 
37 static unsigned long shotsfired;
38 
39 static vec3_t spawnpoints[] = {
40 	{30,  100, 16},
41 	{30, -100, 16}
42 };
43 
44 static vec3_t beameffects[] = {
45 	{12.58, -43.71, 68.88},
46 	{3.43, 58.72, 68.41}
47 };
48 
49 static float sweep_angles[] = {
50 //	32.0, 26.0, 20.0, 11.5, 3.0, -8.0, -13.0, -27.0, -41.0
51 	32.0, 26.0, 20.0, 10.0, 0.0, -6.5, -13.0, -27.0, -41.0
52 };
53 
54 vec3_t stalker_mins = {-28, -28, -18};
55 vec3_t stalker_maxs = {28, 28, 18};
56 
57 unsigned int	widow_damage_multiplier;
58 
59 void widow_run (edict_t *self);
60 void widow_stand (edict_t *self);
61 void widow_dead (edict_t *self);
62 void widow_attack (edict_t *self);
63 void widow_attack_blaster (edict_t *self);
64 void widow_reattack_blaster (edict_t *self);
65 void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point);
66 
67 void widow_start_spawn (edict_t *self);
68 void widow_done_spawn (edict_t *self);
69 void widow_spawn_check (edict_t *self);
70 void widow_prep_spawn (edict_t *self);
71 void widow_attack_rail (edict_t *self);
72 
73 void widow_start_run_5 (edict_t *self);
74 void widow_start_run_10 (edict_t *self);
75 void widow_start_run_12 (edict_t *self);
76 
77 void WidowCalcSlots (edict_t *self);
78 
79 void drawbbox (edict_t *self);
80 
showme(edict_t * self)81 void showme (edict_t *self)
82 {
83 	gi.dprintf ("frame %d\n", self->s.frame);
84 }
85 
widow_search(edict_t * self)86 void widow_search (edict_t *self)
87 {
88 //	if (random() < 0.5)
89 //		gi.sound (self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
90 }
91 
widow_sight(edict_t * self,edict_t * other)92 void widow_sight (edict_t *self, edict_t *other)
93 {
94 	self->monsterinfo.pausetime = 0;
95 //	gi.sound (self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
96 
97 //	if ((g_showlogic) && (g_showlogic->value))
98 //		gi.dprintf ("widow: found target!\n");
99 }
100 
101 mmove_t widow_move_attack_post_blaster;
102 mmove_t widow_move_attack_post_blaster_r;
103 mmove_t widow_move_attack_post_blaster_l;
104 mmove_t widow_move_attack_blaster;
105 
target_angle(edict_t * self)106 float target_angle (edict_t *self)
107 {
108 	vec3_t target;
109 	float enemy_yaw;
110 
111 	VectorSubtract (self->s.origin, self->enemy->s.origin, target);
112 	enemy_yaw = self->s.angles[YAW] - vectoyaw2(target);
113 	if (enemy_yaw < 0)
114 		enemy_yaw += 360.0;
115 
116 	// this gets me 0 degrees = forward
117 	enemy_yaw -= 180.0;
118 	// positive is to right, negative to left
119 
120 	return enemy_yaw;
121 }
122 
WidowTorso(edict_t * self)123 int WidowTorso (edict_t *self)
124 {
125 	float enemy_yaw;
126 
127 	enemy_yaw = target_angle (self);
128 
129 //	if ((g_showlogic) && (g_showlogic->value))
130 //		gi.dprintf ("%2.2f -> ", enemy_yaw);
131 
132 	if (enemy_yaw >= 105)
133 	{
134 		self->monsterinfo.currentmove = &widow_move_attack_post_blaster_r;
135 		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
136 		return 0;
137 	}
138 
139 	if (enemy_yaw <= -75.0)
140 	{
141 		self->monsterinfo.currentmove = &widow_move_attack_post_blaster_l;
142 		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
143 		return 0;
144 	}
145 
146 	if (enemy_yaw >= 95)
147 		return FRAME_fired03;
148 	else if (enemy_yaw >= 85)
149 		return FRAME_fired04;
150 	else if (enemy_yaw >= 75)
151 		return FRAME_fired05;
152 	else if (enemy_yaw >= 65)
153 		return FRAME_fired06;
154 	else if (enemy_yaw >= 55)
155 		return FRAME_fired07;
156 	else if (enemy_yaw >= 45)
157 		return FRAME_fired08;
158 	else if (enemy_yaw >= 35)
159 		return FRAME_fired09;
160 	else if (enemy_yaw >= 25)
161 		return FRAME_fired10;
162 	else if (enemy_yaw >= 15)
163 		return FRAME_fired11;
164 	else if (enemy_yaw >= 5)
165 		return FRAME_fired12;
166 	else if (enemy_yaw >= -5)
167 		return FRAME_fired13;
168 	else if (enemy_yaw >= -15)
169 		return FRAME_fired14;
170 	else if (enemy_yaw >= -25)
171 		return FRAME_fired15;
172 	else if (enemy_yaw >= -35)
173 		return FRAME_fired16;
174 	else if (enemy_yaw >= -45)
175 		return FRAME_fired17;
176 	else if (enemy_yaw >= -55)
177 		return FRAME_fired18;
178 	else if (enemy_yaw >= -65)
179 		return FRAME_fired19;
180 	else if (enemy_yaw >= -75)
181 		return FRAME_fired20;
182 /*
183 	if (fabs(enemy_yaw) < 11.25)
184 		return FRAME_fired03;
185 	else if (fabs(enemy_yaw) > 56.25)
186 	{
187 		self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
188 		self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
189 		return;
190 	}
191 	else if ((enemy_yaw >= 11.25) && (enemy_yaw < 33.75))
192 		return FRAME_fired04;
193 	else if (enemy_yaw >= 33.75)
194 		return FRAME_fired05;
195 	else if ((enemy_yaw <= -11.25) && (enemy_yaw > -33.75))
196 		return FRAME_fired06;
197 	else if (enemy_yaw <= -33.75)
198 		return FRAME_fired07;
199 */
200 }
201 
202 #define	VARIANCE 15.0
203 
WidowBlaster(edict_t * self)204 void WidowBlaster (edict_t *self)
205 {
206 	vec3_t	forward, right, target, vec, targ_angles;
207 	vec3_t	start;
208 	int		flashnum;
209 	int		effect;
210 
211 	if (!self->enemy)
212 		return;
213 
214 	shotsfired++;
215 	if (!(shotsfired % 4))
216 		effect = EF_BLASTER;
217 	else
218 		effect = 0;
219 
220 	AngleVectors (self->s.angles, forward, right, NULL);
221 	if ((self->s.frame >= FRAME_spawn05) && (self->s.frame <= FRAME_spawn13))
222 	{
223 		// sweep
224 		flashnum = MZ2_WIDOW_BLASTER_SWEEP1 + self->s.frame - FRAME_spawn05;
225 		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
226 		VectorSubtract (self->enemy->s.origin, start, target);
227 		vectoangles2 (target, targ_angles);
228 
229 		VectorCopy (self->s.angles, vec);
230 
231 		vec[PITCH] += targ_angles[PITCH];
232 		vec[YAW] -= sweep_angles[flashnum-MZ2_WIDOW_BLASTER_SWEEP1];
233 
234 		AngleVectors (vec, forward, NULL, NULL);
235 		monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
236 
237 /*		if (self->s.frame == FRAME_spawn13)
238 		{
239 			VectorMA (start, 1024, forward, debugend);
240 
241 			gi.WriteByte (svc_temp_entity);
242 			gi.WriteByte (TE_DEBUGTRAIL);
243 			gi.WritePosition (start);
244 			gi.WritePosition (debugend);
245 			gi.multicast (start, MULTICAST_ALL);
246 
247 			drawbbox (self);
248 			self->monsterinfo.aiflags |= AI_HOLD_FRAME|AI_MANUAL_STEERING;
249 		}
250 */
251 	}
252 	else if ((self->s.frame >= FRAME_fired02a) && (self->s.frame <= FRAME_fired20))
253 	{
254 		vec3_t angles;
255 		float aim_angle, target_angle;
256 		float error;
257 
258 		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
259 
260 		self->monsterinfo.nextframe = WidowTorso (self);
261 
262 		if (!self->monsterinfo.nextframe)
263 			self->monsterinfo.nextframe = self->s.frame;
264 
265 //		if ((g_showlogic) && (g_showlogic->value))
266 //			gi.dprintf ("%d\n", self->monsterinfo.nextframe);
267 
268 		if (self->s.frame == FRAME_fired02a)
269 			flashnum = MZ2_WIDOW_BLASTER_0;
270 		else
271 			flashnum = MZ2_WIDOW_BLASTER_100 + self->s.frame - FRAME_fired03;
272 
273 		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
274 
275 		PredictAim (self->enemy, start, 1000, true, ((random()*0.1)-0.05), forward, NULL);
276 
277 		// clamp it to within 10 degrees of the aiming angle (where she's facing)
278 		vectoangles2 (forward, angles);
279 		// give me 100 -> -70
280 		aim_angle = 100 - (10*(flashnum-MZ2_WIDOW_BLASTER_100));
281 		if (aim_angle <= 0)
282 			aim_angle += 360;
283 		target_angle = self->s.angles[YAW] - angles[YAW];
284 		if (target_angle <= 0)
285 			target_angle += 360;
286 
287 		error = aim_angle - target_angle;
288 
289 		// positive error is to entity's left, aka positive direction in engine
290 		// unfortunately, I decided that for the aim_angle, positive was right.  *sigh*
291 		if (error > VARIANCE)
292 		{
293 //			if ((g_showlogic) && (g_showlogic->value))
294 //				gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
295 			angles[YAW] = (self->s.angles[YAW] - aim_angle) + VARIANCE;
296 //			if ((g_showlogic) && (g_showlogic->value))
297 //			{
298 //				if (angles[YAW] <= 0)
299 //					angles[YAW] += 360;
300 //				gi.dprintf (" %2.2f\n", angles[YAW]);
301 //			}
302 			AngleVectors (angles, forward, NULL, NULL);
303 		}
304 		else if (error < -VARIANCE)
305 		{
306 //			if ((g_showlogic) && (g_showlogic->value))
307 //				gi.dprintf ("angle %2.2f (really %2.2f) (%2.2f off of %2.2f) corrected to", target_angle, angles[YAW], error, aim_angle);
308 			angles[YAW] = (self->s.angles[YAW] - aim_angle) - VARIANCE;
309 //			if ((g_showlogic) && (g_showlogic->value))
310 //			{
311 //				if (angles[YAW] <= 0)
312 //					angles[YAW] += 360;
313 //				gi.dprintf (" %2.2f\n", angles[YAW]);
314 //			}
315 			AngleVectors (angles, forward, NULL, NULL);
316 		}
317 //		gi.dprintf ("%2.2f - %2.2f - %2.2f - %2.2f\n", aim_angle, self->s.angles[YAW] - angles[YAW], target_angle, error);
318 //		gi.dprintf ("%2.2f - %2.2f - %2.2f\n", angles[YAW], aim_angle, self->s.angles[YAW]);
319 
320 /*
321 		if (self->s.frame == FRAME_fired20)
322 		{
323 			VectorMA (start, 512, forward, debugend);
324 			gi.WriteByte (svc_temp_entity);
325 			gi.WriteByte (TE_DEBUGTRAIL);
326 			gi.WritePosition (start);
327 			gi.WritePosition (forward);
328 			gi.multicast (start, MULTICAST_ALL);
329 
330 			drawbbox (self);
331 			self->monsterinfo.aiflags |= AI_HOLD_FRAME;
332 			self->monsterinfo.nextframe = FRAME_fired20;
333 			self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
334 		}
335 */
336 /*
337 		if (!(self->plat2flags % 3))
338 			effect = EF_HYPERBLASTER;
339 		else
340 			effect = 0;
341 		self->plat2flags ++;
342 */
343 		monster_fire_blaster2 (self, start, forward, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
344 	}
345 	else if ((self->s.frame >= FRAME_run01) && (self->s.frame <= FRAME_run08))
346 	{
347 		flashnum = MZ2_WIDOW_RUN_1 + self->s.frame - FRAME_run01;
348 		G_ProjectSource (self->s.origin, monster_flash_offset[flashnum], forward, right, start);
349 
350 		VectorSubtract (self->enemy->s.origin, start, target);
351 		target[2] += self->enemy->viewheight;
352 
353 		monster_fire_blaster2 (self, start, target, BLASTER2_DAMAGE*widow_damage_multiplier, 1000, flashnum, effect);
354 	}
355 //	else
356 //	{
357 //		if ((g_showlogic) && (g_showlogic->value))
358 //			gi.dprintf ("widow: firing on non-fire frame!\n");
359 //	}
360 }
361 
WidowSpawn(edict_t * self)362 void WidowSpawn (edict_t *self)
363 {
364 	vec3_t	f, r, u, offset, startpoint, spawnpoint;
365 	edict_t	*ent, *designated_enemy;
366 	int		i;
367 
368 	AngleVectors (self->s.angles, f, r, u);
369 
370 	for (i=0; i < 2; i++)
371 	{
372 		VectorCopy (spawnpoints[i], offset);
373 
374 		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
375 
376 		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
377 		{
378 			ent = CreateGroundMonster (spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256);
379 
380 			if (!ent)
381 				continue;
382 
383 			self->monsterinfo.monster_used++;
384 			ent->monsterinfo.commander = self;
385 //			if ((g_showlogic) && (g_showlogic->value))
386 //				gi.dprintf ("widow: post-spawn : %d slots left out of %d\n", SELF_SLOTS_LEFT, self->monsterinfo.monster_slots);
387 
388 			ent->nextthink = level.time;
389 			ent->think (ent);
390 
391 			ent->monsterinfo.aiflags |= AI_SPAWNED_WIDOW|AI_DO_NOT_COUNT|AI_IGNORE_SHOTS;
392 
393 			if (!(coop && coop->value))
394 			{
395 				designated_enemy = self->enemy;
396 			}
397 			else
398 			{
399 				designated_enemy = PickCoopTarget(ent);
400 				if (designated_enemy)
401 				{
402 					// try to avoid using my enemy
403 					if (designated_enemy == self->enemy)
404 					{
405 						designated_enemy = PickCoopTarget(ent);
406 						if (designated_enemy)
407 						{
408 //							if ((g_showlogic) && (g_showlogic->value))
409 //							{
410 //								gi.dprintf ("PickCoopTarget returned a %s - ", designated_enemy->classname);
411 //								if (designated_enemy->client)
412 //									gi.dprintf ("with name %s\n", designated_enemy->client->pers.netname);
413 //								else
414 //									gi.dprintf ("NOT A CLIENT\n");
415 //							}
416 						}
417 						else
418 						{
419 //							if ((g_showlogic) && (g_showlogic->value))
420 //								gi.dprintf ("pick coop failed, using my current enemy\n");
421 							designated_enemy = self->enemy;
422 						}
423 					}
424 				}
425 				else
426 				{
427 //					if ((g_showlogic) && (g_showlogic->value))
428 //						gi.dprintf ("pick coop failed, using my current enemy\n");
429 					designated_enemy = self->enemy;
430 				}
431 			}
432 
433 			if ((designated_enemy->inuse) && (designated_enemy->health > 0))
434 			{
435 				ent->enemy = designated_enemy;
436 				FoundTarget (ent);
437 				ent->monsterinfo.attack(ent);
438 			}
439 		}
440 	}
441 }
442 
widow_spawn_check(edict_t * self)443 void widow_spawn_check (edict_t *self)
444 {
445 	WidowBlaster(self);
446 	WidowSpawn (self);
447 }
448 
widow_ready_spawn(edict_t * self)449 void widow_ready_spawn (edict_t *self)
450 {
451 	vec3_t	f, r, u, offset, startpoint, spawnpoint;
452 	int		i;
453 
454 	WidowBlaster(self);
455 	AngleVectors (self->s.angles, f, r, u);
456 
457 	for (i=0; i < 2; i++)
458 	{
459 		VectorCopy (spawnpoints[i], offset);
460 		G_ProjectSource2 (self->s.origin, offset, f, r, u, startpoint);
461 		if (FindSpawnPoint (startpoint, stalker_mins, stalker_maxs, spawnpoint, 64))
462 		{
463 			SpawnGrow_Spawn (spawnpoint, 1);
464 		}
465 	}
466 }
467 
widow_step(edict_t * self)468 void widow_step (edict_t *self)
469 {
470 	gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep3.wav"), 1, ATTN_NORM, 0);
471 }
472 
473 mframe_t widow_frames_stand [] =
474 {
475 	ai_stand, 0, NULL,
476 	ai_stand, 0, NULL,
477 	ai_stand, 0, NULL,
478 	ai_stand, 0, NULL,
479 	ai_stand, 0, NULL,
480 	ai_stand, 0, NULL,
481 	ai_stand, 0, NULL,
482 	ai_stand, 0, NULL,
483 	ai_stand, 0, NULL,
484 	ai_stand, 0, NULL,
485 	ai_stand, 0, NULL
486 };
487 mmove_t	widow_move_stand = {FRAME_idle01, FRAME_idle11, widow_frames_stand, NULL};
488 
489 mframe_t widow_frames_walk [] =
490 {
491 	// hand generated numbers
492 /*
493 	ai_run,	6,	NULL,
494 	ai_run,	3,	NULL,
495 	ai_run,	3,	NULL,
496 	ai_run,	3,	NULL,
497 	ai_run,	4,	NULL,			//5
498 	ai_run,	4,	NULL,
499 	ai_run,	4,	NULL,
500 	ai_run,	4.5,	NULL,
501 	ai_run,	3,	NULL,
502 	ai_run,	5,	NULL,			//10
503 	ai_run,	8,	NULL,
504 	ai_run,	8,	NULL,
505 	ai_run,	6.5,	NULL
506 */
507 	// auto generated numbers
508 	ai_walk,	2.79,	widow_step,
509 	ai_walk,	2.77,	NULL,
510 	ai_walk,	3.53,	NULL,
511 	ai_walk,	3.97,	NULL,
512 	ai_walk,	4.13,	NULL,			//5
513 	ai_walk,	4.09,	NULL,
514 	ai_walk,	3.84,	NULL,
515 	ai_walk,	3.62,	widow_step,
516 	ai_walk,	3.29,	NULL,
517 	ai_walk,	6.08,	NULL,			//10
518 	ai_walk,	6.94,	NULL,
519 	ai_walk,	5.73,	NULL,
520 	ai_walk,	2.85,	NULL
521 };
522 mmove_t widow_move_walk = {FRAME_walk01, FRAME_walk13, widow_frames_walk, NULL};
523 
524 
525 mframe_t widow_frames_run [] =
526 {
527 	ai_run,	2.79,	widow_step,
528 	ai_run,	2.77,	NULL,
529 	ai_run,	3.53,	NULL,
530 	ai_run,	3.97,	NULL,
531 	ai_run,	4.13,	NULL,			//5
532 	ai_run,	4.09,	NULL,
533 	ai_run,	3.84,	NULL,
534 	ai_run,	3.62,	widow_step,
535 	ai_run,	3.29,	NULL,
536 	ai_run,	6.08,	NULL,			//10
537 	ai_run,	6.94,	NULL,
538 	ai_run,	5.73,	NULL,
539 	ai_run,	2.85,	NULL
540 };
541 mmove_t widow_move_run = {FRAME_walk01, FRAME_walk13, widow_frames_run, NULL};
542 
widow_stepshoot(edict_t * self)543 void widow_stepshoot (edict_t *self)
544 {
545 	gi.sound (self, CHAN_BODY, gi.soundindex("widow/bwstep2.wav"), 1, ATTN_NORM,0);
546 	WidowBlaster (self);
547 }
548 
549 mframe_t widow_frames_run_attack [] =
550 {
551 	ai_charge,	13,	widow_stepshoot,
552 	ai_charge,	11.72,	WidowBlaster,
553 	ai_charge,	18.04,	WidowBlaster,
554 	ai_charge,	14.58,	WidowBlaster,
555 	ai_charge,	13,	widow_stepshoot,			//5
556 	ai_charge,	12.12,	WidowBlaster,
557 	ai_charge,	19.63,	WidowBlaster,
558 	ai_charge,	11.37,	WidowBlaster
559 };
560 mmove_t widow_move_run_attack = {FRAME_run01, FRAME_run08, widow_frames_run_attack, widow_run};
561 
562 
563 //
564 // These three allow specific entry into the run sequence
565 //
566 
widow_start_run_5(edict_t * self)567 void widow_start_run_5 (edict_t *self)
568 {
569 	self->monsterinfo.currentmove = &widow_move_run;
570 	self->monsterinfo.nextframe = FRAME_walk05;
571 }
572 
widow_start_run_10(edict_t * self)573 void widow_start_run_10 (edict_t *self)
574 {
575 	self->monsterinfo.currentmove = &widow_move_run;
576 	self->monsterinfo.nextframe = FRAME_walk10;
577 }
578 
widow_start_run_12(edict_t * self)579 void widow_start_run_12 (edict_t *self)
580 {
581 	self->monsterinfo.currentmove = &widow_move_run;
582 	self->monsterinfo.nextframe = FRAME_walk12;
583 }
584 
585 
586 mframe_t widow_frames_attack_pre_blaster [] =
587 {
588 	ai_charge,	0,	NULL,
589 	ai_charge,	0,	NULL,
590 	ai_charge,	0,	widow_attack_blaster
591 };
592 mmove_t widow_move_attack_pre_blaster = {FRAME_fired01, FRAME_fired02a, widow_frames_attack_pre_blaster, NULL};
593 
594 // Loop this
595 mframe_t widow_frames_attack_blaster [] =
596 {
597 	ai_charge,	0,	widow_reattack_blaster,		// straight ahead
598 	ai_charge,	0,	widow_reattack_blaster,		// 100 degrees right
599 	ai_charge,	0,	widow_reattack_blaster,
600 	ai_charge,	0,	widow_reattack_blaster,
601 	ai_charge,	0,	widow_reattack_blaster,
602 	ai_charge,	0,	widow_reattack_blaster,
603 	ai_charge,	0,	widow_reattack_blaster,		// 50 degrees right
604 	ai_charge,	0,	widow_reattack_blaster,
605 	ai_charge,	0,	widow_reattack_blaster,
606 	ai_charge,	0,	widow_reattack_blaster,
607 	ai_charge,	0,	widow_reattack_blaster,
608 	ai_charge,	0,	widow_reattack_blaster,		// straight
609 	ai_charge,	0,	widow_reattack_blaster,
610 	ai_charge,	0,	widow_reattack_blaster,
611 	ai_charge,	0,	widow_reattack_blaster,
612 	ai_charge,	0,	widow_reattack_blaster,
613 	ai_charge,	0,	widow_reattack_blaster,		// 50 degrees left
614 	ai_charge,	0,	widow_reattack_blaster,
615 	ai_charge,	0,	widow_reattack_blaster		// 70 degrees left
616 };
617 mmove_t widow_move_attack_blaster = {FRAME_fired02a, FRAME_fired20, widow_frames_attack_blaster, NULL};
618 
619 mframe_t widow_frames_attack_post_blaster [] =
620 {
621 	ai_charge,	0,	NULL,
622 	ai_charge,	0,	NULL
623 };
624 mmove_t widow_move_attack_post_blaster = {FRAME_fired21, FRAME_fired22, widow_frames_attack_post_blaster, widow_run};
625 
626 mframe_t widow_frames_attack_post_blaster_r [] =
627 {
628 	ai_charge,	-2,	NULL,
629 	ai_charge,	-10,	NULL,
630 	ai_charge,	-2,	NULL,
631 	ai_charge,	0,	NULL,
632 	ai_charge,	0,	widow_start_run_12
633 };
634 mmove_t widow_move_attack_post_blaster_r = {FRAME_transa01, FRAME_transa05, widow_frames_attack_post_blaster_r, NULL};
635 
636 mframe_t widow_frames_attack_post_blaster_l [] =
637 {
638 	ai_charge,	0,	NULL,
639 	ai_charge,	14,	NULL,
640 	ai_charge,	-2,	NULL,
641 	ai_charge,	10,	NULL,
642 	ai_charge,	10,	widow_start_run_12
643 };
644 mmove_t widow_move_attack_post_blaster_l = {FRAME_transb01, FRAME_transb05, widow_frames_attack_post_blaster_l, NULL};
645 
646 mmove_t widow_move_attack_rail;
647 mmove_t widow_move_attack_rail_l;
648 mmove_t widow_move_attack_rail_r;
649 
WidowRail(edict_t * self)650 void WidowRail (edict_t *self)
651 {
652 	vec3_t	start;
653 	vec3_t	dir;
654 	vec3_t	forward, right;
655 	int		flash;
656 
657 //	gi.dprintf ("railing!\n");
658 	AngleVectors (self->s.angles, forward, right, NULL);
659 
660 	if (self->monsterinfo.currentmove == &widow_move_attack_rail)
661 		flash = MZ2_WIDOW_RAIL;
662 	else if (self->monsterinfo.currentmove == &widow_move_attack_rail_l)
663 	{
664 		flash = MZ2_WIDOW_RAIL_LEFT;
665 	}
666 	else if (self->monsterinfo.currentmove == &widow_move_attack_rail_r)
667 	{
668 		flash = MZ2_WIDOW_RAIL_RIGHT;
669 	}
670 
671 	G_ProjectSource (self->s.origin, monster_flash_offset[flash], forward, right, start);
672 
673 	// calc direction to where we targeted
674 	VectorSubtract (self->pos1, start, dir);
675 	VectorNormalize (dir);
676 
677 	monster_fire_railgun (self, start, dir, WIDOW_RAIL_DAMAGE*widow_damage_multiplier, 100, flash);
678 	self->timestamp = level.time + RAIL_TIME;
679 }
680 
WidowSaveLoc(edict_t * self)681 void WidowSaveLoc (edict_t *self)
682 {
683 	VectorCopy (self->enemy->s.origin, self->pos1);	//save for aiming the shot
684 	self->pos1[2] += self->enemy->viewheight;
685 };
686 
widow_start_rail(edict_t * self)687 void widow_start_rail (edict_t *self)
688 {
689 	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
690 }
691 
widow_rail_done(edict_t * self)692 void widow_rail_done (edict_t *self)
693 {
694 	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
695 }
696 
697 mframe_t widow_frames_attack_pre_rail [] =
698 {
699 	ai_charge,	0,	widow_start_rail,
700 	ai_charge,	0,	NULL,
701 	ai_charge,	0,	NULL,
702 	ai_charge,	0,	widow_attack_rail
703 };
704 mmove_t widow_move_attack_pre_rail = {FRAME_transc01, FRAME_transc04, widow_frames_attack_pre_rail, NULL};
705 
706 mframe_t widow_frames_attack_rail [] =
707 {
708 	ai_charge, 0, NULL,
709 	ai_charge, 0, NULL,
710 	ai_charge, 0, WidowSaveLoc,
711 	ai_charge, -10, WidowRail,
712 	ai_charge, 0, NULL,
713 	ai_charge, 0, NULL,
714 	ai_charge, 0, NULL,
715 	ai_charge, 0, NULL,
716 	ai_charge, 0, widow_rail_done
717 };
718 mmove_t widow_move_attack_rail = {FRAME_firea01, FRAME_firea09, widow_frames_attack_rail, widow_run};
719 
720 mframe_t widow_frames_attack_rail_r [] =
721 {
722 	ai_charge, 0, NULL,
723 	ai_charge, 0, NULL,
724 	ai_charge, 0, WidowSaveLoc,
725 	ai_charge, -10, WidowRail,
726 	ai_charge, 0, NULL,
727 	ai_charge, 0, NULL,
728 	ai_charge, 0, NULL,
729 	ai_charge, 0, NULL,
730 	ai_charge, 0, widow_rail_done
731 };
732 mmove_t widow_move_attack_rail_r = {FRAME_fireb01, FRAME_fireb09, widow_frames_attack_rail_r, widow_run};
733 
734 mframe_t widow_frames_attack_rail_l [] =
735 {
736 	ai_charge, 0, NULL,
737 	ai_charge, 0, NULL,
738 	ai_charge, 0, WidowSaveLoc,
739 	ai_charge, -10, WidowRail,
740 	ai_charge, 0, NULL,
741 	ai_charge, 0, NULL,
742 	ai_charge, 0, NULL,
743 	ai_charge, 0, NULL,
744 	ai_charge, 0, widow_rail_done
745 };
746 mmove_t widow_move_attack_rail_l = {FRAME_firec01, FRAME_firec09, widow_frames_attack_rail_l, widow_run};
747 
widow_attack_rail(edict_t * self)748 void widow_attack_rail (edict_t *self)
749 {
750 	float	enemy_angle;
751 //	gi.dprintf ("going to the rail!\n");
752 
753 	enemy_angle = target_angle (self);
754 
755 	if (enemy_angle < -15)
756 		self->monsterinfo.currentmove = &widow_move_attack_rail_l;
757 	else if (enemy_angle > 15)
758 		self->monsterinfo.currentmove = &widow_move_attack_rail_r;
759 	else
760 		self->monsterinfo.currentmove = &widow_move_attack_rail;
761 }
762 
widow_start_spawn(edict_t * self)763 void widow_start_spawn (edict_t *self)
764 {
765 	self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
766 }
767 
widow_done_spawn(edict_t * self)768 void widow_done_spawn (edict_t *self)
769 {
770 	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
771 }
772 
773 mframe_t widow_frames_spawn [] =
774 {
775 	ai_charge,	0,	NULL,						//1
776 	ai_charge,	0,	NULL,
777 	ai_charge,	0,	NULL,
778 	ai_charge,	0,	widow_start_spawn,
779 	ai_charge,	0,	NULL,						//5
780 	ai_charge,	0,	WidowBlaster,				//6
781 	ai_charge,	0,	widow_ready_spawn,			//7
782 	ai_charge,	0,	WidowBlaster,
783 	ai_charge,	0,	WidowBlaster,				//9
784 	ai_charge,	0,	widow_spawn_check,
785 	ai_charge,	0,	WidowBlaster,				//11
786 	ai_charge,	0,	WidowBlaster,
787 	ai_charge,	0,	WidowBlaster,				//13
788 	ai_charge,	0,	NULL,
789 	ai_charge,	0,	NULL,
790 	ai_charge,	0,	NULL,
791 	ai_charge,	0,	NULL,
792 	ai_charge,	0,	widow_done_spawn
793 };
794 mmove_t widow_move_spawn = {FRAME_spawn01, FRAME_spawn18, widow_frames_spawn, widow_run};
795 
796 mframe_t widow_frames_pain_heavy [] =
797 {
798 	ai_move,	0,	NULL,
799 	ai_move,	0,	NULL,
800 	ai_move,	0,	NULL,
801 	ai_move,	0,	NULL,
802 	ai_move,	0,	NULL,
803 	ai_move,	0,	NULL,
804 	ai_move,	0,	NULL,
805 	ai_move,	0,	NULL,
806 	ai_move,	0,	NULL,
807 	ai_move,	0,	NULL,
808 	ai_move,	0,	NULL,
809 	ai_move,	0,	NULL,
810 	ai_move,	0,	NULL
811 };
812 mmove_t widow_move_pain_heavy = {FRAME_pain01, FRAME_pain13, widow_frames_pain_heavy, widow_run};
813 
814 mframe_t widow_frames_pain_light [] =
815 {
816 	ai_move,	0,	NULL,
817 	ai_move,	0,	NULL,
818 	ai_move,	0,	NULL
819 };
820 mmove_t widow_move_pain_light = {FRAME_pain201, FRAME_pain203, widow_frames_pain_light, widow_run};
821 
spawn_out_start(edict_t * self)822 void spawn_out_start (edict_t *self)
823 {
824 	vec3_t startpoint,f,r,u;
825 	self->wait = level.time + 2.0;
826 
827 //	gi.sound (self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
828 	AngleVectors (self->s.angles, f, r, u);
829 
830 	G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
831 	gi.WriteByte (svc_temp_entity);
832 	gi.WriteByte (TE_WIDOWBEAMOUT);
833 	gi.WriteShort (20001);
834 	gi.WritePosition (startpoint);
835 	gi.multicast (startpoint, MULTICAST_ALL);
836 
837 	G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
838 	gi.WriteByte (svc_temp_entity);
839 	gi.WriteByte (TE_WIDOWBEAMOUT);
840 	gi.WriteShort (20002);
841 	gi.WritePosition (startpoint);
842 	gi.multicast (startpoint, MULTICAST_ALL);
843 
844 	gi.sound (self, CHAN_VOICE, gi.soundindex ("misc/bwidowbeamout.wav"), 1, ATTN_NORM, 0);
845 }
846 
spawn_out_do(edict_t * self)847 void spawn_out_do (edict_t *self)
848 {
849 	vec3_t startpoint,f,r,u;
850 
851 	AngleVectors (self->s.angles, f, r, u);
852 	G_ProjectSource2 (self->s.origin, beameffects[0], f, r, u, startpoint);
853 	gi.WriteByte (svc_temp_entity);
854 	gi.WriteByte (TE_WIDOWSPLASH);
855 	gi.WritePosition (startpoint);
856 	gi.multicast (startpoint, MULTICAST_ALL);
857 
858 	G_ProjectSource2 (self->s.origin, beameffects[1], f, r, u, startpoint);
859 	gi.WriteByte (svc_temp_entity);
860 	gi.WriteByte (TE_WIDOWSPLASH);
861 	gi.WritePosition (startpoint);
862 	gi.multicast (startpoint, MULTICAST_ALL);
863 
864 	VectorCopy (self->s.origin, startpoint);
865 	startpoint[2] += 36;
866 	gi.WriteByte (svc_temp_entity);
867 	gi.WriteByte (TE_BOSSTPORT);
868 	gi.WritePosition (startpoint);
869 	gi.multicast (startpoint, MULTICAST_PVS);
870 
871 	Widowlegs_Spawn (self->s.origin, self->s.angles);
872 
873 	G_FreeEdict (self);
874 }
875 
876 mframe_t widow_frames_death [] =
877 {
878 	ai_move,	0,	NULL,
879 	ai_move,	0,	NULL,
880 	ai_move,	0,	NULL,
881 	ai_move,	0,	NULL,
882 	ai_move,	0,	NULL,		//5
883 	ai_move,	0,	NULL,
884 	ai_move,	0,	NULL,
885 	ai_move,	0,	NULL,
886 	ai_move,	0,	NULL,
887 	ai_move,	0,	spawn_out_start,	//10
888 	ai_move,	0,	NULL,
889 	ai_move,	0,	NULL,
890 	ai_move,	0,	NULL,
891 	ai_move,	0,	NULL,
892 	ai_move,	0,	NULL,				//15
893 	ai_move,	0,	NULL,
894 	ai_move,	0,	NULL,
895 	ai_move,	0,	NULL,
896 	ai_move,	0,	NULL,
897 	ai_move,	0,	NULL,				//20
898 	ai_move,	0,	NULL,
899 	ai_move,	0,	NULL,
900 	ai_move,	0,	NULL,
901 	ai_move,	0,	NULL,
902 	ai_move,	0,	NULL,				//25
903 	ai_move,	0,	NULL,
904 	ai_move,	0,	NULL,
905 	ai_move,	0,	NULL,
906 	ai_move,	0,	NULL,
907 	ai_move,	0,	NULL,				//30
908 	ai_move,	0,	spawn_out_do
909 };
910 mmove_t widow_move_death = {FRAME_death01, FRAME_death31, widow_frames_death, NULL};
911 
widow_attack_kick(edict_t * self)912 void widow_attack_kick (edict_t *self)
913 {
914 	vec3_t	aim;
915 
916 //	VectorSet (aim, MELEE_DISTANCE, 0, 4);
917 	VectorSet (aim, 100, 0, 4);
918 	if (self->enemy->groundentity)
919 		fire_hit (self, aim, (50 + (rand() % 6)), 500);
920 	else	// not as much kick if they're in the air .. makes it harder to land on her head
921 		fire_hit (self, aim, (50 + (rand() % 6)), 250);
922 
923 }
924 
925 mframe_t widow_frames_attack_kick [] =
926 {
927 	ai_move, 0, NULL,
928 	ai_move, 0, NULL,
929 	ai_move, 0, NULL,
930 	ai_move, 0, widow_attack_kick,
931 	ai_move, 0, NULL,				// 5
932 	ai_move, 0, NULL,
933 	ai_move, 0, NULL,
934 	ai_move, 0, NULL
935 };
936 
937 mmove_t widow_move_attack_kick = {FRAME_kick01, FRAME_kick08, widow_frames_attack_kick, widow_run};
938 
widow_stand(edict_t * self)939 void widow_stand (edict_t *self)
940 {
941 //	gi.dprintf ("widow stand\n");
942 	gi.sound (self, CHAN_WEAPON, gi.soundindex ("widow/laugh.wav"), 1, ATTN_NORM, 0);
943 	self->monsterinfo.currentmove = &widow_move_stand;
944 }
945 
widow_run(edict_t * self)946 void widow_run (edict_t *self)
947 {
948 	self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
949 
950 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
951 		self->monsterinfo.currentmove = &widow_move_stand;
952 	else
953 		self->monsterinfo.currentmove = &widow_move_run;
954 }
955 
widow_walk(edict_t * self)956 void widow_walk (edict_t *self)
957 {
958 	self->monsterinfo.currentmove = &widow_move_walk;
959 }
960 
widow_attack(edict_t * self)961 void widow_attack (edict_t *self)
962 {
963 	float	luck;
964 	qboolean rail_frames = false, blaster_frames = false, blocked = false, anger = false;
965 
966 	self->movetarget = NULL;
967 
968 	if (self->monsterinfo.aiflags & AI_BLOCKED)
969 	{
970 		blocked = true;
971 		self->monsterinfo.aiflags &= ~AI_BLOCKED;
972 	}
973 
974 	if (self->monsterinfo.aiflags & AI_TARGET_ANGER)
975 	{
976 		anger = true;
977 		self->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
978 	}
979 
980 	if ((!self->enemy) || (!self->enemy->inuse))
981 		return;
982 
983 	if (self->bad_area)
984 	{
985 		if ((random() < 0.1) || (level.time < self->timestamp))
986 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
987 		else
988 		{
989 			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
990 			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
991 		}
992 		return;
993 	}
994 
995 	// frames FRAME_walk13, FRAME_walk01, FRAME_walk02, FRAME_walk03 are rail gun start frames
996 	// frames FRAME_walk09, FRAME_walk10, FRAME_walk11, FRAME_walk12 are spawn & blaster start frames
997 
998 	if ((self->s.frame == FRAME_walk13) || ((self->s.frame >= FRAME_walk01) && (self->s.frame <= FRAME_walk03)))
999 		rail_frames = true;
1000 
1001 	if ((self->s.frame >= FRAME_walk09) && (self->s.frame <= FRAME_walk12))
1002 		blaster_frames = true;
1003 
1004 	WidowCalcSlots(self);
1005 
1006 	// if we can't see the target, spawn stuff regardless of frame
1007 	if ((self->monsterinfo.attack_state == AS_BLIND) && (SELF_SLOTS_LEFT >= 2))
1008 	{
1009 //		if ((g_showlogic) && (g_showlogic->value))
1010 //			gi.dprintf ("attacking blind!\n");
1011 		self->monsterinfo.currentmove = &widow_move_spawn;
1012 		return;
1013 	}
1014 
1015 	// accept bias towards spawning regardless of frame
1016 	if (blocked && (SELF_SLOTS_LEFT >= 2))
1017 	{
1018 		self->monsterinfo.currentmove = &widow_move_spawn;
1019 		return;
1020 	}
1021 
1022 	if ((realrange(self, self->enemy) > 300) && (!anger) && (random() < 0.5)  && (!blocked))
1023 	{
1024 		self->monsterinfo.currentmove = &widow_move_run_attack;
1025 		return;
1026 	}
1027 
1028 	if (blaster_frames)
1029 	{
1030 //		gi.dprintf ("blaster frame %2.2f <= %2.2f\n", self->monsterinfo.pausetime + BLASTER_TIME, level.time);
1031 		if (SELF_SLOTS_LEFT >= 2)
1032 		{
1033 			self->monsterinfo.currentmove = &widow_move_spawn;
1034 			return;
1035 		}
1036 		else if (self->monsterinfo.pausetime + BLASTER_TIME <= level.time)
1037 		{
1038 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1039 			return;
1040 		}
1041 	}
1042 
1043 	if (rail_frames)
1044 	{
1045 //		gi.dprintf ("rail frame %2.2f - %2.2f\n", level.time, self->timestamp);
1046 		if (!(level.time < self->timestamp))
1047 		{
1048 			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1049 			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1050 		}
1051 	}
1052 
1053 	if ((rail_frames) || (blaster_frames))
1054 		return;
1055 
1056 //	if ((g_showlogic) && (g_showlogic->value))
1057 //		gi.dprintf ("widow: unknown start frame, picking randomly\n");
1058 
1059 	luck = random();
1060 	if (SELF_SLOTS_LEFT >= 2)
1061 	{
1062 		if ((luck <= 0.40) && (self->monsterinfo.pausetime + BLASTER_TIME <= level.time))
1063 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1064 		else if ((luck <= 0.7) && !(level.time < self->timestamp))
1065 		{
1066 			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1067 			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1068 		}
1069 		else
1070 			self->monsterinfo.currentmove = &widow_move_spawn;
1071 	}
1072 	else
1073 	{
1074 		if (level.time < self->timestamp)
1075 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1076 		else if ((luck <= 0.50) || (level.time + BLASTER_TIME >= self->monsterinfo.pausetime))
1077 		{
1078 			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1079 			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1080 		}
1081 		else // holdout to blaster
1082 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1083 	}
1084 }
1085 /*
1086 void widow_attack (edict_t *self)
1087 {
1088 	float	range, luck;
1089 
1090 //	gi.dprintf ("widow attack\n");
1091 
1092 	if ((!self->enemy) || (!self->enemy->inuse))
1093 		return;
1094 
1095 	if (self->bad_area)
1096 	{
1097 		if ((random() < 0.1) || (level.time < self->timestamp))
1098 			self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1099 		else
1100 		{
1101 			gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1102 			self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1103 		}
1104 		return;
1105 	}
1106 
1107 	// if we can't see the target, spawn stuff
1108 	if ((self->monsterinfo.attack_state == AS_BLIND) && (blaster_frames))
1109 	{
1110 		self->monsterinfo.currentmove = &widow_move_spawn;
1111 		return;
1112 	}
1113 
1114 	range = realrange (self, self->enemy);
1115 
1116 	if (range < 600)
1117 	{
1118 		luck = random();
1119 		if (SLOTS_LEFT >= 2)
1120 		{
1121 			if (luck <= 0.40)
1122 				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1123 			else if ((luck <= 0.7) && !(level.time < self->timestamp))
1124 			{
1125 				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1126 				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1127 			}
1128 			else
1129 				self->monsterinfo.currentmove = &widow_move_spawn;
1130 		}
1131 		else
1132 		{
1133 			if ((luck <= 0.50) || (level.time < self->timestamp))
1134 				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1135 			else
1136 			{
1137 				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1138 				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1139 			}
1140 		}
1141 	}
1142 	else
1143 	{
1144 		luck = random();
1145 		if (SLOTS_LEFT >= 2)
1146 		{
1147 			if (luck < 0.3)
1148 				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1149 			else if ((luck < 0.65) || (level.time < self->timestamp))
1150 				self->monsterinfo.currentmove = &widow_move_spawn;
1151 			else
1152 			{
1153 				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1154 				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1155 			}
1156 		}
1157 		else
1158 		{
1159 			if ((luck < 0.45) || (level.time < self->timestamp))
1160 				self->monsterinfo.currentmove = &widow_move_attack_pre_blaster;
1161 			else
1162 			{
1163 				gi.sound (self, CHAN_WEAPON, sound_rail, 1, ATTN_NORM, 0);
1164 				self->monsterinfo.currentmove = &widow_move_attack_pre_rail;
1165 			}
1166 		}
1167 	}
1168 }
1169 */
widow_attack_blaster(edict_t * self)1170 void widow_attack_blaster (edict_t *self)
1171 {
1172 	self->monsterinfo.pausetime = level.time + 1.0 + (2.0*random());
1173 //	self->monsterinfo.pausetime = level.time + 100;
1174 //	self->plat2flags = 0;
1175 	self->monsterinfo.currentmove = &widow_move_attack_blaster;
1176 	self->monsterinfo.nextframe = WidowTorso (self);
1177 }
1178 
widow_reattack_blaster(edict_t * self)1179 void widow_reattack_blaster (edict_t *self)
1180 {
1181 	WidowBlaster(self);
1182 
1183 //	if ((g_showlogic) && (g_showlogic->value))
1184 //	{
1185 //		if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l)
1186 //			gi.dprintf ("pulling left!\n");
1187 //		if (self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r)
1188 //			gi.dprintf ("pulling right!\n");
1189 //	}
1190 
1191 //	self->monsterinfo.currentmove = &widow_move_attack_blaster;
1192 //		self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
1193 //	return;
1194 	// if WidowBlaster bailed us out of the frames, just bail
1195 	if ((self->monsterinfo.currentmove == &widow_move_attack_post_blaster_l) ||
1196 		(self->monsterinfo.currentmove == &widow_move_attack_post_blaster_r))
1197 		return;
1198 
1199 	// if we're not done with the attack, don't leave the sequence
1200 	if (self->monsterinfo.pausetime >= level.time)
1201 		return;
1202 
1203 	self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1204 
1205 	self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1206 }
1207 /*
1208 	if ( infront(self, self->enemy) )
1209 		if (random() <= 0.5)
1210 			if ((random() < 0.7) || (SLOTS_LEFT <= 1))
1211 				self->monsterinfo.currentmove = &widow_move_attack_blaster;
1212 			else
1213 				self->monsterinfo.currentmove = &widow_move_spawn;
1214 		else
1215 			self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1216 	else
1217 		self->monsterinfo.currentmove = &widow_move_attack_post_blaster;
1218 }
1219 */
1220 
1221 
widow_pain(edict_t * self,edict_t * other,float kick,int damage)1222 void widow_pain (edict_t *self, edict_t *other, float kick, int damage)
1223 {
1224 	if (self->health < (self->max_health / 2))
1225 		self->s.skinnum = 1;
1226 
1227 	if (skill->value == 3)
1228 		return;		// no pain anims in nightmare
1229 
1230 	if (level.time < self->pain_debounce_time)
1231 		return;
1232 
1233 	if (self->monsterinfo.pausetime == 100000000)
1234 		self->monsterinfo.pausetime = 0;
1235 
1236 	self->pain_debounce_time = level.time + 5;
1237 
1238 	if (damage < 15)
1239 	{
1240 		gi.sound (self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
1241 	}
1242 	else if (damage < 75)
1243 	{
1244 		if ((skill->value < 3) && (random() < (0.6 - (0.2*((float)skill->value)))))
1245 		{
1246 			self->monsterinfo.currentmove = &widow_move_pain_light;
1247 			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1248 		}
1249 		gi.sound (self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
1250 	}
1251 	else
1252 	{
1253 		if ((skill->value < 3) && (random() < (0.75 - (0.1*((float)skill->value)))))
1254 		{
1255 			self->monsterinfo.currentmove = &widow_move_pain_heavy;
1256 			self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
1257 		}
1258 		gi.sound (self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
1259 	}
1260 }
1261 
widow_dead(edict_t * self)1262 void widow_dead (edict_t *self)
1263 {
1264 	VectorSet (self->mins, -56, -56, 0);
1265 	VectorSet (self->maxs, 56, 56, 80);
1266 	self->movetype = MOVETYPE_TOSS;
1267 	self->svflags |= SVF_DEADMONSTER;
1268 	self->nextthink = 0;
1269 	gi.linkentity (self);
1270 }
1271 
widow_die(edict_t * self,edict_t * inflictor,edict_t * attacker,int damage,vec3_t point)1272 void widow_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
1273 {
1274 	self->deadflag = DEAD_DEAD;
1275 	self->takedamage = DAMAGE_NO;
1276 	self->count = 0;
1277 	self->monsterinfo.quad_framenum = 0;
1278 	self->monsterinfo.double_framenum = 0;
1279 	self->monsterinfo.invincible_framenum = 0;
1280 	self->monsterinfo.currentmove = &widow_move_death;
1281 }
1282 
widow_melee(edict_t * self)1283 void widow_melee (edict_t *self)
1284 {
1285 //	monster_done_dodge (self);
1286 	self->monsterinfo.currentmove = &widow_move_attack_kick;
1287 }
1288 
WidowGoinQuad(edict_t * self,float framenum)1289 void WidowGoinQuad (edict_t *self, float framenum)
1290 {
1291 	self->monsterinfo.quad_framenum = framenum;
1292 	widow_damage_multiplier = 4;
1293 }
1294 
WidowDouble(edict_t * self,float framenum)1295 void WidowDouble (edict_t *self, float framenum)
1296 {
1297 	self->monsterinfo.double_framenum = framenum;
1298 	widow_damage_multiplier = 2;
1299 }
1300 
WidowPent(edict_t * self,float framenum)1301 void WidowPent (edict_t *self, float framenum)
1302 {
1303 	self->monsterinfo.invincible_framenum = framenum;
1304 }
1305 
WidowPowerArmor(edict_t * self)1306 void WidowPowerArmor (edict_t *self)
1307 {
1308 	self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
1309 	// I don't like this, but it works
1310 	if (self->monsterinfo.power_armor_power <= 0)
1311 		self->monsterinfo.power_armor_power += 250 * skill->value;
1312 }
1313 
WidowRespondPowerup(edict_t * self,edict_t * other)1314 void WidowRespondPowerup (edict_t *self, edict_t *other)
1315 {
1316 	if (other->s.effects & EF_QUAD)
1317 	{
1318 		if (skill->value == 1)
1319 			WidowDouble (self, other->client->quad_framenum);
1320 		else if (skill->value == 2)
1321 			WidowGoinQuad (self, other->client->quad_framenum);
1322 		else if (skill->value == 3)
1323 		{
1324 			WidowGoinQuad (self, other->client->quad_framenum);
1325 			WidowPowerArmor (self);
1326 		}
1327 	}
1328 	else if (other->s.effects & EF_DOUBLE)
1329 	{
1330 		if (skill->value == 2)
1331 			WidowDouble (self, other->client->double_framenum);
1332 		else if (skill->value == 3)
1333 		{
1334 			WidowDouble (self, other->client->double_framenum);
1335 			WidowPowerArmor (self);
1336 		}
1337 	}
1338 	else
1339 		widow_damage_multiplier = 1;
1340 
1341 	if (other->s.effects & EF_PENT)
1342 	{
1343 		if (skill->value == 1)
1344 			WidowPowerArmor (self);
1345 		else if (skill->value == 2)
1346 			WidowPent (self, other->client->invincible_framenum);
1347 		else if (skill->value == 3)
1348 		{
1349 			WidowPent (self, other->client->invincible_framenum);
1350 			WidowPowerArmor (self);
1351 		}
1352 	}
1353 }
1354 
WidowPowerups(edict_t * self)1355 void WidowPowerups (edict_t *self)
1356 {
1357 	int player;
1358 	edict_t *ent;
1359 
1360 	if (!(coop && coop->value))
1361 	{
1362 		WidowRespondPowerup (self, self->enemy);
1363 	}
1364 	else
1365 	{
1366 		// in coop, check for pents, then quads, then doubles
1367 		for (player = 1; player <= game.maxclients; player++)
1368 		{
1369 			ent = &g_edicts[player];
1370 			if (!ent->inuse)
1371 				continue;
1372 			if (!ent->client)
1373 				continue;
1374 			if (ent->s.effects & EF_PENT)
1375 			{
1376 				WidowRespondPowerup (self, ent);
1377 				return;
1378 			}
1379 		}
1380 
1381 		for (player = 1; player <= game.maxclients; player++)
1382 		{
1383 			ent = &g_edicts[player];
1384 			if (!ent->inuse)
1385 				continue;
1386 			if (!ent->client)
1387 				continue;
1388 			if (ent->s.effects & EF_QUAD)
1389 			{
1390 				WidowRespondPowerup (self, ent);
1391 				return;
1392 			}
1393 		}
1394 
1395 		for (player = 1; player <= game.maxclients; player++)
1396 		{
1397 			ent = &g_edicts[player];
1398 			if (!ent->inuse)
1399 				continue;
1400 			if (!ent->client)
1401 				continue;
1402 			if (ent->s.effects & EF_DOUBLE)
1403 			{
1404 				WidowRespondPowerup (self, ent);
1405 				return;
1406 			}
1407 		}
1408 	}
1409 }
1410 
Widow_CheckAttack(edict_t * self)1411 qboolean Widow_CheckAttack (edict_t *self)
1412 {
1413 	vec3_t	spot1, spot2;
1414 	vec3_t	temp;
1415 	float	chance;
1416 	trace_t	tr;
1417 	qboolean	enemy_infront;
1418 	int			enemy_range;
1419 	float		enemy_yaw;
1420 	float		real_enemy_range;
1421 
1422 	if (!self->enemy)
1423 		return false;
1424 
1425 	WidowPowerups(self);
1426 
1427 	if (self->monsterinfo.currentmove == &widow_move_run)
1428 	{
1429 		// if we're in run, make sure we're in a good frame for attacking before doing anything else
1430 		// frames 1,2,3,9,10,11,13 good to fire
1431 		switch (self->s.frame)
1432 		{
1433 			case FRAME_walk04:
1434 			case FRAME_walk05:
1435 			case FRAME_walk06:
1436 			case FRAME_walk07:
1437 			case FRAME_walk08:
1438 			case FRAME_walk12:
1439 				{
1440 //					if ((g_showlogic) && (g_showlogic->value))
1441 //						gi.dprintf ("Not in good walk frame (%d), not attacking\n", (self->s.frame - FRAME_walk01+1));
1442 					return false;
1443 				}
1444 			default:
1445 				break;
1446 		}
1447 	}
1448 
1449 	// give a LARGE bias to spawning things when we have room
1450 	// use AI_BLOCKED as a signal to attack to spawn
1451 	if ((random() < 0.8) && (SELF_SLOTS_LEFT >= 2) && (realrange(self, self->enemy) > 150))
1452 	{
1453 		self->monsterinfo.aiflags |= AI_BLOCKED;
1454 		self->monsterinfo.attack_state = AS_MISSILE;
1455 		return true;
1456 	}
1457 
1458 	if (self->enemy->health > 0)
1459 	{
1460 	// see if any entities are in the way of the shot
1461 		VectorCopy (self->s.origin, spot1);
1462 		spot1[2] += self->viewheight;
1463 		VectorCopy (self->enemy->s.origin, spot2);
1464 		spot2[2] += self->enemy->viewheight;
1465 
1466 		tr = gi.trace (spot1, NULL, NULL, spot2, self, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_SLIME|CONTENTS_LAVA);
1467 
1468 		// do we have a clear shot?
1469 		if (tr.ent != self->enemy)
1470 		{
1471 			// go ahead and spawn stuff if we're mad a a client
1472 			if (self->enemy->client && SELF_SLOTS_LEFT >= 2)
1473 			{
1474 				self->monsterinfo.attack_state = AS_BLIND;
1475 				return true;
1476 			}
1477 
1478 			// PGM - we want them to go ahead and shoot at info_notnulls if they can.
1479 			if(self->enemy->solid != SOLID_NOT || tr.fraction < 1.0)		//PGM
1480 				return false;
1481 		}
1482 	}
1483 
1484 	enemy_infront = infront(self, self->enemy);
1485 
1486 	enemy_range = range(self, self->enemy);
1487 	VectorSubtract (self->enemy->s.origin, self->s.origin, temp);
1488 	enemy_yaw = vectoyaw2(temp);
1489 
1490 	self->ideal_yaw = enemy_yaw;
1491 
1492 	real_enemy_range = realrange (self, self->enemy);
1493 
1494 //	if (g_showlogic->value)
1495 //		gi.dprintf ("range = %2.2f\n", real_enemy_range);
1496 
1497 	// melee attack
1498 //	if (enemy_range == RANGE_MELEE)
1499 	if (real_enemy_range <= (MELEE_DISTANCE+20))
1500 	{
1501 		// don't always melee in easy mode
1502 		if (skill->value == 0 && (rand()&3) )
1503 			return false;
1504 		if (self->monsterinfo.melee)
1505 			self->monsterinfo.attack_state = AS_MELEE;
1506 		else
1507 			self->monsterinfo.attack_state = AS_MISSILE;
1508 		return true;
1509 	}
1510 
1511 	if (level.time < self->monsterinfo.attack_finished)
1512 		return false;
1513 
1514 	if (self->monsterinfo.aiflags & AI_STAND_GROUND)
1515 	{
1516 		chance = 0.4;
1517 	}
1518 	else if (enemy_range == RANGE_MELEE)
1519 	{
1520 		chance = 0.8;
1521 	}
1522 	else if (enemy_range == RANGE_NEAR)
1523 	{
1524 		chance = 0.7;
1525 	}
1526 	else if (enemy_range == RANGE_MID)
1527 	{
1528 		chance = 0.6;
1529 	}
1530 	else if (enemy_range == RANGE_FAR)
1531 	{
1532 		chance = 0.5;
1533 	}
1534 
1535 	// PGM - go ahead and shoot every time if it's a info_notnull
1536 	if ((random () < chance) || (self->enemy->solid == SOLID_NOT))
1537 	{
1538 		self->monsterinfo.attack_state = AS_MISSILE;
1539 		return true;
1540 	}
1541 
1542 	return false;
1543 }
1544 
widow_blocked(edict_t * self,float dist)1545 qboolean widow_blocked (edict_t *self, float dist)
1546 {
1547 	// if we get blocked while we're in our run/attack mode, turn on a meaningless (in this context)AI flag,
1548 	// and call attack to get a new attack sequence.  make sure to turn it off when we're done.
1549 	//
1550 	// I'm using AI_TARGET_ANGER for this purpose
1551 
1552 	if (self->monsterinfo.currentmove == &widow_move_run_attack)
1553 	{
1554 		self->monsterinfo.aiflags |= AI_TARGET_ANGER;
1555 		if (self->monsterinfo.checkattack(self))
1556 			self->monsterinfo.attack(self);
1557 		else
1558 			self->monsterinfo.run(self);
1559 		return true;
1560 	}
1561 
1562 	if(blocked_checkshot (self, 0.25 + (0.05 * skill->value) ))
1563 		return true;
1564 
1565 /*
1566 	if(blocked_checkjump (self, dist, 192, 40))
1567 	{
1568 		infantry_jump(self);
1569 		return true;
1570 	}
1571 
1572 	if(blocked_checkplat (self, dist))
1573 		return true;
1574 */
1575 	return false;
1576 }
1577 
WidowCalcSlots(edict_t * self)1578 void WidowCalcSlots (edict_t *self)
1579 {
1580 	int old_slots;
1581 
1582 	old_slots = self->monsterinfo.monster_slots;
1583 
1584 	switch ((int)skill->value)
1585 	{
1586 		case 0:
1587 		case 1:
1588 			self->monsterinfo.monster_slots = 3;
1589 			break;
1590 		case 2:
1591 			self->monsterinfo.monster_slots = 4;
1592 			break;
1593 		case 3:
1594 			self->monsterinfo.monster_slots = 6;
1595 			break;
1596 		default:
1597 			self->monsterinfo.monster_slots = 3;
1598 			break;
1599 	}
1600 	if (coop->value)
1601 	{
1602 		self->monsterinfo.monster_slots = min (6, self->monsterinfo.monster_slots + ((skill->value)*(CountPlayers()-1)));
1603 	}
1604 //	if ((g_showlogic) && (g_showlogic->value) && (old_slots != self->monsterinfo.monster_slots))
1605 //		gi.dprintf ("number of slots changed from %d to %d\n", old_slots, self->monsterinfo.monster_slots);
1606 }
1607 
WidowPrecache()1608 void WidowPrecache ()
1609 {
1610 	// cache in all of the stalker stuff, widow stuff, spawngro stuff, gibs
1611 	gi.soundindex ("stalker/pain.wav");
1612 	gi.soundindex ("stalker/death.wav");
1613 	gi.soundindex ("stalker/sight.wav");
1614 	gi.soundindex ("stalker/melee1.wav");
1615 	gi.soundindex ("stalker/melee2.wav");
1616 	gi.soundindex ("stalker/idle.wav");
1617 
1618 	gi.soundindex ("tank/tnkatck3.wav");
1619 	gi.modelindex ("models/proj/laser2/tris.md2");
1620 
1621 	gi.modelindex ("models/monsters/stalker/tris.md2");
1622 	gi.modelindex ("models/items/spawngro2/tris.md2");
1623 	gi.modelindex ("models/objects/gibs/sm_metal/tris.md2");
1624 	gi.modelindex ("models/objects/gibs/gear/tris.md2");
1625 	gi.modelindex ("models/monsters/blackwidow/gib1/tris.md2");
1626 	gi.modelindex ("models/monsters/blackwidow/gib2/tris.md2");
1627 	gi.modelindex ("models/monsters/blackwidow/gib3/tris.md2");
1628 	gi.modelindex ("models/monsters/blackwidow/gib4/tris.md2");
1629 	gi.modelindex ("models/monsters/blackwidow2/gib1/tris.md2");
1630 	gi.modelindex ("models/monsters/blackwidow2/gib2/tris.md2");
1631 	gi.modelindex ("models/monsters/blackwidow2/gib3/tris.md2");
1632 	gi.modelindex ("models/monsters/blackwidow2/gib4/tris.md2");
1633 	gi.modelindex ("models/monsters/legs/tris.md2");
1634 	gi.soundindex ("misc/bwidowbeamout.wav");
1635 
1636 	gi.soundindex ("misc/bigtele.wav");
1637 	gi.soundindex ("widow/bwstep3.wav");
1638 	gi.soundindex ("widow/bwstep2.wav");
1639 }
1640 
1641 
1642 /*QUAKED monster_widow (1 .5 0) (-40 -40 0) (40 40 144) Ambush Trigger_Spawn Sight
1643 */
SP_monster_widow(edict_t * self)1644 void SP_monster_widow (edict_t *self)
1645 {
1646 	if (deathmatch->value)
1647 	{
1648 		G_FreeEdict (self);
1649 		return;
1650 	}
1651 
1652 	sound_pain1 = gi.soundindex ("widow/bw1pain1.wav");
1653 	sound_pain2 = gi.soundindex ("widow/bw1pain2.wav");
1654 	sound_pain3 = gi.soundindex ("widow/bw1pain3.wav");
1655 	sound_search1 = gi.soundindex ("bosshovr/bhvunqv1.wav");
1656 //	sound_sight	= gi.soundindex ("widow/sight.wav");
1657 	sound_rail = gi.soundindex ("gladiator/railgun.wav");
1658 
1659 //	self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");
1660 
1661 	self->movetype = MOVETYPE_STEP;
1662 	self->solid = SOLID_BBOX;
1663 	self->s.modelindex = gi.modelindex ("models/monsters/blackwidow/tris.md2");
1664 	VectorSet (self->mins, -40, -40, 0);
1665 	VectorSet (self->maxs, 40, 40, 144);
1666 
1667 	self->health = 2000 + 1000*(skill->value);
1668 	if (coop->value)
1669 		self->health += 500*(skill->value);
1670 //	self->health = 1;
1671 	self->gib_health = -5000;
1672 	self->mass = 1500;
1673 /*
1674 	if (skill->value == 2)
1675 	{
1676 		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
1677 		self->monsterinfo.power_armor_power = 250;
1678 	}
1679 	else */if (skill->value == 3)
1680 	{
1681 		self->monsterinfo.power_armor_type = POWER_ARMOR_SHIELD;
1682 		self->monsterinfo.power_armor_power = 500;
1683 	}
1684 
1685 	self->yaw_speed = 30;
1686 
1687 	self->flags |= FL_IMMUNE_LASER;
1688 	self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
1689 
1690 	self->pain = widow_pain;
1691 	self->die = widow_die;
1692 
1693 	self->monsterinfo.melee = widow_melee;
1694 	self->monsterinfo.stand = widow_stand;
1695 	self->monsterinfo.walk = widow_walk;
1696 	self->monsterinfo.run = widow_run;
1697 	self->monsterinfo.attack = widow_attack;
1698 	self->monsterinfo.search = widow_search;
1699 	self->monsterinfo.checkattack = Widow_CheckAttack;
1700 	self->monsterinfo.sight = widow_sight;
1701 
1702 	self->monsterinfo.blocked = widow_blocked;
1703 
1704 	gi.linkentity (self);
1705 
1706 	self->monsterinfo.currentmove = &widow_move_stand;
1707 	self->monsterinfo.scale = MODEL_SCALE;
1708 
1709 	WidowPrecache();
1710 	WidowCalcSlots(self);
1711 	widow_damage_multiplier = 1;
1712 
1713 	walkmonster_start (self);
1714 }
1715