mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-12-03 08:52:59 +00:00
232 lines
6.9 KiB
C++
232 lines
6.9 KiB
C++
/*
|
|
** a_randomspawner.cpp
|
|
** A thing that randomly spawns one item in a list of many, before disappearing.
|
|
** bouncecount is used to keep track of recursions (so as to prevent infinite loops).
|
|
** Species is used to store the index of the spawned actor's name.
|
|
*/
|
|
|
|
#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 "gstrings.h"
|
|
#include "v_text.h"
|
|
#include "doomstat.h"
|
|
#include "doomdata.h"
|
|
|
|
#define MAX_RANDOMSPAWNERS_RECURSION 32 // Should be largely more than enough, honestly.
|
|
static FRandom pr_randomspawn("RandomSpawn");
|
|
|
|
static bool IsMonster(DDropItem *di)
|
|
{
|
|
const PClass *pclass = PClass::FindClass(di->Name);
|
|
|
|
if (NULL == pclass)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return 0 != (GetDefaultByType(pclass)->flags3 & MF3_ISMONSTER);
|
|
}
|
|
|
|
class ARandomSpawner : public AActor
|
|
{
|
|
DECLARE_CLASS (ARandomSpawner, AActor)
|
|
|
|
// To handle "RandomSpawning" missiles, the code has to be split in two parts.
|
|
// If the following code is not done in BeginPlay, missiles will use the
|
|
// random spawner's velocity (0...) instead of their own.
|
|
void BeginPlay()
|
|
{
|
|
DDropItem *di; // di will be our drop item list iterator
|
|
DDropItem *drop; // while drop stays as the reference point.
|
|
int n = 0;
|
|
bool nomonsters = (dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS);
|
|
|
|
Super::BeginPlay();
|
|
drop = di = GetDropItems();
|
|
if (di != NULL)
|
|
{
|
|
while (di != NULL)
|
|
{
|
|
if (di->Name != NAME_None)
|
|
{
|
|
if (!nomonsters || !IsMonster(di))
|
|
{
|
|
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 = di->Next;
|
|
}
|
|
}
|
|
if (n == 0)
|
|
{ // Nothing left to spawn. They must have all been monsters, and monsters are disabled.
|
|
Destroy();
|
|
return;
|
|
}
|
|
// Then we reset the iterator to the start position...
|
|
di = drop;
|
|
// Take a random number...
|
|
n = pr_randomspawn(n);
|
|
// And iterate in the array up to the random number chosen.
|
|
while (n > -1 && di != NULL)
|
|
{
|
|
if (di->Name != NAME_None &&
|
|
(!nomonsters || !IsMonster(di)))
|
|
{
|
|
n -= di->Amount;
|
|
if ((di->Next != NULL) && (n > -1))
|
|
di = di->Next;
|
|
else
|
|
n = -1;
|
|
}
|
|
else
|
|
{
|
|
di = di->Next;
|
|
}
|
|
}
|
|
// So now we can spawn the dropped item.
|
|
if (di == NULL || bouncecount >= MAX_RANDOMSPAWNERS_RECURSION) // Prevents infinite recursions
|
|
{
|
|
Spawn("Unknown", Pos(), NO_REPLACE); // Show that there's a problem.
|
|
Destroy();
|
|
return;
|
|
}
|
|
else if (pr_randomspawn() <= di->Probability) // prob 255 = always spawn, prob 0 = almost never spawn.
|
|
{
|
|
// Handle replacement here so as to get the proper speed and flags for missiles
|
|
PClassActor *cls;
|
|
cls = PClass::FindActor(di->Name);
|
|
if (cls != NULL)
|
|
{
|
|
PClassActor *rep = cls->GetReplacement();
|
|
if (rep != NULL)
|
|
{
|
|
cls = rep;
|
|
}
|
|
}
|
|
if (cls != NULL)
|
|
{
|
|
Species = cls->TypeName;
|
|
AActor *defmobj = GetDefaultByType(cls);
|
|
this->Speed = defmobj->Speed;
|
|
this->flags |= (defmobj->flags & MF_MISSILE);
|
|
this->flags2 |= (defmobj->flags2 & MF2_SEEKERMISSILE);
|
|
this->flags4 |= (defmobj->flags4 & MF4_SPECTRAL);
|
|
}
|
|
else
|
|
{
|
|
Printf(TEXTCOLOR_RED "Unknown item class %s to drop from a random spawner\n", di->Name.GetChars());
|
|
Species = NAME_None;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The second half of random spawning. Now that the spawner is initialized, the
|
|
// real actor can be created. If the following code were in BeginPlay instead,
|
|
// missiles would not have yet obtained certain information that is absolutely
|
|
// necessary to them -- such as their source and destination.
|
|
void PostBeginPlay()
|
|
{
|
|
Super::PostBeginPlay();
|
|
|
|
AActor *newmobj = NULL;
|
|
bool boss = false;
|
|
|
|
if (Species == NAME_None)
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
PClassActor *cls = PClass::FindActor(Species);
|
|
if (this->flags & MF_MISSILE && target && target->target) // Attempting to spawn a missile.
|
|
{
|
|
if ((tracer == NULL) && (flags2 & MF2_SEEKERMISSILE))
|
|
{
|
|
tracer = target->target;
|
|
}
|
|
newmobj = P_SpawnMissileXYZ(Pos(), target, target->target, cls, false);
|
|
}
|
|
else
|
|
{
|
|
newmobj = Spawn(cls, Pos(), NO_REPLACE);
|
|
}
|
|
if (newmobj != NULL)
|
|
{
|
|
// copy everything relevant
|
|
newmobj->SpawnAngle = SpawnAngle;
|
|
newmobj->Angles = Angles;
|
|
newmobj->SpawnPoint = SpawnPoint;
|
|
newmobj->special = special;
|
|
newmobj->args[0] = args[0];
|
|
newmobj->args[1] = args[1];
|
|
newmobj->args[2] = args[2];
|
|
newmobj->args[3] = args[3];
|
|
newmobj->args[4] = args[4];
|
|
newmobj->special1 = special1;
|
|
newmobj->special2 = special2;
|
|
newmobj->SpawnFlags = SpawnFlags & ~MTF_SECRET; // MTF_SECRET needs special treatment to avoid incrementing the secret counter twice. It had already been processed for the spawner itself.
|
|
newmobj->HandleSpawnFlags();
|
|
newmobj->SpawnFlags = SpawnFlags;
|
|
newmobj->tid = tid;
|
|
newmobj->AddToHash();
|
|
newmobj->Vel = Vel;
|
|
newmobj->master = master; // For things such as DamageMaster/DamageChildren, transfer mastery.
|
|
newmobj->target = target;
|
|
newmobj->tracer = tracer;
|
|
newmobj->CopyFriendliness(this, false);
|
|
// This handles things such as projectiles with the MF4_SPECTRAL flag that have
|
|
// a health set to -2 after spawning, for internal reasons.
|
|
if (health != SpawnHealth()) newmobj->health = health;
|
|
if (!(flags & MF_DROPPED)) newmobj->flags &= ~MF_DROPPED;
|
|
// Handle special altitude flags
|
|
if (newmobj->flags & MF_SPAWNCEILING)
|
|
{
|
|
newmobj->SetZ(newmobj->ceilingz - newmobj->Height - SpawnPoint.Z);
|
|
}
|
|
else if (newmobj->flags2 & MF2_SPAWNFLOAT)
|
|
{
|
|
double space = newmobj->ceilingz - newmobj->Height - newmobj->floorz;
|
|
if (space > 48)
|
|
{
|
|
space -= 40;
|
|
newmobj->SetZ((space * pr_randomspawn()) / 256. + newmobj->floorz + 40);
|
|
}
|
|
newmobj->AddZ(SpawnPoint.Z);
|
|
}
|
|
if (newmobj->flags & MF_MISSILE)
|
|
P_CheckMissileSpawn(newmobj, 0);
|
|
// Bouncecount is used to count how many recursions we're in.
|
|
if (newmobj->IsKindOf(PClass::FindClass("RandomSpawner")))
|
|
newmobj->bouncecount = ++bouncecount;
|
|
// If the spawned actor has either of those flags, it's a boss.
|
|
if ((newmobj->flags4 & MF4_BOSSDEATH) || (newmobj->flags2 & MF2_BOSS))
|
|
boss = true;
|
|
// If a replaced actor has either of those same flags, it's also a boss.
|
|
AActor *rep = GetDefaultByType(GetClass()->GetReplacee());
|
|
if (rep && ((rep->flags4 & MF4_BOSSDEATH) || (rep->flags2 & MF2_BOSS)))
|
|
boss = true;
|
|
}
|
|
if (boss)
|
|
this->tracer = newmobj;
|
|
else // "else" because a boss-replacing spawner must wait until it can call A_BossDeath.
|
|
Destroy();
|
|
}
|
|
|
|
void Tick() // This function is needed for handling boss replacers
|
|
{
|
|
Super::Tick();
|
|
if (tracer == NULL || tracer->health <= 0)
|
|
{
|
|
A_BossDeath(this);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
IMPLEMENT_CLASS(ARandomSpawner, false, false)
|