The way this was done was a major headache inducer, requiring reconstruction of the function each time the value was changed and in general made actor damage a major hassle. There was a DECORATE wrapper to mimic the original behavior but this looked quite broken because it completely ignored the different semantics of both damage calculation types. It also made it impossible to determine if damage was a function or a value. This accessor has been reverted to what it should be, only returning the constant, which now is -1 for a damage function. I am sorry if this may break the odd mod out but a quick look over some DECORATE-heavy stuff showed that this was never combined in any of them so that accessing 'damage' in DECORATE code depended on an actual damage function. To get proper damage, a future commit will add a DECORATE function which calls AActor::GetMissileDamage.
325 lines
7.9 KiB
C++
325 lines
7.9 KiB
C++
/*
|
|
#include "actor.h"
|
|
#include "info.h"
|
|
#include "m_random.h"
|
|
#include "p_local.h"
|
|
#include "p_enemy.h"
|
|
#include "s_sound.h"
|
|
#include "statnums.h"
|
|
#include "a_specialspot.h"
|
|
#include "thingdef/thingdef.h"
|
|
#include "doomstat.h"
|
|
#include "g_level.h"
|
|
*/
|
|
|
|
static FRandom pr_brainscream ("BrainScream");
|
|
static FRandom pr_brainexplode ("BrainExplode");
|
|
static FRandom pr_spawnfly ("SpawnFly");
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BrainAwake)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
// killough 3/26/98: only generates sound now
|
|
S_Sound (self, CHAN_VOICE, "brain/sight", 1, ATTN_NONE);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BrainPain)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
S_Sound (self, CHAN_VOICE, "brain/pain", 1, ATTN_NONE);
|
|
return 0;
|
|
}
|
|
|
|
static void BrainishExplosion (const DVector3 &pos)
|
|
{
|
|
AActor *boom = Spawn("Rocket", pos, NO_REPLACE);
|
|
if (boom != NULL)
|
|
{
|
|
boom->DeathSound = "misc/brainexplode";
|
|
boom->Vel.Z = pr_brainscream() /128.;
|
|
|
|
PClassActor *cls = PClass::FindActor("BossBrain");
|
|
if (cls != NULL)
|
|
{
|
|
FState *state = cls->FindState(NAME_Brainexplode);
|
|
if (state != NULL)
|
|
boom->SetState (state);
|
|
}
|
|
boom->effects = 0;
|
|
boom->SetDamage(0); // disables collision detection which is not wanted here
|
|
boom->tics -= pr_brainscream() & 7;
|
|
if (boom->tics < 1)
|
|
boom->tics = 1;
|
|
}
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BrainScream)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
for (double x = -196; x < +320; x += 8)
|
|
{
|
|
// (1 / 512.) is actually what the original value of 128 did, even though it probably meant 128 map units.
|
|
BrainishExplosion(self->Vec2OffsetZ(x, -320, (1 / 512.) + pr_brainexplode() * 2));
|
|
}
|
|
S_Sound (self, CHAN_VOICE, "brain/death", 1, ATTN_NONE);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BrainExplode)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
double x = pr_brainexplode.Random2() / 32.;
|
|
DVector3 pos = self->Vec2OffsetZ(x, 0, 1 / 512. + pr_brainexplode() * 2);
|
|
BrainishExplosion(pos);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION(AActor, A_BrainDie)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
|
|
// [RH] If noexit, then don't end the level.
|
|
if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT))
|
|
return 0;
|
|
|
|
// New dmflag: Kill all boss spawned monsters before ending the level.
|
|
if (dmflags2 & DF2_KILLBOSSMONST)
|
|
{
|
|
int count; // Repeat until we have no more boss-spawned monsters.
|
|
do // (e.g. Pain Elementals can spawn more to kill upon death.)
|
|
{
|
|
TThinkerIterator<AActor> it;
|
|
AActor *mo;
|
|
count = 0;
|
|
while ((mo = it.Next()))
|
|
{
|
|
if (mo->health > 0 && mo->flags4 & MF4_BOSSSPAWNED)
|
|
{
|
|
P_DamageMobj(mo, self, self, mo->health, NAME_None,
|
|
DMG_NO_ARMOR|DMG_FORCED|DMG_THRUSTLESS|DMG_NO_FACTOR);
|
|
count++;
|
|
}
|
|
}
|
|
} while (count != 0);
|
|
}
|
|
|
|
G_ExitLevel (0, false);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BrainSpit)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
PARAM_CLASS_OPT(spawntype, AActor) { spawntype = NULL; }
|
|
|
|
DSpotState *state = DSpotState::GetSpotState();
|
|
AActor *targ;
|
|
AActor *spit;
|
|
bool isdefault = false;
|
|
|
|
// shoot a cube at current target
|
|
targ = state->GetNextInList(PClass::FindClass("BossTarget"), G_SkillProperty(SKILLP_EasyBossBrain));
|
|
|
|
if (targ != NULL)
|
|
{
|
|
if (spawntype == NULL)
|
|
{
|
|
spawntype = PClass::FindActor("SpawnShot");
|
|
isdefault = true;
|
|
}
|
|
|
|
// spawn brain missile
|
|
spit = P_SpawnMissile (self, targ, spawntype);
|
|
|
|
if (spit != NULL)
|
|
{
|
|
// Boss cubes should move freely to their destination so it's
|
|
// probably best to disable all collision detection for them.
|
|
if (spit->flags & MF_NOCLIP) spit->flags5 |= MF5_NOINTERACTION;
|
|
|
|
spit->target = targ;
|
|
spit->master = self;
|
|
// [RH] Do this correctly for any trajectory. Doom would divide by 0
|
|
// if the target had the same y coordinate as the spitter.
|
|
if (spit->Vel.X == 0 && spit->Vel.Y == 0)
|
|
{
|
|
spit->special2 = 0;
|
|
}
|
|
else if (fabs(spit->Vel.Y) > fabs(spit->Vel.X))
|
|
{
|
|
spit->special2 = int((targ->Y() - self->Y()) / spit->Vel.Y);
|
|
}
|
|
else
|
|
{
|
|
spit->special2 = int((targ->X() - self->X()) / spit->Vel.X);
|
|
}
|
|
// [GZ] Calculates when the projectile will have reached destination
|
|
spit->special2 += level.maptime;
|
|
spit->flags6 |= MF6_BOSSCUBE;
|
|
}
|
|
|
|
if (!isdefault)
|
|
{
|
|
S_Sound(self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NONE);
|
|
}
|
|
else
|
|
{
|
|
// compatibility fallback
|
|
S_Sound (self, CHAN_WEAPON, "brain/spit", 1, ATTN_NONE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void SpawnFly(AActor *self, PClassActor *spawntype, FSoundID sound)
|
|
{
|
|
AActor *newmobj;
|
|
AActor *fog;
|
|
AActor *eye = self->master; // The eye is the spawnshot's master, not the target!
|
|
AActor *targ = self->target; // Unlike other projectiles, the target is the intended destination.
|
|
int r;
|
|
|
|
// [GZ] Should be more viable than a countdown...
|
|
if (self->special2 != 0)
|
|
{
|
|
if (self->special2 > level.maptime)
|
|
return; // still flying
|
|
}
|
|
else
|
|
{
|
|
if (self->reactiontime == 0 || --self->reactiontime != 0)
|
|
return; // still flying
|
|
}
|
|
|
|
if (spawntype != NULL)
|
|
{
|
|
fog = Spawn (spawntype, targ->Pos(), ALLOW_REPLACE);
|
|
if (fog != NULL) S_Sound (fog, CHAN_BODY, sound, 1, ATTN_NORM);
|
|
}
|
|
|
|
FName SpawnName;
|
|
|
|
DDropItem *di; // di will be our drop item list iterator
|
|
DDropItem *drop; // while drop stays as the reference point.
|
|
int n = 0;
|
|
|
|
// First see if this cube has its own actor list
|
|
drop = self->GetDropItems();
|
|
|
|
// If not, then default back to its master's list
|
|
if (drop == NULL && eye != NULL)
|
|
drop = eye->GetDropItems();
|
|
|
|
if (drop != NULL)
|
|
{
|
|
for (di = drop; di != NULL; di = di->Next)
|
|
{
|
|
if (di->Name != NAME_None)
|
|
{
|
|
if (di->Amount < 0)
|
|
{
|
|
di->Amount = 1; // default value is -1, we need a positive value.
|
|
}
|
|
n += di->Amount; // this is how we can weight the list.
|
|
}
|
|
}
|
|
di = drop;
|
|
n = pr_spawnfly(n);
|
|
while (n >= 0)
|
|
{
|
|
if (di->Name != NAME_None)
|
|
{
|
|
n -= di->Amount; // logically, none of the -1 values have survived by now.
|
|
}
|
|
if ((di->Next != NULL) && (n >= 0))
|
|
{
|
|
di = di->Next;
|
|
}
|
|
else
|
|
{
|
|
n = -1;
|
|
}
|
|
}
|
|
SpawnName = di->Name;
|
|
}
|
|
if (SpawnName == NAME_None)
|
|
{
|
|
// Randomly select monster to spawn.
|
|
r = pr_spawnfly ();
|
|
|
|
// Probability distribution (kind of :),
|
|
// decreasing likelihood.
|
|
if (r < 50) SpawnName = "DoomImp";
|
|
else if (r < 90) SpawnName = "Demon";
|
|
else if (r < 120) SpawnName = "Spectre";
|
|
else if (r < 130) SpawnName = "PainElemental";
|
|
else if (r < 160) SpawnName = "Cacodemon";
|
|
else if (r < 162) SpawnName = "Archvile";
|
|
else if (r < 172) SpawnName = "Revenant";
|
|
else if (r < 192) SpawnName = "Arachnotron";
|
|
else if (r < 222) SpawnName = "Fatso";
|
|
else if (r < 246) SpawnName = "HellKnight";
|
|
else SpawnName = "BaronOfHell";
|
|
}
|
|
spawntype = PClass::FindActor(SpawnName);
|
|
if (spawntype != NULL)
|
|
{
|
|
newmobj = Spawn (spawntype, targ->Pos(), ALLOW_REPLACE);
|
|
if (newmobj != NULL)
|
|
{
|
|
// Make the new monster hate what the boss eye hates
|
|
if (eye != NULL)
|
|
{
|
|
newmobj->CopyFriendliness (eye, false);
|
|
}
|
|
// Make it act as if it was around when the player first made noise
|
|
// (if the player has made noise).
|
|
newmobj->LastHeard = newmobj->Sector->SoundTarget;
|
|
|
|
if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true, NULL))
|
|
{
|
|
newmobj->SetState (newmobj->SeeState);
|
|
}
|
|
if (!(newmobj->ObjectFlags & OF_EuthanizeMe))
|
|
{
|
|
// telefrag anything in this spot
|
|
P_TeleportMove (newmobj, newmobj->Pos(), true);
|
|
}
|
|
newmobj->flags4 |= MF4_BOSSSPAWNED;
|
|
}
|
|
}
|
|
|
|
// remove self (i.e., cube).
|
|
self->Destroy ();
|
|
}
|
|
|
|
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SpawnFly)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
PARAM_CLASS_OPT (spawntype, AActor) { spawntype = NULL; }
|
|
|
|
FSoundID sound;
|
|
|
|
if (spawntype != NULL)
|
|
{
|
|
sound = GetDefaultByType(spawntype)->SeeSound;
|
|
}
|
|
else
|
|
{
|
|
spawntype = PClass::FindActor("SpawnFire");
|
|
sound = "brain/spawn";
|
|
}
|
|
SpawnFly(self, spawntype, sound);
|
|
return 0;
|
|
}
|
|
|
|
// travelling cube sound
|
|
DEFINE_ACTION_FUNCTION(AActor, A_SpawnSound)
|
|
{
|
|
PARAM_ACTION_PROLOGUE;
|
|
S_Sound (self, CHAN_BODY, "brain/cube", 1, ATTN_IDLE);
|
|
SpawnFly(self, PClass::FindActor("SpawnFire"), "brain/spawn");
|
|
return 0;
|
|
}
|