mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-11 07:11:54 +00:00
This commit is contained in:
commit
d707f1c22e
12 changed files with 331 additions and 276 deletions
|
@ -1213,7 +1213,6 @@ set (PCH_SOURCES
|
||||||
g_shared/a_lightning.cpp
|
g_shared/a_lightning.cpp
|
||||||
g_shared/a_morph.cpp
|
g_shared/a_morph.cpp
|
||||||
g_shared/a_quake.cpp
|
g_shared/a_quake.cpp
|
||||||
g_shared/a_randomspawner.cpp
|
|
||||||
g_shared/a_skies.cpp
|
g_shared/a_skies.cpp
|
||||||
g_shared/a_soundenvironment.cpp
|
g_shared/a_soundenvironment.cpp
|
||||||
g_shared/a_soundsequence.cpp
|
g_shared/a_soundsequence.cpp
|
||||||
|
|
|
@ -626,7 +626,7 @@ public:
|
||||||
void CallBeginPlay();
|
void CallBeginPlay();
|
||||||
|
|
||||||
void LevelSpawned(); // Called after BeginPlay if this actor was spawned by the world
|
void LevelSpawned(); // Called after BeginPlay if this actor was spawned by the world
|
||||||
virtual void HandleSpawnFlags(); // Translates SpawnFlags into in-game flags.
|
void HandleSpawnFlags(); // Translates SpawnFlags into in-game flags.
|
||||||
|
|
||||||
virtual void MarkPrecacheSounds() const; // Marks sounds used by this actor for precaching.
|
virtual void MarkPrecacheSounds() const; // Marks sounds used by this actor for precaching.
|
||||||
|
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
/*
|
|
||||||
** 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"
|
|
||||||
#include "g_levellocals.h"
|
|
||||||
#include "virtual.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;
|
|
||||||
|
|
||||||
IFVIRTUAL(ARandomSpawner, PostSpawn)
|
|
||||||
{
|
|
||||||
VMValue params[2] = { (DObject*)this, newmobj };
|
|
||||||
GlobalVMStack.Call(func, params, 2, nullptr, 0, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
14
src/info.cpp
14
src/info.cpp
|
@ -552,6 +552,13 @@ PClassActor *PClassActor::GetReplacement(bool lookskill)
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_ACTION_FUNCTION(AActor, GetReplacement)
|
||||||
|
{
|
||||||
|
PARAM_PROLOGUE;
|
||||||
|
PARAM_POINTER(c, PClassActor);
|
||||||
|
ACTION_RETURN_POINTER(c->GetReplacement());
|
||||||
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
// PClassActor :: GetReplacee
|
// PClassActor :: GetReplacee
|
||||||
|
@ -595,6 +602,13 @@ PClassActor *PClassActor::GetReplacee(bool lookskill)
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_ACTION_FUNCTION(AActor, GetReplacee)
|
||||||
|
{
|
||||||
|
PARAM_PROLOGUE;
|
||||||
|
PARAM_POINTER(c, PClassActor);
|
||||||
|
ACTION_RETURN_POINTER(c->GetReplacee());
|
||||||
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
//
|
//
|
||||||
// PClassActor :: SetDamageFactor
|
// PClassActor :: SetDamageFactor
|
||||||
|
|
|
@ -4772,6 +4772,13 @@ void AActor::HandleSpawnFlags ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFINE_ACTION_FUNCTION(AActor, HandleSpawnFlags)
|
||||||
|
{
|
||||||
|
PARAM_SELF_PROLOGUE(AActor);
|
||||||
|
self->HandleSpawnFlags();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void AActor::BeginPlay ()
|
void AActor::BeginPlay ()
|
||||||
{
|
{
|
||||||
// If the actor is spawned with the dormant flag set, clear it, and use
|
// If the actor is spawned with the dormant flag set, clear it, and use
|
||||||
|
|
|
@ -492,8 +492,16 @@ FxExpression *FxConstant::MakeConstant(PSymbol *sym, const FScriptPosition &pos)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
pos.Message(MSG_ERROR, "'%s' is not a constant\n", sym->SymbolName.GetChars());
|
PSymbolConstString *csym = dyn_cast<PSymbolConstString>(sym);
|
||||||
x = nullptr;
|
if (csym != nullptr)
|
||||||
|
{
|
||||||
|
x = new FxConstant(csym->Str, pos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pos.Message(MSG_ERROR, "'%s' is not a constant\n", sym->SymbolName.GetChars());
|
||||||
|
x = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return x;
|
return x;
|
||||||
}
|
}
|
||||||
|
@ -1037,11 +1045,12 @@ ExpEmit FxFloatCast::Emit(VMFunctionBuilder *build)
|
||||||
//
|
//
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
|
||||||
FxNameCast::FxNameCast(FxExpression *x)
|
FxNameCast::FxNameCast(FxExpression *x, bool explicitly)
|
||||||
: FxExpression(EFX_NameCast, x->ScriptPosition)
|
: FxExpression(EFX_NameCast, x->ScriptPosition)
|
||||||
{
|
{
|
||||||
basex = x;
|
basex = x;
|
||||||
ValueType = TypeName;
|
ValueType = TypeName;
|
||||||
|
mExplicit = explicitly;
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -1066,7 +1075,18 @@ FxExpression *FxNameCast::Resolve(FCompileContext &ctx)
|
||||||
CHECKRESOLVED();
|
CHECKRESOLVED();
|
||||||
SAFE_RESOLVE(basex, ctx);
|
SAFE_RESOLVE(basex, ctx);
|
||||||
|
|
||||||
if (basex->ValueType == TypeName)
|
if (mExplicit && basex->ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)))
|
||||||
|
{
|
||||||
|
if (basex->isConstant())
|
||||||
|
{
|
||||||
|
auto constval = static_cast<FxConstant *>(basex)->GetValue().GetPointer();
|
||||||
|
FxExpression *x = new FxConstant(static_cast<PClass*>(constval)->TypeName, ScriptPosition);
|
||||||
|
delete this;
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (basex->ValueType == TypeName)
|
||||||
{
|
{
|
||||||
FxExpression *x = basex;
|
FxExpression *x = basex;
|
||||||
basex = nullptr;
|
basex = nullptr;
|
||||||
|
@ -1100,13 +1120,25 @@ FxExpression *FxNameCast::Resolve(FCompileContext &ctx)
|
||||||
|
|
||||||
ExpEmit FxNameCast::Emit(VMFunctionBuilder *build)
|
ExpEmit FxNameCast::Emit(VMFunctionBuilder *build)
|
||||||
{
|
{
|
||||||
ExpEmit from = basex->Emit(build);
|
if (basex->ValueType == TypeString)
|
||||||
assert(!from.Konst);
|
{
|
||||||
assert(basex->ValueType == TypeString);
|
ExpEmit from = basex->Emit(build);
|
||||||
from.Free(build);
|
assert(!from.Konst);
|
||||||
ExpEmit to(build, REGT_INT);
|
assert(basex->ValueType == TypeString);
|
||||||
build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2N);
|
from.Free(build);
|
||||||
return to;
|
ExpEmit to(build, REGT_INT);
|
||||||
|
build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2N);
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExpEmit ptr = basex->Emit(build);
|
||||||
|
assert(ptr.RegType == REGT_POINTER);
|
||||||
|
ptr.Free(build);
|
||||||
|
ExpEmit to(build, REGT_INT);
|
||||||
|
build->Emit(OP_LW, to.RegNum, ptr.RegNum, build->GetConstantInt(myoffsetof(PClassActor, TypeName)));
|
||||||
|
return to;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
|
@ -1471,7 +1503,7 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
|
||||||
}
|
}
|
||||||
else if (ValueType == TypeName)
|
else if (ValueType == TypeName)
|
||||||
{
|
{
|
||||||
FxExpression *x = new FxNameCast(basex);
|
FxExpression *x = new FxNameCast(basex, Explicit);
|
||||||
x = x->Resolve(ctx);
|
x = x->Resolve(ctx);
|
||||||
basex = nullptr;
|
basex = nullptr;
|
||||||
delete this;
|
delete this;
|
||||||
|
@ -8394,7 +8426,7 @@ FxExpression *FxFormat::Resolve(FCompileContext& ctx)
|
||||||
{
|
{
|
||||||
CHECKRESOLVED();
|
CHECKRESOLVED();
|
||||||
|
|
||||||
for (int i = 0; i < ArgList.Size(); i++)
|
for (unsigned i = 0; i < ArgList.Size(); i++)
|
||||||
{
|
{
|
||||||
ArgList[i] = ArgList[i]->Resolve(ctx);
|
ArgList[i] = ArgList[i]->Resolve(ctx);
|
||||||
if (ArgList[i] == nullptr)
|
if (ArgList[i] == nullptr)
|
||||||
|
@ -8605,7 +8637,7 @@ ExpEmit FxFormat::Emit(VMFunctionBuilder *build)
|
||||||
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
|
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
|
||||||
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
|
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
|
||||||
|
|
||||||
for (int i = 0; i < ArgList.Size(); i++)
|
for (unsigned i = 0; i < ArgList.Size(); i++)
|
||||||
EmitParameter(build, ArgList[i], ScriptPosition);
|
EmitParameter(build, ArgList[i], ScriptPosition);
|
||||||
build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1);
|
build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1);
|
||||||
|
|
||||||
|
|
|
@ -611,10 +611,11 @@ public:
|
||||||
class FxNameCast : public FxExpression
|
class FxNameCast : public FxExpression
|
||||||
{
|
{
|
||||||
FxExpression *basex;
|
FxExpression *basex;
|
||||||
|
bool mExplicit;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
FxNameCast(FxExpression *x);
|
FxNameCast(FxExpression *x, bool explicitly = false);
|
||||||
~FxNameCast();
|
~FxNameCast();
|
||||||
FxExpression *Resolve(FCompileContext&);
|
FxExpression *Resolve(FCompileContext&);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "zscript/shared/ice.txt"
|
#include "zscript/shared/ice.txt"
|
||||||
#include "zscript/shared/dog.txt"
|
#include "zscript/shared/dog.txt"
|
||||||
#include "zscript/shared/fastprojectile.txt"
|
#include "zscript/shared/fastprojectile.txt"
|
||||||
|
#include "zscript/shared/randomspawner.txt"
|
||||||
#include "zscript/shared/dynlights.txt"
|
#include "zscript/shared/dynlights.txt"
|
||||||
|
|
||||||
#include "zscript/compatibility.txt"
|
#include "zscript/compatibility.txt"
|
||||||
|
|
|
@ -107,8 +107,8 @@ class Actor : Thinker native
|
||||||
native int ReactionTime;
|
native int ReactionTime;
|
||||||
native int Threshold;
|
native int Threshold;
|
||||||
native readonly int DefThreshold;
|
native readonly int DefThreshold;
|
||||||
native readonly vector3 SpawnPoint;
|
native vector3 SpawnPoint;
|
||||||
native readonly uint16 SpawnAngle;
|
native uint16 SpawnAngle;
|
||||||
native int StartHealth;
|
native int StartHealth;
|
||||||
native uint8 WeaveIndexXY;
|
native uint8 WeaveIndexXY;
|
||||||
native uint8 WeaveIndexZ;
|
native uint8 WeaveIndexZ;
|
||||||
|
@ -330,6 +330,8 @@ class Actor : Thinker native
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
native static class<Actor> GetReplacement(class<Actor> cls);
|
||||||
|
native static class<Actor> GetReplacee(class<Actor> cls);
|
||||||
native static int GetSpriteIndex(name sprt);
|
native static int GetSpriteIndex(name sprt);
|
||||||
native static double GetDefaultSpeed(class<Actor> type);
|
native static double GetDefaultSpeed(class<Actor> type);
|
||||||
native static class<Actor> GetSpawnableType(int spawnnum);
|
native static class<Actor> GetSpawnableType(int spawnnum);
|
||||||
|
@ -359,6 +361,7 @@ class Actor : Thinker native
|
||||||
native void ClearInterpolation();
|
native void ClearInterpolation();
|
||||||
native Vector3 PosRelative(sector sec);
|
native Vector3 PosRelative(sector sec);
|
||||||
|
|
||||||
|
native void HandleSpawnFlags();
|
||||||
native void ExplodeMissile(line lin = null, Actor target = null);
|
native void ExplodeMissile(line lin = null, Actor target = null);
|
||||||
native void RestoreDamage();
|
native void RestoreDamage();
|
||||||
native int SpawnHealth();
|
native int SpawnHealth();
|
||||||
|
|
|
@ -1088,3 +1088,33 @@ enum EPlayerCheats
|
||||||
CF_BUDDHA = 1 << 27, // [SP] Buddha mode - take damage, but don't die
|
CF_BUDDHA = 1 << 27, // [SP] Buddha mode - take damage, but don't die
|
||||||
CF_NOCLIP2 = 1 << 30, // [RH] More Quake-like noclip
|
CF_NOCLIP2 = 1 << 30, // [RH] More Quake-like noclip
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TEXTCOLOR_BRICK = "\034A";
|
||||||
|
const TEXTCOLOR_TAN = "\034B";
|
||||||
|
const TEXTCOLOR_GRAY = "\034C";
|
||||||
|
const TEXTCOLOR_GREY = "\034C";
|
||||||
|
const TEXTCOLOR_GREEN = "\034D";
|
||||||
|
const TEXTCOLOR_BROWN = "\034E";
|
||||||
|
const TEXTCOLOR_GOLD = "\034F";
|
||||||
|
const TEXTCOLOR_RED = "\034G";
|
||||||
|
const TEXTCOLOR_BLUE = "\034H";
|
||||||
|
const TEXTCOLOR_ORANGE = "\034I";
|
||||||
|
const TEXTCOLOR_WHITE = "\034J";
|
||||||
|
const TEXTCOLOR_YELLOW = "\034K";
|
||||||
|
const TEXTCOLOR_UNTRANSLATED = "\034L";
|
||||||
|
const TEXTCOLOR_BLACK = "\034M";
|
||||||
|
const TEXTCOLOR_LIGHTBLUE = "\034N";
|
||||||
|
const TEXTCOLOR_CREAM = "\034O";
|
||||||
|
const TEXTCOLOR_OLIVE = "\034P";
|
||||||
|
const TEXTCOLOR_DARKGREEN = "\034Q";
|
||||||
|
const TEXTCOLOR_DARKRED = "\034R";
|
||||||
|
const TEXTCOLOR_DARKBROWN = "\034S";
|
||||||
|
const TEXTCOLOR_PURPLE = "\034T";
|
||||||
|
const TEXTCOLOR_DARKGRAY = "\034U";
|
||||||
|
const TEXTCOLOR_CYAN = "\034V";
|
||||||
|
|
||||||
|
const TEXTCOLOR_NORMAL = "\034-";
|
||||||
|
const TEXTCOLOR_BOLD = "\034+";
|
||||||
|
|
||||||
|
const TEXTCOLOR_CHAT = "\034*";
|
||||||
|
const TEXTCOLOR_TEAMCHAT = "\034!";
|
||||||
|
|
224
wadsrc/static/zscript/shared/randomspawner.txt
Normal file
224
wadsrc/static/zscript/shared/randomspawner.txt
Normal file
|
@ -0,0 +1,224 @@
|
||||||
|
|
||||||
|
// Random spawner ----------------------------------------------------------
|
||||||
|
|
||||||
|
class RandomSpawner : Actor
|
||||||
|
{
|
||||||
|
|
||||||
|
const MAX_RANDOMSPAWNERS_RECURSION = 32; // Should be largely more than enough, honestly.
|
||||||
|
|
||||||
|
Default
|
||||||
|
{
|
||||||
|
+NOBLOCKMAP
|
||||||
|
+NOSECTOR
|
||||||
|
+NOGRAVITY
|
||||||
|
+THRUACTORS
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void PostSpawn(Actor spawned)
|
||||||
|
{}
|
||||||
|
|
||||||
|
static bool IsMonster(DropItem di)
|
||||||
|
{
|
||||||
|
class<Actor> pclass = di.Name;
|
||||||
|
if (null == pclass)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetDefaultByType(pclass).bIsMonster;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
override void BeginPlay()
|
||||||
|
{
|
||||||
|
DropItem di; // di will be our drop item list iterator
|
||||||
|
DropItem drop; // while drop stays as the reference point.
|
||||||
|
int n = 0;
|
||||||
|
bool nomonsters = sv_nomonsters || level.nomonsters;
|
||||||
|
|
||||||
|
Super.BeginPlay();
|
||||||
|
drop = di = GetDropItems();
|
||||||
|
if (di != null)
|
||||||
|
{
|
||||||
|
while (di != null)
|
||||||
|
{
|
||||||
|
if (di.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 = random[randomspawn](0, n-1);
|
||||||
|
// And iterate in the array up to the random number chosen.
|
||||||
|
while (n > -1 && di != null)
|
||||||
|
{
|
||||||
|
if (di.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 (random[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
|
||||||
|
Class<Actor> cls = di.Name;
|
||||||
|
if (cls != null)
|
||||||
|
{
|
||||||
|
Class<Actor> rep = GetReplacement(cls);
|
||||||
|
if (rep != null)
|
||||||
|
{
|
||||||
|
cls = rep;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cls != null)
|
||||||
|
{
|
||||||
|
Species = Name(cls);
|
||||||
|
readonly<Actor> defmobj = GetDefaultByType(cls);
|
||||||
|
Speed = defmobj.Speed;
|
||||||
|
bMissile |= defmobj.bMissile;
|
||||||
|
bSeekerMissile |= defmobj.bSeekerMissile;
|
||||||
|
bSpectral |= defmobj.bSpectral;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
A_Log(TEXTCOLOR_RED .. "Unknown item class ".. di.Name .." to drop from a random spawner\n");
|
||||||
|
Species = '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.
|
||||||
|
override void PostBeginPlay()
|
||||||
|
{
|
||||||
|
Super.PostBeginPlay();
|
||||||
|
|
||||||
|
Actor newmobj = null;
|
||||||
|
bool boss = false;
|
||||||
|
|
||||||
|
if (Species == 'None')
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Class<Actor> cls = Species;
|
||||||
|
if (bMissile && target && target.target) // Attempting to spawn a missile.
|
||||||
|
{
|
||||||
|
if ((tracer == null) && bSeekerMissile)
|
||||||
|
{
|
||||||
|
tracer = target.target;
|
||||||
|
}
|
||||||
|
newmobj = target.SpawnMissileXYZ(Pos, target.target, cls, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newmobj = Spawn(cls, Pos, NO_REPLACE);
|
||||||
|
}
|
||||||
|
if (newmobj != null)
|
||||||
|
{
|
||||||
|
// copy everything relevant
|
||||||
|
newmobj.SpawnAngle = SpawnAngle;
|
||||||
|
newmobj.Angle = Angle;
|
||||||
|
newmobj.Pitch = Pitch;
|
||||||
|
newmobj.Roll = Roll;
|
||||||
|
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.ChangeTid(tid);
|
||||||
|
newmobj.Vel = Vel;
|
||||||
|
newmobj.master = master; // For things such as DamageMaster/DamageChildren, transfer mastery.
|
||||||
|
newmobj.target = target;
|
||||||
|
newmobj.tracer = tracer;
|
||||||
|
newmobj.CopyFriendliness(self, 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 (!bDropped) newmobj.bDropped = false;
|
||||||
|
// Handle special altitude flags
|
||||||
|
if (newmobj.bSpawnCeiling)
|
||||||
|
{
|
||||||
|
newmobj.SetZ(newmobj.ceilingz - newmobj.Height - SpawnPoint.Z);
|
||||||
|
}
|
||||||
|
else if (newmobj.bSpawnFloat)
|
||||||
|
{
|
||||||
|
double space = newmobj.ceilingz - newmobj.Height - newmobj.floorz;
|
||||||
|
if (space > 48)
|
||||||
|
{
|
||||||
|
space -= 40;
|
||||||
|
newmobj.SetZ((space * random[randomspawn]()) / 256. + newmobj.floorz + 40);
|
||||||
|
}
|
||||||
|
newmobj.AddZ(SpawnPoint.Z);
|
||||||
|
}
|
||||||
|
if (newmobj.bMissile)
|
||||||
|
newmobj.CheckMissileSpawn(0);
|
||||||
|
// Bouncecount is used to count how many recursions we're in.
|
||||||
|
if (newmobj is 'RandomSpawner')
|
||||||
|
newmobj.bouncecount = ++bouncecount;
|
||||||
|
// If the spawned actor has either of those flags, it's a boss.
|
||||||
|
if (newmobj.bBossDeath || newmobj.bBoss)
|
||||||
|
boss = true;
|
||||||
|
// If a replaced actor has either of those same flags, it's also a boss.
|
||||||
|
readonly<Actor> rep = GetDefaultByType(GetReplacee(GetClass()));
|
||||||
|
if (rep && (rep.bBossDeath || rep.bBoss))
|
||||||
|
boss = true;
|
||||||
|
|
||||||
|
PostSpawn(newmobj);
|
||||||
|
}
|
||||||
|
if (boss)
|
||||||
|
tracer = newmobj;
|
||||||
|
else // "else" because a boss-replacing spawner must wait until it can call A_BossDeath.
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void Tick() // This function is needed for handling boss replacers
|
||||||
|
{
|
||||||
|
Super.Tick();
|
||||||
|
if (tracer == null || tracer.health <= 0)
|
||||||
|
{
|
||||||
|
A_BossDeath();
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -178,22 +178,6 @@ class SwitchingDecoration : SwitchableDecoration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Random spawner ----------------------------------------------------------
|
|
||||||
|
|
||||||
class RandomSpawner : Actor native
|
|
||||||
{
|
|
||||||
Default
|
|
||||||
{
|
|
||||||
+NOBLOCKMAP
|
|
||||||
+NOSECTOR
|
|
||||||
+NOGRAVITY
|
|
||||||
+THRUACTORS
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void PostSpawn(Actor spawned)
|
|
||||||
{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sector flag setter ------------------------------------------------------
|
// Sector flag setter ------------------------------------------------------
|
||||||
|
|
||||||
class SectorFlagSetter : Actor
|
class SectorFlagSetter : Actor
|
||||||
|
|
Loading…
Reference in a new issue