- scriptified the RandomSpawner.

- fixed: String constants were not processed by the compiler backend.
- added an explicit name cast for class types.
This commit is contained in:
Christoph Oelckers 2017-01-14 02:05:52 +01:00
commit 40e7fa5be2
12 changed files with 331 additions and 276 deletions

View file

@ -1163,7 +1163,6 @@ set (PCH_SOURCES
g_shared/a_lightning.cpp
g_shared/a_morph.cpp
g_shared/a_quake.cpp
g_shared/a_randomspawner.cpp
g_shared/a_skies.cpp
g_shared/a_soundenvironment.cpp
g_shared/a_soundsequence.cpp

View file

@ -626,7 +626,7 @@ public:
void CallBeginPlay();
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.

View file

@ -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)

View file

@ -552,6 +552,13 @@ PClassActor *PClassActor::GetReplacement(bool lookskill)
return rep;
}
DEFINE_ACTION_FUNCTION(AActor, GetReplacement)
{
PARAM_PROLOGUE;
PARAM_POINTER(c, PClassActor);
ACTION_RETURN_POINTER(c->GetReplacement());
}
//==========================================================================
//
// PClassActor :: GetReplacee
@ -595,6 +602,13 @@ PClassActor *PClassActor::GetReplacee(bool lookskill)
return rep;
}
DEFINE_ACTION_FUNCTION(AActor, GetReplacee)
{
PARAM_PROLOGUE;
PARAM_POINTER(c, PClassActor);
ACTION_RETURN_POINTER(c->GetReplacee());
}
//==========================================================================
//
// PClassActor :: SetDamageFactor

View file

@ -4772,6 +4772,13 @@ void AActor::HandleSpawnFlags ()
}
}
DEFINE_ACTION_FUNCTION(AActor, HandleSpawnFlags)
{
PARAM_SELF_PROLOGUE(AActor);
self->HandleSpawnFlags();
return 0;
}
void AActor::BeginPlay ()
{
// If the actor is spawned with the dormant flag set, clear it, and use

View file

@ -492,8 +492,16 @@ FxExpression *FxConstant::MakeConstant(PSymbol *sym, const FScriptPosition &pos)
}
else
{
pos.Message(MSG_ERROR, "'%s' is not a constant\n", sym->SymbolName.GetChars());
x = nullptr;
PSymbolConstString *csym = dyn_cast<PSymbolConstString>(sym);
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;
}
@ -1037,11 +1045,12 @@ ExpEmit FxFloatCast::Emit(VMFunctionBuilder *build)
//
//==========================================================================
FxNameCast::FxNameCast(FxExpression *x)
FxNameCast::FxNameCast(FxExpression *x, bool explicitly)
: FxExpression(EFX_NameCast, x->ScriptPosition)
{
basex = x;
ValueType = TypeName;
mExplicit = explicitly;
}
//==========================================================================
@ -1066,7 +1075,18 @@ FxExpression *FxNameCast::Resolve(FCompileContext &ctx)
CHECKRESOLVED();
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;
basex = nullptr;
@ -1100,13 +1120,25 @@ FxExpression *FxNameCast::Resolve(FCompileContext &ctx)
ExpEmit FxNameCast::Emit(VMFunctionBuilder *build)
{
ExpEmit from = basex->Emit(build);
assert(!from.Konst);
assert(basex->ValueType == TypeString);
from.Free(build);
ExpEmit to(build, REGT_INT);
build->Emit(OP_CAST, to.RegNum, from.RegNum, CAST_S2N);
return to;
if (basex->ValueType == TypeString)
{
ExpEmit from = basex->Emit(build);
assert(!from.Konst);
assert(basex->ValueType == TypeString);
from.Free(build);
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)
{
FxExpression *x = new FxNameCast(basex);
FxExpression *x = new FxNameCast(basex, Explicit);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
@ -8394,7 +8426,7 @@ FxExpression *FxFormat::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
for (int i = 0; i < ArgList.Size(); i++)
for (unsigned i = 0; i < ArgList.Size(); i++)
{
ArgList[i] = ArgList[i]->Resolve(ctx);
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
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);
build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1);

View file

@ -611,10 +611,11 @@ public:
class FxNameCast : public FxExpression
{
FxExpression *basex;
bool mExplicit;
public:
FxNameCast(FxExpression *x);
FxNameCast(FxExpression *x, bool explicitly = false);
~FxNameCast();
FxExpression *Resolve(FCompileContext&);