qzdoom/src/g_shared/a_quake.cpp

409 lines
12 KiB
C++
Raw Normal View History

//-----------------------------------------------------------------------------
//
// Copyright 1994-1996 Raven Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//-----------------------------------------------------------------------------
//
// Hexen's earthquake system, significantly enhanced
//
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 "a_sharedglobal.h"
2016-09-19 17:58:04 +00:00
#include "serializer.h"
2016-03-01 15:47:10 +00:00
#include "d_player.h"
#include "r_utility.h"
#include "g_levellocals.h"
2016-03-01 15:47:10 +00:00
static FRandom pr_quake ("Quake");
IMPLEMENT_CLASS(DEarthquake, false, true)
IMPLEMENT_POINTERS_START(DEarthquake)
IMPLEMENT_POINTER(m_Spot)
IMPLEMENT_POINTERS_END
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// DEarthquake :: DEarthquake public constructor
//
//==========================================================================
void DEarthquake::Construct(AActor *center, int intensityX, int intensityY, int intensityZ, int duration,
2016-03-23 19:45:48 +00:00
int damrad, int tremrad, FSoundID quakesound, int flags,
double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint,
double rollIntensity, double rollWave)
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;
m_RollIntensity = rollIntensity;
m_RollWave = rollWave;
2016-03-01 15:47:10 +00:00
}
//==========================================================================
//
// DEarthquake :: Serialize
//
//==========================================================================
2016-09-19 17:58:04 +00:00
void DEarthquake::Serialize(FSerializer &arc)
2016-03-01 15:47:10 +00:00
{
Super::Serialize (arc);
2016-09-19 17:58:04 +00:00
arc("spot", m_Spot)
("intensity", m_Intensity)
("countdown", m_Countdown)
("tremorradius", m_TremorRadius)
("damageradius", m_DamageRadius)
("quakesfx", m_QuakeSFX)
("quakeflags", m_Flags)
("countdownstart", m_CountdownStart)
("wavespeed", m_WaveSpeed)
("falloff", m_Falloff)
("highpoint", m_Highpoint)
("minicount", m_MiniCount)
("rollintensity", m_RollIntensity)
("rollwave", m_RollWave);
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_Quake);
2016-03-01 15:47:10 +00:00
}
// 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.
//
//==========================================================================
double DEarthquake::GetModWave(double ticFrac, double waveMultiplier) const
2016-03-01 15:47:10 +00:00
{
double time = m_Countdown - ticFrac;
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-09-04 21:49:57 +00:00
double DEarthquake::GetModIntensity(double intensity, bool fake) 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
2016-09-04 21:49:57 +00:00
if (!fake && (m_Flags & QF_FULLINTENSITY))
2016-03-01 15:47:10 +00:00
{
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;
assert(tremorsize > 0);
return (1. - ((dist - m_Falloff) / 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(double ticFrac, AActor *victim, FQuakeJiggers &jiggers)
2016-03-01 15:47:10 +00:00
{
if (victim->player != NULL && (victim->player->cheats & CF_NOCLIP))
{
return 0;
}
TThinkerIterator<DEarthquake> iterator(STAT_EARTHQUAKE);
DEarthquake *quake;
int count = 0;
2016-09-04 21:49:57 +00:00
while ( (quake = iterator.Next()) != nullptr)
2016-03-01 15:47:10 +00:00
{
2016-09-04 21:49:57 +00:00
if (quake->m_Spot != nullptr)
2016-03-01 15:47:10 +00:00
{
2016-09-04 21:49:57 +00:00
const double dist = quake->m_Spot->Distance2D(victim, true);
2016-03-01 15:47:10 +00:00
if (dist < quake->m_TremorRadius)
{
++count;
2016-09-04 21:49:57 +00:00
const double falloff = quake->GetFalloff(dist);
const double r = quake->GetModIntensity(quake->m_RollIntensity);
const double strength = quake->GetModIntensity(1.0, true);
DVector3 intensity;
intensity.X = quake->GetModIntensity(quake->m_Intensity.X);
intensity.Y = quake->GetModIntensity(quake->m_Intensity.Y);
intensity.Z = quake->GetModIntensity(quake->m_Intensity.Z);
2016-03-01 15:47:10 +00:00
if (!(quake->m_Flags & QF_WAVE))
{
jiggers.RollIntensity = MAX(r, jiggers.RollIntensity) * falloff;
2016-09-04 21:49:57 +00:00
intensity *= falloff;
2016-03-01 15:47:10 +00:00
if (quake->m_Flags & QF_RELATIVE)
{
2016-09-04 21:49:57 +00:00
jiggers.RelIntensity.X = MAX(intensity.X, jiggers.RelIntensity.X);
jiggers.RelIntensity.Y = MAX(intensity.Y, jiggers.RelIntensity.Y);
jiggers.RelIntensity.Z = MAX(intensity.Z, jiggers.RelIntensity.Z);
2016-03-01 15:47:10 +00:00
}
else
{
2016-09-04 21:49:57 +00:00
jiggers.Intensity.X = MAX(intensity.X, jiggers.Intensity.X);
jiggers.Intensity.Y = MAX(intensity.Y, jiggers.Intensity.Y);
jiggers.Intensity.Z = MAX(intensity.Z, jiggers.Intensity.Z);
2016-03-01 15:47:10 +00:00
}
}
else
{
jiggers.RollWave = r * quake->GetModWave(ticFrac, quake->m_RollWave) * falloff * strength;
2016-09-04 21:49:57 +00:00
intensity.X *= quake->GetModWave(ticFrac, quake->m_WaveSpeed.X);
intensity.Y *= quake->GetModWave(ticFrac, quake->m_WaveSpeed.Y);
intensity.Z *= quake->GetModWave(ticFrac, quake->m_WaveSpeed.Z);
intensity *= strength * falloff;
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.
2016-09-04 21:49:57 +00:00
// [MC] Now does so. And they stack rather well. I'm a little
// surprised at how easy it was.
2016-03-01 15:47:10 +00:00
if (quake->m_Flags & QF_RELATIVE)
{
2016-09-04 21:49:57 +00:00
jiggers.RelOffset += intensity;
2016-03-01 15:47:10 +00:00
}
else
{
2016-09-04 21:49:57 +00:00
jiggers.Offset += intensity;
2016-03-01 15:47:10 +00:00
}
}
}
}
}
return count;
}
//==========================================================================
//
// P_StartQuake
//
//==========================================================================
bool P_StartQuakeXYZ(FLevelLocals *Level, AActor *activator, int tid, int intensityX, int intensityY, int intensityZ, int duration,
2016-03-01 15:47:10 +00:00
int damrad, int tremrad, FSoundID quakesfx, int flags,
double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint,
double rollIntensity, double rollWave)
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)
{
Level->CreateThinker<DEarthquake>(activator, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint, rollIntensity, rollWave);
2016-03-01 15:47:10 +00:00
return true;
}
}
else
{
auto iterator = Level->GetActorIterator(tid);
2016-03-01 15:47:10 +00:00
while ( (center = iterator.Next ()) )
{
res = true;
Level->CreateThinker<DEarthquake>(center, intensityX, intensityY, intensityZ, duration, damrad, tremrad,
quakesfx, flags, waveSpeedX, waveSpeedY, waveSpeedZ, falloff, highpoint, rollIntensity, rollWave);
2016-03-01 15:47:10 +00:00
}
}
return res;
}
bool P_StartQuake(FLevelLocals *Level, 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(Level, activator, tid, intensity, intensity, 0, duration, damrad, tremrad, quakesfx, 0, 0, 0, 0, 0, 0, 0, 0);
2016-03-01 15:47:10 +00:00
}