From 0d1fbcf65fdc7b66140277343fba5c43728ea7a2 Mon Sep 17 00:00:00 2001
From: Christoph Oelckers <c.oelckers@zdoom.fake>
Date: Wed, 23 Mar 2016 14:03:10 +0100
Subject: [PATCH] - flattened the following commits by Major Cooke:

* Added falloff parameter to A_QuakeEx.
- Treated just like A_Explode's 'fullradiusdamage' parameter, where the quake will fall off from this distance on out to the edge. Default is 0, which means no falloff.
- Credits to MaxED and Michaelis for helping.

* - Added HighPoint parameter to QuakeEx.
- Allows fine tuning of where the quake's maximum or minimum occurs, in tics. This must be a range between [1, duration).
- For up or down scaling quakes, this sets the quake to reach maximum sooner or start minimizing later.
- For both, this indicates when the strongest will occur. Default is 0, or in the middle.

The original commits were nearly impossible to find in the convoluted commit tree, so I think it's preferable to have one clean commit instead.
---
 src/g_shared/a_quake.cpp          | 128 +++++++++++++++++++++++++-----
 src/g_shared/a_sharedglobal.h     |   6 +-
 src/p_acs.cpp                     |   4 +-
 src/p_spec.h                      |   2 +-
 src/r_utility.cpp                 |  19 +++--
 src/thingdef/thingdef_codeptr.cpp |   6 +-
 src/version.h                     |   2 +-
 wadsrc/static/actors/actor.txt    |   2 +-
 8 files changed, 131 insertions(+), 38 deletions(-)

diff --git a/src/g_shared/a_quake.cpp b/src/g_shared/a_quake.cpp
index 2e1c5fa16..c93c574a3 100644
--- a/src/g_shared/a_quake.cpp
+++ b/src/g_shared/a_quake.cpp
@@ -37,7 +37,7 @@ DEarthquake::DEarthquake()
 
 DEarthquake::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)
+						  double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint)
 						  : DThinker(STAT_EARTHQUAKE)
 {
 	m_QuakeSFX = quakesound;
@@ -54,6 +54,9 @@ DEarthquake::DEarthquake (AActor *center, int intensityX, int intensityY, int in
 	m_WaveSpeedX = (float)waveSpeedX;
 	m_WaveSpeedY = (float)waveSpeedY;
 	m_WaveSpeedZ = (float)waveSpeedZ;
+	m_Falloff = falloff << FRACBITS;
+	m_Highpoint = highpoint;
+	m_MiniCount = highpoint;
 }
 
 //==========================================================================
@@ -97,6 +100,14 @@ void DEarthquake::Serialize (FArchive &arc)
 	{
 		arc << m_WaveSpeedX << m_WaveSpeedY << m_WaveSpeedZ;
 	}
+	if (SaveVersion < 4534)
+	{
+		m_Falloff = m_Highpoint = m_MiniCount = 0;
+	}
+	else
+	{
+		arc << m_Falloff << m_Highpoint << m_MiniCount;
+	}
 }
 
 //==========================================================================
@@ -122,6 +133,7 @@ void DEarthquake::Tick ()
 	{
 		S_Sound (m_Spot, CHAN_BODY | CHAN_LOOP, m_QuakeSFX, 1, ATTN_NORM);
 	}
+
 	if (m_DamageRadius > 0)
 	{
 		for (i = 0; i < MAXPLAYERS; i++)
@@ -158,6 +170,8 @@ void DEarthquake::Tick ()
 		}
 	}
 	
+	if (m_MiniCount > 0)
+		m_MiniCount--;
 	if (--m_Countdown == 0)
 	{
 		if (S_IsActorPlayingSomething(m_Spot, CHAN_BODY, m_QuakeSFX))
@@ -168,12 +182,18 @@ void DEarthquake::Tick ()
 	}
 }
 
+//==========================================================================
+//
+// DEarthquake :: GetModWave
+//
+// QF_WAVE converts intensity into amplitude and unlocks a new property, the 
+// wave length. This is, in short, waves per second. Named waveMultiplier 
+// because that's as the name implies: adds more waves per second.
+//
+//==========================================================================
+
 fixed_t DEarthquake::GetModWave(double waveMultiplier) const
 {
-	//QF_WAVE converts intensity into amplitude and unlocks a new property, the wave length.
-	//This is, in short, waves per second (full cycles, mind you, from 0 to 360.)
-	//Named waveMultiplier because that's as the name implies: adds more waves per second.
-
 	double time = m_Countdown - FIXED2DBL(r_TicFrac);
 	return FLOAT2FIXED(sin(waveMultiplier * time * (M_PI * 2 / TICRATE)));
 }
