1 #include "templates.h"
2 #include "doomtype.h"
3 #include "doomstat.h"
4 #include "p_local.h"
5 #include "actor.h"
6 #include "m_bbox.h"
7 #include "m_random.h"
8 #include "s_sound.h"
9 #include "a_sharedglobal.h"
10 #include "statnums.h"
11 #include "farchive.h"
12 
13 static FRandom pr_quake ("Quake");
14 
15 IMPLEMENT_POINTY_CLASS (DEarthquake)
DECLARE_POINTER(m_Spot)16  DECLARE_POINTER (m_Spot)
17 END_POINTERS
18 
19 //==========================================================================
20 //
21 // DEarthquake :: DEarthquake private constructor
22 //
23 //==========================================================================
24 
25 DEarthquake::DEarthquake()
26 : DThinker(STAT_EARTHQUAKE)
27 {
28 }
29 
30 //==========================================================================
31 //
32 // DEarthquake :: DEarthquake public constructor
33 //
34 //==========================================================================
35 
DEarthquake(AActor * center,int intensityX,int intensityY,int intensityZ,int duration,int damrad,int tremrad,FSoundID quakesound,int flags,double waveSpeedX,double waveSpeedY,double waveSpeedZ)36 DEarthquake::DEarthquake (AActor *center, int intensityX, int intensityY, int intensityZ, int duration,
37 						  int damrad, int tremrad, FSoundID quakesound, int flags,
38 						  double waveSpeedX, double waveSpeedY, double waveSpeedZ)
39 						  : DThinker(STAT_EARTHQUAKE)
40 {
41 	m_QuakeSFX = quakesound;
42 	m_Spot = center;
43 	// Radii are specified in tile units (64 pixels)
44 	m_DamageRadius = damrad << FRACBITS;
45 	m_TremorRadius = tremrad << FRACBITS;
46 	m_IntensityX = intensityX << FRACBITS;
47 	m_IntensityY = intensityY << FRACBITS;
48 	m_IntensityZ = intensityZ << FRACBITS;
49 	m_CountdownStart = duration;
50 	m_Countdown = duration;
51 	m_Flags = flags;
52 	m_WaveSpeedX = (float)waveSpeedX;
53 	m_WaveSpeedY = (float)waveSpeedY;
54 	m_WaveSpeedZ = (float)waveSpeedZ;
55 }
56 
57 //==========================================================================
58 //
59 // DEarthquake :: Serialize
60 //
61 //==========================================================================
62 
Serialize(FArchive & arc)63 void DEarthquake::Serialize (FArchive &arc)
64 {
65 	Super::Serialize (arc);
66 	arc << m_Spot << m_IntensityX << m_Countdown
67 		<< m_TremorRadius << m_DamageRadius
68 		<< m_QuakeSFX;
69 	if (SaveVersion < 4519)
70 	{
71 		m_IntensityY = m_IntensityX;
72 		m_IntensityZ = 0;
73 		m_Flags = 0;
74 	}
75 	else
76 	{
77 		arc << m_IntensityY << m_IntensityZ << m_Flags;
78 	}
79 	if (SaveVersion < 4520)
80 	{
81 		m_CountdownStart = 0;
82 	}
83 	else
84 	{
85 		arc << m_CountdownStart;
86 	}
87 	if (SaveVersion < 4521)
88 	{
89 		m_WaveSpeedX = m_WaveSpeedY = m_WaveSpeedZ = 0;
90 		m_IntensityX <<= FRACBITS;
91 		m_IntensityY <<= FRACBITS;
92 		m_IntensityZ <<= FRACBITS;
93 	}
94 	else
95 	{
96 		arc << m_WaveSpeedX << m_WaveSpeedY << m_WaveSpeedZ;
97 	}
98 }
99 
100 //==========================================================================
101 //
102 // DEarthquake :: Tick
103 //
104 // Deals damage to any players near the earthquake and makes sure it's
105 // making noise.
106 //
107 //==========================================================================
108 
Tick()109 void DEarthquake::Tick ()
110 {
111 	int i;
112 
113 	if (m_Spot == NULL)
114 	{
115 		Destroy ();
116 		return;
117 	}
118 
119 	if (!S_IsActorPlayingSomething (m_Spot, CHAN_BODY, m_QuakeSFX))
120 	{
121 		S_Sound (m_Spot, CHAN_BODY | CHAN_LOOP, m_QuakeSFX, 1, ATTN_NORM);
122 	}
123 	if (m_DamageRadius > 0)
124 	{
125 		for (i = 0; i < MAXPLAYERS; i++)
126 		{
127 			if (playeringame[i] && !(players[i].cheats & CF_NOCLIP))
128 			{
129 				AActor *victim = players[i].mo;
130 				fixed_t dist;
131 
132 				dist = m_Spot->AproxDistance (victim, true);
133 				// Check if in damage radius
134 				if (dist < m_DamageRadius && victim->Z() <= victim->floorz)
135 				{
136 					if (pr_quake() < 50)
137 					{
138 						P_DamageMobj (victim, NULL, NULL, pr_quake.HitDice (1), NAME_None);
139 					}
140 					// Thrust player around
141 					angle_t an = victim->angle + ANGLE_1*pr_quake();
142 					if (m_IntensityX == m_IntensityY)
143 					{ // Thrust in a circle
144 						P_ThrustMobj (victim, an, m_IntensityX << (FRACBITS-1));
145 					}
146 					else
147 					{ // Thrust in an ellipse
148 						an >>= ANGLETOFINESHIFT;
149 						// So this is actually completely wrong, but it ought to be good
150 						// enough. Otherwise, I'd have to use tangents and square roots.
151 						victim->velx += FixedMul(m_IntensityX << (FRACBITS-1), finecosine[an]);
152 						victim->vely += FixedMul(m_IntensityY << (FRACBITS-1), finesine[an]);
153 					}
154 				}
155 			}
156 		}
157 	}
158 
159 	if (--m_Countdown == 0)
160 	{
161 		if (S_IsActorPlayingSomething(m_Spot, CHAN_BODY, m_QuakeSFX))
162 		{
163 			S_StopSound(m_Spot, CHAN_BODY);
164 		}
165 		Destroy();
166 	}
167 }
168 
GetModWave(double waveMultiplier) const169 fixed_t DEarthquake::GetModWave(double waveMultiplier) const
170 {
171 	//QF_WAVE converts intensity into amplitude and unlocks a new property, the wave length.
172 	//This is, in short, waves per second (full cycles, mind you, from 0 to 360.)
173 	//Named waveMultiplier because that's as the name implies: adds more waves per second.
174 
175 	double time = m_Countdown - FIXED2DBL(r_TicFrac);
176 	return FLOAT2FIXED(sin(waveMultiplier * time * (M_PI * 2 / TICRATE)));
177 }
178 
179 //==========================================================================
180 //
181 // DEarthquake :: GetModIntensity
182 //
183 // Given a base intensity, modify it according to the quake's flags.
184 //
185 //==========================================================================
186 
GetModIntensity(fixed_t intensity) const187 fixed_t DEarthquake::GetModIntensity(fixed_t intensity) const
188 {
189 	assert(m_CountdownStart >= m_Countdown);
190 	intensity += intensity;		// always doubled
191 
192 	if (m_Flags & (QF_SCALEDOWN | QF_SCALEUP))
193 	{
194 		int scalar;
195 		if ((m_Flags & (QF_SCALEDOWN | QF_SCALEUP)) == (QF_SCALEDOWN | QF_SCALEUP))
196 		{
197 			scalar = (m_Flags & QF_MAX) ? MAX(m_Countdown, m_CountdownStart - m_Countdown)
198 				: MIN(m_Countdown, m_CountdownStart - m_Countdown);
199 
200 			if (m_Flags & QF_FULLINTENSITY)
201 			{
202 				scalar *= 2;
203 			}
204 		}
205 		else if (m_Flags & QF_SCALEDOWN)
206 		{
207 			scalar = m_Countdown;
208 		}
209 		else			// QF_SCALEUP
210 		{
211 			scalar = m_CountdownStart - m_Countdown;
212 		}
213 		assert(m_CountdownStart > 0);
214 		intensity = Scale(intensity, scalar, m_CountdownStart);
215 	}
216 	return intensity;
217 }
218 
219 //==========================================================================
220 //
221 // DEarthquake::StaticGetQuakeIntensity
222 //
223 // Searches for all quakes near the victim and returns their combined
224 // intensity.
225 //
226 // Pre: jiggers was pre-zeroed by the caller.
227 //
228 //==========================================================================
229 
StaticGetQuakeIntensities(AActor * victim,FQuakeJiggers & jiggers)230 int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jiggers)
231 {
232 	if (victim->player != NULL && (victim->player->cheats & CF_NOCLIP))
233 	{
234 		return 0;
235 	}
236 
237 	TThinkerIterator<DEarthquake> iterator(STAT_EARTHQUAKE);
238 	DEarthquake *quake;
239 	int count = 0;
240 
241 	while ( (quake = iterator.Next()) != NULL)
242 	{
243 		if (quake->m_Spot != NULL)
244 		{
245 			fixed_t dist = quake->m_Spot->AproxDistance (victim, true);
246 			if (dist < quake->m_TremorRadius)
247 			{
248 				++count;
249 				fixed_t x = quake->GetModIntensity(quake->m_IntensityX);
250 				fixed_t y = quake->GetModIntensity(quake->m_IntensityY);
251 				fixed_t z = quake->GetModIntensity(quake->m_IntensityZ);
252 				if (!(quake->m_Flags & QF_WAVE))
253 				{
254 					if (quake->m_Flags & QF_RELATIVE)
255 					{
256 						jiggers.RelIntensityX = MAX(x, jiggers.RelIntensityX);
257 						jiggers.RelIntensityY = MAX(y, jiggers.RelIntensityY);
258 						jiggers.RelIntensityZ = MAX(z, jiggers.RelIntensityZ);
259 					}
260 					else
261 					{
262 						jiggers.IntensityX = MAX(x, jiggers.IntensityX);
263 						jiggers.IntensityY = MAX(y, jiggers.IntensityY);
264 						jiggers.IntensityZ = MAX(z, jiggers.IntensityZ);
265 					}
266 				}
267 				else
268 				{
269 					fixed_t mx = FixedMul(x, quake->GetModWave(quake->m_WaveSpeedX));
270 					fixed_t my = FixedMul(y, quake->GetModWave(quake->m_WaveSpeedY));
271 					fixed_t mz = FixedMul(z, quake->GetModWave(quake->m_WaveSpeedZ));
272 
273 					// [RH] This only gives effect to the last sine quake. I would
274 					// prefer if some way was found to make multiples coexist
275 					// peacefully, but just summing them together is undesirable
276 					// because they could cancel each other out depending on their
277 					// relative phases.
278 					if (quake->m_Flags & QF_RELATIVE)
279 					{
280 						jiggers.RelOffsetX = mx;
281 						jiggers.RelOffsetY = my;
282 						jiggers.RelOffsetZ = mz;
283 					}
284 					else
285 					{
286 						jiggers.OffsetX = mx;
287 						jiggers.OffsetY = my;
288 						jiggers.OffsetZ = mz;
289 					}
290 				}
291 			}
292 		}
293 	}
294 	return count;
295 }
296 
297 //==========================================================================
298 //
299 // P_StartQuake
300 //
301 //==========================================================================
302 
P_StartQuakeXYZ(AActor * activator,int tid,int intensityX,int intensityY,int intensityZ,int duration,int damrad,int tremrad,FSoundID quakesfx,int flags,double waveSpeedX,double waveSpeedY,double waveSpeedZ)303 bool P_StartQuakeXYZ(AActor *activator, int tid, int intensityX, int intensityY, int intensityZ, int duration,
304 	int damrad, int tremrad, FSoundID quakesfx, int flags,
305 	double waveSpeedX, double waveSpeedY, double waveSpeedZ)
306 {
307 	AActor *center;
308 	bool res = false;
309 
310 	if (intensityX)		intensityX = clamp(intensityX, 1, 9);
311 	if (intensityY)		intensityY = clamp(intensityY, 1, 9);
312 	if (intensityZ)		intensityZ = clamp(intensityZ, 1, 9);
313 
314 	if (tid == 0)
315 	{
316 		if (activator != NULL)
317 		{
318 			new DEarthquake(activator, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
319 				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ);
320 			return true;
321 		}
322 	}
323 	else
324 	{
325 		FActorIterator iterator (tid);
326 		while ( (center = iterator.Next ()) )
327 		{
328 			res = true;
329 			new DEarthquake(center, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
330 				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ);
331 		}
332 	}
333 
334 	return res;
335 }
336 
P_StartQuake(AActor * activator,int tid,int intensity,int duration,int damrad,int tremrad,FSoundID quakesfx)337 bool P_StartQuake(AActor *activator, int tid, int intensity, int duration, int damrad, int tremrad, FSoundID quakesfx)
338 {	//Maintains original behavior by passing 0 to intensityZ, and flags.
339 	return P_StartQuakeXYZ(activator, tid, intensity, intensity, 0, duration, damrad, tremrad, quakesfx, 0, 0, 0, 0);
340 }
341