1//**************************************************************************
2//**
3//**    ##   ##    ##    ##   ##   ####     ####   ###     ###
4//**    ##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5//**     ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6//**     ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7//**      ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8//**       #    ##    ##    #      ####     ####   ##       ##
9//**
10//**    $Id: Actor.DoomAttack.vc 4325 2010-07-15 23:11:16Z firebrand_kh $
11//**
12//**    Copyright (C) 1999-2006 Jānis Legzdiņš
13//**
14//**    This program is free software; you can redistribute it and/or
15//**  modify it under the terms of the GNU General Public License
16//**  as published by the Free Software Foundation; either version 2
17//**  of the License, or (at your option) any later version.
18//**
19//**    This program is distributed in the hope that it will be useful,
20//**  but WITHOUT ANY WARRANTY; without even the implied warranty of
21//**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22//**  GNU General Public License for more details.
23//**
24//**************************************************************************
25
26//**************************************************************************
27//	Original Doom/Strife monster attacks
28//**************************************************************************
29
30//==========================================================================
31//
32//  A_PosAttack
33//
34//  Zombieman attack.
35//
36//==========================================================================
37
38final void A_PosAttack()
39{
40	int damage;
41	TVec dir;
42
43	if (!Target)
44	{
45		return;
46	}
47
48	A_FaceTarget();
49
50	AimLineAttack(dir, Angles, MISSILERANGE);
51	VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0);
52
53	PlaySound('grunt/attack', CHAN_WEAPON);
54
55	damage = ((P_Random() % 5) + 1) * 3;
56
57	LineAttack(dir, MISSILERANGE, damage, BulletPuff);
58}
59
60//==========================================================================
61//
62//  A_SPosAttack
63//
64//  For DeHackEd compatibility only.
65//
66//==========================================================================
67
68final void A_SPosAttack()
69{
70	if (!Target)
71	{
72		return;
73	}
74
75	PlaySound('shotguy/attack', CHAN_WEAPON);
76	DoSPosAttack();
77}
78
79//==========================================================================
80//
81//  A_SPosAttackUseAtkSound
82//
83//  Shotgun guy, Spider mastermind attack.
84//
85//==========================================================================
86
87final void A_SPosAttackUseAtkSound()
88{
89	if (!Target)
90	{
91		return;
92	}
93
94	PlaySound(AttackSound, CHAN_WEAPON);
95	DoSPosAttack();
96}
97
98//==========================================================================
99//
100//  DoSPosAttack
101//
102//==========================================================================
103
104final void DoSPosAttack()
105{
106	int i;
107	int damage;
108	TVec aimDir;
109	TVec dir;
110
111	A_FaceTarget();
112	AimLineAttack(aimDir, Angles, MISSILERANGE);
113	for (i = 0; i < 3; i++)
114	{
115		dir = aimDir;
116		VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0);
117		damage = ((P_Random() % 5) + 1) * 3;
118		LineAttack(dir, MISSILERANGE, damage, BulletPuff);
119	}
120}
121
122//==========================================================================
123//
124//  A_CPosAttack
125//
126//  Heavy weapon dude attack.
127//
128//==========================================================================
129
130final void A_CPosAttack()
131{
132	int damage;
133	TVec dir;
134
135	if (!Target)
136	{
137		return;
138	}
139
140	//	Andy Baker's stealth monsters
141	if (bStealth)
142	{
143		VisDir = 1;
144	}
145
146	//FIXME
147	PlaySound(AttackSound, CHAN_WEAPON);
148	A_FaceTarget();
149	AimLineAttack(dir, Angles, MISSILERANGE);
150	VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0 / 2.0);
151	damage = ((P_Random() % 5) + 1) * 3;
152	LineAttack(dir, MISSILERANGE, damage, BulletPuff);
153}
154
155//==========================================================================
156//
157//  A_CPosRefire
158//
159//  Heavy weapon dude refire.
160//
161//==========================================================================
162
163final void A_CPosRefire()
164{
165	// keep firing unless target got out of sight
166	A_FaceTarget();
167
168	if (P_Random() < 40)
169	{
170		return;
171	}
172
173	if (!Target || Target.Health <= 0 || !CanSee(Target))
174	{
175		SetState(SeeState);
176	}
177}
178
179//==========================================================================
180//
181//  A_SpidRefire
182//
183//  Spider mastermind refire.
184//
185//==========================================================================
186
187final void A_SpidRefire()
188{
189	// keep firing unless target got out of sight
190	A_FaceTarget();
191
192	if (P_Random() < 10)
193		return;
194
195	if (!Target || Target.Health <= 0 || !CanSee(Target))
196	{
197		SetState(SeeState);
198	}
199}
200
201//==========================================================================
202//
203//  A_TroopAttack
204//
205//  Imp attack.
206//
207//==========================================================================
208
209final void A_TroopAttack()
210{
211	int damage;
212
213	if (!Target)
214	{
215		return;
216	}
217
218	A_FaceTarget();
219	if (CheckMeleeRange())
220	{
221		PlaySound('imp/melee', CHAN_WEAPON);
222		damage = (P_Random() % 8 + 1) * 3;
223		Target.Damage(self, self, damage);
224		return;
225	}
226
227	// launch a missile
228	SpawnMissile(Target, DoomImpBall);
229}
230
231//==========================================================================
232//
233//  A_SargAttack
234//
235//  Demon, Spectre attack.
236//
237//==========================================================================
238
239final void A_SargAttack()
240{
241	int damage;
242
243	if (!Target)
244	{
245		return;
246	}
247
248	A_FaceTarget();
249	if (CheckMeleeRange())
250	{
251		damage = ((P_Random() % 10) + 1) * 4;
252		Target.Damage(self, self, damage);
253	}
254}
255
256//==========================================================================
257//
258//  A_HeadAttack
259//
260//  Cacodemon attack.
261//
262//==========================================================================
263
264final void A_HeadAttack()
265{
266	int damage;
267
268	if (!Target)
269	{
270		return;
271	}
272
273	A_FaceTarget();
274	if (CheckMeleeRange())
275	{
276		damage = (P_Random() % 6 + 1) * 10;
277		Target.Damage(self, self, damage);
278		return;
279	}
280
281	// launch a missile
282	SpawnMissile(Target, CacodemonBall);
283}
284
285//==========================================================================
286//
287//  A_BruisAttack
288//
289//  Hell knight, Baron of hell attack.
290//
291//==========================================================================
292
293final void A_BruisAttack()
294{
295	int damage;
296
297	if (!Target)
298	{
299		return;
300	}
301
302	if (CheckMeleeRange())
303	{
304		PlaySound('baron/melee', CHAN_WEAPON);
305		damage = (P_Random() % 8 + 1) * 10;
306		Target.Damage(self, self, damage);
307		return;
308	}
309
310	// launch a missile
311	SpawnMissile(Target, BaronBall);
312}
313
314//==========================================================================
315//
316//  A_SkullAttack
317//
318//  Lost soul attack. Fly at the player like a missile.
319//
320//==========================================================================
321
322const float SKULLSPEED = 700.0;
323
324final void A_SkullAttack()
325{
326	if (!Target)
327	{
328		return;
329	}
330
331	bSkullFly = true;
332
333	PlaySound(AttackSound, CHAN_VOICE);
334	A_FaceTarget();
335	Velocity = Normalise(Target.GetCentre() - Origin) * SKULLSPEED;
336}
337
338//==========================================================================
339//
340//  A_SkullAttack
341//
342//==========================================================================
343
344final void decorate_A_SkullAttack(optional float SkullSpeed)
345{
346	if (!specified_SkullSpeed)
347	{
348		SkullSpeed = 20.0;
349	}
350	if (!Target)
351	{
352		return;
353	}
354
355	bSkullFly = true;
356
357	PlaySound(AttackSound, CHAN_VOICE);
358	A_FaceTarget();
359	Velocity = Normalise(Target.GetCentre() - Origin) * SkullSpeed * 35.0;
360}
361
362//==========================================================================
363//
364//  A_BspiAttack
365//
366//  Arachnotron attack.
367//
368//==========================================================================
369
370final void A_BspiAttack()
371{
372	if (!Target)
373	{
374		return;
375	}
376
377	A_FaceTarget();
378
379	// launch a missile
380	SpawnMissile(Target, ArachnotronPlasma);
381}
382
383//==========================================================================
384//
385//  A_CyberAttack
386//
387//  Cyberdemon attack.
388//
389//==========================================================================
390
391final void A_CyberAttack()
392{
393	if (!Target)
394	{
395		return;
396	}
397
398	A_FaceTarget();
399	SpawnMissile(Target, Rocket);
400}
401
402//==========================================================================
403//
404//  A_PainAttack
405//
406//  Pain elemental attack. Spawn a lost soul and launch it at the target.
407//
408//==========================================================================
409
410final void A_PainAttack()
411{
412	decorate_A_PainAttack(LostSoul);
413}
414
415//==========================================================================
416//
417//  decorate_A_PainAttack
418//
419//  Pain elemental attack. Spawn a lost soul and launch it at the target.
420//
421//==========================================================================
422
423final void decorate_A_PainAttack(optional class<Actor> SpawnType)
424{
425	if (!Target)
426	{
427		return;
428	}
429
430	A_FaceTarget();
431	PainShootSkull(SpawnType, Angles.yaw);
432}
433
434//==========================================================================
435//
436//  A_DualPainAttack
437//
438//==========================================================================
439
440final void A_DualPainAttack(optional class<Actor> SpawnType)
441{
442	if (!Target)
443	{
444		return;
445	}
446
447	A_FaceTarget();
448	PainShootSkull(SpawnType, Angles.yaw + 45.0);
449	PainShootSkull(SpawnType, Angles.yaw - 45.0);
450}
451
452//==========================================================================
453//
454//  A_PainDie
455//
456//  Pain elemental death.
457//
458//==========================================================================
459
460final void A_PainDie()
461{
462	decorate_A_PainDie(LostSoul);
463}
464
465//==========================================================================
466//
467//  decorate_A_PainDie
468//
469//  Pain elemental death.
470//
471//==========================================================================
472
473final void decorate_A_PainDie(optional class<Actor> SpawnType)
474{
475	A_Fall();
476	PainShootSkull(SpawnType, Angles.yaw + 90.0);
477	PainShootSkull(SpawnType, Angles.yaw + 180.0);
478	PainShootSkull(SpawnType, Angles.yaw + 270.0);
479}
480
481//==========================================================================
482//
483//  A_SkelFist
484//
485//  Ravenant close attack.
486//
487//==========================================================================
488
489final void A_SkelFist()
490{
491	int damage;
492
493	if (!Target)
494	{
495		return;
496	}
497
498	A_FaceTarget();
499
500	if (CheckMeleeRange())
501	{
502		damage = ((P_Random() % 10) + 1) * 6;
503		PlaySound('skeleton/melee', CHAN_WEAPON);
504		Target.Damage(self, self, damage);
505	}
506}
507
508//==========================================================================
509//
510//  A_SkelMissile
511//
512//  Ravenant far attack.
513//
514//==========================================================================
515
516final void A_SkelMissile()
517{
518	EntityEx A;
519
520	if (!Target)
521	{
522		return;
523	}
524
525	A_FaceTarget();
526	Origin.z += 16.0;	// so missile spawns higher
527	A = SpawnMissile(Target, RevenantTracer);
528	Origin.z -= 16.0;	// back to normal
529
530	if (A)
531	{
532		A.UnlinkFromWorld();
533		A.Origin = A.Origin + A.Velocity * 0.03;
534		A.LinkToWorld();
535		A.Tracer = Target;
536		A.bSeekerMissile = true;
537	}
538}
539
540//==========================================================================
541//
542//  A_FatAttack1
543//
544//  Mancubus attack, firing three missiles (bruisers) in three different
545// directions? Doesn't look like it.
546//
547//==========================================================================
548
549final void A_FatAttack1()
550{
551	decorate_A_FatAttack1(FatShot);
552}
553
554//==========================================================================
555//
556//  decorate_A_FatAttack1
557//
558//  Mancubus attack, firing three missiles (bruisers) in three different
559// directions? Doesn't look like it.
560//
561//==========================================================================
562
563const float FatsoSpreadAngle = 90.0 / 8.0;
564
565final void decorate_A_FatAttack1(optional class<EntityEx> SpawnType)
566{
567	EntityEx A;
568
569	if (!SpawnType)
570	{
571		SpawnType = FatShot;
572	}
573
574	if (!Target)
575	{
576		return;
577	}
578
579	A_FaceTarget();
580	// Change direction  to ...
581	Angles.yaw = AngleMod360(Angles.yaw + FatsoSpreadAngle);
582	SpawnMissile(Target, SpawnType);
583
584	A = SpawnMissile(Target, SpawnType);
585	if (A)
586	{
587		A.Angles.yaw = AngleMod360(A.Angles.yaw + FatsoSpreadAngle);
588		VectorRotateAroundZ(&A.Velocity, FatsoSpreadAngle);
589	}
590}
591
592//==========================================================================
593//
594//  A_FatAttack2
595//
596//  Mancubus attack, second direction.
597//
598//==========================================================================
599
600final void A_FatAttack2()
601{
602	decorate_A_FatAttack2(FatShot);
603}
604
605//==========================================================================
606//
607//  decorate_A_FatAttack2
608//
609//  Mancubus attack, second direction.
610//
611//==========================================================================
612
613final void decorate_A_FatAttack2(optional class<EntityEx> SpawnType)
614{
615	EntityEx A;
616
617	if (!SpawnType)
618	{
619		SpawnType = FatShot;
620	}
621
622	if (!Target)
623	{
624		return;
625	}
626
627	A_FaceTarget();
628	// Now here choose opposite deviation.
629	Angles.yaw = AngleMod360(Angles.yaw - FatsoSpreadAngle);
630	SpawnMissile(Target, SpawnType);
631
632	A = SpawnMissile(Target, SpawnType);
633	if (A)
634	{
635		A.Angles.yaw = AngleMod360(A.Angles.yaw - FatsoSpreadAngle * 2.0);
636		VectorRotateAroundZ(&A.Velocity, -FatsoSpreadAngle * 2.0);
637	}
638}
639
640//==========================================================================
641//
642//  A_FatAttack3
643//
644//  Mancubus attack, third direction.
645//
646//==========================================================================
647
648final void A_FatAttack3()
649{
650	decorate_A_FatAttack3(FatShot);
651}
652
653//==========================================================================
654//
655//  decorate_A_FatAttack3
656//
657//  Mancubus attack, third direction.
658//
659//==========================================================================
660
661final void decorate_A_FatAttack3(optional class<EntityEx> SpawnType)
662{
663	EntityEx A;
664
665	if (!SpawnType)
666	{
667		SpawnType = FatShot;
668	}
669
670	if (!Target)
671	{
672		return;
673	}
674
675	A_FaceTarget();
676
677	A = SpawnMissile(Target, SpawnType);
678	if (A)
679	{
680		A.Angles.yaw = AngleMod360(A.Angles.yaw - FatsoSpreadAngle / 2.0);
681		VectorRotateAroundZ(&A.Velocity, -FatsoSpreadAngle / 2.0);
682	}
683
684	A = SpawnMissile(Target, SpawnType);
685	if (A)
686	{
687		A.Angles.yaw = AngleMod360(A.Angles.yaw + FatsoSpreadAngle / 2.0);
688		VectorRotateAroundZ(&A.Velocity, FatsoSpreadAngle / 2.0);
689	}
690}
691
692//==========================================================================
693//
694//  A_VileTarget
695//
696//  Spawn the hellfire.
697//
698//==========================================================================
699
700final void A_VileTarget()
701{
702	EntityEx fog;
703
704	if (!Target)
705	{
706		return;
707	}
708
709	A_FaceTarget();
710
711	fog = Spawn(ArchvileFire, Target.Origin);
712
713	Tracer = fog;
714	fog.Target = self;
715	fog.Tracer = Target;
716	fog.SetState(fog.IdleState);
717}
718
719//==========================================================================
720//
721//  A_VileAttack
722//
723//  Arch-vile attack.
724//
725//==========================================================================
726
727final void A_VileAttack()
728{
729	EntityEx fire;
730
731	if (!Target)
732	{
733		return;
734	}
735
736	A_FaceTarget();
737
738	if (!CanSee(Target))
739	{
740		return;
741	}
742
743	PlaySound('vile/stop', CHAN_WEAPON);
744	Target.Damage(self, self, 20);
745
746	fire = Tracer;
747
748	if (!fire)
749	{
750		return;
751	}
752
753	// move the fire between the vile and the player
754	fire.UnlinkFromWorld();
755	fire.Origin.x = Target.Origin.x - 24.0 * cos(Angles.yaw);
756	fire.Origin.y = Target.Origin.y - 24.0 * sin(Angles.yaw);
757	fire.LinkToWorld();
758	fire.RadiusAttack(self, 70, 70.0, true);
759	// change velocity Z component before doing RadiusAttack
760	Target.Velocity.z = (1000.0 / Target.Mass) * 35.0;
761}
762
763//==========================================================================
764//
765//  A_BrainSpit
766//
767//  Spawn a cube.
768//
769//==========================================================================
770
771final void A_BrainSpit()
772{
773	BrainState		BState;
774	EntityEx		targ;
775	EntityEx		A;
776	float			Frac;
777
778	//	Find brain state object, create it if not found.
779	BState = none;
780	foreach AllThinkers(BrainState, BState)
781	{
782		break;
783	}
784	if (!BState)
785	{
786		BState = Spawn(BrainState);
787		BState.FindTargets();
788	}
789
790	if (!BState.Targets.Num)
791	{
792		//	No brain targets placed on a map.
793		return;
794	}
795
796	BState.bEasy = !BState.bEasy;
797	if (Level.World.bSkillEasyBossBrain && !BState.bEasy)
798	{
799		return;
800	}
801
802	// shoot a cube at current target
803	targ = BState.Targets[BState.TargetOn];
804	BState.TargetOn = (BState.TargetOn + 1) % BState.Targets.Num;
805
806	// spawn brain missile
807	A = SpawnMissile(targ, SpawnShot);
808	if (A)
809	{
810		A.Target = targ;
811		A.Master = self;
812		//	Prevent division by 0
813		if (!A.Velocity.x && !A.Velocity.y)
814		{
815			Frac = 0.0;
816		}
817		else if (fabs(A.Velocity.x) > fabs(A.Velocity.y))
818		{
819			Frac = (targ.Origin.x - Origin.x) / A.Velocity.x;
820		}
821		else
822		{
823			Frac = (targ.Origin.y - Origin.y) / A.Velocity.y;
824		}
825		A.ReactionCount = ftoi(Frac / GetStateDuration(A.State));
826	}
827
828	PlaySound('brain/spit', CHAN_WEAPON, 1.0, ATTN_NONE);
829}
830
831//==========================================================================
832//
833//	SpawnFly
834//
835//	Cube flying, spawn monster, when finished.
836//
837//==========================================================================
838
839final void SpawnFly(class<EntityEx> SpawnType, name SpawnSound)
840{
841	EntityEx newmobj;
842	EntityEx fog;
843	int r;
844	class<EntityEx> type;
845
846	if (--ReactionCount)
847		return;	// still flying
848
849	// First spawn teleport fog.
850	fog = Spawn(SpawnType, Target.Origin, vector(0.0, 0.0, 0.0));
851	fog.PlaySound(SpawnSound, CHAN_VOICE);
852
853	// Randomly select monster to spawn.
854	r = P_Random();
855
856	// Probability distribution (kind of :),
857	// decreasing likelihood.
858	if (r < 50)
859		type = DoomImp;
860	else if (r < 90)
861		type = Demon;
862	else if (r < 120)
863		type = Spectre;
864	else if (r < 130)
865		type = PainElemental;
866	else if (r < 160)
867		type = Cacodemon;
868	else if (r < 162)
869		type = Archvile;
870	else if (r < 172)
871		type = Revenant;
872	else if (r < 192)
873		type = Arachnotron;
874	else if (r < 222)
875		type = Fatso;
876	else if (r < 246)
877		type = HellKnight;
878	else
879		type = BaronOfHell;
880
881	newmobj = Spawn(type, Target.Origin, vector(0.0, 0.0, 0.0));
882	//	Make new monster hate the same thing eye hates. Note that this
883	// differs from ZDoom which copies friendliness from target spot.
884	if (Master)
885	{
886		newmobj.CopyFriendliness(Master, false);
887	}
888	if (newmobj.SeeState && newmobj.LookForPlayers(true))
889	{
890		newmobj.SetState(newmobj.SeeState);
891	}
892
893	// telefrag anything in this spot
894	if (!newmobj.IsDestroyed())
895	{
896		newmobj.bTelestomp = true;
897		newmobj.TeleportMove(newmobj.Origin);
898	}
899
900	// remove self (i.e., cube).
901	Destroy();
902}
903
904//==========================================================================
905//
906//  A_SpawnFly
907//
908//==========================================================================
909
910final void A_SpawnFly()
911{
912	SpawnFly(SpawnFire, 'brain/spawn');
913}
914
915//==========================================================================
916//
917//  decorate_A_SpawnFly
918//
919//==========================================================================
920
921final void decorate_A_SpawnFly(optional class<EntityEx> SpawnType)
922{
923	name SpawnSound;
924	if (SpawnType)
925	{
926		SpawnSound = SpawnType.default.SightSound;
927	}
928	else
929	{
930		SpawnType = SpawnFire;
931		SpawnSound = 'brain/spawn';
932	}
933	SpawnFly(SpawnType, SpawnSound);
934}
935
936//==========================================================================
937//
938//  A_SpawnSound
939//
940//  Travelling cube sound.
941//
942//==========================================================================
943
944final void A_SpawnSound()
945{
946	PlaySound('brain/cube', CHAN_VOICE);
947	SpawnFly(SpawnFire, 'brain/spawn');
948}
949
950//==========================================================================
951//
952//  A_BrainScream
953//
954//  Brain death sound, make explosions.
955//
956//==========================================================================
957
958final void A_BrainScream()
959{
960	TVec org;
961
962	for (org.x = Origin.x - 196.0; org.x < Origin.x + 320.0; org.x += 8.0)
963	{
964		org.y = Origin.y - 320.0;
965		org.z = 1.0 / 512.0 + Random() * 512.0;
966		BrainishExplosion(org);
967	}
968
969	PlaySound('brain/death', CHAN_VOICE, 1.0, ATTN_NONE);
970}
971
972//==========================================================================
973//
974//  A_BrainExplode
975//
976//  Brain explosions.
977//
978//==========================================================================
979
980final void A_BrainExplode()
981{
982	TVec org;
983
984	org.x = Origin.x + (Random() - Random()) * 8.0;
985	org.y = Origin.y;
986	org.z = 1.0 / 512.0 + Random() * 512.0;
987	BrainishExplosion(org);
988}
989
990//==========================================================================
991//
992//	BrainishExplosion
993//
994//==========================================================================
995
996final void BrainishExplosion(TVec org)
997{
998	EntityEx A = Spawn(Rocket, org,,, false);
999	A.DeathSound = 'misc/brainexplode';
1000	//	Disables collision detection which is not wanted here
1001	A.MissileDamage = 0;
1002	A.Velocity.z = Random() * 2.0 * 35.0;
1003
1004	A.SetState(FindClassState(BossBrain, 'BrainExplode'));
1005
1006	A.StateTime -= Random() * 0.2;
1007	if (A.StateTime < 0.1)
1008	{
1009		A.StateTime = 0.1;
1010	}
1011}
1012
1013//==========================================================================
1014//
1015//	A_Mushroom
1016//
1017//==========================================================================
1018
1019final void A_Mushroom()
1020{
1021	decorate_A_Mushroom();
1022}
1023
1024//==========================================================================
1025//
1026//	decorate_A_Mushroom
1027//
1028//	killough 9/98: a mushroom explosion effect, sorta :)
1029//	Original idea: Linguica
1030//
1031//==========================================================================
1032
1033final void decorate_A_Mushroom(optional class<Actor> SpawnType,
1034	optional int Amount, optional int Flags, optional float vrange,
1035	optional float hrange)
1036{
1037	int			i;
1038	int			j;
1039
1040	if (!SpawnType)
1041	{
1042		SpawnType = FatShot;
1043	}
1044	if (!Amount)
1045	{
1046		Amount = GetMissileDamage(0, 1);
1047	}
1048
1049	// First make normal explosion
1050	RadiusAttack(self, 128, 128.0, !(Flags & 2), DamageType);
1051
1052	// Now launch mushroom cloud
1053	// We need something to aim at.
1054	Actor target = Spawn(MapSpot,,,, false);
1055	target.Height = Height;
1056	for (i = -Amount; i <= Amount; i += 8)
1057	{
1058		for (j = -Amount; j <= Amount; j += 8)
1059		{
1060			// Aim in many directions from source
1061			target.UnlinkFromWorld();
1062			target.Origin.x = Origin.x + itof(i);
1063			target.Origin.y = Origin.y + itof(j);
1064			target.LinkToWorld();
1065			// Aim up fairly high
1066			target.Origin.z = Origin.z + Length(vector(itof(i), itof(j),
1067				0.0)) * (vrange ? vrange : 4.0);
1068			EntityEx mo;
1069			// Launch fireball
1070			if (Flags & 1)
1071			{
1072				TVec org = vector(Origin.x, Origin.y, Origin.z + 32.0);
1073				mo = SpawnMissileXYZ(org, target, SpawnType);
1074			}
1075			else
1076			{
1077				mo = SpawnMissile(target, SpawnType);
1078			}
1079			if (mo)
1080			{
1081				// Slow it down a bit
1082				mo.Velocity = mo.Velocity * (hrange ? hrange : 0.5);
1083				// Make debris fall under gravity
1084				mo.bNoGravity = false;
1085			}
1086		}
1087	}
1088	target.Destroy();
1089}
1090
1091//===========================================================================
1092//
1093//  A_M_Saw
1094//
1095//===========================================================================
1096
1097final void A_M_Saw()
1098{
1099	float		angle;
1100	int			damage;
1101	TAVec		aimAng;
1102	TVec		dir;
1103	EntityEx	AimTarget;
1104
1105	if (!Target)
1106	{
1107		return;
1108	}
1109
1110	A_FaceTarget();
1111	if (CheckMeleeRange())
1112	{
1113		damage = 2 * (P_Random() % 10 + 1);
1114		aimAng = Angles;
1115		aimAng.yaw = AngleMod360(aimAng.yaw + (Random() -
1116			Random()) * 45.0 / 8.0);
1117
1118		// use meleerange + 1 se the puff doesn't skip the flash
1119		AimTarget = AimLineAttack(dir, aimAng, MELEERANGE + 0.00001);
1120		LineAttack(dir, MELEERANGE + 0.00001, damage, BulletPuff);
1121
1122		if (!AimTarget)
1123		{
1124			PlaySound('weapons/sawfull', CHAN_WEAPON);
1125			return;
1126		}
1127		PlaySound('weapons/sawhit', CHAN_WEAPON);
1128
1129		// turn to face target
1130		angle = atan2(AimTarget.Origin.y - Origin.y,
1131			AimTarget.Origin.x - Origin.x);
1132		if (AngleMod360(angle - Angles.yaw) > 180.0)
1133		{
1134			if (AngleMod360(angle - Angles.yaw) < -90.0 / 20.0)
1135				Angles.yaw = angle + 90.0 / 21.0;
1136			else
1137				Angles.yaw = Angles.yaw - 90.0 / 20.0;
1138		}
1139		else
1140		{
1141			if (AngleMod360(angle - Angles.yaw) > 90.0 / 20.0)
1142				Angles.yaw = angle - 90.0 / 21.0;
1143			else
1144				Angles.yaw += 90.0 / 20.0;
1145		}
1146		Angles.yaw = AngleMod360(Angles.yaw);
1147	}
1148	else
1149	{
1150		PlaySound('weapons/sawfull', CHAN_WEAPON);
1151	}
1152}
1153
1154//==========================================================================
1155//
1156//	A_SentinelRefire
1157//
1158//==========================================================================
1159
1160final void A_SentinelRefire()
1161{
1162	// keep firing unless target got out of sight
1163	A_FaceTarget();
1164
1165	if (P_Random() < 30)
1166	{
1167		return;
1168	}
1169
1170	if (!Target || Target.Health <= 0 || !CanSee(Target) || P_Random() < 40)
1171	{
1172		SetState(SeeState);
1173	}
1174}
1175