@@ -189,35 +209,96 @@ fixed_t DEarthquake::GetModWave(double waveMultiplier) const
 fixed_t DEarthquake::GetModIntensity(fixed_t intensity) const
 {
 	assert(m_CountdownStart >= m_Countdown);
+
 	intensity += intensity;		// always doubled
 
 	if (m_Flags & (QF_SCALEDOWN | QF_SCALEUP))
 	{
+		// Adjustable maximums must use a range between 1 and m_CountdownStart to constrain between no quake and full quake.
+		bool check = !!(m_Highpoint > 0 && m_Highpoint < m_CountdownStart);
+		int divider = (check) ? m_Highpoint : m_CountdownStart;
 		int scalar;
+		
 		if ((m_Flags & (QF_SCALEDOWN | QF_SCALEUP)) == (QF_SCALEDOWN | QF_SCALEUP))
 		{
-			scalar = (m_Flags & QF_MAX) ? MAX(m_Countdown, m_CountdownStart - m_Countdown)
-				: MIN(m_Countdown, m_CountdownStart - m_Countdown);
+			if (check)
+			{
+				if (m_MiniCount > 0)
+					scalar = (m_Flags & QF_MAX) ? m_MiniCount : (m_Highpoint - m_MiniCount);
+				else
+				{
+					divider = m_CountdownStart - m_Highpoint;
+					scalar = (m_Flags & QF_MAX) ? (divider - m_Countdown) : m_Countdown;
+				}
+			}
+			else
+			{
+				// Defaults to middle of the road.
+				divider = m_CountdownStart;
+				scalar = (m_Flags & QF_MAX) ? MAX(m_Countdown, m_CountdownStart - m_Countdown)
+					: MIN(m_Countdown, m_CountdownStart - m_Countdown);
+			}
+			scalar = (scalar > divider) ? divider : scalar;
 
 			if (m_Flags & QF_FULLINTENSITY)
 			{
 				scalar *= 2;
 			}
 		}
-		else if (m_Flags & QF_SCALEDOWN)
+		else 
 		{
-			scalar = m_Countdown;
-		}
-		else			// QF_SCALEUP
-		{
-			scalar = m_CountdownStart - m_Countdown;
-		}
-		assert(m_CountdownStart > 0);
-		intensity = Scale(intensity, scalar, m_CountdownStart);
+			if (m_Flags & QF_SCALEDOWN)
+			{
+				scalar = m_Countdown;
+			}
+			else			// QF_SCALEUP
+			{ 
+				scalar = m_CountdownStart - m_Countdown;
+				if (m_Highpoint > 0)
+				{
+					if ((m_Highpoint - m_MiniCount) < divider)
+						scalar = m_Highpoint - m_MiniCount;
+					else
+						scalar = divider;
+				}
+			}
+			scalar = (scalar > divider) ? divider : scalar;			
+		}		
+		assert(divider > 0);
+		intensity = Scale(intensity, scalar, divider);
 	}
 	return intensity;
 }
 
+//==========================================================================
+//
+// DEarthquake :: GetFalloff
+//
+// Given the distance of the player from the quake, find the multiplier.
+// Process everything as doubles, and output again as fixed_t (mainly
+// because fixed_t was misbehaving here...)
+//
+//==========================================================================
+
+fixed_t DEarthquake::GetFalloff(fixed_t dist) const
+{
+	if ((dist < m_Falloff) || (m_Falloff >= m_TremorRadius) || (m_Falloff <= 0) || (m_TremorRadius - m_Falloff <= 0))
+	{ //Player inside the minimum falloff range, or safety check kicked in.
+		return FRACUNIT;
+	}
+	else if ((dist > m_Falloff) && (dist < m_TremorRadius))
+	{ //Player inside the radius, and outside the min distance for falloff.
+		fixed_t tremorsize = m_TremorRadius - m_Falloff;
+		fixed_t tremordist = dist - m_Falloff;
+		assert(tremorsize > 0);
+		return (FRACUNIT - FixedMul(FRACUNIT,tremordist) / tremorsize);
+	}
+	else 
+	{ //Shouldn't happen.
+		return FRACUNIT;
+	}
+}
+
 //==========================================================================
 //
 // DEarthquake::StaticGetQuakeIntensity
