mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-11-29 23:33:00 +00:00
682 lines
18 KiB
C++
682 lines
18 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 "r_defs.h"
|
|
#include "a_pickups.h"
|
|
#include "cmdlib.h"
|
|
#include "p_lnspec.h"
|
|
#include "decallib.h"
|
|
#include "thingdef.h"
|
|
#include "backend/codegen.h"
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
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;
|
|
double DeathHeight, BurnHeight;
|
|
};
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
static void ParseInsideDecoration (Baggage &bag, AActor *defaults,
|
|
FExtraInfo &extra, EDefinitionType def, FScanner &sc, TArray<FState> &StateArray, TArray<FScriptPosition> &SourceLines);
|
|
static void ParseSpriteFrames (PClassActor *info, TArray<FState> &states, TArray<FScriptPosition> &SourceLines, FScanner &sc);
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
static const char *RenderStyles[] =
|
|
{
|
|
"STYLE_None",
|
|
"STYLE_Normal",
|
|
"STYLE_Fuzzy",
|
|
"STYLE_SoulTrans",
|
|
"STYLE_OptFuzzy",
|
|
"STYLE_Stencil",
|
|
"STYLE_Translucent",
|
|
"STYLE_Add",
|
|
//"STYLE_Shaded",
|
|
nullptr
|
|
};
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseOldDecoration
|
|
//
|
|
// Reads an old style decoration object
|
|
//
|
|
//==========================================================================
|
|
PClassActor *DecoDerivedClass(const FScriptPosition &sc, PClassActor *parent, FName typeName);
|
|
|
|
void ParseOldDecoration(FScanner &sc, EDefinitionType def, PNamespace *ns)
|
|
{
|
|
Baggage bag;
|
|
TArray<FState> StateArray;
|
|
TArray<FScriptPosition> SourceLines;
|
|
FExtraInfo extra;
|
|
PClassActor *type;
|
|
PClassActor *parent;
|
|
|
|
parent = (def == DEF_Pickup) ? PClass::FindActor("FakeInventory") : RUNTIME_CLASS(AActor);
|
|
|
|
sc.MustGetString();
|
|
FName typeName = FName(sc.String);
|
|
type = DecoDerivedClass(FScriptPosition(sc), parent, typeName);
|
|
ResetBaggage(&bag, parent);
|
|
bag.Namespace = ns;
|
|
bag.Info = type;
|
|
bag.fromDecorate = true;
|
|
bag.Version = { 2, 0, 0 };
|
|
#ifdef _DEBUG
|
|
bag.ClassName = type->TypeName;
|
|
#endif
|
|
|
|
sc.MustGetStringName("{");
|
|
|
|
memset (&extra, 0, sizeof(extra));
|
|
ParseInsideDecoration (bag, (AActor *)(type->Defaults), extra, def, sc, StateArray, SourceLines);
|
|
|
|
if (StateArray.Size() == 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 = StateArray[extra.IceDeathEnd-1];
|
|
FScriptPosition icepos = SourceLines[extra.IceDeathEnd - 1];
|
|
StateArray.Push (icecopy);
|
|
SourceLines.Push(icepos);
|
|
}
|
|
|
|
FState *states;
|
|
states = (FState*)ClassDataAllocator.Alloc(StateArray.Size() * sizeof(FState));
|
|
SaveStateSourceLines(states, SourceLines);
|
|
memcpy (states, &StateArray[0], StateArray.Size() * sizeof(states[0]));
|
|
if (StateArray.Size() == 1)
|
|
{
|
|
states->Tics = -1;
|
|
states->TicRange = 0;
|
|
states->Misc1 = 0;
|
|
}
|
|
else
|
|
{
|
|
size_t i;
|
|
|
|
// Spawn states loop endlessly
|
|
for (i = extra.SpawnStart; i < extra.SpawnEnd-1; ++i)
|
|
{
|
|
states[i].NextState = &states[i+1];
|
|
}
|
|
states[i].NextState = &states[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)
|
|
{
|
|
states[i].NextState = &states[i+1];
|
|
}
|
|
FState *state = &states[i];
|
|
if (extra.bDiesAway || def == DEF_Projectile)
|
|
{
|
|
state->NextState = nullptr;
|
|
}
|
|
else
|
|
{
|
|
state->Tics = -1;
|
|
state->TicRange = 0;
|
|
state->Misc1 = 0;
|
|
}
|
|
|
|
if (def == DEF_Projectile)
|
|
{
|
|
if (extra.bExplosive)
|
|
{
|
|
states[extra.DeathStart].SetAction("A_Explode");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The first frame plays the death sound and
|
|
// the second frame makes it nonsolid.
|
|
states[extra.DeathStart].SetAction("A_Scream");
|
|
if (extra.bSolidOnDeath)
|
|
{
|
|
}
|
|
else if (extra.DeathStart + 1 < extra.DeathEnd)
|
|
{
|
|
states[extra.DeathStart+1].SetAction("A_NoBlocking");
|
|
}
|
|
else
|
|
{
|
|
states[extra.DeathStart].SetAction("A_ScreamAndUnblock");
|
|
}
|
|
|
|
if (extra.DeathHeight == 0)
|
|
{
|
|
extra.DeathHeight = ((AActor*)(type->Defaults))->Height;
|
|
}
|
|
((AActor*)(type->Defaults))->FloatVar("DeathHeight") = extra.DeathHeight;
|
|
}
|
|
bag.statedef.SetStateLabel("Death", &states[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)
|
|
{
|
|
states[i].NextState = &states[i+1];
|
|
}
|
|
FState *state = &states[i];
|
|
if (extra.bBurnAway)
|
|
{
|
|
state->NextState = nullptr;
|
|
}
|
|
else
|
|
{
|
|
state->Tics = -1;
|
|
state->TicRange = 0;
|
|
state->Misc1 = 0;
|
|
}
|
|
|
|
// The first frame plays the burn sound and
|
|
// the second frame makes it nonsolid.
|
|
states[extra.FireDeathStart].SetAction("A_ActiveSound");
|
|
if (extra.bSolidOnBurn)
|
|
{
|
|
}
|
|
else if (extra.FireDeathStart + 1 < extra.FireDeathEnd)
|
|
{
|
|
states[extra.FireDeathStart+1].SetAction("A_NoBlocking");
|
|
}
|
|
else
|
|
{
|
|
states[extra.FireDeathStart].SetAction("A_ActiveAndUnblock");
|
|
}
|
|
|
|
if (extra.BurnHeight == 0) extra.BurnHeight = ((AActor*)(type->Defaults))->Height;
|
|
((AActor*)(type->Defaults))->FloatVar("BurnHeight") = extra.BurnHeight;
|
|
|
|
bag.statedef.SetStateLabel("Burn", &states[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)
|
|
{
|
|
states[i].NextState = &states[i+1];
|
|
}
|
|
FState *state = &states[i];
|
|
state->NextState = &states[StateArray.Size() - 1];
|
|
state->Tics = 5;
|
|
state->TicRange = 0;
|
|
state->Misc1 = 0;
|
|
state->SetAction("A_FreezeDeath");
|
|
|
|
i = StateArray.Size() - 1;
|
|
state->NextState = &states[i];
|
|
state->Tics = 1;
|
|
state->TicRange = 0;
|
|
state->Misc1 = 0;
|
|
state->SetAction("A_FreezeDeathChunks");
|
|
bag.statedef.SetStateLabel("Ice", &states[extra.IceDeathStart]);
|
|
}
|
|
else if (extra.bGenericIceDeath)
|
|
{
|
|
bag.statedef.SetStateLabel("Ice", RUNTIME_CLASS(AActor)->FindState(NAME_GenericFreezeDeath));
|
|
}
|
|
}
|
|
if (def == DEF_BreakableDecoration)
|
|
{
|
|
((AActor *)(type->Defaults))->flags |= MF_SHOOTABLE;
|
|
}
|
|
if (def == DEF_Projectile)
|
|
{
|
|
((AActor *)(type->Defaults))->flags |= MF_DROPOFF|MF_MISSILE;
|
|
}
|
|
bag.statedef.SetStateLabel("Spawn", &states[extra.SpawnStart]);
|
|
bag.statedef.InstallStates (type, ((AActor *)(type->Defaults)));
|
|
bag.Info->ActorInfo()->OwnedStates = states;
|
|
bag.Info->ActorInfo()->NumOwnedStates = StateArray.Size();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseInsideDecoration
|
|
//
|
|
// Parses attributes out of a definition terminated with a }
|
|
//
|
|
//==========================================================================
|
|
|
|
static void ParseInsideDecoration (Baggage &bag, AActor *defaults,
|
|
FExtraInfo &extra, EDefinitionType def, FScanner &sc, TArray<FState> &StateArray, TArray<FScriptPosition> &SourceLines)
|
|
{
|
|
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]");
|
|
}
|
|
bag.Info->ActorInfo()->DoomEdNum = (int16_t)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]");
|
|
}
|
|
bag.Info->ActorInfo()->SpawnID = (uint8_t)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 (sc.StringLen != 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 = StateArray.Size();
|
|
ParseSpriteFrames (bag.Info, StateArray, SourceLines, sc);
|
|
extra.SpawnEnd = StateArray.Size();
|
|
}
|
|
else if ((def == DEF_BreakableDecoration || def == DEF_Projectile) &&
|
|
sc.Compare ("DeathFrames"))
|
|
{
|
|
sc.MustGetString ();
|
|
extra.DeathStart = StateArray.Size();
|
|
ParseSpriteFrames (bag.Info, StateArray, SourceLines, sc);
|
|
extra.DeathEnd = StateArray.Size();
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("IceDeathFrames"))
|
|
{
|
|
sc.MustGetString ();
|
|
extra.IceDeathStart = StateArray.Size();
|
|
ParseSpriteFrames (bag.Info, StateArray, SourceLines, sc);
|
|
extra.IceDeathEnd = StateArray.Size();
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("BurnDeathFrames"))
|
|
{
|
|
sc.MustGetString ();
|
|
extra.FireDeathStart = StateArray.Size();
|
|
ParseSpriteFrames (bag.Info, StateArray, SourceLines, sc);
|
|
extra.FireDeathEnd = StateArray.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 = clamp (sc.Float, 0.0, 1.0);
|
|
}
|
|
else if (sc.Compare ("Scale"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
defaults->Scale.X = defaults->Scale.Y = sc.Float;
|
|
}
|
|
else if (sc.Compare ("RenderStyle"))
|
|
{
|
|
sc.MustGetString ();
|
|
defaults->RenderStyle = LegacyRenderStyles[sc.MustMatchString (RenderStyles)];
|
|
}
|
|
else if (sc.Compare ("Radius"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
defaults->radius = sc.Float;
|
|
}
|
|
else if (sc.Compare ("Height"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
defaults->Height = sc.Float;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("DeathHeight"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
extra.DeathHeight = sc.Float;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("BurnHeight"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
extra.BurnHeight = sc.Float;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("Health"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
defaults->health = sc.Number;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("ExplosionRadius"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
defaults->IntVar(NAME_ExplosionRadius) = sc.Number;
|
|
extra.bExplosive = true;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("ExplosionDamage"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
defaults->IntVar(NAME_ExplosionDamage) = sc.Number;
|
|
extra.bExplosive = true;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("DoNotHurtShooter"))
|
|
{
|
|
defaults->BoolVar(NAME_DontHurtShooter) = true;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("Damage"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
defaults->SetDamage(sc.Number);
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("DamageType"))
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("Normal"))
|
|
{
|
|
defaults->DamageType = NAME_None;
|
|
}
|
|
else
|
|
{
|
|
defaults->DamageType = sc.String;
|
|
}
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("Speed"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
defaults->Speed = sc.Float;
|
|
}
|
|
else if (sc.Compare ("Mass"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
defaults->Mass = int32_t(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 = sc.String;
|
|
}
|
|
else if (def == DEF_BreakableDecoration && sc.Compare ("BurnDeathSound"))
|
|
{
|
|
sc.MustGetString ();
|
|
defaults->ActiveSound = sc.String;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("SpawnSound"))
|
|
{
|
|
sc.MustGetString ();
|
|
defaults->SeeSound = sc.String;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("DoomBounce"))
|
|
{
|
|
defaults->BounceFlags = BOUNCE_DoomCompat;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("HereticBounce"))
|
|
{
|
|
defaults->BounceFlags = BOUNCE_HereticCompat;
|
|
}
|
|
else if (def == DEF_Projectile && sc.Compare ("HexenBounce"))
|
|
{
|
|
defaults->BounceFlags = BOUNCE_HexenCompat;
|
|
}
|
|
else if (def == DEF_Pickup && sc.Compare ("PickupSound"))
|
|
{
|
|
sc.MustGetString ();
|
|
defaults->IntVar(NAME_PickupSound) = FSoundID(sc.String);
|
|
}
|
|
else if (def == DEF_Pickup && sc.Compare ("PickupMessage"))
|
|
{
|
|
sc.MustGetString ();
|
|
defaults->StringVar(NAME_PickupMsg) = sc.String;
|
|
}
|
|
else if (def == DEF_Pickup && sc.Compare ("Respawns"))
|
|
{
|
|
defaults->BoolVar(NAME_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] != '*')
|
|
{
|
|
HandleActorFlag(sc, bag, sc.String, nullptr, '+');
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError (nullptr);
|
|
}
|
|
sc.MustGetString ();
|
|
}
|
|
|
|
unsigned int i;
|
|
int spr = GetSpriteIndex(sprite);
|
|
|
|
for (i = 0; i < StateArray.Size(); ++i)
|
|
{
|
|
StateArray[i].sprite = spr;
|
|
}
|
|
if (extra.DeathSprite[0] && extra.DeathEnd != 0)
|
|
{
|
|
int spr = GetSpriteIndex(extra.DeathSprite);
|
|
for (i = extra.DeathStart; i < extra.DeathEnd; ++i)
|
|
{
|
|
StateArray[i].sprite = spr;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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 (PClassActor *info, TArray<FState> &states, TArray<FScriptPosition> &SourceLines, FScanner &sc)
|
|
{
|
|
FState state;
|
|
char *token = strtok (sc.String, ",\t\n\r");
|
|
|
|
memset (&state, 0, sizeof(state));
|
|
state.UseFlags = info->ActorInfo()->DefaultStateUsage;
|
|
|
|
while (token != nullptr)
|
|
{
|
|
// Skip leading white space
|
|
while (*token == ' ')
|
|
token++;
|
|
|
|
int rate = 4;
|
|
bool firstState = true;
|
|
char *colon = strchr (token, ':');
|
|
|
|
if (colon != nullptr)
|
|
{
|
|
char *stop;
|
|
|
|
*colon = 0;
|
|
rate = (int)strtoll (token, &stop, 10);
|
|
if (stop == token || rate < 1 || rate > 65534)
|
|
{
|
|
sc.ScriptError ("Rates must be in the range [0,65534]");
|
|
}
|
|
token = colon + 1;
|
|
}
|
|
|
|
state.Tics = rate;
|
|
state.TicRange = 0;
|
|
|
|
while (*token)
|
|
{
|
|
if (*token == ' ')
|
|
{
|
|
}
|
|
else if (*token == '*')
|
|
{
|
|
if (firstState)
|
|
{
|
|
sc.ScriptError ("* must come after a frame");
|
|
}
|
|
state.StateFlags |= STF_FULLBRIGHT;
|
|
}
|
|
else if (*token < 'A' || *token > ']')
|
|
{
|
|
sc.ScriptError ("Frames must be A-Z, [, \\, or ]");
|
|
}
|
|
else
|
|
{
|
|
if (!firstState)
|
|
{
|
|
states.Push (state);
|
|
SourceLines.Push(sc);
|
|
}
|
|
firstState = false;
|
|
state.Frame = *token-'A';
|
|
}
|
|
++token;
|
|
}
|
|
if (!firstState)
|
|
{
|
|
states.Push (state);
|
|
SourceLines.Push(sc);
|
|
}
|
|
|
|
token = strtok (nullptr, ",\t\n\r");
|
|
}
|
|
}
|
|
|