1//**************************************************************************
2//**
3//**    ##   ##    ##    ##   ##   ####     ####   ###     ###
4//**    ##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5//**     ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6//**     ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7//**      ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8//**       #    ##    ##    #      ####     ####   ##       ##
9//**
10//**    $Id: Actor.Hexen.vc 4319 2010-07-03 18:24:22Z 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
26const float FIREDEMON_ATTACK_RANGE = 512.0;
27
28const int SORCBALL_INITIAL_SPEED 		= 7;
29const int SORCBALL_TERMINAL_SPEED		= 25;
30const int SORCBALL_SPEED_ROTATIONS 		= 5;
31const int SORCFX4_RAPIDFIRE_TIME		= (6*3);	// 3 seconds
32const float SORCFX4_SPREAD_ANGLE		= 20.0;
33const float SORC_DEFENSE_HEIGHT			= 45.0;
34const int SORC_DEFENSE_TIME				= 255;
35const int BOUNCE_TIME_UNIT				= (35/2);
36
37enum
38{
39	SORC_DECELERATE,
40	SORC_ACCELERATE,
41	SORC_STOPPING,
42	SORC_FIRESPELL,
43	SORC_STOPPED,
44	SORC_NORMAL,
45	SORC_FIRING_SPELL
46};
47
48const int KORAX_FIRST_TELEPORT_TID = 248;
49const int KORAX_TELEPORT_TID = 249;
50
51const float KORAX_MISSILE_DELTA_ANGLE = 85.0;
52const float KORAX_ARM_EXTENSION_SHORT = 40.0;
53const float KORAX_ARM_EXTENSION_LONG = 55.0;
54
55const float KORAX_ARM1_HEIGHT = 108.0;
56const float KORAX_ARM2_HEIGHT = 82.0;
57const float KORAX_ARM3_HEIGHT = 54.0;
58const float KORAX_ARM4_HEIGHT = 104.0;
59const float KORAX_ARM5_HEIGHT = 86.0;
60const float KORAX_ARM6_HEIGHT = 53.0;
61
62const float KORAX_COMMAND_HEIGHT = 120.0;
63const float KORAX_COMMAND_OFFSET = 27.0;
64
65const float KORAX_BOLT_HEIGHT = 48.0;
66const int KORAX_BOLT_LIFETIME = 3;
67
68//==========================================================================
69//
70//  A_SmBounce
71//
72//==========================================================================
73
74final void A_SmBounce()
75{
76	// give some more momentum (x,y,&z)
77	Origin.z = FloorZ + 1.0;
78	Velocity.z = (2.0 + Random() * 4.0) * 35.0;
79	Velocity.x = Random() * 3.0 * 35.0;
80	Velocity.y = Random() * 3.0 * 35.0;
81}
82
83//==========================================================================
84//
85//  A_IceGuyMissileExplode
86//
87//==========================================================================
88
89final void A_IceGuyMissileExplode()
90{
91	EntityEx mo;
92	int i;
93
94	for (i = 0; i < 8; i++)
95	{
96		mo = SpawnMissileAngle(IceGuyFX2, itof(i) * 45.0, -0.3 * 35.0, 3.0);
97		if (mo)
98		{
99			mo.Target = Target;
100		}
101	}
102}
103
104//==========================================================================
105//
106//	A_SerpentHeadCheck
107//
108//==========================================================================
109
110final void A_SerpentHeadCheck()
111{
112	if (Origin.z <= FloorZ)
113	{
114		if (GetFloorType()->bLiquid)
115		{
116			HitFloorType();
117			SetState(none);
118		}
119		else
120		{
121			SetState(FindState('Death'));
122		}
123	}
124}
125
126//==========================================================================
127//
128//	A_FloatGib
129//
130//==========================================================================
131
132final void A_FloatGib()
133{
134	FloorClip -= 1.0;
135}
136
137//==========================================================================
138//
139//	A_SinkGib
140//
141//==========================================================================
142
143final void A_SinkGib()
144{
145	FloorClip += 1.0;
146}
147
148//==========================================================================
149//
150//	A_DelayGib
151//
152//==========================================================================
153
154final void A_DelayGib()
155{
156	StateTime -= Random() * 2.0;
157}
158
159//==========================================================================
160//
161//  A_WraithFX2
162//
163//  Spawns sparkle tail of missile.
164//
165//==========================================================================
166
167final void A_WraithFX2()
168{
169	Actor A;
170	float angle;
171	int i;
172
173	for (i = 0; i < 2; i++)
174	{
175		A = Spawn(WraithFX2, Origin);
176		if (A)
177		{
178			if (Random() < 0.5)
179			{
180				angle = AngleMod360(Angles.yaw + Random() * 90.0);
181			}
182			else
183			{
184				angle = AngleMod360(Angles.yaw - Random() * 90.0);
185			}
186			A.Velocity.z = 0.0;
187			A.Velocity.x = (Random() / 2.0 + 1.0) * cos(angle) * 35.0;
188			A.Velocity.y = (Random() / 2.0 + 1.0) * sin(angle) * 35.0;
189			A.Target = self;
190			A.FloorClip = 10.0;
191		}
192	}
193}
194
195//============================================================================
196//
197//	A_DragonFX2
198//
199//============================================================================
200
201final void A_DragonFX2()
202{
203	Actor A;
204	int i;
205	float delay;
206
207	delay = 0.5 + Random();
208	for (i = 1 + (P_Random() & 3); i; i--)
209	{
210		A = Spawn(DragonExplosion, Origin + vector((Random() - 0.5) * 64.0,
211			(Random() - 0.5) * 64.0, (Random() - 0.5) * 16.0));
212		if (A)
213		{
214			A.StateTime = delay + Random() * 0.2 * itof(i);
215			A.Target = Target;
216		}
217	}
218}
219
220//============================================================================
221//
222//  A_SorcSpinBalls
223//
224//  Spawn spinning balls above head - actor is sorcerer.
225//
226//============================================================================
227
228final void A_SorcSpinBalls()
229{
230	Actor ball;
231	TVec org;
232
233	//	To prevent spawning balls more than once.
234	IdleState = GetStatePlus(IdleState, 2);
235
236	A_SlowBalls();
237	Args[0] = 0;	// Currently no defense
238	Args[3] = SORC_NORMAL;
239	Args[4] = SORCBALL_INITIAL_SPEED;	// Initial orbit speed
240	Special1f = 1.0;
241	org = Origin;
242	org.z = Origin.z - FloorClip + Height;
243
244	ball = Spawn(SorcBall1, org,,, false);
245	if (ball)
246	{
247		ball.Target = self;
248		ball.Special2 = SORCFX4_RAPIDFIRE_TIME;
249	}
250	ball = Spawn(SorcBall2, org,,, false);
251	if (ball)
252		ball.Target = self;
253	ball = Spawn(SorcBall3, org,,, false);
254	if (ball)
255		ball.Target = self;
256}
257
258//==========================================================================
259//
260//  A_SorcFX1Seek
261//
262//  Yellow spell - offense
263//
264//==========================================================================
265
266final void A_SorcFX1Seek()
267{
268	if (Args[4]-- <= 0)
269	{
270		if (Args[3]-- <= 0)
271		{
272			SetState(FindState('Death'));
273			PlaySound('SorcererHeadScream', CHAN_VOICE, 1.0, ATTN_NONE);
274		}
275		else
276		{
277			Args[4] = BOUNCE_TIME_UNIT;
278		}
279	}
280
281	SeekerMissile(2.0, 6.0);
282}
283
284//==========================================================================
285// Blue spell - defense
286//==========================================================================
287//
288// FX2 Variables
289//      Special1f       current angle
290//      Args[0]     0 = CW,  1 = CCW
291//==========================================================================
292
293//==========================================================================
294//
295//  A_SorcFX2Split
296//
297//  Split ball in two
298//
299//==========================================================================
300
301final void A_SorcFX2Split()
302{
303	Actor A;
304
305	A = Spawn(SorcFX2, Origin,,, false);
306	if (A)
307	{
308		A.Target = Target;
309		A.Args[0] = 0;	// CW
310		A.Special1f = Angles.yaw;	// Set angle
311		A.SetState(A.FindState('Orbit'));
312	}
313	A = Spawn(SorcFX2, Origin,,, false);
314	if (A)
315	{
316		A.Target = Target;
317		A.Args[0] = 1;	// CCW
318		A.Special1f = Angles.yaw;	// Set angle
319		A.SetState(A.FindState('Orbit'));
320	}
321	SetState(none);
322}
323
324//==========================================================================
325//
326//  A_SorcFX2Orbit
327//
328//  Orbit FX2 about sorcerer
329//
330//==========================================================================
331
332final void A_SorcFX2Orbit()
333{
334	float angle;
335	float x, y, z;
336	float dist = Target.Radius;
337
338	if ((Target.Health <= 0) ||	// Sorcerer is dead
339		(!Target.Args[0]))	// Time expired
340	{
341		SetState(FindState('Death'));
342		Target.Args[0] = 0;
343		Actor(Target).bReflective = false;
344		Actor(Target).bInvulnerable = false;
345	}
346
347	if (Args[0] && (Target.Args[0]-- <= 0))	// Time expired
348	{
349		SetState(FindState('Death'));
350		Target.Args[0] = 0;
351		Actor(Target).bReflective = false;
352	}
353
354	// Move to new position based on angle
355	if (Args[0])	// Counter clock-wise
356	{
357		Special1f = AngleMod360(Special1f + 10.0);
358		angle = Special1f;
359		x = Target.Origin.x + dist * cos(angle);
360		y = Target.Origin.y + dist * sin(angle);
361		z = Target.Origin.z - Target.FloorClip + SORC_DEFENSE_HEIGHT;
362		z += 15.0 * cos(angle);
363		// Spawn trailer
364		Spawn(SorcFX2T1, vector(x, y, z));
365	}
366	else	// Clock wise
367	{
368		Special1f = AngleMod360(Special1f - 10.0);
369		angle = Special1f;
370		x = Target.Origin.x + dist * cos(angle);
371		y = Target.Origin.y + dist * sin(angle);
372		z = Target.Origin.z - Target.FloorClip + SORC_DEFENSE_HEIGHT;
373		z += 20.0 * sin(angle);
374		// Spawn trailer
375		Spawn(SorcFX2T1, vector(x, y, z));
376	}
377
378	Origin.x = x;
379	Origin.y = y;
380	Origin.z = z;
381}
382
383//==========================================================================
384//
385//  A_SorcFX4Check
386//
387//  FX4 - rapid fire balls
388//
389//==========================================================================
390
391final void A_SorcFX4Check()
392{
393	if (Special2-- <= 0)
394	{
395		SetState(FindState('Death'));
396	}
397}
398
399//==========================================================================
400//
401//  A_KBoltRaise
402//
403//==========================================================================
404
405final void A_KBoltRaise()
406{
407	float z;
408
409	// Spawn a child upward
410	z = Origin.z + KORAX_BOLT_HEIGHT;
411
412	if (z + KORAX_BOLT_HEIGHT < CeilingZ)
413	{
414		Actor A = Spawn(KoraxBolt, vector(Origin.x, Origin.y, z));
415		A.Special1 = KORAX_BOLT_LIFETIME;
416	}
417	else
418	{
419		// Maybe cap it off here
420	}
421}
422
423//==========================================================================
424//
425//  A_KBolt
426//
427//==========================================================================
428
429final void A_KBolt()
430{
431	// Countdown lifetime
432	if (Special1-- <= 0)
433	{
434		SetState(none);
435	}
436}
437
438//==========================================================================
439//
440//  A_KSpiritSeeker
441//
442//==========================================================================
443
444final void A_KSpiritSeeker(float thresh, float turnMax)
445{
446	int dir;
447	float dist;
448	float delta;
449	float angle;
450	float newZ;
451	float deltaZ;
452
453	if (Tracer == none)
454	{
455		return;
456	}
457	dir = FaceActor(Actor(Tracer), delta);
458	if (delta > thresh)
459	{
460		delta /= 2.0;
461		if (delta > turnMax)
462		{
463			delta = turnMax;
464		}
465	}
466	if (dir)
467	{
468		// Turn clockwise
469		Angles.yaw = AngleMod360(Angles.yaw + delta);
470	}
471	else
472	{
473		// Turn counter clockwise
474		Angles.yaw = AngleMod360(Angles.yaw - delta);
475	}
476	angle = Angles.yaw;
477	Velocity.x = Speed * cos(angle);
478	Velocity.y = Speed * sin(angle);
479
480	if (!(XLevel.TicTime & 15) ||
481		Origin.z > Tracer.Origin.z + Tracer.Height ||
482		Origin.z + Height < Tracer.Origin.z)
483	{
484		newZ = Tracer.Origin.z + Random() * Tracer.Height;
485		deltaZ = newZ - Origin.z;
486		if (fabs(deltaZ) > 15.0)
487		{
488			if (deltaZ > 0.0)
489			{
490				deltaZ = 15.0;
491			}
492			else
493			{
494				deltaZ = -15.0;
495			}
496		}
497		dist = DistTo2(Tracer);
498		dist = dist / Speed;
499		if (dist < 1.0)
500		{
501			dist = 1.0;
502		}
503		Velocity.z = deltaZ / dist;
504	}
505	return;
506}
507
508//==========================================================================
509//
510//  A_KSpiritWeave
511//
512//==========================================================================
513
514final void A_KSpiritWeave()
515{
516	float newX, newY;
517	float angle;
518
519	angle = AngleMod360(Angles.yaw + 90.0);
520	int WeaveXY = Special2 >> 16;
521	int WeaveZ = Special2 & 0xffff;
522	newX = Origin.x - cos(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0;
523	newY = Origin.y - sin(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0;
524	WeaveXY = (WeaveXY + (P_Random() % 5)) & 63;
525	newX += cos(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0;
526	newY += sin(angle) * Level.Game.FloatBobOffsets[WeaveXY] * 4.0;
527	TryMove(vector(newX, newY, Origin.z), false);
528	Origin.z -= Level.Game.FloatBobOffsets[WeaveZ] * 2.0;
529	WeaveZ = (WeaveZ + (P_Random() % 5)) & 63;
530	Origin.z += Level.Game.FloatBobOffsets[WeaveZ] * 2.0;
531	Special2 = (WeaveXY << 16) | (WeaveZ & 0xffff);
532}
533
534//==========================================================================
535//
536//  A_KSpiritRoam
537//
538//==========================================================================
539
540final void A_KSpiritRoam()
541{
542	if (Special1-- <= 0)
543	{
544		PlaySound('SpiritDie', CHAN_VOICE);
545		SetState(FindState('Death'));
546	}
547	else
548	{
549		if (Tracer)
550		{
551			A_KSpiritSeeker(10.0, 20.0);
552		}
553		A_KSpiritWeave();
554		if (Random() < 0.1953125)
555		{
556			PlaySound('SpiritActive', CHAN_VOICE, 1.0, ATTN_NONE);
557		}
558	}
559}
560
561//==========================================================================
562//
563//	A_CFlameRotate
564//
565//==========================================================================
566
567final void A_CFlameRotate()
568{
569	float an;
570
571	an = AngleMod360(Angles.yaw + 90.0);
572	Velocity.x = Special1f + 2.0 * 35.0 * cos(an);
573	Velocity.y = Special2f + 2.0 * 35.0 * sin(an);
574	Angles.yaw = AngleMod360(Angles.yaw + 90.0 / 15.0);
575}
576
577//============================================================================
578//
579//	CHolyTailFollow
580//
581//============================================================================
582
583final void CHolyTailFollow(float dist)
584{
585	EntityEx child;
586	float an;
587	float oldDistance, newDistance;
588
589	child = Tracer;
590	if (child)
591	{
592		an = atan2(child.Origin.y - Origin.y,
593			Origin.x - child.Origin.x);
594		oldDistance = DistTo(child);
595		if (child.TryMove(vector(Origin.x + dist * cos(an),
596				Origin.y + dist * sin(an), child.Origin.z), false))
597		{
598			newDistance = DistTo2(child) - 1.0;
599			if (oldDistance < 1.0)
600			{
601				if (child.Origin.z < Origin.z)
602				{
603					child.Origin.z = Origin.z - dist;
604				}
605				else
606				{
607					child.Origin.z = Origin.z + dist;
608				}
609			}
610			else
611			{
612				child.Origin.z = Origin.z + newDistance / oldDistance *
613					(child.Origin.z - Origin.z);
614			}
615		}
616		Actor(child).CHolyTailFollow(dist - 1.0);
617	}
618}
619
620//============================================================================
621//
622//	CHolyTailRemove
623//
624//============================================================================
625
626final void CHolyTailRemove()
627{
628	if (Tracer)
629	{
630		Actor(Tracer).CHolyTailRemove();
631	}
632	Destroy();
633}
634
635//============================================================================
636//
637//	A_CHolyTail
638//
639//============================================================================
640
641final void A_CHolyTail()
642{
643	EntityEx parent;
644
645	parent = Target;
646
647	if (parent)
648	{
649		if (!parent.bMissile)
650		{
651			// Ghost removed, so remove all tail parts
652			CHolyTailRemove();
653			return;
654		}
655		else if (TryMove(vector(
656				parent.Origin.x - 14.0 * cos(parent.Angles.yaw),
657				parent.Origin.y - 14.0 * sin(parent.Angles.yaw), Origin.z), false))
658		{
659			Origin.z = parent.Origin.z - 5.0;
660		}
661		CHolyTailFollow(10.0);
662	}
663}
664
665//===========================================================================
666//
667// Bat Spawner Variables
668//	Special1    frequency counter
669//	Args[0]     frequency of spawn (1=fastest, 10=slowest)
670//	Args[1]     spread angle (0..255)
671//	Args[2]
672//	Args[3]     duration of bats (in octics)
673//	Args[4]     turn amount per move (in degrees)
674//
675// Bat Variables
676//	Special2  - lifetime counter
677//	Args[4]   - turn amount per move (in degrees)
678//
679//===========================================================================
680
681//===========================================================================
682//
683//  A_BatSpawnInit
684//
685//===========================================================================
686
687final void A_BatSpawnInit()
688{
689	Special1 = 0;	// Frequency count
690}
691
692//===========================================================================
693//
694//  A_BatSpawn
695//
696//===========================================================================
697
698final void A_BatSpawn()
699{
700	EntityEx mo;
701	float delta;
702	float angle;
703
704	// Countdown until next spawn
705	if (Special1-- > 0)
706	{
707		return;
708	}
709	Special1 = Args[0];	// Reset frequency count
710
711	delta = itof(Args[1]);
712	if (delta == 0.0)
713		delta = 1.0;
714	angle = AngleMod360(Angles.yaw +
715		delta * (Random() - 0.5) * 360.0 / 256.0);
716	mo = SpawnMissileAngle(Bat, angle, 0.0);
717	if (mo)
718	{
719		mo.Args[0] = P_Random() & 63;	// floatbob index
720		mo.Args[4] = Args[4];	// turn degrees
721		mo.Special2 = Args[3] << 3;	// Set lifetime
722		mo.Target = self;
723	}
724}
725
726//===========================================================================
727//
728//  A_BatMove
729//
730//===========================================================================
731
732final void A_BatMove()
733{
734	float newangle;
735	float newSpeed;
736
737	if (Special2 < 0)
738	{
739		SetState(FindState('Death'));
740	}
741	Special2 -= 2;	// Called every 2 tics
742
743	if (Random() < 0.5)
744	{
745		newangle = AngleMod360(Angles.yaw + itof(Args[4]));
746	}
747	else
748	{
749		newangle = AngleMod360(Angles.yaw - itof(Args[4]));
750	}
751
752	// Adjust momentum vector to new direction
753	newSpeed = Speed * Random() * 4.0;
754	Velocity.x = newSpeed * cos(newangle);
755	Velocity.y = newSpeed * sin(newangle);
756
757	if (Random() < 0.05859375)
758		PlaySound('BatScream', CHAN_VOICE);
759
760	// Handle Z movement
761	Origin.z = Target.Origin.z + 2.0 * Level.Game.FloatBobOffsets[Args[0]];
762	Args[0] = (Args[0] + 3) & 63;
763}
764
765//==========================================================================
766//
767//	A_TimeBomb
768//
769//==========================================================================
770
771final void A_TimeBomb()
772{
773	// Time Bombs
774	Origin.z += 32.0;
775	RenderStyle = STYLE_Normal;
776	Alpha = 1.0;
777	A_Explode();
778}
779
780//===========================================================================
781//
782//  A_CheckThrowBomb
783//
784//===========================================================================
785
786final void A_CheckThrowBomb()
787{
788	if (!--Health)
789	{
790		SetState(FindState('Death'));
791	}
792}
793
794//===========================================================================
795//
796//  A_CheckThrowBomb2
797//
798//===========================================================================
799
800final void A_CheckThrowBomb2()
801{
802	if (fabs(Velocity.x) < 1.5 * 35.0 &&
803		fabs(Velocity.y) < 1.5 * 35.0 &&
804		Velocity.z < 2.0 * 35.0)
805	{
806		SetState(GetStatePlus(IdleState, 6, true));
807		Origin.z = FloorZ;
808		Velocity.z = 0.0;
809		BounceType = BOUNCE_None;
810		bMissile = false;
811	}
812	A_CheckThrowBomb();
813}
814
815//==========================================================================
816//
817// Fog Variables:
818//
819//      Special1    Counter for spawn frequency
820//      Special2    Index into floatbob table
821//      args[0]     Speed (0..10) of fog
822//      args[1]     Angle of spread (0..128)
823//      args[2]     Frequency of spawn (1..10)
824//      args[3]     Lifetime countdown
825//      args[4]     Boolean: fog moving?
826//
827//==========================================================================
828
829//==========================================================================
830//
831//  A_FogSpawn
832//
833//==========================================================================
834
835final void A_FogSpawn()
836{
837	Actor A;
838	float delta;
839
840	if (Special1-- > 0)
841	{
842		return;
843	}
844
845	Special1 = Args[2];	// Reset frequency count
846
847	switch (P_Random() % 3)
848	{
849	case 0:
850		A = Spawn(FogPatchSmall, Origin);
851		break;
852	case 1:
853		A = Spawn(FogPatchMedium, Origin);
854		break;
855	case 2:
856		A = Spawn(FogPatchLarge, Origin);
857		break;
858	}
859
860	if (A)
861	{
862		delta = itof(Args[1]);
863		if (delta == 0.0)
864			delta = 1.0;
865		A.Angles.yaw = AngleMod360(Angles.yaw +
866			((Random() * delta) - (delta * 0.5)) * 360.0 / 256.0);
867		A.Target = self;
868		if (Args[0] < 1)
869			Args[0] = 1;
870		A.Args[0] = (P_Random() % (Args[0])) + 1;	// P_Random speed
871		A.Args[3] = Args[3];	// Set lifetime
872		A.Args[4] = 1;	// Set to moving
873		A.Special2 = P_Random() & 63;
874	}
875}
876
877//==========================================================================
878//
879//	A_FogMove
880//
881//==========================================================================
882
883final void A_FogMove()
884{
885	float speed;
886	float angle;
887	int weaveindex;
888
889	if (!Args[4])
890		return;
891
892	if (Args[3]-- <= 0)
893	{
894		SetState(FindState('Death'));
895		return;
896	}
897
898	if ((Args[3] % 4) == 0)
899	{
900		Origin.z += Level.Game.FloatBobOffsets[Special2] / 2.0;
901		Special2 = (Special2 + 1) & 63;
902	}
903
904	speed = itof(Args[0]);
905	angle = Angles.yaw;
906	Velocity.x = speed * cos(angle) * 35.0;
907	Velocity.y = speed * sin(angle) * 35.0;
908}
909
910//==========================================================================
911//
912//  A_PotteryChooseBit
913//
914//==========================================================================
915
916final void A_PotteryChooseBit()
917{
918	Special1 = 1 + 2 * (P_Random() % 5);
919	SetState(GetStatePlus(FindState('Death'), Special1, true));
920	StateTime = 8.0 + Random() * 16.0;
921}
922
923//==========================================================================
924//
925//  A_PotteryCheck
926//
927//==========================================================================
928
929final void A_PotteryCheck()
930{
931	int i;
932	Actor pmo;
933
934	for (i = 0; i < MAXPLAYERS; i++)
935	{
936		if (!Level.Game.Players[i] || !Level.Game.Players[i].bSpawned)
937		{
938			continue;
939		}
940		pmo = Actor(Level.Game.Players[i].MO);
941		if (CanSee(pmo) && (fabs(AngleMod180(atan2(Origin.y - pmo.Origin.y,
942			Origin.x - pmo.Origin.x) - pmo.Angles.yaw)) <= 45.0))
943		{
944			// Previous state (pottery bit waiting state)
945			SetState(GetStatePlus(FindState('Death'), Special1, true));
946			return;
947		}
948	}
949}
950
951//============================================================================
952//
953//  A_CorpseExplode
954//
955//============================================================================
956
957final void A_CorpseExplode()
958{
959	Actor A;
960	int i;
961
962	for (i = (P_Random() & 3) + 3; i; i--)
963	{
964		A = Spawn(CorpseBit, Origin);
965		A.SetState(GetStatePlus(A.IdleState, P_Random() % 3, true));
966		if (A)
967		{
968			A.Velocity.x = (Random() - Random()) * 4.0 * 35.0;
969			A.Velocity.y = (Random() - Random()) * 4.0 * 35.0;
970			A.Velocity.z = (Random() * 8.0 + 5.0) * (3.0 / 4.0) * 35.0;
971		}
972	}
973	// Spawn a skull
974	A = Spawn(CorpseBit, Origin);
975	A.SetState(GetStatePlus(A.IdleState, 3, true));
976	if (A)
977	{
978		A.Velocity.x = (Random() - Random()) * 4.0 * 35.0;
979		A.Velocity.y = (Random() - Random()) * 4.0 * 35.0;
980		A.Velocity.z = (Random() * 8.0 + 5.0) * (3.0 / 4.0) * 35.0;
981		A.PlaySound('FireDemonDeath', CHAN_VOICE);
982	}
983	Destroy();
984}
985
986//==========================================================================
987//
988//  A_LeafSpawn
989//
990//==========================================================================
991
992final void A_LeafSpawn()
993{
994	Actor A;
995	int i;
996
997	for (i = (P_Random() & 3) + 1; i; i--)
998	{
999		A = Spawn(P_Random() & 1 ? class<Actor>(Leaf2) : class<Actor>(Leaf1),
1000			Origin + vector((Random() - Random()) * 64.0,
1001			(Random() - Random()) * 64.0, Random() * 64.0));
1002		if (A)
1003		{
1004			A.Thrust(Angles.yaw, Random() * 2.0 + 3.0);
1005			A.Target = self;
1006		}
1007	}
1008}
1009
1010//==========================================================================
1011//
1012//  A_LeafThrust
1013//
1014//==========================================================================
1015
1016final void A_LeafThrust()
1017{
1018	if (Random() > 0.375)
1019	{
1020		return;
1021	}
1022	Velocity.z += (Random() * 2.0 + 1.0) * 35.0;
1023}
1024
1025//==========================================================================
1026//
1027//  A_LeafCheck
1028//
1029//==========================================================================
1030
1031final void A_LeafCheck()
1032{
1033	Special1++;
1034	if (Special1 >= 20)
1035	{
1036		SetState(none);
1037		return;
1038	}
1039	if (Random() > 0.25)
1040	{
1041		if (!Velocity.x && !Velocity.y)
1042		{
1043			Thrust(Target.Angles.yaw, Random() * 2.0 + 1.0);
1044		}
1045		return;
1046	}
1047	SetState(GetStatePlus(IdleState, 7));
1048	Velocity.z = (Random() * 2.0 + 1.0) * 35.0;
1049	Thrust(Target.Angles.yaw, Random() * 2.0 + 2.0);
1050	bMissile = true;
1051}
1052
1053//===========================================================================
1054//
1055//  A_SoAExplode
1056//
1057//  Suit of Armor Explode
1058//
1059//===========================================================================
1060
1061final void A_SoAExplode()
1062{
1063	EntityEx A;
1064	int i;
1065
1066	for (i = 0; i < 10; i++)
1067	{
1068		A = Spawn(ZArmorChunk, Origin + vector((Random() - 0.5) * 16.0,
1069			(Random() - 0.5) * 16.0, Random() * Height));
1070		A.SetState(GetStatePlus(A.IdleState, i, true));
1071		if (A)
1072		{
1073			A.Velocity.x = (Random() - Random()) * 4.0 * 35.0;
1074			A.Velocity.y = (Random() - Random()) * 4.0 * 35.0;
1075			A.Velocity.z = (Random() * 8.0 + 5.0) * 35.0;
1076		}
1077	}
1078	if (Args[0])
1079	{
1080		// Spawn an item. Don't spawn monsters it they are disabled.
1081		class<EntityEx> Cls = class<EntityEx>(FindClassFromScriptId(Args[0],
1082			LineSpecialGameInfo(Level.Game).GameFilterFlag));
1083		if (Cls && (!Level.Game.nomonsters || !Cls.default.bMonster))
1084		{
1085			Spawn(Cls, Origin, vector(0.0, 0.0, 0.0));
1086		}
1087	}
1088	A.PlaySound('SuitofArmorBreak', CHAN_VOICE);
1089	Destroy();
1090}
1091
1092//
1093// Fire Demon AI
1094//
1095
1096//	Special1	Index into floatbob
1097//	Special2	Whether strafing or not
1098
1099//============================================================================
1100//
1101//  A_FiredSpawnRock
1102//
1103//============================================================================
1104
1105final void A_FiredSpawnRock()
1106{
1107	Actor mo;
1108	TVec rockOrg;
1109	class<Actor> rtype;
1110
1111	switch (P_Random() % 5)
1112	{
1113	case 0:
1114		rtype = FireDemonRock1;
1115		break;
1116	case 1:
1117		rtype = FireDemonRock2;
1118		break;
1119	case 2:
1120		rtype = FireDemonRock3;
1121		break;
1122	case 3:
1123		rtype = FireDemonRock4;
1124		break;
1125	case 4:
1126		rtype = FireDemonRock5;
1127		break;
1128	}
1129
1130	rockOrg.x = Origin.x + (Random() - 0.5) * 16.0;
1131	rockOrg.y = Origin.y + (Random() - 0.5) * 16.0;
1132	rockOrg.z = Origin.z + Random() * 8.0;
1133	mo = Spawn(rtype, rockOrg);
1134	if (mo)
1135	{
1136		mo.Target = self;
1137		mo.Velocity.x = (Random() - 0.5) * 4.0 * 35.0;
1138		mo.Velocity.y = (Random() - 0.5) * 4.0 * 35.0;
1139		mo.Velocity.z = Random() * 4.0 * 35.0;
1140		mo.Special1 = 2;	// Number bounces
1141	}
1142
1143	// Initialise fire demon
1144	Special2 = 0;
1145	bJustAttacked = false;
1146}
1147
1148//============================================================================
1149//
1150//  A_FiredRocks
1151//
1152//============================================================================
1153
1154final void A_FiredRocks()
1155{
1156	A_FiredSpawnRock();
1157	A_FiredSpawnRock();
1158	A_FiredSpawnRock();
1159	A_FiredSpawnRock();
1160	A_FiredSpawnRock();
1161}
1162
1163//============================================================================
1164//
1165//  A_FiredChase
1166//
1167//============================================================================
1168
1169final void A_FiredChase()
1170{
1171	float ang;
1172	float dist;
1173
1174	if (ReactionCount)
1175		ReactionCount--;
1176	if (Threshold)
1177		Threshold--;
1178
1179	// Float up and down
1180	Origin.z += Level.Game.FloatBobOffsets[Special1];
1181	Special1 = (Special1 + 2) & 63;
1182
1183
1184	// Insure it stays above certain height
1185	if (Origin.z < FloorZ + 64.0)
1186	{
1187		Origin.z += 2.0;
1188	}
1189
1190	if (!Target || !Target.bShootable)
1191	{
1192		// Invalid enemy
1193		LookForPlayers(true);
1194		return;
1195	}
1196
1197	// Strafe
1198	if (Special2 > 0)
1199	{
1200		Special2--;
1201	}
1202	else
1203	{
1204		Special2 = 0;
1205		Velocity.x = 0.0;
1206		Velocity.y = 0.0;
1207		dist = DistTo(Target);
1208		if (dist < FIREDEMON_ATTACK_RANGE)
1209		{
1210			if (P_Random() < 30)
1211			{
1212				ang = atan2(Target.Origin.y - Origin.y,
1213					Target.Origin.x - Origin.x);
1214				if (P_Random() < 128)
1215					ang = AngleMod360(ang + 90.0);
1216				else
1217					ang = AngleMod360(ang - 90.0);
1218				Velocity.x = 8.0 * cos(ang) * 35.0;
1219				Velocity.y = 8.0 * sin(ang) * 35.0;
1220				Special2 = 3;	// strafe time
1221			}
1222		}
1223	}
1224
1225	FaceMovementDirection();
1226
1227	// Normal movement
1228	if (!Special2)
1229	{
1230		if (--MoveCount < 0 || !StepMove())
1231		{
1232			NewChaseDir();
1233		}
1234	}
1235
1236	// Do missile attack
1237	if (!bJustAttacked)
1238	{
1239		if (CheckMissileRange() && (P_Random() < 20))
1240		{
1241			SetState(MissileState);
1242			bJustAttacked = true;
1243			return;
1244		}
1245	}
1246	else
1247	{
1248		bJustAttacked = false;
1249	}
1250
1251	// make active sound
1252	if (ActiveSound && P_Random() < 3)
1253	{
1254		PlaySound(ActiveSound, CHAN_VOICE);
1255	}
1256}
1257
1258//============================================================================
1259//
1260//  A_FiredAttack
1261//
1262//============================================================================
1263
1264final void A_FiredAttack()
1265{
1266	EntityEx mo;
1267
1268	mo = SpawnMissile(Target, FireDemonMissile);
1269	if (mo)
1270	{
1271		PlaySound('FireDemonAttack', CHAN_WEAPON);
1272	}
1273}
1274
1275//============================================================================
1276//
1277//  A_FiredSplotch
1278//
1279//============================================================================
1280
1281final void A_FiredSplotch()
1282{
1283	Actor A;
1284
1285	A = Spawn(FireDemonSplotch1, Origin);
1286	if (A)
1287	{
1288		A.Velocity.x = (Random() - 0.5) * 8.0 * 35.0;
1289		A.Velocity.y = (Random() - 0.5) * 8.0 * 35.0;
1290		A.Velocity.z = (3.0 + Random() * 4.0) * 35.0;
1291	}
1292	A = Spawn(FireDemonSplotch2, Origin);
1293	if (A)
1294	{
1295		A.Velocity.x = (Random() - 0.5) * 8.0 * 35.0;
1296		A.Velocity.y = (Random() - 0.5) * 8.0 * 35.0;
1297		A.Velocity.z = (3.0 + Random() * 4.0) * 35.0;
1298	}
1299}
1300
1301//==========================================================================
1302//
1303//  A_IceGuyLook
1304//
1305//==========================================================================
1306
1307final void A_IceGuyLook()
1308{
1309	float dist;
1310	float an;
1311	class<Actor> tp;
1312
1313	A_Look();
1314	if (Random() < 0.25)
1315	{
1316		dist = Radius * (Random() * 2.0 - 1.0);
1317		an = AngleMod360(Angles.yaw + 90.0);
1318
1319		if (Random() < 0.5)
1320			tp = IceGuyWisp2;
1321		else
1322			tp = IceGuyWisp1;
1323		Spawn(tp, Origin + vector(dist * cos(an), dist * sin(an), 60.0));
1324	}
1325}
1326
1327//==========================================================================
1328//
1329//  A_IceGuyChase
1330//
1331//==========================================================================
1332
1333final void A_IceGuyChase()
1334{
1335	float dist;
1336	float an;
1337	Actor A;
1338	class<Actor> tp;
1339
1340	A_Chase();
1341	if (Random() < 0.5)
1342	{
1343		dist = Radius * (Random() * 2.0 - 1.0);
1344		an = AngleMod360(Angles.yaw + 90.0);
1345
1346		if (Random() < 0.5)
1347			tp = IceGuyWisp2;
1348		else
1349			tp = IceGuyWisp1;
1350		A = Spawn(tp, Origin + vector(dist * cos(an), dist * sin(an), 60.0));
1351		if (A)
1352		{
1353			A.Velocity = Velocity;
1354			A.Target = self;
1355		}
1356	}
1357}
1358
1359//==========================================================================
1360//
1361//	A_IceGuyAttack
1362//
1363//==========================================================================
1364
1365final void A_IceGuyAttack()
1366{
1367	float an;
1368
1369	if (!Target)
1370	{
1371		return;
1372	}
1373	an = AngleMod360(Angles.yaw + 90.0);
1374	SpawnMissileXYZ(Origin + vector(Radius / 2.0 * cos(an),
1375		Radius / 2.0 * sin(an), 40.0), Target, IceGuyFX);
1376
1377	an = AngleMod360(Angles.yaw - 90.0);
1378	SpawnMissileXYZ(Origin + vector(Radius / 2.0 * cos(an),
1379		Radius / 2.0 * sin(an), 40.0), Target, IceGuyFX);
1380
1381	PlaySound(AttackSound, CHAN_WEAPON);
1382}
1383
1384//	Special1	Number of blurs/missiles to spawn
1385//	Special2	Index into floatbob
1386
1387//==========================================================================
1388//
1389//	A_BishopChase
1390//
1391//==========================================================================
1392
1393final void A_BishopChase()
1394{
1395	Origin.z -= Level.Game.FloatBobOffsets[Special2] / 2.0;
1396	Special2 = (Special2 + 4) & 63;
1397	Origin.z += Level.Game.FloatBobOffsets[Special2] / 2.0;
1398}
1399
1400//==========================================================================
1401//
1402//  A_BishopDecide
1403//
1404//==========================================================================
1405
1406final void A_BishopDecide()
1407{
1408	if (P_Random() < 220)
1409	{
1410		return;
1411	}
1412	else
1413	{
1414		SetState(FindState('Blur'));
1415	}
1416}
1417
1418//==========================================================================
1419//
1420//  A_BishopDoBlur
1421//
1422//==========================================================================
1423
1424final void A_BishopDoBlur()
1425{
1426	Special1 = (P_Random() & 3) + 3;	// P_Random number of blurs
1427	if (P_Random() < 120)
1428	{
1429		Thrust(AngleMod360(Angles.yaw + 90.0), 11.0);
1430	}
1431	else if (P_Random() > 125)
1432	{
1433		Thrust(AngleMod360(Angles.yaw - 90.0), 11.0);
1434	}
1435	else
1436	{
1437		// Thrust forward
1438		Thrust(Angles.yaw, 11.0);
1439	}
1440	PlaySound('BishopBlur', CHAN_VOICE);
1441}
1442
1443//==========================================================================
1444//
1445//	A_BishopSpawnBlur
1446//
1447//==========================================================================
1448
1449final void A_BishopSpawnBlur()
1450{
1451	Actor blur;
1452
1453	if (!--Special1)
1454	{
1455		Velocity.x = 0.0;
1456		Velocity.y = 0.0;
1457		if (P_Random() > 96)
1458		{
1459			SetState(SeeState);
1460		}
1461		else
1462		{
1463			SetState(MissileState);
1464		}
1465	}
1466	blur = Spawn(BishopBlur, Origin);
1467	if (blur)
1468	{
1469		blur.Angles = Angles;
1470	}
1471}
1472
1473//==========================================================================
1474//
1475//	A_BishopAttack
1476//
1477//==========================================================================
1478
1479final void A_BishopAttack()
1480{
1481	if (!Target)
1482	{
1483		return;
1484	}
1485	PlaySound(AttackSound, CHAN_WEAPON);
1486	if (CheckMeleeRange())
1487	{
1488		Target.Damage(self, self, HITDICE(4));
1489		return;
1490	}
1491	Special1 = (P_Random() & 3) + 5;
1492}
1493
1494//==========================================================================
1495//
1496//  A_BishopAttack2
1497//
1498//  Spawns one of a string of bishop missiles
1499//
1500//==========================================================================
1501
1502final void A_BishopAttack2()
1503{
1504	EntityEx mo;
1505
1506	if (!Target || !Special1)
1507	{
1508		Special1 = 0;
1509		SetState(SeeState);
1510		return;
1511	}
1512	mo = SpawnMissile(Target, BishopFX);
1513	if (mo)
1514	{
1515		mo.Tracer = Target;
1516		mo.Special2 = 16;
1517	}
1518	Special1--;
1519}
1520
1521//==========================================================================
1522//
1523//  A_BishopPainBlur
1524//
1525//==========================================================================
1526
1527final void A_BishopPainBlur()
1528{
1529	Actor blur;
1530
1531	if (P_Random() < 64)
1532	{
1533		SetState(FindState('Blur'));
1534		return;
1535	}
1536	blur = Spawn(BishopPainBlur,
1537		Origin + vector((Random() - Random()) * 16.0,
1538		(Random() - Random()) * 16.0, (Random() - Random()) * 8.0));
1539	if (blur)
1540	{
1541		blur.Angles = Angles;
1542	}
1543}
1544
1545//==========================================================================
1546//
1547//  A_SerpentHumpDecide
1548//
1549//  Decided whether to hump up or to missile attack
1550//
1551//==========================================================================
1552
1553final void A_SerpentHumpDecide()
1554{
1555	if (MissileState)
1556	{
1557		if (P_Random() > 30)
1558		{
1559			return;
1560		}
1561		if (P_Random() < 40)
1562		{
1563			// Missile attack
1564			SetState(MeleeState);
1565			return;
1566		}
1567		if (!CheckMeleeRange())
1568		{
1569			// The hump shouldn't occur when within melee range
1570			if (P_Random() < 128)
1571			{
1572				SetState(MeleeState);
1573			}
1574			else
1575			{
1576				SetState(FindState('Hump'));
1577				PlaySound('SerpentActive', CHAN_VOICE);
1578			}
1579		}
1580	}
1581	else
1582	{
1583		if (P_Random() > 3)
1584		{
1585			return;
1586		}
1587		if (!CheckMeleeRange())
1588		{
1589			// The hump shouldn't occur when within melee range
1590			SetState(FindState('Hump'));
1591			PlaySound('SerpentActive', CHAN_VOICE);
1592		}
1593	}
1594}
1595
1596//==========================================================================
1597//
1598//	A_SerpentUnHide
1599//
1600//==========================================================================
1601
1602final void A_SerpentUnHide()
1603{
1604	bInvisible = false;
1605	FloorClip = 24.0;
1606}
1607
1608//==========================================================================
1609//
1610//	A_SerpentRaiseHump
1611//
1612//	Raises the hump above the surface by raising the FloorClip level
1613//
1614//==========================================================================
1615
1616final void A_SerpentRaiseHump()
1617{
1618	FloorClip -= 4.0;
1619}
1620
1621//==========================================================================
1622//
1623//	A_SerpentLowerHump
1624//
1625//==========================================================================
1626
1627final void A_SerpentLowerHump()
1628{
1629	FloorClip += 4.0;
1630}
1631
1632//==========================================================================
1633//
1634//	A_SerpentHide
1635//
1636//==========================================================================
1637
1638final void A_SerpentHide()
1639{
1640	bInvisible = true;
1641	FloorClip = 0.0;
1642}
1643
1644//==========================================================================
1645//
1646//  A_SerpentCheckForAttack
1647//
1648//==========================================================================
1649
1650final void A_SerpentCheckForAttack()
1651{
1652	if (!Target)
1653	{
1654		return;
1655	}
1656	if (MissileState && !CheckMeleeRange())
1657	{
1658		SetState(FindState('Attack'));
1659		return;
1660	}
1661	if (CheckMeleeRange2())
1662	{
1663		SetState(FindState('Walk'));
1664	}
1665	else if (CheckMeleeRange())
1666	{
1667		if (P_Random() < 32)
1668		{
1669			SetState(FindState('Walk'));
1670		}
1671		else
1672		{
1673			SetState(FindState('Attack'));
1674		}
1675	}
1676}
1677
1678//==========================================================================
1679//
1680//  A_SerpentChooseAttack
1681//
1682//==========================================================================
1683
1684final void A_SerpentChooseAttack()
1685{
1686	if (MissileState)
1687	{
1688		if (!Target || CheckMeleeRange())
1689		{
1690			return;
1691		}
1692		SetState(MissileState);
1693	}
1694}
1695
1696//==========================================================================
1697//
1698//	A_SerpentMeleeAttack
1699//
1700//==========================================================================
1701
1702final void A_SerpentMeleeAttack()
1703{
1704	if (!Target)
1705	{
1706		return;
1707	}
1708	if (CheckMeleeRange())
1709	{
1710		Target.Damage(self, self, HITDICE(5));
1711		PlaySound('SerpentMeleeHit', CHAN_WEAPON);
1712	}
1713	if (P_Random() < 96)
1714	{
1715		A_SerpentCheckForAttack();
1716	}
1717}
1718
1719//==========================================================================
1720//
1721//	A_SerpentSpawnGibs
1722//
1723//==========================================================================
1724
1725final void A_SerpentSpawnGibs()
1726{
1727	Actor A;
1728
1729	A = Spawn(SerpentGib1, vector(Origin.x + (Random() - 0.5) * 16.0,
1730		Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0));
1731	if (A)
1732	{
1733		A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0;
1734		A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0;
1735		A.FloorClip = 6.0;
1736		A.Translation = Translation;
1737	}
1738	A = Spawn(SerpentGib2, vector(Origin.x + (Random() - 0.5) * 16.0,
1739		Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0));
1740	if (A)
1741	{
1742		A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0;
1743		A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0;
1744		A.FloorClip = 6.0;
1745		A.Translation = Translation;
1746	}
1747	A = Spawn(SerpentGib3, vector(Origin.x + (Random() - 0.5) * 16.0,
1748		Origin.y + (Random() - 0.5) * 16.0, FloorZ + 1.0));
1749	if (A)
1750	{
1751		A.Velocity.x = (Random() - 0.5) / 4.0 * 35.0;
1752		A.Velocity.y = (Random() - 0.5) / 4.0 * 35.0;
1753		A.FloorClip = 6.0;
1754		A.Translation = Translation;
1755	}
1756}
1757
1758//
1759// Wraith Variables
1760//
1761
1762//	Special1	Internal index into floatbob
1763
1764//==========================================================================
1765//
1766//  A_WraithInit
1767//
1768//==========================================================================
1769
1770final void A_WraithInit()
1771{
1772	Origin.z += 48.0;
1773	Special1 = 0;	// index into floatbob
1774}
1775
1776//==========================================================================
1777//
1778//  A_WraithFX3
1779//
1780//  Spawn an FX3 around the actor during attacks.
1781//
1782//==========================================================================
1783
1784final void A_WraithFX3()
1785{
1786	Actor A;
1787	int numdropped = P_Random() % 15;
1788	int i;
1789
1790	for (i = 0; i < numdropped; i++)
1791	{
1792		A = Spawn(WraithFX3, Origin);
1793		if (A)
1794		{
1795			A.Origin.x += (Random() - 0.5) * 8.0;
1796			A.Origin.y += (Random() - 0.5) * 8.0;
1797			A.Origin.z += Random() * 4.0;
1798			A.Target = self;
1799		}
1800	}
1801}
1802
1803//==========================================================================
1804//
1805//  A_WraithFX4
1806//
1807//  Spawn an FX4 during movement.
1808//
1809//==========================================================================
1810
1811final void A_WraithFX4()
1812{
1813	Actor mo;
1814	int chance = P_Random();
1815	int spawn4, spawn5;
1816
1817	if (chance < 10)
1818	{
1819		spawn4 = true;
1820		spawn5 = false;
1821	}
1822	else if (chance < 20)
1823	{
1824		spawn4 = false;
1825		spawn5 = true;
1826	}
1827	else if (chance < 25)
1828	{
1829		spawn4 = true;
1830		spawn5 = true;
1831	}
1832	else
1833	{
1834		spawn4 = false;
1835		spawn5 = false;
1836	}
1837
1838	if (spawn4)
1839	{
1840		mo = Spawn(WraithFX4, Origin);
1841		if (mo)
1842		{
1843			mo.Origin.x += (Random() - 0.5) * 16.0;
1844			mo.Origin.y += (Random() - 0.5) * 16.0;
1845			mo.Origin.z += Random() * 4.0;
1846			mo.Target = self;
1847		}
1848	}
1849	if (spawn5)
1850	{
1851		mo = Spawn(WraithFX5, Origin);
1852		if (mo)
1853		{
1854			mo.Origin.x += (Random() - 0.5) * 8.0;
1855			mo.Origin.y += (Random() - 0.5) * 8.0;
1856			mo.Origin.z += Random() * 4.0;
1857			mo.Target = self;
1858		}
1859	}
1860}
1861
1862//==========================================================================
1863//
1864//  A_WraithChase
1865//
1866//==========================================================================
1867
1868final void A_WraithChase()
1869{
1870	Origin.z += Level.Game.FloatBobOffsets[Special1];
1871	Special1 = (Special1 + 2) & 63;
1872//  if (actor->FloorClip > 0)
1873//  {
1874//      SetState(S_WRAITH_RAISE2);
1875//      return;
1876//  }
1877	A_Chase();
1878	A_WraithFX4();
1879}
1880
1881//==========================================================================
1882//
1883//  A_WraithMelee
1884//
1885//==========================================================================
1886
1887final void A_WraithMelee()
1888{
1889	int amount;
1890
1891	// Steal health from enemy and give to player
1892	if (CheckMeleeRange() && (P_Random() < 220))
1893	{
1894		amount = HITDICE(2);
1895		Target.Damage(self, self, amount);
1896		Health += amount;
1897	}
1898}
1899
1900//==========================================================================
1901//
1902//  A_WraithRaiseInit
1903//
1904//==========================================================================
1905
1906final void A_WraithRaiseInit()
1907{
1908	bInvisible = false;
1909	bDontBlast = false;
1910	bNonShootable = false;
1911	bShootable = true;
1912	bSolid = true;
1913	FloorClip = Height;
1914}
1915
1916//============================================================================
1917//
1918//  RaiseFromFloor
1919//
1920//  Raise incrementally from the floor
1921//
1922//============================================================================
1923
1924bool RaiseFromFloor()
1925{
1926	bool done;
1927
1928	done = true;
1929	// Raise a mobj from the ground
1930	if (FloorClip > 0.0)
1931	{
1932		FloorClip -= 2.0;
1933		if (FloorClip <= 0.0)
1934		{
1935			FloorClip = 0.0;
1936			done = true;
1937		}
1938		else
1939		{
1940			done = false;
1941		}
1942	}
1943	return done;	// Reached target height
1944}
1945
1946//==========================================================================
1947//
1948//  A_WraithRaise
1949//
1950//==========================================================================
1951
1952final void A_WraithRaise()
1953{
1954	if (RaiseFromFloor())
1955	{
1956		// Reached it's target height
1957		bNoMorph = false;
1958		SetState(FindState('Chase'));
1959		PainChance = Wraith.default.PainChance;
1960	}
1961
1962	SpawnDirt(Radius);
1963}
1964
1965//==========================================================================
1966//
1967//	A_DragonInitFlight
1968//
1969//==========================================================================
1970
1971final void A_DragonInitFlight()
1972{
1973	Tracer = none;
1974	do
1975	{
1976		// find the first tid identical to the dragon's tid
1977		Tracer = Actor(Level.FindMobjFromTID(TID, Tracer));
1978		if (!Tracer)
1979		{
1980			SetState(IdleState);
1981			return;
1982		}
1983	}
1984	while (Tracer == self);
1985	SetTID(0);
1986}
1987
1988//==========================================================================
1989//
1990//	DragonSeek
1991//
1992//==========================================================================
1993
1994final void DragonSeek(float thresh, float turnMax)
1995{
1996	int dir;
1997	float dist;
1998	float delta;
1999	EntityEx tempTarget;
2000	int i;
2001	int bestArg;
2002	float bestAngle;
2003	float angleToSpot, angleToTarget;
2004	Actor A;
2005	EntityEx oldTarget;
2006
2007	tempTarget = Tracer;
2008	if (!tempTarget)
2009	{
2010		return;
2011	}
2012	dir = FaceActor(Actor(tempTarget), delta);
2013	if (delta > thresh)
2014	{
2015		delta /= 2.0;
2016		if (delta > turnMax)
2017		{
2018			delta = turnMax;
2019		}
2020	}
2021	if (dir)
2022	{
2023		// Turn clockwise
2024		Angles.yaw = AngleMod360(Angles.yaw + delta);
2025	}
2026	else
2027	{
2028		// Turn counter clockwise
2029		Angles.yaw = AngleMod360(Angles.yaw - delta);
2030	}
2031	Velocity.x = Speed * cos(Angles.yaw);
2032	Velocity.y = Speed * sin(Angles.yaw);
2033	if (Origin.z + Height < tempTarget.Origin.z ||
2034		tempTarget.Origin.z + tempTarget.Height < Origin.z)
2035	{
2036		dist = DistTo2(tempTarget);
2037		dist = dist / Speed;
2038		if (dist < 1.0)
2039		{
2040			dist = 1.0;
2041		}
2042		Velocity.z = (tempTarget.Origin.z - Origin.z) / dist;
2043	}
2044	else
2045	{
2046		dist = DistTo2(tempTarget);
2047		dist = dist / Speed;
2048	}
2049	if (tempTarget.bShootable && P_Random() < 64)
2050	{
2051		// attack the destination mobj if it's attackable
2052		if (fabs(AngleMod180(Angles.yaw -
2053			atan2(tempTarget.Origin.y - Origin.y,
2054			tempTarget.Origin.x - Origin.x))) < 45.0 / 2.0)
2055		{
2056			oldTarget = Target;
2057			Target = tempTarget;
2058			if (CheckMeleeRange())
2059			{
2060				Target.Damage(self, self, HITDICE(10));
2061				PlaySound('DragonAttack', CHAN_WEAPON);
2062			}
2063			else if (P_Random() < 128 && CheckMissileRange())
2064			{
2065				SpawnMissile(tempTarget, DragonFireball);
2066				PlaySound('DragonAttack', CHAN_WEAPON);
2067			}
2068			Target = oldTarget;
2069		}
2070	}
2071	if (dist * 35.0 < 4.0)
2072	{
2073		// Hit the target thing
2074		if (Target && P_Random() < 200)
2075		{
2076			bestArg = -1;
2077			bestAngle = 360.0;
2078			angleToTarget = atan2(Target.Origin.y - Origin.y,
2079				Target.Origin.x - Origin.x);
2080			for (i = 0; i < 5; i++)
2081			{
2082				if (!tempTarget.Args[i])
2083				{
2084					continue;
2085				}
2086				A = Actor(Level.FindMobjFromTID(tempTarget.Args[i], none));
2087				angleToSpot = atan2(A.Origin.y - Origin.y,
2088					A.Origin.x - Origin.x);
2089				delta = fabs(AngleMod180(angleToSpot - angleToTarget));
2090				if (delta < bestAngle)
2091				{
2092					bestAngle = delta;
2093					bestArg = i;
2094				}
2095			}
2096			if (bestArg != -1)
2097			{
2098				Tracer = Actor(Level.FindMobjFromTID(tempTarget.Args[bestArg],
2099					none));
2100			}
2101		}
2102		else
2103		{
2104			do
2105			{
2106				i = (P_Random() >> 2) % 5;
2107			}
2108			while (!tempTarget.Args[i]);
2109			Tracer = Actor(Level.FindMobjFromTID(tempTarget.Args[i], none));
2110		}
2111	}
2112}
2113
2114//==========================================================================
2115//
2116//	A_DragonFlight
2117//
2118//==========================================================================
2119
2120final void A_DragonFlight()
2121{
2122	float angle;
2123
2124	DragonSeek(4.0, 8.0);
2125	if (Target)
2126	{
2127		if (!Target.bShootable)
2128		{
2129			// enemy died
2130			Target = none;
2131			return;
2132		}
2133		angle = atan2(Target.Origin.y - Origin.y, Target.Origin.x - Origin.x);
2134		if (fabs(AngleMod180(Angles.yaw - angle)) < 45.0 / 2.0
2135			&& CheckMeleeRange())
2136		{
2137			Target.Damage(self, self, HITDICE(8));
2138			PlaySound('DragonAttack', CHAN_WEAPON);
2139		}
2140		else if (fabs(AngleMod180(Angles.yaw - angle)) <= 20.0)
2141		{
2142			SetState(MissileState);
2143			PlaySound('DragonAttack', CHAN_WEAPON);
2144		}
2145	}
2146	else
2147	{
2148		LookForPlayers(true);
2149	}
2150}
2151
2152//==========================================================================
2153//
2154//	A_DragonFlap
2155//
2156//==========================================================================
2157
2158final void A_DragonFlap()
2159{
2160	A_DragonFlight();
2161	if (P_Random() < 240)
2162	{
2163		PlaySound('DragonWingflap', CHAN_BODY);
2164	}
2165	else
2166	{
2167		PlaySound(ActiveSound, CHAN_VOICE);
2168	}
2169}
2170
2171//==========================================================================
2172//
2173//  A_DragonPain
2174//
2175//==========================================================================
2176
2177final void A_DragonPain()
2178{
2179	A_Pain();
2180	if (!Tracer)
2181	{
2182		// no destination spot yet
2183		SetState(SeeState);
2184	}
2185}
2186
2187//
2188//	Korax
2189//
2190// Korax Scripts (reserved)
2191//  249     Tell scripts that we are below half health
2192//  250-254 Control scripts
2193//  255     Death script
2194//
2195// Korax TIDs (reserved)
2196//  245     Reserved for Korax himself
2197//  248     Initial teleport destination
2198//  249     Teleport destination
2199//  250-254 For use in respective control scripts
2200//  255     For use in death script (spawn spots)
2201//
2202// Arm projectiles
2203//      arm positions numbered:
2204//          1   top left
2205//          2   middle left
2206//          3   lower left
2207//          4   top right
2208//          5   middle right
2209//          6   lower right
2210//
2211// Korax Variables
2212//
2213//  Special1    last teleport destination
2214//  Special2    set if "below half" script not yet run
2215//
2216
2217//==========================================================================
2218//
2219//  A_KoraxChase
2220//
2221//==========================================================================
2222
2223final void A_KoraxChase()
2224{
2225	Actor spot;
2226
2227	if (!Special2 && (Health <= (default.Health / 2)))
2228	{
2229		spot = Actor(Level.FindMobjFromTID(KORAX_FIRST_TELEPORT_TID, none));
2230		if (spot)
2231		{
2232			Teleport(spot.Origin, spot.Angles.yaw, true, true, false);
2233		}
2234
2235		XLevel.StartACS(249, 0, 0, 0, 0, self, NULL, 0, false, false);
2236		Special2 = true;	// Don't run again
2237
2238		return;
2239	}
2240
2241	if (!Target)
2242		return;
2243	if (Random() < 0.1171875)
2244	{
2245		SetState(MissileState);
2246	}
2247	else if (Random() < 0.1171875)
2248	{
2249		PlaySound('KoraxActive', CHAN_VOICE, 1.0, ATTN_NONE);
2250	}
2251
2252	// Teleport away
2253	if (Health < (default.Health >> 1))
2254	{
2255		if (Random() < 0.0390625)
2256		{
2257			spot = Actor(Level.FindMobjFromTID(KORAX_TELEPORT_TID, Tracer));
2258			Tracer = spot;
2259			if (spot)
2260			{
2261				Teleport(spot.Origin, spot.Angles.yaw, true, true, false);
2262			}
2263		}
2264	}
2265}
2266
2267//==========================================================================
2268//
2269//  A_KoraxDecide
2270//
2271//==========================================================================
2272
2273final void A_KoraxDecide()
2274{
2275	if (Random() < 0.859375)
2276	{
2277		SetState(FindState('Attack'));
2278	}
2279	else
2280	{
2281		SetState(FindState('Command'));
2282	}
2283}
2284
2285//==========================================================================
2286//
2287//  SpawnKoraxMissile
2288//
2289//==========================================================================
2290
2291final Actor SpawnKoraxMissile(TVec org, EntityEx dest, class<Actor> type)
2292{
2293	TVec dir;
2294	Actor th;
2295
2296	org.z -= FloorClip;
2297	th = Spawn(type, org);
2298	if (th.SightSound)
2299	{
2300		th.PlaySound(th.SightSound, CHAN_VOICE);
2301	}
2302	th.Target = self;	// Originator
2303	dir = dest.Origin - org;
2304	if (dest.bShadow)
2305	{
2306		// Invisible target
2307		VectorRotateAroundZ(&dir, (Random() - Random()) * 45.0);
2308	}
2309	dir = Normalise(dir);
2310	th.Velocity = dir * th.Speed;
2311	VectorAngles(&dir, &th.Angles);
2312	return th.CheckMissileSpawn() ? th : none;
2313}
2314
2315//==========================================================================
2316// Arm 1 projectile
2317//==========================================================================
2318
2319final void KoraxFire1(class<Actor> type)
2320{
2321	float ang;
2322	float x, y, z;
2323
2324	ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE);
2325	x = Origin.x + KORAX_ARM_EXTENSION_SHORT * cos(ang);
2326	y = Origin.y + KORAX_ARM_EXTENSION_SHORT * sin(ang);
2327	z = Origin.z - FloorClip + KORAX_ARM1_HEIGHT;
2328	SpawnKoraxMissile(vector(x, y, z), Target, type);
2329}
2330
2331//============================================================================
2332// Arm 2 projectile
2333//============================================================================
2334
2335final void KoraxFire2(class<Actor> type)
2336{
2337	float ang;
2338	float x, y, z;
2339
2340	ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE);
2341	x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang);
2342	y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang);
2343	z = Origin.z - FloorClip + KORAX_ARM2_HEIGHT;
2344	SpawnKoraxMissile(vector(x, y, z), Target, type);
2345}
2346
2347//============================================================================
2348// Arm 3 projectile
2349//============================================================================
2350
2351final void KoraxFire3(class<Actor> type)
2352{
2353	float ang;
2354	float x, y, z;
2355
2356	ang = AngleMod360(Angles.yaw - KORAX_MISSILE_DELTA_ANGLE);
2357	x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang);
2358	y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang);
2359	z = Origin.z - FloorClip + KORAX_ARM3_HEIGHT;
2360	SpawnKoraxMissile(vector(x, y, z), Target, type);
2361}
2362
2363//============================================================================
2364// Arm 4 projectile
2365//============================================================================
2366
2367final void KoraxFire4(class<Actor> type)
2368{
2369	float ang;
2370	float x, y, z;
2371
2372	ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE);
2373	x = Origin.x + KORAX_ARM_EXTENSION_SHORT * cos(ang);
2374	y = Origin.y + KORAX_ARM_EXTENSION_SHORT * sin(ang);
2375	z = Origin.z - FloorClip + KORAX_ARM4_HEIGHT;
2376	SpawnKoraxMissile(vector(x, y, z), Target, type);
2377}
2378
2379//============================================================================
2380// Arm 5 projectile
2381//============================================================================
2382
2383final void KoraxFire5(class<Actor> type)
2384{
2385	float ang;
2386	float x, y, z;
2387
2388	ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE);
2389	x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang);
2390	y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang);
2391	z = Origin.z - FloorClip + KORAX_ARM5_HEIGHT;
2392	SpawnKoraxMissile(vector(x, y, z), Target, type);
2393}
2394
2395//============================================================================
2396// Arm 6 projectile
2397//============================================================================
2398
2399final void KoraxFire6(class<Actor> type)
2400{
2401	float ang;
2402	float x, y, z;
2403
2404	ang = AngleMod360(Angles.yaw + KORAX_MISSILE_DELTA_ANGLE);
2405	x = Origin.x + KORAX_ARM_EXTENSION_LONG * cos(ang);
2406	y = Origin.y + KORAX_ARM_EXTENSION_LONG * sin(ang);
2407	z = Origin.z - FloorClip + KORAX_ARM6_HEIGHT;
2408	SpawnKoraxMissile(vector(x, y, z), Target, type);
2409}
2410
2411//============================================================================
2412//
2413//  A_KoraxMissile
2414//
2415//============================================================================
2416
2417final void A_KoraxMissile()
2418{
2419	class<Actor> mtype;
2420	name sound;
2421
2422	PlaySound('KoraxAttack', CHAN_VOICE);
2423
2424	switch (P_Random() % 6)
2425	{
2426	case 0:
2427		mtype = WraithFX1;
2428		sound = 'WraithMissileFire';
2429		break;
2430	case 1:
2431		mtype = Demon1FX1;
2432		sound = 'DemonMissileFire';
2433		break;
2434	case 2:
2435		mtype = Demon2FX1;
2436		sound = 'DemonMissileFire';
2437		break;
2438	case 3:
2439		mtype = FireDemonMissile;
2440		sound = 'FireDemonAttack';
2441		break;
2442	case 4:
2443		mtype = CentaurFX;
2444		sound = 'CentaurLeaderAttack';
2445		break;
2446	case 5:
2447		mtype = SerpentFX;
2448		sound = 'CentaurLeaderAttack';
2449		break;
2450	}
2451
2452	// Fire all 6 missiles at once
2453	PlaySound(sound, CHAN_VOICE, 1.0, ATTN_NONE);
2454	KoraxFire1(mtype);
2455	KoraxFire2(mtype);
2456	KoraxFire3(mtype);
2457	KoraxFire4(mtype);
2458	KoraxFire5(mtype);
2459	KoraxFire6(mtype);
2460}
2461
2462//============================================================================
2463// Call action code scripts (250-254)
2464//============================================================================
2465
2466final void A_KoraxCommand()
2467{
2468	float x, y, z;
2469	float ang;
2470	int numcommands;
2471
2472	PlaySound('KoraxCommand', CHAN_VOICE);
2473
2474	// Shoot stream of lightning to ceiling
2475	ang = AngleMod360(Angles.yaw - 90.0);
2476	x = Origin.x + KORAX_COMMAND_OFFSET * cos(ang);
2477	y = Origin.y + KORAX_COMMAND_OFFSET * sin(ang);
2478	z = Origin.z + KORAX_COMMAND_HEIGHT;
2479	Spawn(KoraxBolt, vector(x, y, z));
2480
2481	if (Health <= (default.Health >> 1))
2482	{
2483		numcommands = 5;
2484	}
2485	else
2486	{
2487		numcommands = 4;
2488	}
2489
2490	switch (P_Random() % numcommands)
2491	{
2492	case 0:
2493		XLevel.StartACS(250, 0, 0, 0, 0, self, NULL, 0, false, false);
2494		break;
2495	case 1:
2496		XLevel.StartACS(251, 0, 0, 0, 0, self, NULL, 0, false, false);
2497		break;
2498	case 2:
2499		XLevel.StartACS(252, 0, 0, 0, 0, self, NULL, 0, false, false);
2500		break;
2501	case 3:
2502		XLevel.StartACS(253, 0, 0, 0, 0, self, NULL, 0, false, false);
2503		break;
2504	case 4:
2505		XLevel.StartACS(254, 0, 0, 0, 0, self, NULL, 0, false, false);
2506		break;
2507	}
2508}
2509
2510//============================================================================
2511//
2512//  A_KoraxBonePop
2513//
2514//============================================================================
2515
2516final void A_KoraxBonePop()
2517{
2518	EntityEx mo;
2519
2520	// Spawn 6 spirits equalangularly
2521	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 0.0, 5.0 * 35.0);
2522	if (mo)
2523	{
2524		KSpiritInit(mo);
2525	}
2526	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 1.0, 5.0 * 35.0);
2527	if (mo)
2528	{
2529		KSpiritInit(mo);
2530	}
2531	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 2.0, 5.0 * 35.0);
2532	if (mo)
2533	{
2534		KSpiritInit(mo);
2535	}
2536	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 3.0, 5.0 * 35.0);
2537	if (mo)
2538	{
2539		KSpiritInit(mo);
2540	}
2541	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 4.0, 5.0 * 35.0);
2542	if (mo)
2543	{
2544		KSpiritInit(mo);
2545	}
2546	mo = SpawnMissileAngle(KoraxSpirit, 60.0 * 5.0, 5.0 * 35.0);
2547	if (mo)
2548	{
2549		KSpiritInit(mo);
2550	}
2551
2552	XLevel.StartACS(255, 0, 0, 0, 0, self, NULL, 0, false, false);	// Death script
2553}
2554
2555//==========================================================================
2556//
2557//  KSpiritInit
2558//
2559//==========================================================================
2560
2561final void KSpiritInit(EntityEx Spirit)
2562{
2563	int i;
2564	EntityEx tail;
2565	EntityEx next;
2566
2567	Spirit.Tracer = self;	// Swarm around korax
2568	Spirit.Special1 = 5 * 35 / 5;	// 5 seconds
2569	Spirit.Special2 = 32 + (P_Random() & 7);	// Float bob index
2570
2571	// Spawn a tail for spirit
2572	tail = Spawn(HolyTail, Spirit.Origin);
2573	tail.Target = Spirit;	// parent
2574	for (i = 1; i < 3; i++)
2575	{
2576		next = Spawn(HolyTailTrail, Spirit.Origin);
2577		tail.Tracer = next;
2578		tail = next;
2579	}
2580}
2581
2582//============================================================================
2583//
2584//  A_CHolyAttack2
2585//
2586//  Spawns the spirits
2587//
2588//============================================================================
2589
2590final void A_CHolyAttack2()
2591{
2592	int j;
2593	int i;
2594	Actor A;
2595	Actor tail;
2596	Actor next;
2597
2598	for (j = 0; j < 4; j++)
2599	{
2600		A = Spawn(HolySpirit, Origin);
2601		if (!A)
2602		{
2603			continue;
2604		}
2605		switch (j)
2606		{	// float bob index
2607		case 0:
2608			HolySpirit(A).WeaveZ = P_Random() & 7;	// upper-left
2609			break;
2610		case 1:
2611			HolySpirit(A).WeaveZ = 32 + (P_Random() & 7);	// upper-right
2612			break;
2613		case 2:
2614			HolySpirit(A).WeaveXY = 32 + (P_Random() & 7);	// lower-left
2615			break;
2616		case 3:
2617			HolySpirit(A).WeaveXY = 32 + (P_Random() & 7);
2618			HolySpirit(A).WeaveZ = 32 + (P_Random() & 7);
2619			break;
2620		}
2621		A.Origin.z = Origin.z;
2622		A.Angles.yaw = AngleMod360(Angles.yaw +
2623			(45.0 + 45.0 / 2.0) - 45.0 * itof(j));
2624		A.Thrust(A.Angles.yaw, A.Speed * Level.Game.frametime);
2625		A.Target = Target;
2626		A.Args[0] = 10;	// initial turn value
2627		A.Args[1] = 0;	// initial look angle
2628		if (Level.Game.deathmatch)
2629		{
2630			// Ghosts last slightly less longer in DeathMatch
2631			A.Health = 85;
2632		}
2633		if (Tracer)
2634		{
2635			A.Tracer = Tracer;
2636			//	Don't colide with world but colide with things, i.e explode
2637			A.bColideWithWorld = false;
2638			A.bSkullFly = true;
2639			A.bMissile = false;
2640		}
2641		tail = Spawn(HolyTail, A.Origin);
2642		tail.Target = A;	// parent
2643		for (i = 1; i < 3; i++)
2644		{
2645			next = Spawn(HolyTailTrail, A.Origin);
2646			tail.Tracer = next;
2647			tail = next;
2648		}
2649	}
2650}
2651
2652//===========================================================================
2653//
2654//  A_PoisonBagInit
2655//
2656//===========================================================================
2657
2658final void A_PoisonBagInit()
2659{
2660	Actor A;
2661
2662	A = Spawn(PoisonCloud, Origin + vector(0.0, 0.0, 28.0));
2663	if (A)
2664	{
2665		PoisonCloud(A).InitCloud(Actor(Target));
2666	}
2667}
2668
2669//===========================================================================
2670//
2671//  A_PoisonShroom
2672//
2673//===========================================================================
2674
2675final void A_PoisonShroom()
2676{
2677	StateTime = 4.0 + Random() * 16.0;
2678}
2679
2680//==========================================================================
2681//
2682//  A_FighterAttack
2683//
2684//==========================================================================
2685
2686final void A_FighterAttack()
2687{
2688	if (!Target)
2689		return;
2690
2691	SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw + 45.0 / 4.0), 0.0);
2692	SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw + 45.0 / 8.0), 0.0);
2693	SpawnMissileAngle(FSwordMissile, Angles.yaw, 0.0);
2694	SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw - 45.0 / 8.0), 0.0);
2695	SpawnMissileAngle(FSwordMissile, AngleMod360(Angles.yaw - 45.0 / 4.0), 0.0);
2696	PlaySound('FighterSwordFire', CHAN_WEAPON);
2697}
2698
2699//==========================================================================
2700//
2701//  A_ClericAttack
2702//
2703//  Spawns the spirits
2704//
2705//==========================================================================
2706
2707final void A_ClericAttack()
2708{
2709	EntityEx	A;
2710
2711	if (!Target)
2712		return;
2713	A = SpawnMissile(Target, HolyMissile, 40.0);
2714	if (A)
2715	{
2716		A.Tracer = Target;
2717	}
2718	PlaySound('HolySymbolFire', CHAN_WEAPON);
2719}
2720
2721//==========================================================================
2722//
2723//  MStaffSpawn2
2724//
2725//==========================================================================
2726
2727void MStaffSpawn2(float angle)
2728{
2729	EntityEx mo;
2730
2731	mo = SpawnMissileAngle(MageStaffFX2, angle, 0.0, 40.0);
2732	if (mo)
2733	{
2734		mo.Target = self;
2735		MageStaffFX2(mo).FindEnemy();
2736	}
2737}
2738
2739//==========================================================================
2740//
2741//  A_MageAttack
2742//
2743//==========================================================================
2744
2745final void A_MageAttack()
2746{
2747	if (!Target)
2748		return;
2749
2750	MStaffSpawn2(Angles.yaw);
2751	MStaffSpawn2(AngleMod360(Angles.yaw - 5.0));
2752	MStaffSpawn2(AngleMod360(Angles.yaw + 5.0));
2753	PlaySound('MageStaffFire', CHAN_WEAPON);
2754}
2755
2756//============================================================================
2757//
2758//  A_SorcererBishopEntry
2759//
2760//============================================================================
2761
2762final void A_SorcererBishopEntry()
2763{
2764	Spawn(SorcFX3Explosion, Origin);
2765	PlaySound(SightSound, CHAN_VOICE);
2766}
2767
2768//============================================================================
2769//
2770//  A_SpawnBishop
2771//
2772//  Green spell - spawn bishops
2773//
2774//============================================================================
2775
2776final void A_SpawnBishop()
2777{
2778	Actor A;
2779
2780	A = Spawn(Bishop, Origin);
2781	if (A)
2782	{
2783		if (!A.TestLocation())
2784		{
2785			A.SetState(none);
2786			Level.TotalKills--;
2787		}
2788		else if (Target)
2789		{
2790			A.CopyFriendliness(Target, true);
2791			A.Master = Target;
2792		}
2793	}
2794	SetState(none);
2795}
2796
2797//============================================================================
2798//
2799// Set balls to slow mode - actor is sorcerer
2800//
2801//============================================================================
2802
2803final void A_SlowBalls()
2804{
2805	Args[3] = SORC_DECELERATE;	// slow mode
2806	Args[2] = SORCBALL_INITIAL_SPEED;	// target speed
2807}
2808
2809//============================================================================
2810//
2811//	Set balls to speed mode - actor is sorcerer
2812//
2813//============================================================================
2814
2815final void A_SpeedBalls()
2816{
2817	Args[3] = SORC_ACCELERATE;	// speed mode
2818	Args[2] = SORCBALL_TERMINAL_SPEED;	// target speed
2819}
2820
2821//============================================================================
2822//
2823//  A_SorcBossAttack
2824//
2825//  Resume ball spinning
2826//
2827//============================================================================
2828
2829final void A_SorcBossAttack()
2830{
2831	Args[3] = SORC_ACCELERATE;
2832	Args[2] = SORCBALL_INITIAL_SPEED;
2833}
2834
2835//============================================================================
2836//
2837//  A_StopBalls
2838//
2839//  Instant stop when rotation gets to ball in _Special2
2840// actor is sorcerer
2841//
2842//============================================================================
2843
2844final void A_StopBalls()
2845{
2846	int chance = P_Random();
2847
2848	Args[3] = SORC_STOPPING;	// stopping mode
2849	Args[1] = 0;	// Reset rotation counter
2850
2851	if ((Args[0] <= 0) && (chance < 200))
2852	{
2853		SpecialCID = SorcBall2;	// Blue
2854	}
2855	else if ((Health < (default.Health >> 1)) && (chance < 200))
2856	{
2857		SpecialCID = SorcBall3;	// Green
2858	}
2859	else
2860	{
2861		SpecialCID = SorcBall1;	// Yellow
2862	}
2863}
2864
2865//============================================================================
2866//
2867//  A_SpawnFizzle
2868//
2869//  Spell cast magic fizzle
2870//
2871//============================================================================
2872
2873final void A_SpawnFizzle()
2874{
2875	TVec org;
2876	float dist = 5.0;
2877	float angle = Angles.yaw;
2878	float rangle;
2879	Actor A;
2880	int ix;
2881
2882	org.x = Origin.x + dist * cos(angle);
2883	org.y = Origin.y + dist * sin(angle);
2884	org.z = Origin.z - FloorClip + Height / 2.0;
2885	for (ix = 0; ix < 5; ix++)
2886	{
2887		A = Spawn(SorcSpark1, org);
2888		if (A)
2889		{
2890			rangle = angle + Random() * 5.0 * 90.0 / 1024.0;
2891			A.Velocity.x = Random() * Speed * cos(rangle);
2892			A.Velocity.y = Random() * Speed * sin(rangle);
2893			A.Velocity.z = 2.0 * 35.0;
2894		}
2895	}
2896}
2897
2898//==========================================================================
2899//
2900//  A_Summon
2901//
2902//  Summon Minotaur
2903//
2904//==========================================================================
2905
2906final void A_Summon()
2907{
2908	EntityEx A;
2909
2910	A = Spawn(MinotaurFriend, Origin,,, true);
2911	if (A)
2912	{
2913		if (!A.TestLocation() || !Tracer)
2914		{
2915			// Didn't fit - change back to artifact
2916			A.SetState(none);
2917			A = Spawn(ArtiDarkServant, Origin,,, true);
2918			if (A)
2919				A.bDropped = true;
2920			return;
2921		}
2922
2923		MinotaurFriend(A).StartTime = XLevel.Time;
2924		if (!Tracer.bCorpse)
2925		{
2926			//	Master isn't dead
2927			A.Tracer = Tracer;	// Pointer to master
2928			Powerup Power = Spawn(PowerMinotaur,,,, false);
2929			Power.bAlwaysPickup = true;
2930			if (!Power.TryPickup(Target))
2931			{
2932				Power.Destroy();
2933			}
2934			if (Tracer.bIsPlayer)
2935			{
2936				A.FriendPlayer = Tracer.Player.GetPlayerNum() + 1;
2937			}
2938		}
2939
2940		// Make smoke puff
2941		Spawn(MinotaurSmoke, Origin,,, true);
2942		PlaySound(A.ActiveSound, CHAN_VOICE);
2943	}
2944}
2945