From 5910d90a191e3154bd945774b89b379764ebe670 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 15 Sep 2009 14:16:55 +0000 Subject: [PATCH] - added RandomSpawner update from Gez's experimental build. - added thing activation types for BUMPSPECIAL and USESPECIAL. Also added a new ClearSpecial flag to the activation type. - added MBF's code for dogs jumping down, controlled by the MF6_JUMPDOWN flag. SVN r1835 (trunk) --- docs/rh-log.txt | 5 + src/actor.h | 1 + src/g_shared/a_randomspawner.cpp | 145 ++++++++++++++++++--------- src/p_enemy.cpp | 19 ++-- src/p_local.h | 4 +- src/p_map.cpp | 53 ++++++++-- src/thingdef/thingdef_parse.cpp | 58 +++++++++-- src/thingdef/thingdef_properties.cpp | 10 ++ wadsrc/static/actors/constants.txt | 12 +++ 9 files changed, 224 insertions(+), 83 deletions(-) diff --git a/docs/rh-log.txt b/docs/rh-log.txt index 9e90b6be8..5142f76d9 100644 --- a/docs/rh-log.txt +++ b/docs/rh-log.txt @@ -1,4 +1,9 @@ September 15, 2009 (Changes by Graf Zahl) +- added RandomSpawner update from Gez's experimental build. +- added thing activation types for BUMPSPECIAL and USESPECIAL. Also added + a new ClearSpecial flag to the activation type. +- added MBF's code for dogs jumping down, controlled by the MF6_JUMPDOWN + flag. - fixed: pr_bounce was declared statically in o_mobj.cpp and redeclared externally in p_map.cpp resulting in a CRC conflict. - fixed: The Dehacked flags parser fix from May 31 (r1624) was undone by diff --git a/src/actor.h b/src/actor.h index 5b34e3436..deb9bfd45 100644 --- a/src/actor.h +++ b/src/actor.h @@ -441,6 +441,7 @@ enum EThingSpecialActivationType THINGSPEC_TriggerTargets = 4, // The trigger changes its target to the thing THINGSPEC_MonsterTrigger = 8, // The thing can be triggered by a monster THINGSPEC_MissileTrigger = 16, // The thing can be triggered by a projectile + THINGSPEC_ClearSpecial = 32, // Clears special after successful activation }; // [RH] Like msecnode_t, but for the blockmap diff --git a/src/g_shared/a_randomspawner.cpp b/src/g_shared/a_randomspawner.cpp index 715e6a8a6..b4b9176ae 100644 --- a/src/g_shared/a_randomspawner.cpp +++ b/src/g_shared/a_randomspawner.cpp @@ -1,6 +1,8 @@ /* ** 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" @@ -21,14 +23,16 @@ class ARandomSpawner : public AActor { DECLARE_CLASS (ARandomSpawner, AActor) - void PostBeginPlay() + // 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() { - AActor *newmobj = NULL; FDropItem *di; // di will be our drop item list iterator FDropItem *drop; // while drop stays as the reference point. int n=0; - Super::PostBeginPlay(); + Super::BeginPlay(); drop = di = GetDropItems(); if (di != NULL) { @@ -46,7 +50,7 @@ class ARandomSpawner : public AActor // Take a random number... n = pr_randomspawn(n); // And iterate in the array up to the random number chosen. - while (n > 0) + while (n > -1) { if (di->Name != NAME_None) { @@ -55,64 +59,109 @@ class ARandomSpawner : public AActor } } // So now we can spawn the dropped item. - if (special1 >= MAX_RANDOMSPAWNERS_RECURSION) // Prevents infinite recursions + if (bouncecount >= MAX_RANDOMSPAWNERS_RECURSION) // Prevents infinite recursions + { Spawn("Unknown", x, y, z, NO_REPLACE); // Show that there's a problem. + Destroy(); return; + } else if (pr_randomspawn() <= di->probability) // prob 255 = always spawn, prob 0 = never spawn. { - newmobj = Spawn(di->Name, x, y, z, ALLOW_REPLACE); - // copy everything relevant - newmobj->SpawnAngle = newmobj->angle = angle; - 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->SpawnFlags = SpawnFlags; - newmobj->HandleSpawnFlags(); - newmobj->tid = tid; - newmobj->AddToHash(); - newmobj->velx = velx; - newmobj->vely = vely; - newmobj->velz = velz; - newmobj->master = master; // For things such as DamageMaster/DamageChildren, transfer mastery. - newmobj->target = target; - newmobj->tracer = tracer; - newmobj->CopyFriendliness(this, false); - if (!(flags & MF_DROPPED)) newmobj->flags &= ~MF_DROPPED; - - // Handle special altitude flags - if (newmobj->flags & MF_SPAWNCEILING) + // Handle replacement here so as to get the proper speed and flags for missiles + const PClass * cls; PClass * rep; + cls = PClass::FindClass(di->Name); + if (cls) rep = cls->ActorInfo->GetReplacement()->Class; + if (rep) cls = rep; + if (cls) { - newmobj->z = newmobj->ceilingz - newmobj->height; + 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 if (newmobj->flags2 & MF2_SPAWNFLOAT) - { - fixed_t space = newmobj->ceilingz - newmobj->height - newmobj->floorz; - if (space > 48*FRACUNIT) - { - space -= 40*FRACUNIT; - newmobj->z = MulScale8 (space, pr_randomspawn()) + newmobj->floorz + 40*FRACUNIT; - } - } - - // Special1 is used to count how many recursions we're in. - if (newmobj->IsKindOf(PClass::FindClass("RandomSpawner"))) - newmobj->special1 = ++special1; - + else Species = NAME_None; } } - if ((newmobj != NULL) && ((newmobj->flags4 & MF4_BOSSDEATH) || (newmobj->flags2 & MF2_BOSS))) - this->target = newmobj; // If the spawned actor has either of those flags, it's a boss. + } + + // 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() + { + AActor * newmobj = NULL; + bool boss = false; + if (Species == NAME_None) { Destroy(); return; } + const PClass * cls = PClass::FindClass(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(x, y, z, target, target->target, cls, false); + } + else newmobj = Spawn(cls, x, y, z, NO_REPLACE); + if (newmobj != NULL) + { + // copy everything relevant + newmobj->SpawnAngle = newmobj->angle = angle; + 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->SpawnFlags = SpawnFlags; + newmobj->HandleSpawnFlags(); + newmobj->tid = tid; + newmobj->AddToHash(); + newmobj->velx = velx; + newmobj->vely = vely; + newmobj->velz = velz; + 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->z = newmobj->ceilingz - newmobj->height; + } + else if (newmobj->flags2 & MF2_SPAWNFLOAT) + { + fixed_t space = newmobj->ceilingz - newmobj->height - newmobj->floorz; + if (space > 48*FRACUNIT) + { + space -= 40*FRACUNIT; + newmobj->z = MulScale8 (space, pr_randomspawn()) + newmobj->floorz + 40*FRACUNIT; + } + } + if (newmobj->flags & MF_MISSILE) + P_CheckMissileSpawn(newmobj); + // 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()->ActorInfo->GetReplacee()->Class); + if (rep && (rep->flags4 & MF4_BOSSDEATH) || (rep->flags2 & MF2_BOSS)) + boss = true; + } + if (boss) this->tracer = newmobj; else Destroy(); // "else" because a boss-replacing spawner must wait until it can call A_BossDeath. } void Tick() // This function is needed for handling boss replacers { Super::Tick(); - if (target == NULL || target->health <= 0) + if (tracer == NULL || tracer->health <= 0) { - health = 0; CALL_ACTION(A_BossDeath, this); Destroy(); } diff --git a/src/p_enemy.cpp b/src/p_enemy.cpp index f63f9b515..a321955a2 100644 --- a/src/p_enemy.cpp +++ b/src/p_enemy.cpp @@ -61,6 +61,7 @@ static FRandom pr_dropitem ("DropItem"); static FRandom pr_look2 ("LookyLooky"); static FRandom pr_look3 ("IGotHooky"); static FRandom pr_slook ("SlooK"); +static FRandom pr_dropoff ("Dropoff"); static FRandom pr_skiptarget("SkipTarget"); @@ -380,6 +381,7 @@ bool P_Move (AActor *actor) int speed = actor->Speed; int movefactor = ORIG_FRICTION_FACTOR; int friction = ORIG_FRICTION; + int dropoff = 0; if (actor->flags2 & MF2_BLASTED) { @@ -410,23 +412,18 @@ bool P_Move (AActor *actor) if ((unsigned)actor->movedir >= 8) I_Error ("Weird actor->movedir!"); - speed = actor->Speed; - -#if 0 // todo - // killough 10/98: allow dogs to drop off of taller ledges sometimes. // dropoff==1 means always allow it, dropoff==2 means only up to 128 high, // and only if the target is immediately on the other side of the line. + AActor *target = actor->target; - if (actor->flags6 & MF6_JUMPDOWN && target && + if ((actor->flags6 & MF6_JUMPDOWN) && target && !(target->IsFriend(actor)) && - P_AproxDistance(actor->x - target->x, - actor->y - target->y) < FRACUNIT*144 && - P_Random(pr_dropoff) < 235) + P_AproxDistance(actor->x - target->x, actor->y - target->y) < FRACUNIT*144 && + pr_dropoff() < 235) { dropoff = 2; } -#endif // [RH] I'm not so sure this is such a good idea // [GZ] That's why it's compat-optioned. @@ -479,12 +476,12 @@ bool P_Move (AActor *actor) try_ok = true; for(int i=1; i < steps; i++) { - try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), false, false, tm); + try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), dropoff, false, tm); if (!try_ok) break; } // killough 3/15/98: don't jump over dropoffs: - if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, false, false, tm); + if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, dropoff, false, tm); // [GrafZahl] Interpolating monster movement as it is done here just looks bad // so make it switchable! diff --git a/src/p_local.h b/src/p_local.h index dd9826e1f..1ac7687e3 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -377,8 +377,8 @@ bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y, FCheckPosition &tm); bool P_CheckPosition (AActor *thing, fixed_t x, fixed_t y); AActor *P_CheckOnmobj (AActor *thing); void P_FakeZMovement (AActor *mo); -bool P_TryMove (AActor* thing, fixed_t x, fixed_t y, bool dropoff, const secplane_t * onfloor, FCheckPosition &tm); -bool P_TryMove (AActor* thing, fixed_t x, fixed_t y, bool dropoff, const secplane_t * onfloor = NULL); +bool P_TryMove (AActor* thing, fixed_t x, fixed_t y, int dropoff, const secplane_t * onfloor, FCheckPosition &tm); +bool P_TryMove (AActor* thing, fixed_t x, fixed_t y, int dropoff, const secplane_t * onfloor = NULL); bool P_CheckMove(AActor *thing, fixed_t x, fixed_t y); void P_ApplyTorque(AActor *mo); bool P_TeleportMove (AActor* thing, fixed_t x, fixed_t y, fixed_t z, bool telefrag); // [RH] Added z and telefrag parameters diff --git a/src/p_map.cpp b/src/p_map.cpp index 40d40461a..09dbf076b 100644 --- a/src/p_map.cpp +++ b/src/p_map.cpp @@ -834,6 +834,29 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) P_DamageMobj (thing, NULL, NULL, thing->health, NAME_None, DMG_FORCED); // kill object return true; } + + // Check for MF6_BUMPSPECIAL + // By default, only players can activate things by bumping into them + if ((thing->flags6 & MF6_BUMPSPECIAL)) + { + if (((tm.thing->player != NULL) + || ((thing->activationtype & THINGSPEC_MonsterTrigger) && (thing->flags3 & MF3_ISMONSTER)) + || ((thing->activationtype & THINGSPEC_MissileTrigger) && (thing->flags & MF_MISSILE)) + )) + { // Target switching mechanism + if (thing->activationtype & THINGSPEC_ThingTargets) thing->target = tm.thing; + if (thing->activationtype & THINGSPEC_TriggerTargets) tm.thing->target = thing; + // Run the special + + int res = LineSpecials[thing->special] (NULL, + ((thing->activationtype & THINGSPEC_ThingActs) ? thing : tm.thing), // Who triggers? + false, thing->args[0], thing->args[1], thing->args[2], thing->args[3], thing->args[4]); + + if (thing->activationtype & THINGSPEC_ClearSpecial && res) thing->special = 0; + + } + } + // Check for skulls slamming into things if (tm.thing->flags & MF_SKULLFLY) { @@ -860,13 +883,6 @@ bool PIT_CheckThing (AActor *thing, FCheckPosition &tm) return false; } } - // Check for players touching a thing with MF6_BUMPSPECIAL - // A blind recreation of what the Skulltag code is probably like. - if (tm.thing->player && (thing->flags6 & MF6_BUMPSPECIAL) && thing->special) - { - LineSpecials[thing->special] (NULL, tm.thing, false, thing->args[0], - thing->args[1], thing->args[2], thing->args[3], thing->args[4]); - } // Check for missile or non-solid MBF bouncer if (tm.thing->flags & MF_MISSILE || ((tm.thing->BounceFlags & BOUNCE_MBF) && !(tm.thing->flags & MF_SOLID))) { @@ -1537,7 +1553,7 @@ static void CheckForPushSpecial (line_t *line, int side, AActor *mobj) // crossing special lines unless MF_TELEPORT is set. // bool P_TryMove (AActor *thing, fixed_t x, fixed_t y, - bool dropoff, // killough 3/15/98: allow dropoff as option + int dropoff, // killough 3/15/98: allow dropoff as option const secplane_t *onfloor, // [RH] Let P_TryMove keep the thing on the floor FCheckPosition &tm) { @@ -1667,6 +1683,13 @@ bool P_TryMove (AActor *thing, fixed_t x, fixed_t y, dropoff = false; } + if (dropoff==2 && // large jump down (e.g. dogs) + (tm.floorz-tm.dropoffz > 128*FRACUNIT || thing->target == NULL || thing->target->z >tm.dropoffz)) + { + dropoff = false; + } + + // killough 3/15/98: Allow certain objects to drop off if ((!dropoff && !(thing->flags & (MF_DROPOFF|MF_FLOAT|MF_MISSILE))) || (thing->flags5&MF5_NODROPOFF)) { @@ -1894,7 +1917,7 @@ pushline: } bool P_TryMove (AActor *thing, fixed_t x, fixed_t y, - bool dropoff, // killough 3/15/98: allow dropoff as option + int dropoff, // killough 3/15/98: allow dropoff as option const secplane_t *onfloor) // [RH] Let P_TryMove keep the thing on the floor { FCheckPosition tm; @@ -3745,12 +3768,20 @@ bool P_UseTraverse(AActor *usething, fixed_t endx, fixed_t endy, bool &foundline // Check thing // Check for puzzle item use or USESPECIAL flag + // Extended to use the same activationtype mechanism as BUMPSPECIAL does if (in->d.thing->flags5 & MF5_USESPECIAL || in->d.thing->special == UsePuzzleItem) - { - if (LineSpecials[in->d.thing->special] (NULL, usething, false, + { // Target switching mechanism + if (in->d.thing->activationtype & THINGSPEC_ThingTargets) in->d.thing->target = usething; + if (in->d.thing->activationtype & THINGSPEC_TriggerTargets) usething->target = in->d.thing; + // Run the special + if (LineSpecials[in->d.thing->special] (NULL, // Who triggers? + ((in->d.thing->activationtype & THINGSPEC_ThingActs) ? in->d.thing : usething), false, in->d.thing->args[0], in->d.thing->args[1], in->d.thing->args[2], in->d.thing->args[3], in->d.thing->args[4])) + { + if (in->d.thing->activationtype & THINGSPEC_ClearSpecial) in->d.thing->special = 0; return true; + } } // Dead things can't talk. if (in->d.thing->health <= 0) diff --git a/src/thingdef/thingdef_parse.cpp b/src/thingdef/thingdef_parse.cpp index fedb89c7a..039d578b4 100644 --- a/src/thingdef/thingdef_parse.cpp +++ b/src/thingdef/thingdef_parse.cpp @@ -395,18 +395,14 @@ void HandleActorFlag(FScanner &sc, Baggage &bag, const char *part1, const char * // //========================================================================== -static int ParseMorphStyle (FScanner &sc) +struct FParseValue { - static const char * morphstyles[]={ - "MRF_ADDSTAMINA", "MRF_FULLHEALTH", "MRF_UNDOBYTOMEOFPOWER", "MRF_UNDOBYCHAOSDEVICE", - "MRF_FAILNOTELEFRAG", "MRF_FAILNOLAUGH", "MRF_WHENINVULNERABLE", "MRF_LOSEACTUALWEAPON", - "MRF_NEWTIDBEHAVIOUR", "MRF_UNDOBYDEATH", "MRF_UNDOBYDEATHFORCED", "MRF_UNDOBYDEATHSAVES", NULL}; - - static const int morphstyle_values[]={ - MORPH_ADDSTAMINA, MORPH_FULLHEALTH, MORPH_UNDOBYTOMEOFPOWER, MORPH_UNDOBYCHAOSDEVICE, - MORPH_FAILNOTELEFRAG, MORPH_FAILNOLAUGH, MORPH_WHENINVULNERABLE, MORPH_LOSEACTUALWEAPON, - MORPH_NEWTIDBEHAVIOUR, MORPH_UNDOBYDEATH, MORPH_UNDOBYDEATHFORCED, MORPH_UNDOBYDEATHSAVES}; + const char *Name; + int Flag; +}; +int ParseFlagExpressionString(FScanner &sc, const FParseValue *vals) +{ // May be given flags by number... if (sc.CheckNumber()) { @@ -422,7 +418,7 @@ static int ParseMorphStyle (FScanner &sc) do { sc.MustGetString(); - style |= morphstyle_values[sc.MustMatchString(morphstyles)]; + style |= vals[sc.MustMatchString(&vals->Name, sizeof (*vals))].Flag; } while (sc.CheckString("|")); if (gotparen) @@ -433,6 +429,46 @@ static int ParseMorphStyle (FScanner &sc) return style; } + + +static int ParseMorphStyle (FScanner &sc) +{ + static const FParseValue morphstyles[]={ + { "MRF_ADDSTAMINA", MORPH_ADDSTAMINA}, + { "MRF_FULLHEALTH", MORPH_FULLHEALTH}, + { "MRF_UNDOBYTOMEOFPOWER", MORPH_UNDOBYTOMEOFPOWER}, + { "MRF_UNDOBYCHAOSDEVICE", MORPH_UNDOBYCHAOSDEVICE}, + { "MRF_FAILNOTELEFRAG", MORPH_FAILNOTELEFRAG}, + { "MRF_FAILNOLAUGH", MORPH_FAILNOLAUGH}, + { "MRF_WHENINVULNERABLE", MORPH_WHENINVULNERABLE}, + { "MRF_LOSEACTUALWEAPON", MORPH_LOSEACTUALWEAPON}, + { "MRF_NEWTIDBEHAVIOUR", MORPH_NEWTIDBEHAVIOUR}, + { "MRF_UNDOBYDEATH", MORPH_UNDOBYDEATH}, + { "MRF_UNDOBYDEATHFORCED", MORPH_UNDOBYDEATHFORCED}, + { "MRF_UNDOBYDEATHSAVES", MORPH_UNDOBYDEATHSAVES}, + { NULL, 0 } + }; + + return ParseFlagExpressionString(sc, morphstyles); +} + +static int ParseThingActivation (FScanner &sc) +{ + static const FParseValue activationstyles[]={ + + { "THINGSPEC_Default", THINGSPEC_Default}, + { "THINGSPEC_ThingActs", THINGSPEC_ThingActs}, + { "THINGSPEC_ThingTargets", THINGSPEC_ThingTargets}, + { "THINGSPEC_TriggerTargets", THINGSPEC_TriggerTargets}, + { "THINGSPEC_MonsterTrigger", THINGSPEC_MonsterTrigger}, + { "THINGSPEC_MissileTrigger", THINGSPEC_MissileTrigger}, + { "THINGSPEC_ClearSpecial", THINGSPEC_ClearSpecial}, + { NULL, 0 } + }; + + return ParseFlagExpressionString(sc, activationstyles); +} + //========================================================================== // // For getting a state address from the parent diff --git a/src/thingdef/thingdef_properties.cpp b/src/thingdef/thingdef_properties.cpp index 03182266d..f04c6ba8c 100644 --- a/src/thingdef/thingdef_properties.cpp +++ b/src/thingdef/thingdef_properties.cpp @@ -1057,6 +1057,16 @@ DEFINE_PROPERTY(projectile, 0, Actor) if (gameinfo.gametype&GAME_Raven) defaults->flags5|=MF5_BLOODSPLATTER; } +//========================================================================== +// +//========================================================================== +DEFINE_PROPERTY(activation, N, Actor) +{ + // How the thing behaves when activated with MF5_USESPECIAL or MF6_BUMPSPECIAL + PROP_INT_PARM(val, 0); + defaults->activationtype = val; +} + //========================================================================== // // Special inventory properties diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 871958e84..4b8fbc387 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -61,6 +61,18 @@ const int MRF_UNDOBYDEATHSAVES = 2048; const int RGF_SILENT = 1; const int RGF_NOPIERCING = 2; +enum +{ + THINGSPEC_Default = 0, + THINGSPEC_ThingActs = 1, + THINGSPEC_ThingTargets = 2, + THINGSPEC_TriggerTargets = 4, + THINGSPEC_MonsterTrigger = 8, + THINGSPEC_MissileTrigger = 16, + THINGSPEC_ClearSpecial = 32, +}; + + // constants for A_PlaySound enum {