@@ -247,12 +328,16 @@ int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jigger
 			fixed_t dist = quake->m_Spot->AproxDistance (victim, true);
 			if (dist < quake->m_TremorRadius)
 			{
+				fixed_t falloff = quake->GetFalloff(dist);
 				++count;
 				fixed_t x = quake->GetModIntensity(quake->m_IntensityX);
 				fixed_t y = quake->GetModIntensity(quake->m_IntensityY);
 				fixed_t z = quake->GetModIntensity(quake->m_IntensityZ);
+				
+
 				if (!(quake->m_Flags & QF_WAVE))
 				{
+					jiggers.Falloff = MAX(falloff, jiggers.Falloff);
 					if (quake->m_Flags & QF_RELATIVE)
 					{
 						jiggers.RelIntensityX = MAX(x, jiggers.RelIntensityX);
@@ -268,6 +353,7 @@ int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jigger
 				}
 				else
 				{
+					jiggers.WFalloff = MAX(falloff, jiggers.WFalloff);
 					fixed_t mx = FixedMul(x, quake->GetModWave(quake->m_WaveSpeedX));
 					fixed_t my = FixedMul(y, quake->GetModWave(quake->m_WaveSpeedY));
 					fixed_t mz = FixedMul(z, quake->GetModWave(quake->m_WaveSpeedZ));
@@ -304,7 +390,7 @@ int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jigger
 
 bool 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)
+	double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint)
 {
 	AActor *center;
 	bool res = false;
@@ -318,7 +404,7 @@ bool P_StartQuakeXYZ(AActor *activator, int tid, int intensityX, int intensityY,
 		if (activator != NULL)
 		{
 			new DEarthquake(activator, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
-				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ);
+				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint);
 			return true;
 		}
 	}
@@ -329,7 +415,7 @@ bool P_StartQuakeXYZ(AActor *activator, int tid, int intensityX, int intensityY,
 		{
 			res = true;
 			new DEarthquake(center, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
-				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ);
+				quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint);
 		}
 	}
 	
@@ -337,6 +423,6 @@ bool P_StartQuakeXYZ(AActor *activator, int tid, int intensityX, int intensityY,
 }
 
 bool P_StartQuake(AActor *activator, int tid, int intensity, int duration, int damrad, int tremrad, FSoundID quakesfx)
-{	//Maintains original behavior by passing 0 to intensityZ, and flags.
-	return P_StartQuakeXYZ(activator, tid, intensity, intensity, 0, duration, damrad, tremrad, quakesfx, 0, 0, 0, 0);
+{	//Maintains original behavior by passing 0 to intensityZ, flags, and everything else after QSFX.
+	return P_StartQuakeXYZ(activator, tid, intensity, intensity, 0, duration, damrad, tremrad, quakesfx, 0, 0, 0, 0, 0, 0);
 }
diff --git a/src/g_shared/a_sharedglobal.h b/src/g_shared/a_sharedglobal.h
index 1e59bf740..1d5fd4ba5 100644
--- a/src/g_shared/a_sharedglobal.h
+++ b/src/g_shared/a_sharedglobal.h
@@ -157,6 +157,7 @@ struct FQuakeJiggers
 	int RelIntensityX, RelIntensityY, RelIntensityZ;
 	int OffsetX, OffsetY, OffsetZ;
 	int RelOffsetX, RelOffsetY, RelOffsetZ;
+	int Falloff, WFalloff;
 };
 
 class DEarthquake : public DThinker
@@ -166,7 +167,7 @@ class DEarthquake : public DThinker
 public:
 	DEarthquake(AActor *center, int intensityX, int intensityY, int intensityZ, int duration,
 		int damrad, int tremrad, FSoundID quakesfx, int flags, 
-		double waveSpeedX, double waveSpeedY, double waveSpeedZ);
+		double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint);
 
 	void Serialize (FArchive &arc);
 	void Tick ();
@@ -178,9 +179,12 @@ public:
 	int m_Flags;
 	fixed_t m_IntensityX, m_IntensityY, m_IntensityZ;
 	float m_WaveSpeedX, m_WaveSpeedY, m_WaveSpeedZ;
