qzdoom/src/g_shared/a_quake.cpp

378 lines
10 KiB
C++
Raw Normal View History

2016-03-01 15:47:10 +00:00
#include "templates.h"
#include "doomtype.h"
#include "doomstat.h"
#include "p_local.h"
#include "actor.h"
#include "m_bbox.h"
#include "m_random.h"
#include "s_sound.h"
#include "a_sharedglobal.h"
#include "statnums.h"
#include "farchive.h"
#include "d_player.h"
#include "r_utility.h"
static FRandom pr_quake ("Quake");
IMPLEMENT_POINTY_CLASS (DEarthquake)
DECLARE_POINTER (m_Spot)
END_POINTERS
//==========================================================================
//
// DEarthquake :: DEarthquake private constructor
//
//==========================================================================
DEarthquake::DEarthquake()
: DThinker(STAT_EARTHQUAKE)
{
}
//==========================================================================
//
// DEarthquake :: DEarthquake public constructor
//
//==========================================================================
2016-03-23 19:45:48 +00:00
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, int falloff, int highpoint)
: DThinker(STAT_EARTHQUAKE)
2016-03-01 15:47:10 +00:00
{
m_QuakeSFX = quakesound;
m_Spot = center;
// Radii are specified in tile units (64 pixels)
2016-03-23 19:45:48 +00:00
m_DamageRadius = damrad;
m_TremorRadius = tremrad;
m_Intensity = DVector3(intensityX, intensityY, intensityZ);
2016-03-01 15:47:10 +00:00
m_CountdownStart = duration;
m_Countdown = duration;
m_Flags = flags;
2016-03-23 19:45:48 +00:00
m_WaveSpeed = DVector3(waveSpeedX, waveSpeedY, waveSpeedZ);
m_Falloff = falloff;
m_Highpoint = highpoint;
m_MiniCount = highpoint;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// DEarthquake :: Serialize
//
//==========================================================================
void DEarthquake::Serialize (FArchive &arc)
{
Super::Serialize (arc);
2016-03-23 19:45:48 +00:00
arc << m_Spot << m_Intensity << m_Countdown
2016-03-01 15:47:10 +00:00
<< m_TremorRadius << m_DamageRadius
2016-03-23 19:45:48 +00:00
<< m_QuakeSFX << m_Flags << m_CountdownStart
<< m_WaveSpeed
<< m_Falloff << m_Highpoint << m_MiniCount;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// DEarthquake :: Tick
//
// Deals damage to any players near the earthquake and makes sure it's
// making noise.
//
//==========================================================================
void DEarthquake::Tick ()
{
int i;
if (m_Spot == NULL)
{
Destroy ();
return;
}
if (!S_IsActorPlayingSomething (m_Spot, CHAN_BODY, m_QuakeSFX))
{
S_Sound (m_Spot, CHAN_BODY | CHAN_LOOP, m_QuakeSFX, 1, ATTN_NORM);
}
2016-03-01 15:47:10 +00:00
if (m_DamageRadius > 0)
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && !(players[i].cheats & CF_NOCLIP))
{
AActor *victim = players[i].mo;
2016-03-23 19:45:48 +00:00
double dist;
2016-03-01 15:47:10 +00:00
2016-03-23 19:45:48 +00:00
dist = m_Spot->Distance2D(victim, true);
// Check if in damage radius
2016-03-20 18:52:35 +00:00
if (dist < m_DamageRadius && victim->Z() <= victim->floorz)
2016-03-01 15:47:10 +00:00
{
if (pr_quake() < 50)
{
P_DamageMobj (victim, NULL, NULL, pr_quake.HitDice (1), NAME_None);
}
// Thrust player around
DAngle an = victim->Angles.Yaw + pr_quake();
2016-03-23 19:45:48 +00:00
victim->Vel.X += m_Intensity.X * an.Cos() * 0.5;
victim->Vel.Y += m_Intensity.Y * an.Sin() * 0.5;
2016-03-01 15:47:10 +00:00
}
}
}
}
if (m_MiniCount > 0)
m_MiniCount--;
2016-03-01 15:47:10 +00:00
if (--m_Countdown == 0)
{
if (S_IsActorPlayingSomething(m_Spot, CHAN_BODY, m_QuakeSFX))
{
S_StopSound(m_Spot, CHAN_BODY);
}
Destroy();
}
}
//==========================================================================
//
// 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.
//
//==========================================================================
2016-03-23 19:45:48 +00:00
double DEarthquake::GetModWave(double waveMultiplier) const
2016-03-01 15:47:10 +00:00
{
double time = m_Countdown - r_TicFracF;
2016-03-23 19:45:48 +00:00
return g_sin(waveMultiplier * time * (M_PI * 2 / TICRATE));
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// DEarthquake :: GetModIntensity
//
// Given a base intensity, modify it according to the quake's flags.
//
//==========================================================================
2016-03-23 19:45:48 +00:00
double DEarthquake::GetModIntensity(double intensity) const
2016-03-01 15:47:10 +00:00
{
assert(m_CountdownStart >= m_Countdown);
2016-03-01 15:47:10 +00:00
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;
2016-03-01 15:47:10 +00:00
int scalar;
2016-03-01 15:47:10 +00:00
if ((m_Flags & (QF_SCALEDOWN | QF_SCALEUP)) == (QF_SCALEDOWN | QF_SCALEUP))
{
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;
2016-03-01 15:47:10 +00:00
if (m_Flags & QF_FULLINTENSITY)
{
scalar *= 2;
}
}
else
2016-03-01 15:47:10 +00:00
{
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);
2016-03-23 19:45:48 +00:00
intensity = intensity * scalar / divider;
2016-03-01 15:47:10 +00:00
}
return intensity;
}
//==========================================================================
//
// DEarthquake :: GetFalloff
//
// Given the distance of the player from the quake, find the multiplier.
//
//==========================================================================
2016-03-23 19:45:48 +00:00
double DEarthquake::GetFalloff(double 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.
2016-03-23 19:45:48 +00:00
return 1.;
}
else if ((dist > m_Falloff) && (dist < m_TremorRadius))
{ //Player inside the radius, and outside the min distance for falloff.
2016-03-23 19:45:48 +00:00
double tremorsize = m_TremorRadius - m_Falloff;
double tremordist = dist - m_Falloff;
assert(tremorsize > 0);
2016-03-23 19:45:48 +00:00
return (1. - tremordist) / tremorsize;
}
else
{ //Shouldn't happen.
2016-03-23 19:45:48 +00:00
return 1.;
}
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// DEarthquake::StaticGetQuakeIntensity
//
// Searches for all quakes near the victim and returns their combined
// intensity.
//
// Pre: jiggers was pre-zeroed by the caller.
//
//==========================================================================
int DEarthquake::StaticGetQuakeIntensities(AActor *victim, FQuakeJiggers &jiggers)
{
if (victim->player != NULL && (victim->player->cheats & CF_NOCLIP))
{
return 0;
}
TThinkerIterator<DEarthquake> iterator(STAT_EARTHQUAKE);
DEarthquake *quake;
int count = 0;
while ( (quake = iterator.Next()) != NULL)
{
if (quake->m_Spot != NULL)
{
2016-03-23 19:45:48 +00:00
double dist = quake->m_Spot->Distance2D (victim, true);
2016-03-01 15:47:10 +00:00
if (dist < quake->m_TremorRadius)
{
2016-03-23 19:45:48 +00:00
double falloff = quake->GetFalloff(dist);
2016-03-01 15:47:10 +00:00
++count;
2016-03-23 19:45:48 +00:00
double x = quake->GetModIntensity(quake->m_Intensity.X);
double y = quake->GetModIntensity(quake->m_Intensity.Y);
double z = quake->GetModIntensity(quake->m_Intensity.Z);
2016-03-01 15:47:10 +00:00
if (!(quake->m_Flags & QF_WAVE))
{
jiggers.Falloff = MAX(falloff, jiggers.Falloff);
2016-03-01 15:47:10 +00:00
if (quake->m_Flags & QF_RELATIVE)
{
2016-03-23 19:45:48 +00:00
jiggers.RelIntensity.X = MAX(x, jiggers.RelIntensity.X);
jiggers.RelIntensity.Y = MAX(y, jiggers.RelIntensity.Y);
jiggers.RelIntensity.Z = MAX(z, jiggers.RelIntensity.Z);
2016-03-01 15:47:10 +00:00
}
else
{
2016-03-23 19:45:48 +00:00
jiggers.Intensity.X = MAX(x, jiggers.Intensity.X);
jiggers.Intensity.Y = MAX(y, jiggers.Intensity.Y);
jiggers.Intensity.Z = MAX(z, jiggers.Intensity.Z);
2016-03-01 15:47:10 +00:00
}
}
else
{
jiggers.WFalloff = MAX(falloff, jiggers.WFalloff);
2016-03-23 19:45:48 +00:00
double mx = x * quake->GetModWave(quake->m_WaveSpeed.X);
double my = y * quake->GetModWave(quake->m_WaveSpeed.Y);
double mz = z * quake->GetModWave(quake->m_WaveSpeed.Z);
2016-03-01 15:47:10 +00:00
// [RH] This only gives effect to the last sine quake. I would
// prefer if some way was found to make multiples coexist
// peacefully, but just summing them together is undesirable
// because they could cancel each other out depending on their
// relative phases.
if (quake->m_Flags & QF_RELATIVE)
{
2016-03-23 19:45:48 +00:00
jiggers.RelOffset.X = mx;
jiggers.RelOffset.Y = my;
jiggers.RelOffset.Z = mz;
2016-03-01 15:47:10 +00:00
}
else
{
2016-03-23 19:45:48 +00:00
jiggers.Offset.X = mx;
jiggers.Offset.Y = my;
jiggers.Offset.Z = mz;
2016-03-01 15:47:10 +00:00
}
}
}
}
}
return count;
}
//==========================================================================
//
// P_StartQuake
//
//==========================================================================
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)
2016-03-01 15:47:10 +00:00
{
AActor *center;
bool res = false;
if (intensityX) intensityX = clamp(intensityX, 1, 9);
if (intensityY) intensityY = clamp(intensityY, 1, 9);
if (intensityZ) intensityZ = clamp(intensityZ, 1, 9);
if (tid == 0)
{
if (activator != NULL)
{
new DEarthquake(activator, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint);
2016-03-01 15:47:10 +00:00
return true;
}
}
else
{
FActorIterator iterator (tid);
while ( (center = iterator.Next ()) )
{
res = true;
new DEarthquake(center, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint);
2016-03-01 15:47:10 +00:00
}
}
return res;
}
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, flags, and everything else after QSFX.
return P_StartQuakeXYZ(activator, tid, intensity, intensity, 0, duration, damrad, tremrad, quakesfx, 0, 0, 0, 0, 0, 0);
2016-03-01 15:47:10 +00:00
}