gzdoom/src/decallib.cpp
Christoph Oelckers 4261a0c592 - print a warning if a decal definition cannot find an animator.
This is important because DECALDEF cannot tentatively find animators declared after the decal.
2017-08-12 12:30:42 +02:00

1452 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 "a_sharedglobal.h"
#include "r_data/r_translate.h"
#include "gi.h"
#include "g_level.h"
#include "colormatcher.h"
#include "b_bot.h"
#include "serializer.h"
#include "g_levellocals.h"
FDecalLib DecalLibrary;
static double ReadScale (FScanner &sc);
static TArray<uint8_t> 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, uint16_t weight)
{
Choices.AddEntry (decal, weight);
}
private:
TWeightedList<FDecalBase *> Choices;
FDecalGroup &operator= (const FDecalGroup &) { return *this; }
};
struct FDecalLib::FTranslation
{
FTranslation (uint32_t start, uint32_t end);
FTranslation *LocateTranslation (uint32_t start, uint32_t end);
uint32_t StartColor, EndColor;
FTranslation *Next;
uint16_t 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(FSerializer &arc);
TObjPtr<DBaseDecal*> TheDecal;
protected:
DDecalThinker () : DThinker (STAT_DECALTHINKER) {}
};
IMPLEMENT_CLASS(DDecalThinker, false, true)
IMPLEMENT_POINTERS_START(DDecalThinker)
IMPLEMENT_POINTER(TheDecal)
IMPLEMENT_POINTERS_END
void DDecalThinker::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("thedecal", 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(FSerializer &arc);
void Tick ();
int TimeToStartDecay;
int TimeToEndDecay;
double 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(FSerializer &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;
double GoalX, GoalY;
};
class DDecalStretcher : public DDecalThinker
{
DECLARE_CLASS (DDecalStretcher, DDecalThinker)
public:
DDecalStretcher (DBaseDecal *decal) : DDecalThinker (decal) {}
void Serialize(FSerializer &arc);
void Tick ();
int TimeToStart;
int TimeToStop;
double GoalX;
double StartX;
double GoalY;
double 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;
double /*DistX,*/ DistY;
};
class DDecalSlider : public DDecalThinker
{
DECLARE_CLASS (DDecalSlider, DDecalThinker)
public:
DDecalSlider (DBaseDecal *decal) : DDecalThinker (decal) {}
void Serialize(FSerializer &arc);
void Tick ();
int TimeToStart;
int TimeToStop;
/* double DistX; */
double DistY;
double StartX;
double 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;
for(unsigned i=0;i<Animators.Size(); i++)
{
delete Animators[i];
}
Animators.Clear();
FDecalCombinerAnim::AnimatorList.Clear();
DecalTranslations.Clear();
DecalLibrary.Clear();
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 < PClassActor::AllActorClasses.Size(); i++)
{
AActor *def = (AActor*)GetDefaultByType (PClassActor::AllActorClasses[i]);
if (nullptr == def)
{
// This is referenced but undefined class
// The corresponding warning should be already reported by DECORATE parser
continue;
}
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);
}
}
}
uint16_t 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.ScriptError ("Decal ID must be between 1 and 65535");
}
return (uint16_t)num;
}
}
void FDecalLib::ParseDecal (FScanner &sc)
{
FString decalName;
uint16_t decalNum;
FDecalTemplate newdecal;
FTextureID picnum;
int lumpnum;
sc.MustGetString ();
decalName = sc.String;
decalNum = GetDecalID (sc);
sc.MustGetStringName ("{");
memset ((void *)&newdecal, 0, sizeof(newdecal));
newdecal.PicNum.SetInvalid();
newdecal.ScaleX = newdecal.ScaleY = 1.;
newdecal.RenderFlags = RF_WALLSPRITE;
newdecal.RenderStyle = STYLE_Normal;
newdecal.Alpha = 1.;
for (;;)
{
sc.MustGetString ();
if (sc.Compare ("}"))
{
AddDecal (decalName, decalNum, newdecal);
break;
}
switch (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 = sc.Float;
newdecal.RenderStyle = STYLE_Add;
break;
case DECAL_TRANSLUCENT:
sc.MustGetFloat ();
newdecal.Alpha = 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);
}
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:
uint32_t startcolor, endcolor;
sc.MustGetString (); startcolor = V_GetColor (NULL, sc);
sc.MustGetString (); endcolor = V_GetColor (NULL, sc);
newdecal.Translation = GenerateTranslation (startcolor, endcolor)->Index;
break;
case DECAL_ANIMATOR:
sc.MustGetString ();
newdecal.Animator = FindAnimator (sc.String);
if (newdecal.Animator == nullptr)
{
sc.ScriptMessage("Unable to find animator %s", sc.String);
}
break;
case DECAL_LOWERDECAL:
sc.MustGetString ();
newdecal.LowerDecal = GetDecalByName (sc.String);
break;
}
}
}
void FDecalLib::ParseDecalGroup (FScanner &sc)
{
FString groupName;
uint16_t 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)
{
PClassActor *type;
FDecalBase *decal;
bool optional = false;
// Get name of generator (actor)
sc.MustGetString ();
optional = sc.Compare("optional");
if (optional) sc.MustGetString();
type = PClass::FindActor (sc.String);
if (type == NULL)
{
if (!optional) sc.ScriptError ("%s is not an actor.", sc.String);
}
// Get name of generated decal
sc.MustGetString ();
if (stricmp (sc.String, "None") == 0)
{
decal = NULL;
}
else
{
decal = ScanTreeForName (sc.String, Root);
if (decal == NULL)
{
if (!optional) sc.ScriptError ("%s has not been defined.", sc.String);
}
}
if (type != NULL)
{
AActor *actor = (AActor *)type->Defaults;
actor->DecalGenerator = decal;
if (decal != NULL)
{
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;
double 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;
double 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 (); // must remain to avoid breaking definitions that accidentally used DistX
Printf ("DistX in slider decal %s is unsupported\n", sliderName.GetChars());
}
else if (sc.Compare ("DistY"))
{
sc.MustGetFloat ();
distY = sc.Float;
}
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);
}
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, uint16_t 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 (uint16_t 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 uint16_t 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 (uint32_t start, uint32_t 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;
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 (uint32_t start, uint32_t end)
{
uint32_t ri, gi, bi, rs, gs, bs;
PalEntry *first, *last;
uint8_t *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 = (uint16_t)TRANSLATION(TRANSLATION_Decals, tablei >> 8);
}
FDecalLib::FTranslation *FDecalLib::FTranslation::LocateTranslation (uint32_t start, uint32_t 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 = decal;
// Repeatedly GetDecal() until the result is constant, since
// the choice might be another FDecalGroup.
if (decal != NULL)
{
do
{
remember = decal;
decal = decal->GetDecal ();
} while (decal != NULL && decal != remember);
}
return static_cast<const FDecalTemplate *>(remember);
}
FDecalAnimator::FDecalAnimator (const char *name)
{
Name = name;
}
FDecalAnimator::~FDecalAnimator ()
{
}
IMPLEMENT_CLASS(DDecalFader, false, false)
void DDecalFader::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("starttime", TimeToStartDecay)
("endtime", TimeToEndDecay)
("starttrans", 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 = StartTrans * distanceToEnd / fadeDistance;
}
}
DThinker *FDecalFaderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
{
DDecalFader *fader = Create<DDecalFader> (actor);
fader->TimeToStartDecay = level.maptime + DecayStart;
fader->TimeToEndDecay = fader->TimeToStartDecay + DecayTime;
fader->StartTrans = -1;
return fader;
}
IMPLEMENT_CLASS(DDecalStretcher, false, false)
void DDecalStretcher::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("starttime", TimeToStart)
("endtime", TimeToStop)
("goalx", GoalX)
("startx", StartX)
("stretchx", bStretchX)
("goaly", GoalY)
("starty", StartY)
("stretchy", bStretchY)
("started", bStarted);
}
DThinker *FDecalStretcherAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
{
DDecalStretcher *thinker = Create<DDecalStretcher> (actor);
thinker->TimeToStart = level.maptime + StretchStart;
thinker->TimeToStop = thinker->TimeToStart + StretchTime;
if (GoalX >= 0)
{
thinker->GoalX = GoalX;
thinker->bStretchX = true;
}
else
{
thinker->GoalX = 0;
thinker->bStretchX = false;
}
if (GoalY >= 0)
{
thinker->GoalY = GoalY;
thinker->bStretchY = true;
}
else
{
thinker->GoalY = 0;
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 + (GoalX - StartX) * distance / maxDistance;
}
if (bStretchY)
{
TheDecal->ScaleY = StartY + (GoalY - StartY) * distance / maxDistance;
}
}
IMPLEMENT_CLASS(DDecalSlider, false, false)
void DDecalSlider::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("starttime", TimeToStart)
("endtime", TimeToStop)
("disty", DistY)
("starty", StartY)
("started", bStarted);
}
DThinker *FDecalSliderAnim::CreateThinker (DBaseDecal *actor, side_t *wall) const
{
DDecalSlider *thinker = Create<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 + DistX * distance / maxDistance);*/
TheDecal->Z = StartY + 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, false, false)
void DDecalColorer::Serialize(FSerializer &arc)
{
Super::Serialize (arc);
arc("starttime", TimeToStartDecay)
("endtime", TimeToEndDecay)
("startcolor", StartColor)
("goalcolor", 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 = Create<DDecalColorer>(actor);
Colorer->TimeToStartDecay = level.maptime + DecayStart;
Colorer->TimeToEndDecay = Colorer->TimeToStartDecay + DecayTime;
Colorer->StartColor = 0xff000000;
Colorer->GoalColor = GoalColor;
return Colorer;
}
static double ReadScale (FScanner &sc)
{
sc.MustGetFloat ();
return clamp (sc.Float, 1/256.0, 256.0);
}