mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-14 00:21:34 +00:00
64fe29bf0e
SVN r2284 (trunk)
1415 lines
30 KiB
C++
1415 lines
30 KiB
C++
/*
|
|
** decallib.cpp
|
|
** Parses DECALDEFs and creates a "library" of decals
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include "decallib.h"
|
|
#include "sc_man.h"
|
|
#include "w_wad.h"
|
|
#include "v_video.h"
|
|
#include "v_palette.h"
|
|
#include "cmdlib.h"
|
|
#include "m_random.h"
|
|
#include "weightedlist.h"
|
|
#include "statnums.h"
|
|
#include "templates.h"
|
|
#include "r_draw.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "r_translate.h"
|
|
#include "gi.h"
|
|
#include "g_level.h"
|
|
#include "colormatcher.h"
|
|
|
|
FDecalLib DecalLibrary;
|
|
|
|
static fixed_t ReadScale (FScanner &sc);
|
|
static TArray<BYTE> DecalTranslations;
|
|
|
|
// A decal group holds multiple decals and returns one randomly
|
|
// when GetDecal() is called.
|
|
|
|
// Sometimes two machines in a game will disagree on the state of
|
|
// decals. I do not know why.
|
|
|
|
static FRandom pr_decalchoice ("DecalChoice");
|
|
static FRandom pr_decal ("Decal");
|
|
|
|
class FDecalGroup : public FDecalBase
|
|
{
|
|
friend class FDecalLib;
|
|
|
|
public:
|
|
FDecalGroup () : Choices (pr_decalchoice) {}
|
|
const FDecalTemplate *GetDecal () const;
|
|
void ReplaceDecalRef (FDecalBase *from, FDecalBase *to)
|
|
{
|
|
Choices.ReplaceValues(from, to);
|
|
}
|
|
void AddDecal (FDecalBase *decal, WORD weight)
|
|
{
|
|
Choices.AddEntry (decal, weight);
|
|
}
|
|
|
|
private:
|
|
TWeightedList<FDecalBase *> Choices;
|
|
|
|
FDecalGroup &operator= (const FDecalGroup &) { return *this; }
|
|
};
|
|
|
|
struct FDecalLib::FTranslation
|
|
{
|
|
FTranslation (DWORD start, DWORD end);
|
|
FTranslation *LocateTranslation (DWORD start, DWORD end);
|
|
|
|
DWORD StartColor, EndColor;
|
|
FTranslation *Next;
|
|
WORD Index;
|
|
};
|
|
|
|
struct FDecalAnimator
|
|
{
|
|
FDecalAnimator (const char *name);
|
|
virtual ~FDecalAnimator ();
|
|
virtual DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const = 0;
|
|
|
|
FName Name;
|
|
};
|
|
|
|
TDeletingArray<FDecalAnimator *> Animators;
|
|
|
|
struct DDecalThinker : public DThinker
|
|
{
|
|
DECLARE_CLASS (DDecalThinker, DThinker)
|
|
HAS_OBJECT_POINTERS
|
|
public:
|
|
DDecalThinker (DBaseDecal *decal) : DThinker (STAT_DECALTHINKER), TheDecal (decal) {}
|
|
void Serialize (FArchive &arc);
|
|
TObjPtr<DBaseDecal> TheDecal;
|
|
protected:
|
|
DDecalThinker () : DThinker (STAT_DECALTHINKER) {}
|
|
};
|
|
|
|
IMPLEMENT_POINTY_CLASS (DDecalThinker)
|
|
DECLARE_POINTER (TheDecal)
|
|
END_POINTERS
|
|
|
|
void DDecalThinker::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << TheDecal;
|
|
}
|
|
|
|
struct FDecalFaderAnim : public FDecalAnimator
|
|
{
|
|
FDecalFaderAnim (const char *name) : FDecalAnimator (name) {}
|
|
DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const;
|
|
|
|
int DecayStart;
|
|
int DecayTime;
|
|
};
|
|
|
|
class DDecalFader : public DDecalThinker
|
|
{
|
|
DECLARE_CLASS (DDecalFader, DDecalThinker)
|
|
public:
|
|
DDecalFader (DBaseDecal *decal) : DDecalThinker (decal) {}
|
|
void Serialize (FArchive &arc);
|
|
void Tick ();
|
|
|
|
int TimeToStartDecay;
|
|
int TimeToEndDecay;
|
|
fixed_t StartTrans;
|
|
private:
|
|
DDecalFader () {}
|
|
};
|
|
|
|
struct FDecalColorerAnim : public FDecalAnimator
|
|
{
|
|
FDecalColorerAnim (const char *name) : FDecalAnimator (name) {}
|
|
DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const;
|
|
|
|
int DecayStart;
|
|
int DecayTime;
|
|
PalEntry GoalColor;
|
|
};
|
|
|
|
class DDecalColorer : public DDecalThinker
|
|
{
|
|
DECLARE_CLASS (DDecalColorer, DDecalThinker)
|
|
public:
|
|
DDecalColorer (DBaseDecal *decal) : DDecalThinker (decal) {}
|
|
void Serialize (FArchive &arc);
|
|
void Tick ();
|
|
|
|
int TimeToStartDecay;
|
|
int TimeToEndDecay;
|
|
PalEntry StartColor;
|
|
PalEntry GoalColor;
|
|
private:
|
|
DDecalColorer () {}
|
|
};
|
|
|
|
struct FDecalStretcherAnim : public FDecalAnimator
|
|
{
|
|
FDecalStretcherAnim (const char *name) : FDecalAnimator (name) {}
|
|
DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const;
|
|
|
|
int StretchStart;
|
|
int StretchTime;
|
|
fixed_t GoalX, GoalY;
|
|
};
|
|
|
|
class DDecalStretcher : public DDecalThinker
|
|
{
|
|
DECLARE_CLASS (DDecalStretcher, DDecalThinker)
|
|
public:
|
|
DDecalStretcher (DBaseDecal *decal) : DDecalThinker (decal) {}
|
|
void Serialize (FArchive &arc);
|
|
void Tick ();
|
|
|
|
int TimeToStart;
|
|
int TimeToStop;
|
|
fixed_t GoalX;
|
|
fixed_t StartX;
|
|
fixed_t GoalY;
|
|
fixed_t StartY;
|
|
bool bStretchX;
|
|
bool bStretchY;
|
|
bool bStarted;
|
|
private:
|
|
DDecalStretcher () {}
|
|
};
|
|
|
|
struct FDecalSliderAnim : public FDecalAnimator
|
|
{
|
|
FDecalSliderAnim (const char *name) : FDecalAnimator (name) {}
|
|
DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const;
|
|
|
|
int SlideStart;
|
|
int SlideTime;
|
|
fixed_t /*DistX,*/ DistY;
|
|
};
|
|
|
|
class DDecalSlider : public DDecalThinker
|
|
{
|
|
DECLARE_CLASS (DDecalSlider, DDecalThinker)
|
|
public:
|
|
DDecalSlider (DBaseDecal *decal) : DDecalThinker (decal) {}
|
|
void Serialize (FArchive &arc);
|
|
void Tick ();
|
|
|
|
int TimeToStart;
|
|
int TimeToStop;
|
|
/* fixed_t DistX; */
|
|
fixed_t DistY;
|
|
fixed_t StartX;
|
|
fixed_t StartY;
|
|
bool bStarted;
|
|
private:
|
|
DDecalSlider () {}
|
|
};
|
|
|
|
struct FDecalCombinerAnim : public FDecalAnimator
|
|
{
|
|
FDecalCombinerAnim (const char *name) : FDecalAnimator (name) {}
|
|
DThinker *CreateThinker (DBaseDecal *actor, side_t *wall) const;
|
|
|
|
int FirstAnimator;
|
|
int NumAnimators;
|
|
|
|
static TArray<FDecalAnimator *> AnimatorList;
|
|
};
|
|
|
|
TArray<FDecalAnimator *> FDecalCombinerAnim::AnimatorList;
|
|
|
|
static const char *DecalKeywords[] =
|
|
{
|
|
"x-scale",
|
|
"y-scale",
|
|
"pic",
|
|
"solid",
|
|
"add",
|
|
"translucent",
|
|
"flipx",
|
|
"flipy",
|
|
"randomflipx",
|
|
"randomflipy",
|
|
"fullbright",
|
|
"fuzzy",
|
|
"shade",
|
|
"colors",
|
|
"animator",
|
|
"lowerdecal",
|
|
NULL
|
|
};
|
|
|
|
enum
|
|
{
|
|
DECAL_XSCALE,
|
|
DECAL_YSCALE,
|
|
DECAL_PIC,
|
|
DECAL_SOLID,
|
|
DECAL_ADD,
|
|
DECAL_TRANSLUCENT,
|
|
DECAL_FLIPX,
|
|
DECAL_FLIPY,
|
|
DECAL_RANDOMFLIPX,
|
|
DECAL_RANDOMFLIPY,
|
|
DECAL_FULLBRIGHT,
|
|
DECAL_FUZZY,
|
|
DECAL_SHADE,
|
|
DECAL_COLORS,
|
|
DECAL_ANIMATOR,
|
|
DECAL_LOWERDECAL
|
|
};
|
|
|
|
const FDecalTemplate *FDecalBase::GetDecal () const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void FDecalTemplate::ReplaceDecalRef(FDecalBase *from, FDecalBase *to)
|
|
{
|
|
if (LowerDecal == from)
|
|
{
|
|
LowerDecal = to;
|
|
}
|
|
}
|
|
|
|
FDecalLib::FDecalLib ()
|
|
{
|
|
Root = NULL;
|
|
Translations = NULL;
|
|
}
|
|
|
|
FDecalLib::~FDecalLib ()
|
|
{
|
|
Clear ();
|
|
}
|
|
|
|
void FDecalLib::Clear ()
|
|
{
|
|
FTranslation *trans;
|
|
|
|
DelTree (Root);
|
|
Root = NULL;
|
|
|
|
trans = Translations;
|
|
while (trans != NULL)
|
|
{
|
|
FTranslation *next = trans->Next;
|
|
delete trans;
|
|
trans = next;
|
|
}
|
|
}
|
|
|
|
void FDecalLib::DelTree (FDecalBase *root)
|
|
{
|
|
if (root != NULL)
|
|
{
|
|
DelTree (root->Left);
|
|
DelTree (root->Right);
|
|
delete root;
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ReadAllDecals ()
|
|
{
|
|
int lump, lastlump = 0;
|
|
unsigned int i;
|
|
|
|
while ((lump = Wads.FindLump ("DECALDEF", &lastlump)) != -1)
|
|
{
|
|
FScanner sc(lump);
|
|
ReadDecals (sc);
|
|
}
|
|
// Supporting code to allow specifying decals directly in the DECORATE lump
|
|
for (i = 0; i < PClass::m_RuntimeActors.Size(); i++)
|
|
{
|
|
AActor *def = (AActor*)GetDefaultByType (PClass::m_RuntimeActors[i]);
|
|
|
|
FName v = ENamedName(intptr_t(def->DecalGenerator));
|
|
if (v.IsValidName())
|
|
{
|
|
def->DecalGenerator = ScanTreeForName (v, Root);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ReadDecals(FScanner &sc)
|
|
{
|
|
while (sc.GetString())
|
|
{
|
|
if (sc.Compare("decal"))
|
|
{
|
|
ParseDecal(sc);
|
|
}
|
|
else if (sc.Compare("decalgroup"))
|
|
{
|
|
ParseDecalGroup(sc);
|
|
}
|
|
else if (sc.Compare("generator"))
|
|
{
|
|
ParseGenerator(sc);
|
|
}
|
|
else if (sc.Compare("fader"))
|
|
{
|
|
ParseFader(sc);
|
|
}
|
|
else if (sc.Compare("stretcher"))
|
|
{
|
|
ParseStretcher(sc);
|
|
}
|
|
else if (sc.Compare("slider"))
|
|
{
|
|
ParseSlider(sc);
|
|
}
|
|
else if (sc.Compare("combiner"))
|
|
{
|
|
ParseCombiner(sc);
|
|
}
|
|
else if (sc.Compare("colorchanger"))
|
|
{
|
|
ParseColorchanger(sc);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError("Unknown decaldef keyword '%s'", sc.String);
|
|
}
|
|
}
|
|
}
|
|
|
|
WORD FDecalLib::GetDecalID (FScanner &sc)
|
|
{
|
|
sc.MustGetString ();
|
|
if (!IsNum (sc.String))
|
|
{
|
|
sc.UnGet ();
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
unsigned long num = strtoul (sc.String, NULL, 10);
|
|
if (num < 1 || num > 65535)
|
|
{
|
|
sc.MustGetStringName ("Decal ID must be between 1 and 65535");
|
|
}
|
|
return (WORD)num;
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseDecal (FScanner &sc)
|
|
{
|
|
FString decalName;
|
|
WORD decalNum;
|
|
FDecalTemplate newdecal;
|
|
int code;
|
|
FTextureID picnum;
|
|
int lumpnum;
|
|
|
|
sc.MustGetString ();
|
|
decalName = sc.String;
|
|
decalNum = GetDecalID (sc);
|
|
sc.MustGetStringName ("{");
|
|
|
|
memset (&newdecal, 0, sizeof(newdecal));
|
|
newdecal.PicNum.SetInvalid();
|
|
newdecal.ScaleX = newdecal.ScaleY = FRACUNIT;
|
|
newdecal.RenderFlags = RF_WALLSPRITE;
|
|
newdecal.RenderStyle = STYLE_Normal;
|
|
newdecal.Alpha = 0x8000;
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
AddDecal (decalName, decalNum, newdecal);
|
|
break;
|
|
}
|
|
switch ((code = sc.MustMatchString (DecalKeywords)))
|
|
{
|
|
case DECAL_XSCALE:
|
|
newdecal.ScaleX = ReadScale (sc);
|
|
break;
|
|
|
|
case DECAL_YSCALE:
|
|
newdecal.ScaleY = ReadScale (sc);
|
|
break;
|
|
|
|
case DECAL_PIC:
|
|
sc.MustGetString ();
|
|
picnum = TexMan.CheckForTexture (sc.String, FTexture::TEX_Any);
|
|
if (!picnum.Exists() && (lumpnum = Wads.CheckNumForName (sc.String, ns_graphics)) >= 0)
|
|
{
|
|
picnum = TexMan.CreateTexture (lumpnum, FTexture::TEX_Decal);
|
|
}
|
|
newdecal.PicNum = picnum;
|
|
break;
|
|
|
|
case DECAL_SOLID:
|
|
newdecal.RenderStyle = STYLE_Normal;
|
|
break;
|
|
|
|
case DECAL_ADD:
|
|
sc.MustGetFloat ();
|
|
newdecal.Alpha = (WORD)(32768.f * sc.Float);
|
|
newdecal.RenderStyle = STYLE_Add;
|
|
break;
|
|
|
|
case DECAL_TRANSLUCENT:
|
|
sc.MustGetFloat ();
|
|
newdecal.Alpha = (WORD)(32768.f * sc.Float);
|
|
newdecal.RenderStyle = STYLE_Translucent;
|
|
break;
|
|
|
|
case DECAL_FLIPX:
|
|
newdecal.RenderFlags |= RF_XFLIP;
|
|
break;
|
|
|
|
case DECAL_FLIPY:
|
|
newdecal.RenderFlags |= RF_YFLIP;
|
|
break;
|
|
|
|
case DECAL_RANDOMFLIPX:
|
|
newdecal.RenderFlags |= FDecalTemplate::DECAL_RandomFlipX;
|
|
break;
|
|
|
|
case DECAL_RANDOMFLIPY:
|
|
newdecal.RenderFlags |= FDecalTemplate::DECAL_RandomFlipY;
|
|
break;
|
|
|
|
case DECAL_FULLBRIGHT:
|
|
newdecal.RenderFlags |= RF_FULLBRIGHT;
|
|
break;
|
|
|
|
case DECAL_FUZZY:
|
|
newdecal.RenderStyle = STYLE_Fuzzy;
|
|
break;
|
|
|
|
case DECAL_SHADE:
|
|
sc.MustGetString ();
|
|
if (!sc.Compare("BloodDefault"))
|
|
{
|
|
newdecal.ShadeColor = V_GetColor (NULL, sc.String);
|
|
}
|
|
else
|
|
{
|
|
newdecal.ShadeColor = gameinfo.defaultbloodcolor;
|
|
}
|
|
newdecal.RenderStyle = STYLE_Shaded;
|
|
newdecal.ShadeColor |=
|
|
ColorMatcher.Pick (RPART(newdecal.ShadeColor),
|
|
GPART(newdecal.ShadeColor), BPART(newdecal.ShadeColor)) << 24;
|
|
break;
|
|
|
|
case DECAL_COLORS:
|
|
DWORD startcolor, endcolor;
|
|
|
|
sc.MustGetString (); startcolor = V_GetColor (NULL, sc.String);
|
|
sc.MustGetString (); endcolor = V_GetColor (NULL, sc.String);
|
|
newdecal.Translation = GenerateTranslation (startcolor, endcolor)->Index;
|
|
break;
|
|
|
|
case DECAL_ANIMATOR:
|
|
sc.MustGetString ();
|
|
newdecal.Animator = FindAnimator (sc.String);
|
|
break;
|
|
|
|
case DECAL_LOWERDECAL:
|
|
sc.MustGetString ();
|
|
newdecal.LowerDecal = GetDecalByName (sc.String);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseDecalGroup (FScanner &sc)
|
|
{
|
|
FString groupName;
|
|
WORD decalNum;
|
|
FDecalBase *targetDecal;
|
|
FDecalGroup *group;
|
|
|
|
sc.MustGetString ();
|
|
groupName = sc.String;
|
|
decalNum = GetDecalID (sc);
|
|
sc.MustGetStringName ("{");
|
|
|
|
group = new FDecalGroup;
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
group->Name = groupName;
|
|
group->SpawnID = decalNum;
|
|
AddDecal (group);
|
|
break;
|
|
}
|
|
|
|
targetDecal = ScanTreeForName (sc.String, Root);
|
|
if (targetDecal == NULL)
|
|
{
|
|
sc.ScriptError ("%s has not been defined", sc.String);
|
|
}
|
|
sc.MustGetNumber ();
|
|
|
|
group->AddDecal (targetDecal, sc.Number);
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseGenerator (FScanner &sc)
|
|
{
|
|
const PClass *type;
|
|
FDecalBase *decal;
|
|
AActor *actor;
|
|
|
|
// Get name of generator (actor)
|
|
sc.MustGetString ();
|
|
type = PClass::FindClass (sc.String);
|
|
if (type == NULL || type->ActorInfo == NULL)
|
|
{
|
|
sc.ScriptError ("%s is not an actor.", sc.String);
|
|
}
|
|
actor = (AActor *)type->Defaults;
|
|
|
|
// Get name of generated decal
|
|
sc.MustGetString ();
|
|
if (stricmp (sc.String, "None") == 0)
|
|
{
|
|
decal = NULL;
|
|
}
|
|
else
|
|
{
|
|
decal = ScanTreeForName (sc.String, Root);
|
|
if (decal == NULL)
|
|
{
|
|
sc.ScriptError ("%s has not been defined.", sc.String);
|
|
}
|
|
}
|
|
|
|
actor->DecalGenerator = decal;
|
|
decal->Users.Push (type);
|
|
}
|
|
|
|
void FDecalLib::ParseFader (FScanner &sc)
|
|
{
|
|
FString faderName;
|
|
int startTime = 0, decayTime = 0;
|
|
|
|
sc.MustGetString ();
|
|
faderName = sc.String;
|
|
sc.MustGetStringName ("{");
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
FDecalFaderAnim *fader = new FDecalFaderAnim (faderName);
|
|
fader->DecayStart = startTime;
|
|
fader->DecayTime = decayTime;
|
|
Animators.Push (fader);
|
|
break;
|
|
}
|
|
else if (sc.Compare ("DecayStart"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
startTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("DecayTime"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
decayTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError ("Unknown fader parameter '%s'", sc.String);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseStretcher (FScanner &sc)
|
|
{
|
|
FString stretcherName;
|
|
fixed_t goalX = -1, goalY = -1;
|
|
int startTime = 0, takeTime = 0;
|
|
|
|
sc.MustGetString ();
|
|
stretcherName = sc.String;
|
|
sc.MustGetStringName ("{");
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
if (goalX >= 0 || goalY >= 0)
|
|
{
|
|
FDecalStretcherAnim *stretcher = new FDecalStretcherAnim (stretcherName);
|
|
stretcher->StretchStart = startTime;
|
|
stretcher->StretchTime = takeTime;
|
|
stretcher->GoalX = goalX;
|
|
stretcher->GoalY = goalY;
|
|
Animators.Push (stretcher);
|
|
}
|
|
break;
|
|
}
|
|
else if (sc.Compare ("StretchStart"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
startTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("StretchTime"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
takeTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("GoalX"))
|
|
{
|
|
goalX = ReadScale (sc);
|
|
}
|
|
else if (sc.Compare ("GoalY"))
|
|
{
|
|
goalY = ReadScale (sc);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError ("Unknown stretcher parameter '%s'", sc.String);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseSlider (FScanner &sc)
|
|
{
|
|
FString sliderName;
|
|
fixed_t distX = 0, distY = 0;
|
|
int startTime = 0, takeTime = 0;
|
|
|
|
sc.MustGetString ();
|
|
sliderName = sc.String;
|
|
sc.MustGetStringName ("{");
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
if ((/*distX |*/ distY) != 0)
|
|
{
|
|
FDecalSliderAnim *slider = new FDecalSliderAnim (sliderName);
|
|
slider->SlideStart = startTime;
|
|
slider->SlideTime = takeTime;
|
|
/*slider->DistX = distX;*/
|
|
slider->DistY = distY;
|
|
Animators.Push (slider);
|
|
}
|
|
break;
|
|
}
|
|
else if (sc.Compare ("SlideStart"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
startTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("SlideTime"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
takeTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("DistX"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
distX = (fixed_t)(sc.Float * FRACUNIT);
|
|
Printf ("DistX in slider decal %s is unsupported\n", sliderName.GetChars());
|
|
}
|
|
else if (sc.Compare ("DistY"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
distY = (fixed_t)(sc.Float * FRACUNIT);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError ("Unknown slider parameter '%s'", sc.String);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseColorchanger (FScanner &sc)
|
|
{
|
|
FString faderName;
|
|
int startTime = 0, decayTime = 0;
|
|
PalEntry goal = 0;
|
|
|
|
sc.MustGetString ();
|
|
faderName = sc.String;
|
|
sc.MustGetStringName ("{");
|
|
|
|
for (;;)
|
|
{
|
|
sc.MustGetString ();
|
|
if (sc.Compare ("}"))
|
|
{
|
|
FDecalColorerAnim *fader = new FDecalColorerAnim (faderName);
|
|
fader->DecayStart = startTime;
|
|
fader->DecayTime = decayTime;
|
|
fader->GoalColor = goal;
|
|
Animators.Push (fader);
|
|
break;
|
|
}
|
|
else if (sc.Compare ("FadeStart"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
startTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("FadeTime"))
|
|
{
|
|
sc.MustGetFloat ();
|
|
decayTime = (int)(sc.Float * TICRATE);
|
|
}
|
|
else if (sc.Compare ("Color"))
|
|
{
|
|
sc.MustGetString ();
|
|
goal = V_GetColor (NULL, sc.String);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError ("Unknown color changer parameter '%s'", sc.String);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ParseCombiner (FScanner &sc)
|
|
{
|
|
FString combinerName;
|
|
size_t first = FDecalCombinerAnim::AnimatorList.Size ();
|
|
|
|
sc.MustGetString ();
|
|
combinerName = sc.String;
|
|
sc.MustGetStringName ("{");
|
|
sc.MustGetString ();
|
|
while (!sc.Compare ("}"))
|
|
{
|
|
FDecalAnimator *anim = FindAnimator (sc.String);
|
|
if (anim == NULL)
|
|
{
|
|
sc.ScriptError ("Undefined animator %s", sc.String);
|
|
}
|
|
FDecalCombinerAnim::AnimatorList.Push (anim);
|
|
sc.MustGetString ();
|
|
}
|
|
|
|
size_t last = FDecalCombinerAnim::AnimatorList.Size ();
|
|
|
|
if (last > first)
|
|
{
|
|
FDecalCombinerAnim *combiner = new FDecalCombinerAnim (combinerName);
|
|
combiner->FirstAnimator = (int)first;
|
|
combiner->NumAnimators = (int)(last - first);
|
|
Animators.Push (combiner);
|
|
}
|
|
}
|
|
|
|
void FDecalLib::ReplaceDecalRef (FDecalBase *from, FDecalBase *to, FDecalBase *root)
|
|
{
|
|
if (root == NULL)
|
|
{
|
|
return;
|
|
}
|
|
ReplaceDecalRef (from, to, root->Left);
|
|
ReplaceDecalRef (from, to, root->Right);
|
|
root->ReplaceDecalRef (from, to);
|
|
}
|
|
|
|
void FDecalLib::AddDecal (const char *name, WORD num, const FDecalTemplate &decal)
|
|
{
|
|
FDecalTemplate *newDecal = new FDecalTemplate;
|
|
|
|
*newDecal = decal;
|
|
newDecal->Name = name;
|
|
newDecal->SpawnID = num;
|
|
AddDecal (newDecal);
|
|
}
|
|
|
|
void FDecalLib::AddDecal (FDecalBase *decal)
|
|
{
|
|
FDecalBase *node = Root, **prev = &Root;
|
|
int num = decal->SpawnID;
|
|
|
|
decal->SpawnID = 0;
|
|
|
|
// Check if this decal already exists.
|
|
while (node != NULL)
|
|
{
|
|
int lexx = stricmp (decal->Name, node->Name);
|
|
if (lexx == 0)
|
|
{
|
|
break;
|
|
}
|
|
else if (lexx < 0)
|
|
{
|
|
prev = &node->Left;
|
|
node = node->Left;
|
|
}
|
|
else
|
|
{
|
|
prev = &node->Right;
|
|
node = node->Right;
|
|
}
|
|
}
|
|
if (node == NULL)
|
|
{ // No, add it.
|
|
decal->SpawnID = 0;
|
|
*prev = decal;
|
|
decal->Left = NULL;
|
|
decal->Right = NULL;
|
|
}
|
|
else
|
|
{ // Yes, replace the old one.
|
|
// If this decal has been used as the lowerdecal for another decal,
|
|
// be sure and update the lowerdecal to use the new decal.
|
|
ReplaceDecalRef(node, decal, Root);
|
|
|
|
decal->Left = node->Left;
|
|
decal->Right = node->Right;
|
|
*prev = decal;
|
|
|
|
// Fix references to the old decal so that they use the new one instead.
|
|
for (unsigned int i = 0; i < node->Users.Size(); ++i)
|
|
{
|
|
((AActor *)node->Users[i]->Defaults)->DecalGenerator = decal;
|
|
}
|
|
decal->Users = node->Users;
|
|
delete node;
|
|
}
|
|
// If this decal has an ID, make sure no existing decals have the same ID.
|
|
if (num != 0)
|
|
{
|
|
FDecalBase *spawner = ScanTreeForNum (num, Root);
|
|
if (spawner != NULL)
|
|
{
|
|
spawner->SpawnID = 0;
|
|
}
|
|
decal->SpawnID = num;
|
|
}
|
|
}
|
|
|
|
const FDecalTemplate *FDecalLib::GetDecalByNum (WORD num) const
|
|
{
|
|
if (num == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
FDecalBase *base = ScanTreeForNum (num, Root);
|
|
if (base != NULL)
|
|
{
|
|
return base->GetDecal ();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const FDecalTemplate *FDecalLib::GetDecalByName (const char *name) const
|
|
{
|
|
if (name == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
FDecalBase *base = ScanTreeForName (name, Root);
|
|
if (base != NULL)
|
|
{
|
|
return static_cast<FDecalTemplate *>(base);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
FDecalBase *FDecalLib::ScanTreeForNum (const WORD num, FDecalBase *root)
|
|
{
|
|
while (root != NULL)
|
|
{
|
|
if (root->SpawnID == num)
|
|
{
|
|
break;
|
|
}
|
|
FDecalBase *leftres = ScanTreeForNum (num, root->Left);
|
|
if (leftres != NULL)
|
|
return leftres;
|
|
root = root->Right; // Avoid tail-recursion
|
|
}
|
|
return root;
|
|
}
|
|
|
|
FDecalBase *FDecalLib::ScanTreeForName (const char *name, FDecalBase *root)
|
|
{
|
|
while (root != NULL)
|
|
{
|
|
int lexx = stricmp (name, root->Name);
|
|
if (lexx == 0)
|
|
{
|
|
break;
|
|
}
|
|
else if (lexx < 0)
|
|
{
|
|
root = root->Left;
|
|
}
|
|
else
|
|
{
|
|
root = root->Right;
|
|
}
|
|
}
|
|
return root;
|
|
}
|
|
|
|
FDecalLib::FTranslation *FDecalLib::GenerateTranslation (DWORD start, DWORD end)
|
|
{
|
|
FTranslation *trans;
|
|
|
|
if (Translations != NULL)
|
|
{
|
|
trans = Translations->LocateTranslation (start, end);
|
|
}
|
|
else
|
|
{
|
|
trans = NULL;
|
|
}
|
|
if (trans == NULL)
|
|
{
|
|
trans = new FTranslation (start, end);
|
|
trans->Next = Translations;
|
|
Translations = trans;
|
|
}
|
|
return trans;
|
|
}
|
|
|
|
FDecalBase::FDecalBase ()
|
|
{
|
|
Name = NAME_None;
|
|
}
|
|
|
|
FDecalBase::~FDecalBase ()
|
|
{
|
|
}
|
|
|
|
void FDecalTemplate::ApplyToDecal (DBaseDecal *decal, side_t *wall) const
|
|
{
|
|
if (RenderStyle.Flags & STYLEF_ColorIsFixed)
|
|
{
|
|
decal->SetShade (ShadeColor);
|
|
}
|
|
decal->Translation = Translation;
|
|
decal->ScaleX = ScaleX;
|
|
decal->ScaleY = ScaleY;
|
|
decal->PicNum = PicNum;
|
|
decal->Alpha = Alpha << 1;
|
|
decal->RenderStyle = RenderStyle;
|
|
decal->RenderFlags = (RenderFlags & ~(DECAL_RandomFlipX|DECAL_RandomFlipY)) |
|
|
(decal->RenderFlags & (RF_RELMASK|RF_CLIPMASK|RF_INVISIBLE|RF_ONESIDED));
|
|
if (RenderFlags & (DECAL_RandomFlipX|DECAL_RandomFlipY))
|
|
{
|
|
decal->RenderFlags ^= pr_decal() &
|
|
((RenderFlags & (DECAL_RandomFlipX|DECAL_RandomFlipY)) >> 8);
|
|
}
|
|
if (Animator != NULL)
|
|
{
|
|
Animator->CreateThinker (decal, wall);
|
|
}
|
|
}
|
|
|
|
const FDecalTemplate *FDecalTemplate::GetDecal () const
|
|
{
|
|
return this;
|
|
}
|
|
|
|
FDecalLib::FTranslation::FTranslation (DWORD start, DWORD end)
|
|
{
|
|
DWORD ri, gi, bi, rs, gs, bs;
|
|
PalEntry *first, *last;
|
|
BYTE *table;
|
|
unsigned int i, tablei;
|
|
|
|
StartColor = start;
|
|
EndColor = end;
|
|
Next = NULL;
|
|
|
|
if (DecalTranslations.Size() == 256*256)
|
|
{
|
|
Printf ("Too many decal translations defined\n");
|
|
Index = 0;
|
|
return;
|
|
}
|
|
|
|
first = (PalEntry *)&StartColor;
|
|
last = (PalEntry *)&EndColor;
|
|
|
|
ri = first->r << 24;
|
|
gi = first->g << 24;
|
|
bi = first->b << 24;
|
|
rs = last->r << 24;
|
|
gs = last->g << 24;
|
|
bs = last->b << 24;
|
|
|
|
rs = (rs - ri) / 255;
|
|
gs = (gs - ri) / 255;
|
|
bs = (bs - bi) / 255;
|
|
|
|
tablei = DecalTranslations.Reserve(256);
|
|
table = &DecalTranslations[tablei];
|
|
|
|
for (i = 1; i < 256; i++, ri += rs, gi += gs, bi += bs)
|
|
{
|
|
table[i] = ColorMatcher.Pick (ri >> 24, gi >> 24, bi >> 24);
|
|
}
|
|
table[0] = table[1];
|
|
Index = (WORD)TRANSLATION(TRANSLATION_Decals, tablei >> 8);
|
|
}
|
|
|
|
FDecalLib::FTranslation *FDecalLib::FTranslation::LocateTranslation (DWORD start, DWORD end)
|
|
{
|
|
FTranslation *trans = this;
|
|
|
|
do
|
|
{
|
|
if (start == trans->StartColor && end == trans->EndColor)
|
|
{
|
|
return trans;
|
|
}
|
|
trans = trans->Next;
|
|
} while (trans != NULL);
|
|
return trans;
|
|
}
|
|
|
|
const FDecalTemplate *FDecalGroup::GetDecal () const
|
|
{
|
|
const FDecalBase *decal = Choices.PickEntry ();
|
|
const FDecalBase *remember;
|
|
|
|
// Repeatedly GetDecal() until the result is constant, since
|
|
// the choice might be another FDecalGroup.
|
|
do
|
|
{
|
|
remember = decal;
|
|
decal = decal->GetDecal ();
|
|
} while (decal != remember);
|
|
return static_cast<const FDecalTemplate *>(decal);
|
|
}
|
|
|
|
FDecalAnimator::FDecalAnimator (const char *name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
FDecalAnimator::~FDecalAnimator ()
|
|
{
|
|
}
|
|
|
|
IMPLEMENT_CLASS (DDecalFader)
|
|
|
|
void DDecalFader::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << TimeToStartDecay << TimeToEndDecay << StartTrans;
|
|
}
|
|
|
|
void DDecalFader::Tick ()
|
|
{
|
|
if (TheDecal == NULL)
|
|
{
|
|
Destroy ();
|
|
}
|
|
else
|
|
{
|
|
if (level.maptime < TimeToStartDecay || bglobal.freeze)
|
|
{
|
|
return;
|
|
}
|
|
else if (level.maptime >= TimeToEndDecay)
|
|
{
|
|
TheDecal->Destroy (); // remove the decal
|
|
Destroy (); // remove myself
|
|
return;
|
|
}
|
|
if (StartTrans == -1)
|
|
{
|
|
StartTrans = TheDecal->Alpha;
|
|
}
|
|
|
|
int distanceToEnd = TimeToEndDecay - level.maptime;
|
|
int fadeDistance = TimeToEndDecay - TimeToStartDecay;
|
|
TheDecal->Alpha = Scale (StartTrans, distanceToEnd, fadeDistance);
|
|
}
|
|
}
|
|
|
|
DThinker *FDecalFaderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
|
|
{
|
|
DDecalFader *fader = new DDecalFader (actor);
|
|
|
|
fader->TimeToStartDecay = level.maptime + DecayStart;
|
|
fader->TimeToEndDecay = fader->TimeToStartDecay + DecayTime;
|
|
fader->StartTrans = -1;
|
|
return fader;
|
|
}
|
|
|
|
IMPLEMENT_CLASS (DDecalStretcher)
|
|
|
|
void DDecalStretcher::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << TimeToStart
|
|
<< TimeToStop
|
|
<< GoalX
|
|
<< StartX
|
|
<< bStretchX
|
|
<< GoalY
|
|
<< StartY
|
|
<< bStretchY
|
|
<< bStarted;
|
|
}
|
|
|
|
DThinker *FDecalStretcherAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
|
|
{
|
|
DDecalStretcher *thinker = new DDecalStretcher (actor);
|
|
|
|
thinker->TimeToStart = level.maptime + StretchStart;
|
|
thinker->TimeToStop = thinker->TimeToStart + StretchTime;
|
|
|
|
if (GoalX >= 0)
|
|
{
|
|
thinker->GoalX = GoalX;
|
|
thinker->bStretchX = true;
|
|
}
|
|
else
|
|
{
|
|
thinker->bStretchX = false;
|
|
}
|
|
if (GoalY >= 0)
|
|
{
|
|
thinker->GoalY = GoalY;
|
|
thinker->bStretchY = true;
|
|
}
|
|
else
|
|
{
|
|
thinker->bStretchY = false;
|
|
}
|
|
thinker->bStarted = false;
|
|
return thinker;
|
|
}
|
|
|
|
void DDecalStretcher::Tick ()
|
|
{
|
|
if (TheDecal == NULL)
|
|
{
|
|
Destroy ();
|
|
return;
|
|
}
|
|
if (level.maptime < TimeToStart || bglobal.freeze)
|
|
{
|
|
return;
|
|
}
|
|
if (level.maptime >= TimeToStop)
|
|
{
|
|
if (bStretchX)
|
|
{
|
|
TheDecal->ScaleX = GoalX;
|
|
}
|
|
if (bStretchY)
|
|
{
|
|
TheDecal->ScaleY = GoalY;
|
|
}
|
|
Destroy ();
|
|
return;
|
|
}
|
|
if (!bStarted)
|
|
{
|
|
bStarted = true;
|
|
StartX = TheDecal->ScaleX;
|
|
StartY = TheDecal->ScaleY;
|
|
}
|
|
|
|
int distance = level.maptime - TimeToStart;
|
|
int maxDistance = TimeToStop - TimeToStart;
|
|
if (bStretchX)
|
|
{
|
|
TheDecal->ScaleX = StartX + Scale (GoalX - StartX, distance, maxDistance);
|
|
}
|
|
if (bStretchY)
|
|
{
|
|
TheDecal->ScaleY = StartY + Scale (GoalY - StartY, distance, maxDistance);
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_CLASS (DDecalSlider)
|
|
|
|
void DDecalSlider::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << TimeToStart
|
|
<< TimeToStop
|
|
/*<< DistX*/
|
|
<< DistY
|
|
/*<< StartX*/
|
|
<< StartY
|
|
<< bStarted;
|
|
}
|
|
|
|
DThinker *FDecalSliderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
|
|
{
|
|
DDecalSlider *thinker = new DDecalSlider (actor);
|
|
|
|
thinker->TimeToStart = level.maptime + SlideStart;
|
|
thinker->TimeToStop = thinker->TimeToStart + SlideTime;
|
|
/*thinker->DistX = DistX;*/
|
|
thinker->DistY = DistY;
|
|
thinker->bStarted = false;
|
|
return thinker;
|
|
}
|
|
|
|
void DDecalSlider::Tick ()
|
|
{
|
|
if (TheDecal == NULL)
|
|
{
|
|
Destroy ();
|
|
return;
|
|
}
|
|
if (level.maptime < TimeToStart || bglobal.freeze)
|
|
{
|
|
return;
|
|
}
|
|
if (!bStarted)
|
|
{
|
|
bStarted = true;
|
|
/*StartX = TheDecal->LeftDistance;*/
|
|
StartY = TheDecal->Z;
|
|
}
|
|
if (level.maptime >= TimeToStop)
|
|
{
|
|
/*TheDecal->LeftDistance = StartX + DistX;*/
|
|
TheDecal->Z = StartY + DistY;
|
|
Destroy ();
|
|
return;
|
|
}
|
|
|
|
int distance = level.maptime - TimeToStart;
|
|
int maxDistance = TimeToStop - TimeToStart;
|
|
/*TheDecal->LeftDistance = StartX + Scale (DistX, distance, maxDistance);*/
|
|
TheDecal->Z = StartY + Scale (DistY, distance, maxDistance);
|
|
}
|
|
|
|
DThinker *FDecalCombinerAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
|
|
{
|
|
DThinker *thinker = NULL;
|
|
|
|
for (int i = 0; i < NumAnimators; ++i)
|
|
{
|
|
thinker = AnimatorList[FirstAnimator+i]->CreateThinker (actor, wall);
|
|
}
|
|
return thinker;
|
|
}
|
|
|
|
FDecalAnimator *FDecalLib::FindAnimator (const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = (int)Animators.Size ()-1; i >= 0; --i)
|
|
{
|
|
if (stricmp (name, Animators[i]->Name) == 0)
|
|
{
|
|
return Animators[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
IMPLEMENT_CLASS (DDecalColorer)
|
|
|
|
void DDecalColorer::Serialize (FArchive &arc)
|
|
{
|
|
Super::Serialize (arc);
|
|
arc << TimeToStartDecay << TimeToEndDecay << StartColor << GoalColor;
|
|
}
|
|
|
|
void DDecalColorer::Tick ()
|
|
{
|
|
if (TheDecal == NULL || !(TheDecal->RenderStyle.Flags & STYLEF_ColorIsFixed))
|
|
{
|
|
Destroy ();
|
|
}
|
|
else
|
|
{
|
|
if (level.maptime < TimeToStartDecay || bglobal.freeze)
|
|
{
|
|
return;
|
|
}
|
|
else if (level.maptime >= TimeToEndDecay)
|
|
{
|
|
TheDecal->SetShade (GoalColor);
|
|
Destroy (); // remove myself
|
|
}
|
|
if (StartColor.a == 255)
|
|
{
|
|
StartColor = TheDecal->AlphaColor & 0xffffff;
|
|
if (StartColor == GoalColor)
|
|
{
|
|
Destroy ();
|
|
return;
|
|
}
|
|
}
|
|
if (level.maptime & 0)
|
|
{ // Changing the shade can be expensive, so don't do it too often.
|
|
return;
|
|
}
|
|
|
|
int distance = level.maptime - TimeToStartDecay;
|
|
int maxDistance = TimeToEndDecay - TimeToStartDecay;
|
|
int r = StartColor.r + Scale (GoalColor.r - StartColor.r, distance, maxDistance);
|
|
int g = StartColor.g + Scale (GoalColor.g - StartColor.g, distance, maxDistance);
|
|
int b = StartColor.b + Scale (GoalColor.b - StartColor.b, distance, maxDistance);
|
|
TheDecal->SetShade (r, g, b);
|
|
}
|
|
}
|
|
|
|
DThinker *FDecalColorerAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
|
|
{
|
|
DDecalColorer *Colorer = new DDecalColorer (actor);
|
|
|
|
Colorer->TimeToStartDecay = level.maptime + DecayStart;
|
|
Colorer->TimeToEndDecay = Colorer->TimeToStartDecay + DecayTime;
|
|
Colorer->StartColor = 0xff000000;
|
|
Colorer->GoalColor = GoalColor;
|
|
return Colorer;
|
|
}
|
|
|
|
static fixed_t ReadScale (FScanner &sc)
|
|
{
|
|
sc.MustGetFloat ();
|
|
return fixed_t(clamp (sc.Float * FRACUNIT, 256.0, 256.0*FRACUNIT));
|
|
}
|