The GCC AppImage build reported /usr/bin/ld: CMakeFiles/zdoom.dir/playsim/mapthinkers/a_quake.cpp.o: in function `AActor::Distance2D(AActor*, bool) const': a_quake.cpp:(.text._ZNK6AActor10Distance2DEPS_b[_ZNK6AActor10Distance2DEPS_b]+0x2c): undefined reference to `AActor::PosRelative(AActor const*) const'
469 lines
14 KiB
C++
469 lines
14 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// 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
|
|
//
|
|
|
|
|
|
#include "doomtype.h"
|
|
#include "doomstat.h"
|
|
#include "p_local.h"
|
|
#include "actor.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "serializer.h"
|
|
#include "serialize_obj.h"
|
|
#include "d_player.h"
|
|
#include "r_utility.h"
|
|
#include "g_levellocals.h"
|
|
#include "actorinlines.h"
|
|
#include <p_maputl.h>
|
|
|
|
static FRandom pr_quake ("Quake");
|
|
|
|
IMPLEMENT_CLASS(DEarthquake, false, true)
|
|
|
|
IMPLEMENT_POINTERS_START(DEarthquake)
|
|
IMPLEMENT_POINTER(m_Spot)
|
|
IMPLEMENT_POINTERS_END
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: DEarthquake public constructor
|
|
//
|
|
//==========================================================================
|
|
|
|
void DEarthquake::Construct(AActor *center, double intensityX, double intensityY, double intensityZ, int duration,
|
|
int damrad, int tremrad, FSoundID quakesound, int flags,
|
|
double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint,
|
|
double rollIntensity, double rollWave, double damageMultiplier, double thrustMultiplier, int damage)
|
|
{
|
|
m_QuakeSFX = quakesound;
|
|
m_Spot = center;
|
|
// Radii are specified in tile units (64 pixels)
|
|
m_DamageRadius = damrad;
|
|
m_TremorRadius = tremrad;
|
|
m_Intensity = DVector3(intensityX, intensityY, intensityZ);
|
|
m_CountdownStart = duration;
|
|
m_Countdown = duration;
|
|
m_Flags = flags;
|
|
m_WaveSpeed = DVector3(waveSpeedX, waveSpeedY, waveSpeedZ);
|
|
m_Falloff = falloff;
|
|
m_Highpoint = highpoint;
|
|
m_MiniCount = highpoint;
|
|
m_RollIntensity = rollIntensity;
|
|
m_RollWave = rollWave;
|
|
m_DamageMultiplier = damageMultiplier;
|
|
m_ThrustMultiplier = thrustMultiplier;
|
|
m_Damage = damage;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: Serialize
|
|
//
|
|
//==========================================================================
|
|
|
|
void DEarthquake::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
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);
|
|
("damagemultiplier", m_DamageMultiplier);
|
|
("thrustmultiplier", m_ThrustMultiplier);
|
|
("damage", m_Damage);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: Tick
|
|
//
|
|
// Deals damage to any actors 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, CHANF_LOOP, m_QuakeSFX, 1, ATTN_NORM);
|
|
}
|
|
|
|
if (m_DamageRadius > 0)
|
|
{
|
|
if (m_Flags & QF_AFFECTACTORS)
|
|
{
|
|
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
|
|
FMultiBlockThingsIterator it(check,m_Spot,m_DamageRadius,false);
|
|
FMultiBlockThingsIterator::CheckResult cres;
|
|
|
|
while (it.Next(&cres))
|
|
{
|
|
AActor *mo = cres.thing;
|
|
if (mo == nullptr || mo == m_Spot) //Ignore null references and the earthquake origin.
|
|
continue;
|
|
|
|
DoQuakeDamage(this, mo, !!(m_Flags & QF_DAMAGEFALLOFF));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
if (Level->PlayerInGame(i) && !(Level->Players[i]->cheats & CF_NOCLIP))
|
|
{
|
|
AActor* victim = Level->Players[i]->mo;
|
|
DoQuakeDamage(this, victim, !!(m_Flags & QF_DAMAGEFALLOFF));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_MiniCount > 0)
|
|
m_MiniCount--;
|
|
if (--m_Countdown == 0)
|
|
{
|
|
if (S_IsActorPlayingSomething(m_Spot, CHAN_BODY, m_QuakeSFX))
|
|
{
|
|
S_StopSound(m_Spot, CHAN_BODY);
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: DoQuakeDamage
|
|
//
|
|
// Handles performing earthquake damage and thrust to the specified victim.
|
|
//
|
|
//==========================================================================
|
|
|
|
void DEarthquake::DoQuakeDamage(DEarthquake *quake, AActor *victim, bool falloff) const
|
|
{
|
|
double dist;
|
|
double thrustfalloff;
|
|
int damage;
|
|
|
|
if (!quake || !victim) return;
|
|
|
|
dist = quake->m_Spot->Distance2D(victim, true);
|
|
thrustfalloff = falloff ? GetFalloff(dist, m_DamageRadius) : 1.0;
|
|
// Check if in damage radius
|
|
if (dist < m_DamageRadius && victim->Z() <= victim->floorz)
|
|
{
|
|
if (!(quake->m_Flags & QF_SHAKEONLY) && pr_quake() < 50)
|
|
{
|
|
if (m_Damage < 1)
|
|
damage = falloff ? (int)(pr_quake.HitDice(1) * GetFalloff(dist, m_DamageRadius) * m_DamageMultiplier) : (int)(pr_quake.HitDice(1) * m_DamageMultiplier);
|
|
//[inkoalawetrust] Do the exact specified damage.
|
|
else
|
|
damage = falloff ? (int)(m_Damage * GetFalloff(dist, m_DamageRadius) * m_DamageMultiplier) : (int)(m_Damage * m_DamageMultiplier);
|
|
|
|
damage = damage < 1 ? 1 : damage; //Do at least a tiny bit of damage when in radius.
|
|
|
|
P_DamageMobj(victim, NULL, NULL, damage, NAME_Quake);
|
|
}
|
|
// Thrust pushable actor around
|
|
if (!(victim->flags7 & MF7_DONTTHRUST) && m_ThrustMultiplier > 0)
|
|
{
|
|
DAngle an = victim->Angles.Yaw + DAngle::fromDeg(pr_quake());
|
|
victim->Vel.X += m_Intensity.X * an.Cos() * m_ThrustMultiplier * thrustfalloff;
|
|
victim->Vel.Y += m_Intensity.Y * an.Sin() * m_ThrustMultiplier * thrustfalloff;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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
|
|
{
|
|
double time = m_Countdown - ticFrac;
|
|
return g_sin(waveMultiplier * time * (M_PI * 2 / TICRATE));
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: GetModIntensity
|
|
//
|
|
// Given a base intensity, modify it according to the quake's flags.
|
|
//
|
|
//==========================================================================
|
|
|
|
double DEarthquake::GetModIntensity(double intensity, bool fake) 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))
|
|
{
|
|
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 (!fake && (m_Flags & QF_FULLINTENSITY))
|
|
{
|
|
scalar *= 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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 = intensity * scalar / divider;
|
|
}
|
|
return intensity;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DEarthquake :: GetFalloff
|
|
//
|
|
// Given the distance of the actor from the quake, find the multiplier.
|
|
//
|
|
//==========================================================================
|
|
|
|
double DEarthquake::GetFalloff(double dist, double radius) const
|
|
{
|
|
if ((dist < m_Falloff) || (m_Falloff >= radius) || (m_Falloff <= 0) || (radius - m_Falloff <= 0))
|
|
{ //Actor inside the minimum falloff range, or safety check kicked in.
|
|
return 1.;
|
|
}
|
|
else if ((dist > m_Falloff) && (dist < radius))
|
|
{ //Actor inside the radius, and outside the min distance for falloff.
|
|
double tremorsize = radius - m_Falloff;
|
|
assert(tremorsize > 0);
|
|
return (1. - ((dist - m_Falloff) / tremorsize));
|
|
}
|
|
else
|
|
{ //Shouldn't happen.
|
|
return 1.;
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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)
|
|
{
|
|
if (victim->player != NULL && (victim->player->cheats & CF_NOCLIP))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
auto iterator = victim->Level->GetThinkerIterator<DEarthquake>(NAME_None, STAT_EARTHQUAKE);
|
|
DEarthquake *quake;
|
|
int count = 0;
|
|
|
|
while ( (quake = iterator.Next()) != nullptr)
|
|
{
|
|
if (quake->m_Spot != nullptr && !(quake->m_Flags & QF_GROUNDONLY && victim->Z() > victim->floorz))
|
|
{
|
|
double dist;
|
|
|
|
if (quake->m_Flags & QF_3D) dist = quake->m_Spot->Distance3D(victim, true);
|
|
else dist = quake->m_Spot->Distance2D(victim, true);
|
|
|
|
if (dist < quake->m_TremorRadius)
|
|
{
|
|
++count;
|
|
const double falloff = quake->GetFalloff(dist, quake->m_TremorRadius);
|
|
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);
|
|
|
|
if (!(quake->m_Flags & QF_WAVE))
|
|
{
|
|
jiggers.RollIntensity = max(r, jiggers.RollIntensity) * falloff;
|
|
|
|
intensity *= falloff;
|
|
if (quake->m_Flags & QF_RELATIVE)
|
|
{
|
|
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);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
jiggers.RollWave = r * quake->GetModWave(ticFrac, quake->m_RollWave) * falloff * strength;
|
|
|
|
|
|
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;
|
|
|
|
// [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.
|
|
|
|
// [MC] Now does so. And they stack rather well. I'm a little
|
|
// surprised at how easy it was.
|
|
|
|
|
|
if (quake->m_Flags & QF_RELATIVE)
|
|
{
|
|
jiggers.RelOffset += intensity;
|
|
}
|
|
else
|
|
{
|
|
jiggers.Offset += intensity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// P_StartQuake
|
|
//
|
|
//==========================================================================
|
|
|
|
bool P_StartQuakeXYZ(FLevelLocals *Level, AActor *activator, int tid, double intensityX, double intensityY, double intensityZ, int duration,
|
|
int damrad, int tremrad, FSoundID quakesfx, int flags,
|
|
double waveSpeedX, double waveSpeedY, double waveSpeedZ, int falloff, int highpoint,
|
|
double rollIntensity, double rollWave, double damageMultiplier, double thrustMultiplier, int damage)
|
|
{
|
|
AActor *center;
|
|
bool res = false;
|
|
|
|
intensityX = clamp<double>(intensityX, 0.0, 9.0);
|
|
intensityY = clamp<double>(intensityY, 0.0, 9.0);
|
|
intensityZ = clamp<double>(intensityZ, 0.0, 9.0);
|
|
|
|
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, damageMultiplier, thrustMultiplier, damage);
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto iterator = Level->GetActorIterator(tid);
|
|
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, damageMultiplier, thrustMultiplier, damage);
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
bool P_StartQuake(FLevelLocals * Level, AActor * activator, int tid, double 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, 1.0, 0.5, 0);
|
|
}
|