+	fixed_t m_Falloff;
+	int m_Highpoint, m_MiniCount;
 
 	fixed_t GetModIntensity(int intensity) const;
 	fixed_t GetModWave(double waveMultiplier) const;
+	fixed_t GetFalloff(fixed_t dist) const;
 
 	static int StaticGetQuakeIntensities(AActor *viewer, FQuakeJiggers &jiggers);
 
diff --git a/src/p_acs.cpp b/src/p_acs.cpp
index 11b169e6a..fe10921d0 100644
--- a/src/p_acs.cpp
+++ b/src/p_acs.cpp
@@ -5747,7 +5747,9 @@ doplaysound:			if (funcIndex == ACSF_PlayActorSound)
 				argCount > 8 ? args[8] : 0,
 				argCount > 9 ? FIXED2DBL(args[9]) : 1.0, 
 				argCount > 10 ? FIXED2DBL(args[10]) : 1.0, 
-				argCount > 11 ? FIXED2DBL(args[11]) : 1.0 );
+				argCount > 11 ? FIXED2DBL(args[11]) : 1.0, 
+				argCount > 12 ? args[12] : 0,
+				argCount > 13 ? args[13] : 0);
 		}
 
 		case ACSF_SetLineActivation:
diff --git a/src/p_spec.h b/src/p_spec.h
index e3b1c05ad..83a50e252 100644
--- a/src/p_spec.h
+++ b/src/p_spec.h
@@ -944,7 +944,7 @@ void P_DoDeferedScripts (void);
 //
 // [RH] p_quake.c
 //
-bool 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);
+bool 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, int falloff, int highpoint);
 bool P_StartQuake(AActor *activator, int tid, int intensity, int duration, int damrad, int tremrad, FSoundID quakesfx);
 
 #endif
diff --git a/src/r_utility.cpp b/src/r_utility.cpp
index f4bd85a9a..a5a422426 100644
--- a/src/r_utility.cpp
+++ b/src/r_utility.cpp
@@ -896,10 +896,9 @@ void R_AddInterpolationPoint(const fixedvec3a &vec)
 //
 //==========================================================================
 
-static fixed_t QuakePower(fixed_t factor, fixed_t intensity, fixed_t offset)
+static fixed_t QuakePower(fixed_t factor, fixed_t intensity, fixed_t offset, fixed_t falloff, fixed_t wfalloff)
 { 
 	fixed_t randumb;
-
 	if (intensity == 0)
 	{
 		randumb = 0;
@@ -908,7 +907,8 @@ static fixed_t QuakePower(fixed_t factor, fixed_t intensity, fixed_t offset)
 	{
 		randumb = pr_torchflicker(intensity * 2) - intensity;
 	}
-	return FixedMul(factor, randumb + offset);
+	fixed_t rn2 = (FixedMul(wfalloff,offset) + FixedMul(falloff, randumb));
+	return FixedMul(factor, rn2);
 }
 
 //==========================================================================
@@ -1050,34 +1050,33 @@ void R_SetupFrame (AActor *actor)
 			if ((jiggers.RelIntensityX | jiggers.RelOffsetX) != 0)
 			{
 				int ang = (camera->angle) >> ANGLETOFINESHIFT;
-				fixed_t power = QuakePower(quakefactor, jiggers.RelIntensityX, jiggers.RelOffsetX);
+				fixed_t power = QuakePower(quakefactor, jiggers.RelIntensityX, jiggers.RelOffsetX, jiggers.Falloff, jiggers.WFalloff);
 				viewx += FixedMul(finecosine[ang], power);
 				viewy += FixedMul(finesine[ang], power);
 			}
 			if ((jiggers.RelIntensityY | jiggers.RelOffsetY) != 0)
 			{
 				int ang = (camera->angle + ANG90) >> ANGLETOFINESHIFT;
-				fixed_t power = QuakePower(quakefactor, jiggers.RelIntensityY, jiggers.RelOffsetY);
+				fixed_t power = QuakePower(quakefactor, jiggers.RelIntensityY, jiggers.RelOffsetY, jiggers.Falloff, jiggers.WFalloff);
 				viewx += FixedMul(finecosine[ang], power);
 				viewy += FixedMul(finesine[ang], power);
 			}
 			// FIXME: Relative Z is not relative
-			// [MC]On it! Will be introducing pitch after QF_WAVE.
 			if ((jiggers.RelIntensityZ | jiggers.RelOffsetZ) != 0)
 			{
-				viewz += QuakePower(quakefactor, jiggers.RelIntensityZ, jiggers.RelOffsetZ);
+				viewz += QuakePower(quakefactor, jiggers.RelIntensityZ, jiggers.RelOffsetZ, jiggers.Falloff, jiggers.WFalloff);
 			}
 			if ((jiggers.IntensityX | jiggers.OffsetX) != 0)
 			{
-				viewx += QuakePower(quakefactor, jiggers.IntensityX, jiggers.OffsetX);
+				viewx += QuakePower(quakefactor, jiggers.IntensityX, jiggers.OffsetX, jiggers.Falloff, jiggers.WFalloff);
 			}
 			if ((jiggers.IntensityY | jiggers.OffsetY) != 0)
 			{
-				viewy += QuakePower(quakefactor, jiggers.IntensityY, jiggers.OffsetY);
+				viewy += QuakePower(quakefactor, jiggers.IntensityY, jiggers.OffsetY, jiggers.Falloff, jiggers.WFalloff);
 			}
 			if ((jiggers.IntensityZ | jiggers.OffsetZ) != 0)
 			{
-				viewz += QuakePower(quakefactor, jiggers.IntensityZ, jiggers.OffsetZ);
+				viewz += QuakePower(quakefactor, jiggers.IntensityZ, jiggers.OffsetZ, jiggers.Falloff, jiggers.WFalloff);
 			}
 		}
 	}
diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp
index c5081f57a..9a492130b 100644
--- a/src/thingdef/thingdef_codeptr.cpp
+++ b/src/thingdef/thingdef_codeptr.cpp
@@ -4969,7 +4969,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Quake)
 // A_QuakeEx
 //
 // Extended version of A_Quake. Takes individual axis into account and can
-// take a flag.
+// take flags.
 //===========================================================================
 
 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_QuakeEx)
@@ -4986,7 +4986,9 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_QuakeEx)
 	PARAM_FLOAT_OPT(mulWaveX) { mulWaveX = 1.; }
 	PARAM_FLOAT_OPT(mulWaveY) { mulWaveY = 1.; }
 	PARAM_FLOAT_OPT(mulWaveZ) { mulWaveZ = 1.; }
-	P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ);
+	PARAM_INT_OPT(falloff) { falloff = 0; }
+	PARAM_INT_OPT(highpoint) { highpoint = 0; }
+	P_StartQuakeXYZ(self, 0, intensityX, intensityY, intensityZ, duration, damrad, tremrad, sound, flags, mulWaveX, mulWaveY, mulWaveZ, falloff, highpoint);
 	return 0;
 }
 
diff --git a/src/version.h b/src/version.h
index 764675f0b..17fd288b1 100644
--- a/src/version.h
+++ b/src/version.h
@@ -76,7 +76,7 @@ const char *GetVersionString();
 
 // Use 4500 as the base git save version, since it's higher than the
 // SVN revision ever got.
-#define SAVEVER 4533
+#define SAVEVER 4534
 
 #define SAVEVERSTRINGIFY2(x) #x
 #define SAVEVERSTRINGIFY(x) SAVEVERSTRINGIFY2(x)
diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt
index aeeaec813..e4a9b152b 100644
--- a/wadsrc/static/actors/actor.txt
+++ b/wadsrc/static/actors/actor.txt
@@ -277,7 +277,7 @@ ACTOR Actor native //: Thinker
 	action native A_SetUserArray(name varname, int index, int value);
 	action native A_SetSpecial(int spec, int arg0 = 0, int arg1 = 0, int arg2 = 0, int arg3 = 0, int arg4 = 0);
 	action native A_Quake(int intensity, int duration, int damrad, int tremrad, sound sfx = "world/quake");
-	action native A_QuakeEx(int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad, sound sfx = "world/quake", int flags = 0, float mulWaveX = 1, float mulWaveY = 1, float mulWaveZ = 1);
+	action native A_QuakeEx(int intensityX, int intensityY, int intensityZ, int duration, int damrad, int tremrad, sound sfx = "world/quake", int flags = 0, float mulWaveX = 1, float mulWaveY = 1, float mulWaveZ = 1, int falloff = 0, int highpoint = 0);
 	action native A_SetTics(int tics);
 	action native A_SetDamageType(name damagetype);
 	action native A_DropItem(class<Actor> item, int dropamount = -1, int chance = 256);