From 40e7fa5be2edba702c460d05e0bccd7fd1b5ac23 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sat, 14 Jan 2017 02:05:52 +0100 Subject: [PATCH] - scriptified the RandomSpawner. - fixed: String constants were not processed by the compiler backend. - added an explicit name cast for class types. --- src/CMakeLists.txt | 1 - src/actor.h | 2 +- src/g_shared/a_randomspawner.cpp | 240 ------------------ src/info.cpp | 14 + src/p_mobj.cpp | 7 + src/scripting/codegeneration/codegen.cpp | 60 ++++- src/scripting/codegeneration/codegen.h | 3 +- wadsrc/static/zscript.txt | 1 + wadsrc/static/zscript/actor.txt | 9 +- wadsrc/static/zscript/constants.txt | 30 +++ .../static/zscript/shared/randomspawner.txt | 224 ++++++++++++++++ wadsrc/static/zscript/shared/sharedmisc.txt | 16 -- 12 files changed, 331 insertions(+), 276 deletions(-) delete mode 100644 src/g_shared/a_randomspawner.cpp create mode 100644 wadsrc/static/zscript/shared/randomspawner.txt diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2fad79c63..267012b7d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/actor.h b/src/actor.h index a764426e5..6b4ce4ae7 100644 --- a/src/actor.h +++ b/src/actor.h @@ -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. diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp deleted file mode 100644 index d084e1316..000000000 --- a/src/g_shared/a_randomspawner.cpp +++ /dev/null @@ -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) diff --git a/src/info.cpp b/src/info.cpp index d3cb4e274..bdfcb4057 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -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 diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index 22eae11a1..5d116f101 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -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 diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index 5e2878cac..fd86299dc 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -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(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(basex)->GetValue().GetPointer(); + FxExpression *x = new FxConstant(static_cast(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); diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index ed4c2f97c..7e72176e7 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -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&); diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 39a26a2f6..199120f75 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -37,6 +37,7 @@ #include "zscript/shared/ice.txt" #include "zscript/shared/dog.txt" #include "zscript/shared/fastprojectile.txt" +#include "zscript/shared/randomspawner.txt" #include "zscript/shared/dynlights.txt" #include "zscript/compatibility.txt" diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index 587fcf6de..b4c0395f1 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -107,8 +107,8 @@ class Actor : Thinker native native int ReactionTime; native int Threshold; native readonly int DefThreshold; - native readonly vector3 SpawnPoint; - native readonly uint16 SpawnAngle; + native vector3 SpawnPoint; + native uint16 SpawnAngle; native int StartHealth; native uint8 WeaveIndexXY; native uint8 WeaveIndexZ; @@ -330,6 +330,8 @@ class Actor : Thinker native } + native static class GetReplacement(class cls); + native static class GetReplacee(class cls); native static int GetSpriteIndex(name sprt); native static double GetDefaultSpeed(class type); native static class GetSpawnableType(int spawnnum); @@ -358,7 +360,8 @@ class Actor : Thinker native native bool IsZeroDamage(); native void ClearInterpolation(); native Vector3 PosRelative(sector sec); - + + native void HandleSpawnFlags(); native void ExplodeMissile(line lin = null, Actor target = null); native void RestoreDamage(); native int SpawnHealth(); diff --git a/wadsrc/static/zscript/constants.txt b/wadsrc/static/zscript/constants.txt index 20df35dd7..d7139c650 100644 --- a/wadsrc/static/zscript/constants.txt +++ b/wadsrc/static/zscript/constants.txt @@ -1084,3 +1084,33 @@ enum EPlayerCheats CF_BUDDHA = 1 << 27, // [SP] Buddha mode - take damage, but don't die 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!"; diff --git a/wadsrc/static/zscript/shared/randomspawner.txt b/wadsrc/static/zscript/shared/randomspawner.txt new file mode 100644 index 000000000..b6e28cc37 --- /dev/null +++ b/wadsrc/static/zscript/shared/randomspawner.txt @@ -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 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 cls = di.Name; + if (cls != null) + { + Class rep = GetReplacement(cls); + if (rep != null) + { + cls = rep; + } + } + if (cls != null) + { + Species = Name(cls); + readonly 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 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 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(); + } + } + +} diff --git a/wadsrc/static/zscript/shared/sharedmisc.txt b/wadsrc/static/zscript/shared/sharedmisc.txt index c692fe4b2..812718d30 100644 --- a/wadsrc/static/zscript/shared/sharedmisc.txt +++ b/wadsrc/static/zscript/shared/sharedmisc.txt @@ -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 ------------------------------------------------------ class SectorFlagSetter : Actor