vkdoom_m/src/playsim/mapthinkers/a_quake.cpp
Christoph Oelckers 06466b9017 add missing include to a_quake.cpp.
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'
2023-10-08 12:14:50 +02:00

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);
}