qzdoom/src/decallib.cpp
Christoph Oelckers e89a598b31 - renamed FTexture's UseType flags and gave them a dedicated type.
This was done mainly to reduce the amount of occurences of the word FTexture but it immediately helped detect two small and mostly harmless bugs that were found due to the stricter type checks.
2018-03-25 20:26:16 +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, ETextureType::Any);
if (!picnum.Exists() && (lumpnum = Wads.CheckNumForName (sc.String, ns_graphics)) >= 0)
{
picnum = TexMan.CreateTexture (lumpnum, ETextureType::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);
}