1//**************************************************************************
2//**
3//**    ##   ##    ##    ##   ##   ####     ####   ###     ###
4//**    ##   ##  ##  ##  ##   ##  ##  ##   ##  ##  ####   ####
5//**     ## ##  ##    ##  ## ##  ##    ## ##    ## ## ## ## ##
6//**     ## ##  ########  ## ##  ##    ## ##    ## ##  ###  ##
7//**      ###   ##    ##   ###    ##  ##   ##  ##  ##       ##
8//**       #    ##    ##    #      ####     ####   ##       ##
9//**
10//**    $Id: SorcBall.vc 3794 2008-09-21 20:42:02Z dj_jl $
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
26class SorcBall : Actor
27	abstract;
28
29//  Heresiarch Ball Variables
30//      Special1f       Previous angle of ball (for woosh)
31//      Special2        Countdown of rapid fire (FX4)
32//      Args[0]         If set, don't play the bounce sound when bouncing
33float BallAngleOffset;
34
35//==========================================================================
36//
37//  FloorBounceMissile
38//
39//==========================================================================
40
41void FloorBounceMissile()
42{
43	Velocity.z = Velocity.z * -0.7;
44	Velocity.x = 2.0 * Velocity.x / 3.0;
45	Velocity.y = 2.0 * Velocity.y / 3.0;
46	if (SightSound && !Args[0])
47	{
48		PlaySound(SightSound, CHAN_VOICE);
49	}
50}
51
52//==========================================================================
53//
54//	GetExplodeParms
55//
56//==========================================================================
57
58void GetExplodeParms(out int damage, out float distance, out byte damageSelf)
59{
60	// Sorcerer balls
61	distance = 255.0;
62	damage = 255;
63	Args[0] = 1;	// don't play bounce
64}
65
66//==========================================================================
67//
68//  A_AccelBalls
69//
70//  Increase ball orbit speed - actor is ball
71//
72//==========================================================================
73
74final void A_AccelBalls()
75{
76	EntityEx sorc = Target;
77
78	if (sorc.Args[4] < sorc.Args[2])
79	{
80		sorc.Args[4]++;
81	}
82	else
83	{
84		sorc.Args[3] = SORC_NORMAL;
85		if (sorc.Args[4] >= SORCBALL_TERMINAL_SPEED && !sorc.bIsPlayer)
86		{
87			// Reached terminal velocity - stop balls
88			Actor(sorc).A_StopBalls();
89		}
90	}
91}
92
93//==========================================================================
94//
95//  A_DecelBalls
96//
97//  Decrease ball orbit speed - actor is ball
98//
99//==========================================================================
100
101final void A_DecelBalls()
102{
103	EntityEx sorc = Target;
104
105	if (sorc.Args[4] > sorc.Args[2])
106	{
107		sorc.Args[4]--;
108	}
109	else
110	{
111		sorc.Args[3] = SORC_NORMAL;
112	}
113}
114
115//==========================================================================
116//
117//  A_SorcUpdateBallAngle
118//
119//  Update angle if first ball - actor is ball
120//
121//==========================================================================
122
123final void A_SorcUpdateBallAngle()
124{
125	if (Class == SorcBall1)
126	{
127		Actor(Target).Special1f = AngleMod360(Actor(Target).Special1f + itof(Target.Args[4]));
128	}
129}
130
131//==========================================================================
132//
133//  A_SorcOffense2
134//
135//  Actor is ball
136//
137//==========================================================================
138
139final void A_SorcOffense2()
140{
141	float ang1;
142	EntityEx mo;
143	float delta, index;
144	float dist;
145
146	index = itof(Args[4]) * 360.0 / 256.0;
147	Args[4] = (Args[4] + 15) & 0xff;
148	delta = sin(index) * SORCFX4_SPREAD_ANGLE;
149	ang1 = AngleMod360(Angles.yaw + delta);
150	mo = Actor(Target).SpawnMissileAngle(SorcFX4, ang1, 0.0);
151	if (mo)
152	{
153		Actor(mo).Special2 = 35 * 5 / 2;	// 5 seconds
154		dist = Target.Target.DistTo2(mo);
155		dist = dist / mo.Speed;
156		if (dist < 1.0)
157			dist = 1.0;
158		mo.Velocity.z = (Target.Target.Origin.z - mo.Origin.z) / dist;
159	}
160}
161
162//==========================================================================
163//
164//  A_CastSorcererSpell
165//
166//  Actor is ball.
167//
168//==========================================================================
169
170void A_CastSorcererSpell()
171{
172}
173
174//==========================================================================
175//
176//  A_SorcBallOrbit
177//
178//==========================================================================
179
180final void A_SorcBallOrbit()
181{
182	if (!Target)
183	{
184		//	Heresiarch dissapeared.
185		Destroy();
186		return;
187	}
188
189	float angle, baseangle;
190	int mode = Target.Args[3];
191	float dist = Target.Radius - (Radius * 2.0);
192	float prevangle = Special1f;
193
194	if (Target.Health <= 0)
195		SetState(FindState('Pain'));
196
197	baseangle = Actor(Target).Special1f;
198	angle = AngleMod360(baseangle + BallAngleOffset);
199	Angles.yaw = angle;
200
201	switch (mode)
202	{
203	case SORC_NORMAL:	// Balls rotating normally
204		A_SorcUpdateBallAngle();
205		break;
206	case SORC_DECELERATE:	// Balls decelerating
207		A_DecelBalls();
208		A_SorcUpdateBallAngle();
209		break;
210	case SORC_ACCELERATE:	// Balls accelerating
211		A_AccelBalls();
212		A_SorcUpdateBallAngle();
213		break;
214	case SORC_STOPPING:	// Balls stopping
215		if ((Actor(Target).SpecialCID == Class) &&
216			(Target.Args[1] > SORCBALL_SPEED_ROTATIONS) &&
217			(fabs(AngleMod180(angle - Target.Angles.yaw)) <
218			15.0 * 45.0 / 16.0))
219		{
220			// Can stop now
221			Target.Args[3] = SORC_FIRESPELL;
222			Target.Args[4] = 0;
223			// Set angle so ball angle == sorcerer angle
224			Actor(Target).Special1f = AngleMod360(Target.Angles.yaw -
225				BallAngleOffset);
226		}
227		else
228		{
229			A_SorcUpdateBallAngle();
230		}
231		break;
232	case SORC_FIRESPELL:	// Casting spell
233		if (Actor(Target).SpecialCID == Class)
234		{
235			// Put sorcerer into special throw spell anim
236			if (Target.Health > 0)
237				Target.SetState(Target.FindState('Attack1'));
238
239			PlaySound('SorcererSpellCast', CHAN_VOICE, 1.0, ATTN_NONE);
240
241			A_CastSorcererSpell();
242		}
243		break;
244	case SORC_FIRING_SPELL:
245		if (Actor(Target).SpecialCID == Class)
246		{
247			if (Special2-- <= 0)
248			{
249				// Done rapid firing
250				Target.Args[3] = SORC_STOPPED;
251				// Back to orbit balls
252				if (Target.Health > 0)
253					Target.SetState(Target.FindState('Attack2'));
254			}
255			else
256			{
257				// Do rapid fire spell
258				A_SorcOffense2();
259			}
260		}
261		break;
262	case SORC_STOPPED:	// Balls stopped
263	default:
264		break;
265	}
266
267	if ((angle < prevangle) && (Target.Args[4] == SORCBALL_TERMINAL_SPEED))
268	{
269		Target.Args[1]++;	// Bump rotation counter
270		// Completed full rotation - make woosh sound
271		PlaySound('SorcererBallWoosh', CHAN_VOICE, 1.0, ATTN_NONE);
272	}
273	Special1f = angle;	// Set previous angle
274	Origin.x = Target.Origin.x + dist * cos(angle);
275	Origin.y = Target.Origin.y + dist * sin(angle);
276	Origin.z = Target.Origin.z - Target.FloorClip + Target.Height;
277}
278
279//==========================================================================
280//
281//  A_SorcBallPop
282//
283//  Ball death - spawn stuff.
284//
285//==========================================================================
286
287final void A_SorcBallPop()
288{
289	PlaySound('SorcererBallPop', CHAN_VOICE, 1.0, ATTN_NONE);
290	bNoGravity = false;
291	Gravity = 0.125;
292	Velocity.x = (Random() * 10.0 - 5.0) * 35.0;
293	Velocity.y = (Random() * 10.0 - 5.0) * 35.0;
294	Velocity.z = (2.0 + Random() * 3.0) * 35.0;
295	Args[4] = BOUNCE_TIME_UNIT;	// Bounce time unit
296	Args[3] = 5;	// Bounce time in seconds
297}
298
299//==========================================================================
300//
301//  A_BounceCheck
302//
303//==========================================================================
304
305final void A_BounceCheck()
306{
307	if (Args[4]-- <= 0)
308	{
309		if (Args[3]-- <= 0)
310		{
311			SetState(FindState('Death'));
312			PlaySound('SorcererBigBallExplode', CHAN_VOICE, 1.0, ATTN_NONE);
313		}
314		else
315		{
316			Args[4] = BOUNCE_TIME_UNIT;
317		}
318	}
319}
320
321defaultproperties
322{
323	Radius = 5.0;
324	Height = 5.0;
325	Speed = 350.0;
326	BounceType = BOUNCE_Hexen;
327	bMissile = true;
328	bNoBlockmap = true;
329	bDropOff = true;
330	bNoGravity = true;
331	bNoTeleport = true;
332	bBloodSplatter = true;
333	bNoWallBounceSnd = true;
334	bFullVolDeath = true;
335	SightSound = 'SorcererBallBounce';
336	DeathSound = 'SorcererBigBallExplode';
337}
338