mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-08 14:01:32 +00:00
63bd2125f3
names for some DECORATE functions. Technically these will be constants of the AActor class but that will make them accessible in all other actor classes. SVN r397 (trunk)
1012 lines
25 KiB
C++
1012 lines
25 KiB
C++
/*
|
|
** decorations.cpp
|
|
** Loads custom actors out of DECORATE lumps.
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2002-2006 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include "actor.h"
|
|
#include "info.h"
|
|
#include "sc_man.h"
|
|
#include "tarray.h"
|
|
#include "w_wad.h"
|
|
#include "templates.h"
|
|
#include "r_defs.h"
|
|
#include "r_draw.h"
|
|
#include "a_pickups.h"
|
|
#include "s_sound.h"
|
|
#include "cmdlib.h"
|
|
#include "p_lnspec.h"
|
|
#include "p_enemy.h"
|
|
#include "a_action.h"
|
|
#include "decallib.h"
|
|
#include "i_system.h"
|
|
#include "thingdef.h"
|
|
#include "vectors.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
enum EDefinitionType
|
|
{
|
|
DEF_Decoration,
|
|
DEF_BreakableDecoration,
|
|
DEF_Pickup,
|
|
DEF_Projectile,
|
|
};
|
|
|
|
struct FExtraInfo
|
|
{
|
|
char DeathSprite[5];
|
|
unsigned int SpawnStart, SpawnEnd;
|
|
unsigned int DeathStart, DeathEnd;
|
|
unsigned int IceDeathStart, IceDeathEnd;
|
|
unsigned int FireDeathStart, FireDeathEnd;
|
|
bool bSolidOnDeath, bSolidOnBurn;
|
|
bool bBurnAway, bDiesAway, bGenericIceDeath;
|
|
bool bExplosive;
|
|
fixed_t DeathHeight, BurnHeight;
|
|
};
|
|
|
|
class AFakeInventory : public AInventory
|
|
{
|
|
DECLARE_STATELESS_ACTOR (AFakeInventory, AInventory);
|
|
public:
|
|
bool Respawnable;
|
|
|
|
bool ShouldRespawn ()
|
|
{
|
|
return Respawnable && Super::ShouldRespawn();
|
|
}
|
|
|
|
bool TryPickup (AActor *toucher)
|
|
{
|
|
INTBOOL success = LineSpecials[special] (NULL, toucher, false,
|
|
args[0], args[1], args[2], args[3], args[4]);
|
|
|
|
if (success)
|
|
{
|
|
GoAwayAndDie ();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void DoPickupSpecial (AActor *toucher)
|
|
{
|
|
// The special was already executed by TryPickup, so do nothing here
|
|
}
|
|
};
|
|
IMPLEMENT_STATELESS_ACTOR (AFakeInventory, Any, -1, 0)
|
|
PROP_Flags (MF_SPECIAL)
|
|
END_DEFAULTS
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
void ProcessActor(void (*process)(FState *, int));
|
|
void ParseGlobalConst();
|
|
void FinishThingdef();
|
|
void InitDecorateTranslations();
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
void A_ScreamAndUnblock (AActor *);
|
|
void A_ActiveAndUnblock (AActor *);
|
|
void A_ActiveSound (AActor *);
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
static void ParseDecorate (void (*process)(FState *, int));
|
|
static void ParseInsideDecoration (FActorInfo *info, AActor *defaults,
|
|
TArray<FState> &states, FExtraInfo &extra, EDefinitionType def);
|
|
static void ParseSpriteFrames (FActorInfo *info, TArray<FState> &states);
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
TArray<FActorInfo *> Decorations;
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
static const char *RenderStyles[] =
|
|
{
|
|
"STYLE_None",
|
|
"STYLE_Normal",
|
|
"STYLE_Fuzzy",
|
|
"STYLE_SoulTrans",
|
|
"STYLE_OptFuzzy",
|
|
"STYLE_Translucent",
|
|
"STYLE_Add",
|
|
//"STYLE_Shaded",
|
|
NULL
|
|
};
|
|
|
|
static const char *FlagNames1[] =
|
|
{
|
|
"*",
|
|
"Solid",
|
|
"*",
|
|
"NoSector",
|
|
|
|
"NoBlockmap",
|
|
"*",
|
|
"*",
|
|
"*",
|
|
|
|
"SpawnCeiling",
|
|
"NoGravity",
|
|
"*",
|
|
"*",
|
|
|
|
"*",
|
|
"*",
|
|
"*",
|
|
"*",
|
|
|
|
"*",
|
|
"*",
|
|
"Shadow",
|
|
"NoBlood",
|
|
|
|
"*",
|
|
"*",
|
|
"*",
|
|
"CountItem",
|
|
NULL
|
|
};
|
|
|
|
static const char *FlagNames2[] =
|
|
{
|
|
"LowGravity",
|
|
"WindThrust",
|
|
"*",
|
|
"*",
|
|
|
|
"*",
|
|
"FloorClip",
|
|
"SpawnFloat",
|
|
"NoTeleport",
|
|
|
|
"Ripper",
|
|
"Pushable",
|
|
"SlidesOnWalls",
|
|
"*",
|
|
|
|
"CanPass",
|
|
"CannotPush",
|
|
"ThruGhost",
|
|
"*",
|
|
|
|
"FireDamage",
|
|
"NoDamageThrust",
|
|
"Telestomp",
|
|
"FloatBob",
|
|
|
|
"*",
|
|
"ActivateImpact",
|
|
"CanPushWalls",
|
|
"ActivateMCross",
|
|
|
|
"ActivatePCross",
|
|
"*",
|
|
"*",
|
|
"*",
|
|
|
|
"*",
|
|
"*",
|
|
"*",
|
|
"Reflective",
|
|
NULL
|
|
};
|
|
|
|
static const char *FlagNames3[] =
|
|
{
|
|
"FloorHugger",
|
|
"CeilingHugger",
|
|
"*",
|
|
"*",
|
|
|
|
"*",
|
|
"*",
|
|
"DontSplash",
|
|
NULL
|
|
};
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// LoadDecorations
|
|
//
|
|
// Called from FActor::StaticInit()
|
|
//
|
|
//==========================================================================
|
|
|
|
void LoadDecorations (void (*process)(FState *, int))
|
|
{
|
|
int lastlump, lump;
|
|
|
|
InitDecorateTranslations();
|
|
lastlump = 0;
|
|
while ((lump = Wads.FindLump ("DECORATE", &lastlump)) != -1)
|
|
{
|
|
SC_OpenLumpNum (lump, "DECORATE");
|
|
ParseDecorate (process);
|
|
SC_Close ();
|
|
}
|
|
FinishThingdef();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseDecorate
|
|
//
|
|
// Parses a single DECORATE lump
|
|
//
|
|
//==========================================================================
|
|
|
|
static void ParseDecorate (void (*process)(FState *, int))
|
|
{
|
|
TArray<FState> states;
|
|
FExtraInfo extra;
|
|
PClass *type;
|
|
PClass *parent;
|
|
EDefinitionType def;
|
|
FActorInfo *info;
|
|
FName typeName;
|
|
int recursion=0;
|
|
|
|
// Get actor class name.
|
|
while (true)
|
|
{
|
|
if (!SC_GetString ())
|
|
{
|
|
if (recursion==0) return;
|
|
SC_Close();
|
|
SC_RestoreScriptState();
|
|
recursion--;
|
|
continue;
|
|
}
|
|
if (SC_Compare ("#include"))
|
|
{
|
|
int lump;
|
|
|
|
SC_MustGetString();
|
|
// This is not using SC_Open because it can print a more useful error message when done here
|
|
lump = Wads.CheckNumForFullName(sc_String);
|
|
if (lump==-1) lump = Wads.CheckNumForName(sc_String);
|
|
if (lump==-1) SC_ScriptError("Lump '%s' not found", sc_String);
|
|
SC_SaveScriptState();
|
|
SC_OpenLumpNum(lump, sc_String);
|
|
recursion++;
|
|
continue;
|
|
}
|
|
if (SC_Compare ("Actor"))
|
|
{
|
|
ProcessActor (process);
|
|
continue;
|
|
}
|
|
if (SC_Compare ("Pickup"))
|
|
{
|
|
parent = RUNTIME_CLASS(AFakeInventory);
|
|
def = DEF_Pickup;
|
|
SC_MustGetString ();
|
|
}
|
|
else if (SC_Compare ("Breakable"))
|
|
{
|
|
parent = RUNTIME_CLASS(AActor);
|
|
def = DEF_BreakableDecoration;
|
|
SC_MustGetString ();
|
|
}
|
|
else if (SC_Compare ("Projectile"))
|
|
{
|
|
parent = RUNTIME_CLASS(AActor);
|
|
def = DEF_Projectile;
|
|
SC_MustGetString ();
|
|
}
|
|
else if (SC_Compare ("Const"))
|
|
{
|
|
ParseGlobalConst();
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
parent = RUNTIME_CLASS(AActor);
|
|
def = DEF_Decoration;
|
|
}
|
|
|
|
typeName = FName(sc_String);
|
|
type = parent->CreateDerivedClass (typeName, parent->Size);
|
|
info = type->ActorInfo;
|
|
info->GameFilter = 0x80;
|
|
Decorations.Push (info);
|
|
ClearStateLabels();
|
|
|
|
SC_MustGetString ();
|
|
while (!SC_Compare ("{"))
|
|
{
|
|
if (SC_Compare ("Doom"))
|
|
{
|
|
info->GameFilter |= GAME_Doom;
|
|
}
|
|
else if (SC_Compare ("Heretic"))
|
|
{
|
|
info->GameFilter |= GAME_Heretic;
|
|
}
|
|
else if (SC_Compare ("Hexen"))
|
|
{
|
|
info->GameFilter |= GAME_Hexen;
|
|
}
|
|
else if (SC_Compare ("Raven"))
|
|
{
|
|
info->GameFilter |= GAME_Raven;
|
|
}
|
|
else if (SC_Compare ("Strife"))
|
|
{
|
|
info->GameFilter |= GAME_Strife;
|
|
}
|
|
else if (SC_Compare ("Any"))
|
|
{
|
|
info->GameFilter = GAME_Any;
|
|
}
|
|
else
|
|
{
|
|
SC_ScriptError ("Unknown game type %s", sc_String);
|
|
}
|
|
SC_MustGetString ();
|
|
}
|
|
if (info->GameFilter == 0x80)
|
|
{
|
|
info->GameFilter = GAME_Any;
|
|
}
|
|
else
|
|
{
|
|
info->GameFilter &= ~0x80;
|
|
}
|
|
|
|
states.Clear ();
|
|
memset (&extra, 0, sizeof(extra));
|
|
ParseInsideDecoration (info, (AActor *)(type->Defaults), states, extra, def);
|
|
|
|
info->NumOwnedStates = states.Size();
|
|
if (info->NumOwnedStates == 0)
|
|
{
|
|
SC_ScriptError ("%s does not define any animation frames", typeName.GetChars() );
|
|
}
|
|
else if (extra.SpawnEnd == 0)
|
|
{
|
|
SC_ScriptError ("%s does not have a Frames definition", typeName.GetChars() );
|
|
}
|
|
else if (def == DEF_BreakableDecoration && extra.DeathEnd == 0)
|
|
{
|
|
SC_ScriptError ("%s does not have a DeathFrames definition", typeName.GetChars() );
|
|
}
|
|
else if (extra.IceDeathEnd != 0 && extra.bGenericIceDeath)
|
|
{
|
|
SC_ScriptError ("You cannot use IceDeathFrames and GenericIceDeath together");
|
|
}
|
|
|
|
if (extra.IceDeathEnd != 0)
|
|
{
|
|
// Make a copy of the final frozen frame for A_FreezeDeathChunks
|
|
FState icecopy = states[extra.IceDeathEnd-1];
|
|
states.Push (icecopy);
|
|
info->NumOwnedStates += 1;
|
|
}
|
|
|
|
info->OwnedStates = new FState[info->NumOwnedStates];
|
|
memcpy (info->OwnedStates, &states[0], info->NumOwnedStates * sizeof(info->OwnedStates[0]));
|
|
if (info->NumOwnedStates == 1)
|
|
{
|
|
info->OwnedStates->Tics = 0;
|
|
info->OwnedStates->Misc1 = 0;
|
|
info->OwnedStates->Frame &= ~SF_BIGTIC;
|
|
}
|
|
else
|
|
{
|
|
size_t i;
|
|
|
|
// Spawn states loop endlessly
|
|
for (i = extra.SpawnStart; i < extra.SpawnEnd-1; ++i)
|
|
{
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[i+1];
|
|
}
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[extra.SpawnStart];
|
|
|
|
// Death states are one-shot and freeze on the final state
|
|
if (extra.DeathEnd != 0)
|
|
{
|
|
for (i = extra.DeathStart; i < extra.DeathEnd-1; ++i)
|
|
{
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[i+1];
|
|
}
|
|
if (extra.bDiesAway || def == DEF_Projectile)
|
|
{
|
|
info->OwnedStates[i].NextState = NULL;
|
|
}
|
|
else
|
|
{
|
|
info->OwnedStates[i].Tics = 0;
|
|
info->OwnedStates[i].Misc1 = 0;
|
|
info->OwnedStates[i].Frame &= ~SF_BIGTIC;
|
|
}
|
|
|
|
if (def == DEF_Projectile)
|
|
{
|
|
if (extra.bExplosive)
|
|
{
|
|
info->OwnedStates[extra.DeathStart].Action = A_ExplodeParms;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The first frame plays the death sound and
|
|
// the second frame makes it nonsolid.
|
|
info->OwnedStates[extra.DeathStart].Action= A_Scream;
|
|
if (extra.bSolidOnDeath)
|
|
{
|
|
}
|
|
else if (extra.DeathStart + 1 < extra.DeathEnd)
|
|
{
|
|
info->OwnedStates[extra.DeathStart+1].Action = A_NoBlocking;
|
|
}
|
|
else
|
|
{
|
|
info->OwnedStates[extra.DeathStart].Action = A_ScreamAndUnblock;
|
|
}
|
|
|
|
if (extra.DeathHeight == 0) extra.DeathHeight = ((AActor*)(type->Defaults))->height;
|
|
info->Class->Meta.SetMetaFixed (AMETA_DeathHeight, extra.DeathHeight);
|
|
}
|
|
AddState("Death", &info->OwnedStates[extra.DeathStart]);
|
|
}
|
|
|
|
// Burn states are the same as death states, except they can optionally terminate
|
|
if (extra.FireDeathEnd != 0)
|
|
{
|
|
for (i = extra.FireDeathStart; i < extra.FireDeathEnd-1; ++i)
|
|
{
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[i+1];
|
|
}
|
|
if (extra.bBurnAway)
|
|
{
|
|
info->OwnedStates[i].NextState = NULL;
|
|
}
|
|
else
|
|
{
|
|
info->OwnedStates[i].Tics = 0;
|
|
info->OwnedStates[i].Misc1 = 0;
|
|
info->OwnedStates[i].Frame &= ~SF_BIGTIC;
|
|
}
|
|
|
|
// The first frame plays the burn sound and
|
|
// the second frame makes it nonsolid.
|
|
info->OwnedStates[extra.FireDeathStart].Action = A_ActiveSound;
|
|
if (extra.bSolidOnBurn)
|
|
{
|
|
}
|
|
else if (extra.FireDeathStart + 1 < extra.FireDeathEnd)
|
|
{
|
|
info->OwnedStates[extra.FireDeathStart+1].Action = A_NoBlocking;
|
|
}
|
|
else
|
|
{
|
|
info->OwnedStates[extra.FireDeathStart].Action = A_ActiveAndUnblock;
|
|
}
|
|
|
|
if (extra.BurnHeight == 0) extra.BurnHeight = ((AActor*)(type->Defaults))->height;
|
|
type->Meta.SetMetaFixed (AMETA_BurnHeight, extra.BurnHeight);
|
|
|
|
AddState("Burn", &info->OwnedStates[extra.FireDeathStart]);
|
|
}
|
|
|
|
// Ice states are similar to burn and death, except their final frame enters
|
|
// a loop that eventually causes them to bust to pieces.
|
|
if (extra.IceDeathEnd != 0)
|
|
{
|
|
for (i = extra.IceDeathStart; i < extra.IceDeathEnd-1; ++i)
|
|
{
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[i+1];
|
|
}
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[info->NumOwnedStates-1];
|
|
info->OwnedStates[i].Tics = 6;
|
|
info->OwnedStates[i].Misc1 = 0;
|
|
info->OwnedStates[i].Action = A_FreezeDeath;
|
|
|
|
i = info->NumOwnedStates - 1;
|
|
info->OwnedStates[i].NextState = &info->OwnedStates[i];
|
|
info->OwnedStates[i].Tics = 2;
|
|
info->OwnedStates[i].Misc1 = 0;
|
|
info->OwnedStates[i].Action = A_FreezeDeathChunks;
|
|
AddState("Ice", &info->OwnedStates[extra.IceDeathStart]);
|
|
}
|
|
else if (extra.bGenericIceDeath)
|
|
{
|
|
AddState("Ice", &AActor::States[AActor::S_GENERICFREEZEDEATH]);
|
|
}
|
|
}
|
|
if (def == DEF_BreakableDecoration)
|
|
{
|
|
((AActor *)(type->Defaults))->flags |= MF_SHOOTABLE;
|
|
}
|
|
if (def == DEF_Projectile)
|
|
{
|
|
((AActor *)(type->Defaults))->flags |= MF_DROPOFF|MF_MISSILE;
|
|
}
|
|
AddState("Spawn", &info->OwnedStates[extra.SpawnStart]);
|
|
InstallStates(info, ((AActor *)(type->Defaults)));
|
|
process (info->OwnedStates, info->NumOwnedStates);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseInsideDecoration
|
|
//
|
|
// Parses attributes out of a definition terminated with a }
|
|
//
|
|
//==========================================================================
|
|
|
|
static void ParseInsideDecoration (FActorInfo *info, AActor *defaults,
|
|
TArray<FState> &states, FExtraInfo &extra, EDefinitionType def)
|
|
{
|
|
AFakeInventory *const inv = static_cast<AFakeInventory *>(defaults);
|
|
char sprite[5] = "TNT1";
|
|
|
|
SC_MustGetString ();
|
|
while (!SC_Compare ("}"))
|
|
{
|
|
if (SC_Compare ("DoomEdNum"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
if (sc_Number < -1 || sc_Number > 32767)
|
|
{
|
|
SC_ScriptError ("DoomEdNum must be in the range [-1,32767]");
|
|
}
|
|
info->DoomEdNum = (SWORD)sc_Number;
|
|
}
|
|
else if (SC_Compare ("SpawnNum"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
if (sc_Number < 0 || sc_Number > 255)
|
|
{
|
|
SC_ScriptError ("SpawnNum must be in the range [0,255]");
|
|
}
|
|
info->SpawnID = (BYTE)sc_Number;
|
|
}
|
|
else if (SC_Compare ("Sprite") || (
|
|
(def == DEF_BreakableDecoration || def == DEF_Projectile) &&
|
|
SC_Compare ("DeathSprite") &&
|
|
(extra.DeathSprite[0] = 1)) // This is intentionally = and not ==
|
|
)
|
|
{
|
|
SC_MustGetString ();
|
|
if (strlen (sc_String) != 4)
|
|
{
|
|
SC_ScriptError ("Sprite name must be exactly four characters long");
|
|
}
|
|
if (extra.DeathSprite[0] == 1)
|
|
{
|
|
memcpy (extra.DeathSprite, sc_String, 4);
|
|
}
|
|
else
|
|
{
|
|
memcpy (sprite, sc_String, 4);
|
|
}
|
|
}
|
|
else if (SC_Compare ("Frames"))
|
|
{
|
|
SC_MustGetString ();
|
|
extra.SpawnStart = states.Size();
|
|
ParseSpriteFrames (info, states);
|
|
extra.SpawnEnd = states.Size();
|
|
}
|
|
else if ((def == DEF_BreakableDecoration || def == DEF_Projectile) &&
|
|
SC_Compare ("DeathFrames"))
|
|
{
|
|
SC_MustGetString ();
|
|
extra.DeathStart = states.Size();
|
|
ParseSpriteFrames (info, states);
|
|
extra.DeathEnd = states.Size();
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("IceDeathFrames"))
|
|
{
|
|
SC_MustGetString ();
|
|
extra.IceDeathStart = states.Size();
|
|
ParseSpriteFrames (info, states);
|
|
extra.IceDeathEnd = states.Size();
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("BurnDeathFrames"))
|
|
{
|
|
SC_MustGetString ();
|
|
extra.FireDeathStart = states.Size();
|
|
ParseSpriteFrames (info, states);
|
|
extra.FireDeathEnd = states.Size();
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("GenericIceDeath"))
|
|
{
|
|
extra.bGenericIceDeath = true;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("BurnsAway"))
|
|
{
|
|
extra.bBurnAway = true;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("DiesAway"))
|
|
{
|
|
extra.bDiesAway = true;
|
|
}
|
|
else if (SC_Compare ("Alpha"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->alpha = int(clamp (sc_Float, 0.0, 1.0) * OPAQUE);
|
|
}
|
|
else if (SC_Compare ("Scale"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->scaleX = defaults->scaleY = FLOAT2FIXED(sc_Float);
|
|
}
|
|
else if (SC_Compare ("RenderStyle"))
|
|
{
|
|
SC_MustGetString ();
|
|
defaults->RenderStyle = SC_MustMatchString (RenderStyles);
|
|
if (defaults->RenderStyle > STYLE_OptFuzzy)
|
|
{
|
|
defaults->RenderStyle += STYLE_Translucent - STYLE_OptFuzzy - 1;
|
|
}
|
|
}
|
|
else if (SC_Compare ("Radius"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->radius = int(sc_Float * FRACUNIT);
|
|
}
|
|
else if (SC_Compare ("Height"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->height = int(sc_Float * FRACUNIT);
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("DeathHeight"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
extra.DeathHeight = int(sc_Float * FRACUNIT);
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("BurnHeight"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
extra.BurnHeight = int(sc_Float * FRACUNIT);
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("Health"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
defaults->health = sc_Number;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("ExplosionRadius"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
info->Class->Meta.SetMetaInt(ACMETA_ExplosionRadius, sc_Number);
|
|
extra.bExplosive = true;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("ExplosionDamage"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
info->Class->Meta.SetMetaInt(ACMETA_ExplosionDamage, sc_Number);
|
|
extra.bExplosive = true;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("DoNotHurtShooter"))
|
|
{
|
|
info->Class->Meta.SetMetaInt(ACMETA_DontHurtShooter, true);
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("Damage"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
defaults->Damage = sc_Number;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("DamageType"))
|
|
{
|
|
SC_MustGetString ();
|
|
if (SC_Compare ("Normal"))
|
|
{
|
|
defaults->DamageType = NAME_None;
|
|
}
|
|
else if (SC_Compare ("Ice"))
|
|
{
|
|
defaults->DamageType = NAME_Ice;
|
|
}
|
|
else if (SC_Compare ("Fire"))
|
|
{
|
|
defaults->DamageType = NAME_Fire;
|
|
}
|
|
else
|
|
{
|
|
SC_ScriptError ("Unknown damage type \"%s\"", sc_String);
|
|
}
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("Speed"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->Speed = fixed_t(sc_Float * 65536.f);
|
|
}
|
|
else if (SC_Compare ("Mass"))
|
|
{
|
|
SC_MustGetFloat ();
|
|
defaults->Mass = SDWORD(sc_Float);
|
|
}
|
|
else if (SC_Compare ("Translation1"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
if (sc_Number < 0 || sc_Number > 2)
|
|
{
|
|
SC_ScriptError ("Translation1 must be in the range [0,2]");
|
|
}
|
|
defaults->Translation = TRANSLATION(TRANSLATION_Standard, sc_Number);
|
|
}
|
|
else if (SC_Compare ("Translation2"))
|
|
{
|
|
SC_MustGetNumber ();
|
|
if (sc_Number < 0 || sc_Number >= MAX_ACS_TRANSLATIONS)
|
|
{
|
|
#define ERROR(foo) "Translation2 must be in the range [0," #foo "]"
|
|
SC_ScriptError (ERROR(MAX_ACS_TRANSLATIONS));
|
|
#undef ERROR
|
|
}
|
|
defaults->Translation = TRANSLATION(TRANSLATION_LevelScripted, sc_Number);
|
|
}
|
|
else if ((def == DEF_BreakableDecoration || def == DEF_Projectile) &&
|
|
SC_Compare ("DeathSound"))
|
|
{
|
|
SC_MustGetString ();
|
|
defaults->DeathSound = S_FindSound (sc_String);
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("BurnDeathSound"))
|
|
{
|
|
SC_MustGetString ();
|
|
defaults->ActiveSound = S_FindSound (sc_String);
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("SpawnSound"))
|
|
{
|
|
SC_MustGetString ();
|
|
defaults->SeeSound = S_FindSound (sc_String);
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("DoomBounce"))
|
|
{
|
|
defaults->flags2 = (defaults->flags2 & ~MF2_BOUNCETYPE) | MF2_DOOMBOUNCE;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("HereticBounce"))
|
|
{
|
|
defaults->flags2 = (defaults->flags2 & ~MF2_BOUNCETYPE) | MF2_HERETICBOUNCE;
|
|
}
|
|
else if (def == DEF_Projectile && SC_Compare ("HexenBounce"))
|
|
{
|
|
defaults->flags2 = (defaults->flags2 & ~MF2_BOUNCETYPE) | MF2_HEXENBOUNCE;
|
|
}
|
|
else if (def == DEF_Pickup && SC_Compare ("PickupSound"))
|
|
{
|
|
SC_MustGetString ();
|
|
inv->PickupSound = S_FindSound (sc_String);
|
|
}
|
|
else if (def == DEF_Pickup && SC_Compare ("PickupMessage"))
|
|
{
|
|
SC_MustGetString ();
|
|
info->Class->Meta.SetMetaString(AIMETA_PickupMessage, sc_String);
|
|
}
|
|
else if (def == DEF_Pickup && SC_Compare ("Respawns"))
|
|
{
|
|
inv->Respawnable = true;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("SolidOnDeath"))
|
|
{
|
|
extra.bSolidOnDeath = true;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && SC_Compare ("SolidOnBurn"))
|
|
{
|
|
extra.bSolidOnBurn = true;
|
|
}
|
|
else if (sc_String[0] != '*')
|
|
{
|
|
int bit = SC_MatchString (FlagNames1);
|
|
if (bit != -1)
|
|
{
|
|
defaults->flags |= 1 << bit;
|
|
}
|
|
else if ((bit = SC_MatchString (FlagNames2)) != -1)
|
|
{
|
|
defaults->flags2 |= 1 << bit;
|
|
}
|
|
else if ((bit = SC_MatchString (FlagNames3)) != -1)
|
|
{
|
|
defaults->flags3 |= 1 << bit;
|
|
}
|
|
else
|
|
{
|
|
SC_ScriptError (NULL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SC_ScriptError (NULL);
|
|
}
|
|
SC_MustGetString ();
|
|
}
|
|
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < states.Size(); ++i)
|
|
{
|
|
memcpy (states[i].sprite.name, sprite, 4);
|
|
}
|
|
if (extra.DeathSprite[0] && extra.DeathEnd != 0)
|
|
{
|
|
for (i = extra.DeathStart; i < extra.DeathEnd; ++i)
|
|
{
|
|
memcpy (states[i].sprite.name, extra.DeathSprite, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseSpriteFrames
|
|
//
|
|
// Parses a sprite-based animation sequence out of a decoration definition.
|
|
// You can have multiple sequences, and they will be appended together.
|
|
// A sequence definition looks like this:
|
|
//
|
|
// "<rate>:<frames>,<rate>:<frames>,<rate>:<frames>,..."
|
|
//
|
|
// Rate is a number describing the number of tics between frames in this
|
|
// sequence. If you don't specify it, then a rate of 4 is used. Frames is
|
|
// a list of consecutive frame characters. Each frame can be postfixed with
|
|
// the * character to indicate that it is fullbright.
|
|
//
|
|
// Examples:
|
|
// ShortRedTorch looks like this:
|
|
// "ABCD"
|
|
//
|
|
// HeadCandles looks like this:
|
|
// "6:AB"
|
|
//
|
|
// TechLamp looks like this:
|
|
// "A*B*C*D*"
|
|
//
|
|
// BloodyTwich looks like this:
|
|
// "10:A, 15:B, 8:C, 6:B"
|
|
//==========================================================================
|
|
|
|
static void ParseSpriteFrames (FActorInfo *info, TArray<FState> &states)
|
|
{
|
|
FState state;
|
|
char *token = strtok (sc_String, ",\t\n\r");
|
|
|
|
memset (&state, 0, sizeof(state));
|
|
|
|
while (token != NULL)
|
|
{
|
|
// Skip leading white space
|
|
while (*token == ' ')
|
|
token++;
|
|
|
|
int rate = 5;
|
|
bool firstState = true;
|
|
char *colon = strchr (token, ':');
|
|
|
|
if (colon != NULL)
|
|
{
|
|
char *stop;
|
|
|
|
*colon = 0;
|
|
rate = strtol (token, &stop, 10);
|
|
if (stop == token || rate < 1 || rate > 65534)
|
|
{
|
|
SC_ScriptError ("Rates must be in the range [0,65534]");
|
|
}
|
|
token = colon + 1;
|
|
rate += 1;
|
|
}
|
|
|
|
state.Tics = rate & 0xff;
|
|
state.Misc1 = (rate >> 8);
|
|
|
|
while (*token)
|
|
{
|
|
if (*token == ' ')
|
|
{
|
|
}
|
|
else if (*token == '*')
|
|
{
|
|
if (firstState)
|
|
{
|
|
SC_ScriptError ("* must come after a frame");
|
|
}
|
|
state.Frame |= SF_FULLBRIGHT;
|
|
}
|
|
else if (*token < 'A' || *token > ']')
|
|
{
|
|
SC_ScriptError ("Frames must be A-Z, [, \\, or ]");
|
|
}
|
|
else
|
|
{
|
|
if (!firstState)
|
|
{
|
|
states.Push (state);
|
|
}
|
|
firstState = false;
|
|
state.Frame = (rate >= 256) ? (SF_BIGTIC | (*token-'A')) : (*token-'A');
|
|
}
|
|
++token;
|
|
}
|
|
if (!firstState)
|
|
{
|
|
states.Push (state);
|
|
}
|
|
|
|
token = strtok (NULL, ",\t\n\r");
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_ScreamAndUnblock
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_ScreamAndUnblock (AActor *actor)
|
|
{
|
|
A_Scream (actor);
|
|
A_NoBlocking (actor);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_ActiveAndUnblock
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_ActiveAndUnblock (AActor *actor)
|
|
{
|
|
A_ActiveSound (actor);
|
|
A_NoBlocking (actor);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// A_ActiveSound
|
|
//
|
|
//===========================================================================
|
|
|
|
void A_ActiveSound (AActor *actor)
|
|
{
|
|
if (actor->ActiveSound)
|
|
{
|
|
S_SoundID (actor, CHAN_VOICE, actor->ActiveSound, 1, ATTN_NORM);
|
|
}
|
|
}
|