qzdoom/src/g_shared/sbarinfo.cpp

1581 lines
41 KiB
C++

/*
** sbarinfo_display.cpp
**
** Contains all functions required for the display of custom statusbars.
**
**---------------------------------------------------------------------------
** Copyright 2008 Braden Obrzut
** 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 "doomtype.h"
#include "doomstat.h"
#include "v_font.h"
#include "v_video.h"
#include "sbar.h"
#include "r_defs.h"
#include "w_wad.h"
#include "m_random.h"
#include "d_player.h"
#include "st_stuff.h"
#include "m_swap.h"
#include "a_keys.h"
#include "templates.h"
#include "i_system.h"
#include "sbarinfo.h"
#include "gi.h"
#include "r_data/r_translate.h"
#include "a_weaponpiece.h"
#include "a_strifeglobal.h"
#include "g_level.h"
#include "v_palette.h"
#include "p_acs.h"
#include "gstrings.h"
#include "version.h"
#include "cmdlib.h"
#define ARTIFLASH_OFFSET (statusBar->invBarOffset+6)
enum
{
imgARTIBOX,
imgSELECTBOX,
imgCURSOR,
imgINVLFGEM1,
imgINVLFGEM2,
imgINVRTGEM1,
imgINVRTGEM2,
};
EXTERN_CVAR(Int, fraglimit)
EXTERN_CVAR(Int, screenblocks)
EXTERN_CVAR(Bool, vid_fps)
EXTERN_CVAR(Bool, hud_scale)
class DSBarInfo;
static double nulclip[] = { 0,0,0,0 };
////////////////////////////////////////////////////////////////////////////////
/**
* This class is used to help prevent errors that may occur from adding or
* subtracting from coordinates.
*
* In order to provide the maximum flexibility, coordinates are packed into
* an int with one bit reserved for relCenter.
*/
class SBarInfoCoordinate
{
public:
SBarInfoCoordinate &Add(int add)
{
value += add;
return *this;
}
int Coordinate() const { return value; }
bool RelCenter() const { return relCenter; }
void Set(int coord, bool center) { value = coord; relCenter = center; }
void SetCoord(int coord) { value = coord; }
void SetRelCenter(bool center) { relCenter = center; }
int operator* () const { return Coordinate(); }
SBarInfoCoordinate operator+ (int add) const { return SBarInfoCoordinate(*this).Add(add); }
SBarInfoCoordinate operator+ (const SBarInfoCoordinate &other) const { return SBarInfoCoordinate(*this).Add(other.Coordinate()); }
SBarInfoCoordinate operator- (int sub) const { return SBarInfoCoordinate(*this).Add(-sub); }
SBarInfoCoordinate operator- (const SBarInfoCoordinate &other) const { return SBarInfoCoordinate(*this).Add(-other.Coordinate()); }
void operator+= (int add) { Add(add); }
void operator-= (int sub) { Add(-sub); }
protected:
unsigned relCenter:1;
int value:31;
};
class SBarInfoMainBlock;
////////////////////////////////////////////////////////////////////////////////
/* There are three major classes here. The SBarInfoCommand is our root class.
* SBarInfoCommandFlowControl would be the base class for command which
* implements a sub-block. And SBarInfoMainBlock which is the root for a
* single hud (so all commands are held inside a MainBlock at some point).
*
* A MainBlock can be passed NULL for the first argument of the Draw function.
*/
class SBarInfoCommand
{
public:
enum Offset
{
TOP = 0x1,
VMIDDLE = 0x2,
BOTTOM = 0x4,
LEFT = 0x10,
RIGHT = 0x20,
HMIDDLE = 0x40,
CENTER = VMIDDLE|HMIDDLE,
CENTER_BOTTOM = BOTTOM|HMIDDLE
};
SBarInfoCommand(SBarInfo *script) : script(script) {}
virtual ~SBarInfoCommand() {}
virtual void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)=0;
virtual void Parse(FScanner &sc, bool fullScreenOffsets)=0;
virtual void Reset() {}
virtual void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) {}
SBarInfo *GetScript() { return script; }
protected:
void GetCoordinates(FScanner &sc, bool fullScreenOffsets, SBarInfoCoordinate &x, SBarInfoCoordinate &y)
{
bool negative = false;
bool relCenter = false;
SBarInfoCoordinate *coords[2] = {&x, &y};
for(int i = 0;i < 2;i++)
{
negative = false;
relCenter = false;
if(i > 0)
sc.MustGetToken(',');
// [-]INT center
negative = sc.CheckToken('-');
sc.MustGetToken(TK_IntConst);
coords[i]->Set(negative ? -sc.Number : sc.Number, false);
if(sc.CheckToken('+'))
{
sc.MustGetToken(TK_Identifier);
if(!sc.Compare("center"))
sc.ScriptError("Expected 'center' but got '%s' instead.", sc.String);
relCenter = true;
}
if(fullScreenOffsets)
{
coords[i]->SetRelCenter(relCenter);
}
}
//if(!fullScreenOffsets)
// y.SetCoord((negative ? -sc.Number : sc.Number) - (200 - script->height));
}
EColorRange GetTranslation(FScanner &sc)
{
sc.MustGetToken(TK_Identifier);
EColorRange returnVal = CR_UNTRANSLATED;
FString namedTranslation; //we must send in "[translation]"
const BYTE *trans_ptr;
namedTranslation.Format("[%s]", sc.String);
trans_ptr = (const BYTE *)(&namedTranslation[0]);
if((returnVal = V_ParseFontColor(trans_ptr, CR_UNTRANSLATED, CR_UNTRANSLATED)) == CR_UNDEFINED)
{
sc.ScriptError("Missing definition for color %s.", sc.String);
}
return returnVal;
}
SBarInfo *script;
};
class SBarInfoCommandFlowControl : public SBarInfoCommand
{
public:
SBarInfoCommandFlowControl(SBarInfo *script) : SBarInfoCommand(script), truth(false) {}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
for(auto command : commands[truth])
command->Draw(block, statusBar);
}
int NumCommands() const { return commands[truth].Size(); }
void Parse(FScanner &sc, bool fullScreenOffsets)
{
ParseBlock(commands[1], sc, fullScreenOffsets);
if(sc.CheckToken(TK_Else))
ParseBlock(commands[0], sc, fullScreenOffsets);
}
void Reset()
{
for(unsigned int i = 0;i < 2;i++)
{
for(auto command : commands[i])
command->Reset();
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
for(auto command : commands[truth])
command->Tick(block, statusBar, hudChanged);
}
protected:
void SetTruth(bool truth, const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
// If there is no change we don't need to do anything. Do note
// that this should not change more than once per tick. If it does
// there may be cosmetic problems.
if(this->truth == truth)
return;
this->truth = truth;
if(block != NULL)
Tick(block, statusBar, true);
}
void Negate()
{
swapvalues(commands[0], commands[1]);
}
private:
void ParseBlock(TDeletingArray<SBarInfoCommand *> &commands, FScanner &sc, bool fullScreenOffsets)
{
if(sc.CheckToken('{'))
{
while(SBarInfoCommand *cmd = NextCommand(sc))
{
cmd->Parse(sc, fullScreenOffsets);
commands.Push(cmd);
}
}
else
{
if(SBarInfoCommand *cmd = NextCommand(sc))
{
cmd->Parse(sc, fullScreenOffsets);
commands.Push(cmd);
}
else
sc.ScriptError("Missing command for flow control statement.");
}
}
SBarInfoCommand *NextCommand(FScanner &sc);
TDeletingArray<SBarInfoCommand *> commands[2];
bool truth;
};
class SBarInfoNegatableFlowControl : public SBarInfoCommandFlowControl
{
public:
SBarInfoNegatableFlowControl(SBarInfo *script) : SBarInfoCommandFlowControl(script) {}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
bool negate = false;
if(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("not"))
negate = true;
else
sc.UnGet();
}
ParseNegatable(sc, fullScreenOffsets);
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
if(negate)
Negate();
}
virtual void ParseNegatable(FScanner &sc, bool fullScreenOffsets) {}
};
class SBarInfoMainBlock : public SBarInfoCommandFlowControl
{
public:
SBarInfoMainBlock(SBarInfo *script) : SBarInfoCommandFlowControl(script),
alpha(1.), currentAlpha(1.), forceScaled(false),
fullScreenOffsets(false)
{
SetTruth(true, NULL, NULL);
}
double Alpha() const { return currentAlpha; }
// Same as Draw but takes into account ForceScaled and temporarily sets the scaling if needed.
void DrawAux(const SBarInfoMainBlock *block, DSBarInfo *statusBar, int xOffset, int yOffset, double alpha);
// Silence hidden overload warning since this is a special use class.
using SBarInfoCommandFlowControl::Draw;
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, int xOffset, int yOffset, double alpha)
{
this->xOffset = xOffset;
this->yOffset = yOffset;
this->currentAlpha = this->alpha * alpha;
SBarInfoCommandFlowControl::Draw(this, statusBar);
}
bool ForceScaled() const { return forceScaled; }
bool FullScreenOffsets() const { return fullScreenOffsets; }
void Parse(FScanner &sc, bool fullScreenOffsets)
{
this->fullScreenOffsets = fullScreenOffsets;
if(sc.CheckToken(','))
{
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("forcescaled"))
forceScaled = true;
else if(sc.Compare("fullscreenoffsets"))
this->fullScreenOffsets = true;
else
sc.ScriptError("Unkown flag '%s'.", sc.String);
if(!sc.CheckToken('|') && !sc.CheckToken(','))
{
SBarInfoCommandFlowControl::Parse(sc, this->fullScreenOffsets);
return;
}
}
sc.MustGetToken(TK_FloatConst);
alpha = sc.Float;
}
SBarInfoCommandFlowControl::Parse(sc, this->fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged) { SBarInfoCommandFlowControl::Tick(this, statusBar, hudChanged); }
int XOffset() const { return xOffset; }
int YOffset() const { return yOffset; }
protected:
double alpha;
double currentAlpha;
bool forceScaled;
bool fullScreenOffsets;
int xOffset;
int yOffset;
};
////////////////////////////////////////////////////////////////////////////////
SBarInfo *SBarInfoScript[2] = {NULL,NULL};
enum //Key words
{
SBARINFO_BASE,
SBARINFO_HEIGHT,
SBARINFO_INTERPOLATEHEALTH,
SBARINFO_INTERPOLATEARMOR,
SBARINFO_COMPLETEBORDER,
SBARINFO_MONOSPACEFONTS,
SBARINFO_LOWERHEALTHCAP,
SBARINFO_RESOLUTION,
SBARINFO_STATUSBAR,
SBARINFO_MUGSHOT,
SBARINFO_CREATEPOPUP,
};
enum //Bar types
{
STBAR_NONE,
STBAR_FULLSCREEN,
STBAR_NORMAL,
STBAR_AUTOMAP,
STBAR_INVENTORY,
STBAR_INVENTORYFULLSCREEN,
STBAR_POPUPLOG,
STBAR_POPUPKEYS,
STBAR_POPUPSTATUS,
};
static const char *SBarInfoTopLevel[] =
{
"base",
"height",
"interpolatehealth",
"interpolatearmor",
"completeborder",
"monospacefonts",
"lowerhealthcap",
"resolution",
"statusbar",
"mugshot",
"createpopup",
NULL
};
static const char *StatusBars[] =
{
"none",
"fullscreen",
"normal",
"automap",
"inventory",
"inventoryfullscreen",
"popuplog",
"popupkeys",
"popupstatus",
NULL
};
static void FreeSBarInfoScript()
{
for(int i = 0;i < 2;i++)
{
if (SBarInfoScript[i] != NULL)
{
delete SBarInfoScript[i];
SBarInfoScript[i] = NULL;
}
}
}
void SBarInfo::Load()
{
FreeSBarInfoScript();
MugShotStates.Clear();
if(gameinfo.statusbar.IsNotEmpty())
{
int lump = Wads.CheckNumForFullName(gameinfo.statusbar, true);
if(lump != -1)
{
if (!batchrun) Printf ("ParseSBarInfo: Loading default status bar definition.\n");
if(SBarInfoScript[SCRIPT_DEFAULT] == NULL)
SBarInfoScript[SCRIPT_DEFAULT] = new SBarInfo(lump);
else
SBarInfoScript[SCRIPT_DEFAULT]->ParseSBarInfo(lump);
}
}
if(Wads.CheckNumForName("SBARINFO") != -1)
{
if (!batchrun) Printf ("ParseSBarInfo: Loading custom status bar definition.\n");
int lastlump, lump;
lastlump = 0;
while((lump = Wads.FindLump("SBARINFO", &lastlump)) != -1)
{
if(SBarInfoScript[SCRIPT_CUSTOM] == NULL)
SBarInfoScript[SCRIPT_CUSTOM] = new SBarInfo(lump);
else //We now have to load multiple SBarInfo Lumps so the 2nd time we need to use this method instead.
SBarInfoScript[SCRIPT_CUSTOM]->ParseSBarInfo(lump);
}
}
atterm(FreeSBarInfoScript);
}
//SBarInfo Script Reader
void SBarInfo::ParseSBarInfo(int lump)
{
gameType = gameinfo.gametype;
bool baseSet = false;
FScanner sc(lump);
sc.SetCMode(true);
while(sc.CheckToken(TK_Identifier) || sc.CheckToken(TK_Include))
{
if(sc.TokenType == TK_Include)
{
sc.MustGetToken(TK_StringConst);
int lump = Wads.CheckNumForFullName(sc.String, true);
if (lump == -1)
sc.ScriptError("Lump '%s' not found", sc.String);
ParseSBarInfo(lump);
continue;
}
int baselump = -2;
switch(sc.MustMatchString(SBarInfoTopLevel))
{
case SBARINFO_BASE:
baseSet = true;
if(!sc.CheckToken(TK_None))
sc.MustGetToken(TK_Identifier);
if(sc.Compare("Doom"))
{
baselump = Wads.CheckNumForFullName("sbarinfo/doom.txt", true);
}
else if(sc.Compare("Heretic"))
{
baselump = Wads.CheckNumForFullName("sbarinfo/heretic.txt", true);
}
else if(sc.Compare("Hexen"))
{
baselump = Wads.CheckNumForFullName("sbarinfo/hexen.txt", true);
}
else if(sc.Compare("Strife"))
gameType = GAME_Strife;
else if(sc.Compare("None"))
gameType = GAME_Any;
else
sc.ScriptError("Bad game name: %s", sc.String);
// If one of the standard status bar should be loaded, baselump has been set to a different value.
if (baselump != -2)
{
if(baselump == -1)
{
sc.ScriptError("Standard %s status bar not found.", sc.String);
}
else if (Wads.GetLumpFile(baselump) > 0)
{
I_FatalError("File %s is overriding core lump sbarinfo/%s.txt.",
Wads.GetWadFullName(Wads.GetLumpFile(baselump)), sc.String);
}
ParseSBarInfo(baselump);
}
sc.MustGetToken(';');
break;
case SBARINFO_HEIGHT:
sc.MustGetToken(TK_IntConst);
this->height = sc.Number;
sc.MustGetToken(';');
break;
case SBARINFO_INTERPOLATEHEALTH: //mimics heretics interpolated health values.
if(sc.CheckToken(TK_True))
{
interpolateHealth = true;
}
else
{
sc.MustGetToken(TK_False);
interpolateHealth = false;
}
if(sc.CheckToken(',')) //speed param
{
sc.MustGetToken(TK_IntConst);
this->interpolationSpeed = sc.Number;
}
sc.MustGetToken(';');
break;
case SBARINFO_INTERPOLATEARMOR: //Since interpolatehealth is such a popular command
if(sc.CheckToken(TK_True))
{
interpolateArmor = true;
}
else
{
sc.MustGetToken(TK_False);
interpolateArmor = false;
}
if(sc.CheckToken(',')) //speed
{
sc.MustGetToken(TK_IntConst);
this->armorInterpolationSpeed = sc.Number;
}
sc.MustGetToken(';');
break;
case SBARINFO_COMPLETEBORDER: //draws the border instead of an HOM
if(sc.CheckToken(TK_True))
{
completeBorder = true;
}
else
{
sc.MustGetToken(TK_False);
completeBorder = false;
}
sc.MustGetToken(';');
break;
case SBARINFO_MONOSPACEFONTS:
if(sc.CheckToken(TK_True))
{
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst);
spacingCharacter = sc.String[0];
}
else
{
sc.MustGetToken(TK_False);
spacingCharacter = '\0';
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst); //Don't tell anyone we're just ignoring this ;)
}
if(sc.CheckToken(','))
{
// Character alignment
sc.MustGetToken(TK_Identifier);
if(sc.Compare("left"))
spacingAlignment = ALIGN_LEFT;
else if(sc.Compare("center"))
spacingAlignment = ALIGN_CENTER;
else if(sc.Compare("right"))
spacingAlignment = ALIGN_RIGHT;
else
sc.ScriptError("Unknown alignment '%s'.", sc.String);
}
sc.MustGetToken(';');
break;
case SBARINFO_LOWERHEALTHCAP:
if(sc.CheckToken(TK_False))
{
lowerHealthCap = false;
}
else
{
sc.MustGetToken(TK_True);
lowerHealthCap = true;
}
sc.MustGetToken(';');
break;
case SBARINFO_RESOLUTION:
sc.MustGetToken(TK_IntConst);
resW = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
resH = sc.Number;
sc.MustGetToken(';');
break;
case SBARINFO_STATUSBAR:
{
if(!baseSet) //If the user didn't explicitly define a base, do so now.
gameType = GAME_Any;
int barNum = 0;
if(!sc.CheckToken(TK_None))
{
sc.MustGetToken(TK_Identifier);
barNum = sc.MustMatchString(StatusBars);
}
if (this->huds[barNum] != NULL)
{
delete this->huds[barNum];
}
this->huds[barNum] = new SBarInfoMainBlock(this);
if(barNum == STBAR_AUTOMAP)
{
automapbar = true;
}
this->huds[barNum]->Parse(sc, false);
break;
}
case SBARINFO_MUGSHOT:
{
sc.MustGetToken(TK_StringConst);
FMugShotState state(sc.String);
if(sc.CheckToken(',')) //first loop must be a comma
{
do
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("health"))
state.bUsesLevels = true;
else if(sc.Compare("health2"))
state.bUsesLevels = state.bHealth2 = true;
else if(sc.Compare("healthspecial"))
state.bUsesLevels = state.bHealthSpecial = true;
else if(sc.Compare("directional"))
state.bDirectional = true;
else
sc.ScriptError("Unknown MugShot state flag '%s'.", sc.String);
}
while(sc.CheckToken(',') || sc.CheckToken('|'));
}
ParseMugShotBlock(sc, state);
int index;
if((index = FindMugShotStateIndex(state.State)) != -1) //We already had this state, remove the old one.
{
MugShotStates.Delete(index);
}
MugShotStates.Push(state);
break;
}
case SBARINFO_CREATEPOPUP:
{
int pop = 0;
sc.MustGetToken(TK_Identifier);
if(sc.Compare("log"))
pop = 0;
else if(sc.Compare("keys"))
pop = 1;
else if(sc.Compare("status"))
pop = 2;
else
sc.ScriptError("Unkown popup: '%s'", sc.String);
Popup &popup = popups[pop];
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
popup.width = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
popup.height = sc.Number;
sc.MustGetToken(',');
if(!sc.CheckToken(TK_None))
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("slideinbottom"))
{
popup.transition = Popup::TRANSITION_SLIDEINBOTTOM;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
popup.ispeed = sc.Number;
}
else if(sc.Compare("pushup"))
{
popup.transition = Popup::TRANSITION_PUSHUP;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
popup.ispeed = sc.Number;
}
else if(sc.Compare("fade"))
{
popup.transition = Popup::TRANSITION_FADE;
sc.MustGetToken(',');
sc.MustGetToken(TK_FloatConst);
popup.speed = 1.0 / (35.0 * sc.Float);
sc.MustGetToken(',');
sc.MustGetToken(TK_FloatConst);
popup.speed2 = 1.0 / (35.0 * sc.Float);
}
else
sc.ScriptError("Unkown transition type: '%s'", sc.String);
}
popup.init();
sc.MustGetToken(';');
break;
}
}
}
}
void SBarInfo::ParseMugShotBlock(FScanner &sc, FMugShotState &state)
{
sc.MustGetToken('{');
while(!sc.CheckToken('}'))
{
FMugShotFrame frame;
bool multiframe = false;
if(sc.CheckToken('{'))
multiframe = true;
do
{
sc.MustGetToken(TK_Identifier);
if(strlen(sc.String) > 5)
sc.ScriptError("MugShot frames cannot exceed 5 characters.");
frame.Graphic.Push(sc.String);
}
while(multiframe && sc.CheckToken(','));
if(multiframe)
sc.MustGetToken('}');
bool negative = sc.CheckToken('-');
sc.MustGetToken(TK_IntConst);
frame.Delay = (negative ? -1 : 1)*sc.Number;
sc.MustGetToken(';');
state.Frames.Push(frame);
}
}
void SBarInfo::ResetHuds()
{
for(int i = 0;i < NUMHUDS;i++)
huds[i]->Reset();
}
int SBarInfo::newImage(const char *patchname)
{
if(patchname[0] == '\0' || stricmp(patchname, "nullimage") == 0)
{
return -1;
}
for(unsigned int i = 0;i < this->Images.Size();i++) //did we already load it?
{
if(stricmp(this->Images[i], patchname) == 0)
{
return i;
}
}
return this->Images.Push(patchname);
}
SBarInfo::SBarInfo() //make results more predicable
{
Init();
}
SBarInfo::SBarInfo(int lumpnum)
{
Init();
ParseSBarInfo(lumpnum);
}
void SBarInfo::Init()
{
automapbar = false;
interpolateHealth = false;
interpolateArmor = false;
completeBorder = false;
lowerHealthCap = true;
interpolationSpeed = 8;
armorInterpolationSpeed = 8;
height = 0;
spacingCharacter = '\0';
spacingAlignment = ALIGN_CENTER;
resW = 320;
resH = 200;
cleanX = -1;
cleanY = -1;
for(unsigned int i = 0;i < NUMHUDS;i++)
huds[i] = new SBarInfoMainBlock(this);
}
SBarInfo::~SBarInfo()
{
for (size_t i = 0; i < NUMHUDS; ++i)
{
delete huds[i];
// huds[i].commands.Clear();
}
}
//Popup
Popup::Popup() : transition(TRANSITION_NONE), opened(false), moving(false),
height(320), width(200), ispeed(0), speed(0), speed2(0), alpha(1.), x(320),
y(200), displacementX(0), displacementY(0)
{
}
void Popup::init()
{
x = width;
y = height;
switch(transition)
{
case TRANSITION_SLIDEINBOTTOM:
case TRANSITION_PUSHUP:
x = 0;
break;
case TRANSITION_FADE:
alpha = 0;
x = 0;
y = 0;
break;
default:
break;
}
}
void Popup::tick()
{
switch(transition)
{
case TRANSITION_SLIDEINBOTTOM:
case TRANSITION_PUSHUP:
if(moving)
{
int oldY = y;
if(opened)
y -= clamp(height + (y - height), 1, ispeed);
else
y += clamp(height - y, 1, ispeed);
if(transition == TRANSITION_PUSHUP)
displacementY += y - oldY;
}
if(y != 0 && y != height)
moving = true;
else
moving = false;
break;
case TRANSITION_FADE:
if(moving)
{
if(opened)
alpha = clamp(alpha + speed, 0., 1.);
else
alpha = clamp(alpha - speed2, 0., 1.);
}
if(alpha == 0 || alpha == 1.)
moving = false;
else
moving = true;
break;
default:
if(opened)
{
y = 0;
x = 0;
}
else
{
y = height;
x = width;
}
moving = false;
break;
}
}
bool Popup::isDoneMoving()
{
return !moving;
}
int Popup::getXOffset()
{
return x;
}
int Popup::getYOffset()
{
return y;
}
double Popup::getAlpha(double maxAlpha)
{
return alpha * maxAlpha;
}
int Popup::getXDisplacement()
{
return displacementX;
}
int Popup::getYDisplacement()
{
return displacementY;
}
void Popup::open()
{
opened = true;
moving = true;
}
void Popup::close()
{
opened = false;
moving = true;
}
////////////////////////////////////////////////////////////////////////////////
inline void adjustRelCenter(bool relX, bool relY, const double &x, const double &y, double &outX, double &outY, const double &xScale, const double &yScale)
{
if(relX)
outX = x + (SCREENWIDTH/(hud_scale ? xScale*2 : 2));
else
outX = x;
if(relY)
outY = y + (SCREENHEIGHT/(hud_scale ? yScale*2 : 2));
else
outY = y;
}
class DSBarInfo : public DBaseStatusBar
{
DECLARE_CLASS(DSBarInfo, DBaseStatusBar)
HAS_OBJECT_POINTERS
public:
DSBarInfo (SBarInfo *script=NULL) : DBaseStatusBar(script->height, script->resW, script->resH),
ammo1(NULL), ammo2(NULL), ammocount1(0), ammocount2(0), armor(NULL),
pendingPopup(POP_None), currentPopup(POP_None), lastHud(-1),
scalingWasForced(false), lastInventoryBar(NULL), lastPopup(NULL)
{
this->script = script;
static const char *InventoryBarLumps[] =
{
"ARTIBOX", "SELECTBO", "INVCURS", "INVGEML1",
"INVGEML2", "INVGEMR1", "INVGEMR2",
"USEARTIA", "USEARTIB", "USEARTIC", "USEARTID",
};
TArray<const char *> patchnames;
patchnames.Resize(script->Images.Size()+10);
unsigned int i = 0;
for(i = 0;i < script->Images.Size();i++)
{
patchnames[i] = script->Images[i];
}
for(i = 0;i < 10;i++)
{
patchnames[i+script->Images.Size()] = InventoryBarLumps[i];
}
invBarOffset = script->Images.Size();
Images.Init(&patchnames[0], patchnames.Size());
CompleteBorder = script->completeBorder;
}
~DSBarInfo ()
{
Images.Uninit();
}
void ScreenSizeChanged()
{
Super::ScreenSizeChanged();
if (uiscale > 0)
{
script->cleanX = uiscale;
script->cleanY = uiscale;
}
else
{
V_CalcCleanFacs(script->resW, script->resH, SCREENWIDTH, SCREENHEIGHT, &script->cleanX, &script->cleanY);
}
}
void Draw (EHudState state)
{
DBaseStatusBar::Draw(state);
if (script->cleanX <= 0)
{ // Calculate cleanX and cleanY
ScreenSizeChanged();
}
int hud = STBAR_NORMAL;
if(state == HUD_StatusBar)
{
if(script->automapbar && automapactive)
{
hud = STBAR_AUTOMAP;
}
else
{
hud = STBAR_NORMAL;
}
}
else if(state == HUD_Fullscreen)
{
hud = STBAR_FULLSCREEN;
}
else
{
hud = STBAR_NONE;
}
bool oldhud_scale = hud_scale;
if(script->huds[hud]->ForceScaled()) //scale the statusbar
{
if(script->huds[hud]->FullScreenOffsets())
hud_scale = true;
else if(!Scaled)
{
scalingWasForced = true;
SetScaled(true, true);
setsizeneeded = true;
}
}
//prepare ammo counts
GetCurrentAmmo(ammo1, ammo2, ammocount1, ammocount2);
armor = CPlayer->mo->FindInventory<ABasicArmor>();
if(state != HUD_AltHud)
{
if(hud != lastHud)
{
script->huds[hud]->Tick(NULL, this, true);
// Restore scaling if need be.
if(scalingWasForced)
{
scalingWasForced = false;
SetScaled(false);
setsizeneeded = true;
}
}
if(currentPopup != POP_None && !script->huds[hud]->FullScreenOffsets())
script->huds[hud]->Draw(NULL, this, script->popups[currentPopup-1].getXDisplacement(), script->popups[currentPopup-1].getYDisplacement(), 1.);
else
script->huds[hud]->Draw(NULL, this, 0, 0, 1.);
lastHud = hud;
// Handle inventory bar drawing
if(CPlayer->inventorytics > 0 && !(level.flags & LEVEL_NOINVENTORYBAR) && (state == HUD_StatusBar || state == HUD_Fullscreen))
{
SBarInfoMainBlock *inventoryBar = state == HUD_StatusBar ? script->huds[STBAR_INVENTORY] : script->huds[STBAR_INVENTORYFULLSCREEN];
if(inventoryBar != lastInventoryBar)
inventoryBar->Tick(NULL, this, true);
// No overlay? Lets cancel it.
if(inventoryBar->NumCommands() == 0)
CPlayer->inventorytics = 0;
else
inventoryBar->DrawAux(NULL, this, 0, 0, 1.);
}
}
// Handle popups
if(currentPopup != POP_None)
{
int popbar = 0;
if(currentPopup == POP_Log)
popbar = STBAR_POPUPLOG;
else if(currentPopup == POP_Keys)
popbar = STBAR_POPUPKEYS;
else if(currentPopup == POP_Status)
popbar = STBAR_POPUPSTATUS;
if(script->huds[popbar] != lastPopup)
{
lastPopup = script->huds[popbar];
lastPopup->Tick(NULL, this, true);
}
script->huds[popbar]->DrawAux(NULL, this, script->popups[currentPopup-1].getXOffset(), script->popups[currentPopup-1].getYOffset(), script->popups[currentPopup-1].getAlpha());
}
else
lastPopup = NULL;
// Reset hud_scale
hud_scale = oldhud_scale;
}
void NewGame ()
{
if (CPlayer != NULL)
{
AttachToPlayer (CPlayer);
// Reset the huds
script->ResetHuds();
lastHud = -1; // Reset
}
}
bool MustDrawLog (EHudState state)
{
return script->huds[STBAR_POPUPLOG]->NumCommands() == 0;
}
void SetMugShotState (const char *state_name, bool wait_till_done, bool reset)
{
script->MugShot.SetState(state_name, wait_till_done, reset);
}
void Tick ()
{
DBaseStatusBar::Tick();
script->MugShot.Tick(CPlayer);
if(currentPopup != POP_None)
{
script->popups[currentPopup-1].tick();
if(script->popups[currentPopup-1].opened == false && script->popups[currentPopup-1].isDoneMoving())
{
currentPopup = pendingPopup;
if(currentPopup != POP_None)
script->popups[currentPopup-1].open();
}
if (lastPopup != NULL) lastPopup->Tick(NULL, this, false);
}
if(lastHud != -1)
script->huds[lastHud]->Tick(NULL, this, false);
if(lastInventoryBar != NULL && CPlayer->inventorytics > 0)
lastInventoryBar->Tick(NULL, this, false);
}
void ReceivedWeapon(AWeapon *weapon)
{
script->MugShot.Grin();
}
// void DSBarInfo::FlashItem(const PClass *itemtype) - Is defined with CommandDrawSelectedInventory
void FlashItem(const PClass *itemtype);
void ShowPop(int popnum)
{
DBaseStatusBar::ShowPop(popnum);
if(popnum != currentPopup)
{
pendingPopup = popnum;
}
else
pendingPopup = POP_None;
if(currentPopup != POP_None)
script->popups[currentPopup-1].close();
else
{
currentPopup = pendingPopup;
pendingPopup = POP_None;
if(currentPopup != POP_None)
script->popups[currentPopup-1].open();
}
}
//draws an image with the specified flags
void DrawGraphic(FTexture* texture, SBarInfoCoordinate x, SBarInfoCoordinate y, int xOffset, int yOffset, double Alpha, bool fullScreenOffsets, bool translate=false, bool dim=false, int offsetflags=0, bool alphaMap=false, int forceWidth=-1, int forceHeight=-1, const double *clip = nulclip, bool clearDontDraw=false) const
{
if (texture == NULL)
return;
double dx = *x;
double dy = *y;
if((offsetflags & SBarInfoCommand::CENTER) == SBarInfoCommand::CENTER)
{
if (forceWidth < 0) dx -= (texture->GetScaledWidthDouble()/2.0)-texture->GetScaledLeftOffsetDouble();
else dx -= forceWidth*(0.5-(texture->GetScaledLeftOffsetDouble()/texture->GetScaledWidthDouble()));
//Unoptimalized formula is dx -= forceWidth/2.0-(texture->GetScaledLeftOffsetDouble()*forceWidth/texture->GetScaledWidthDouble());
if (forceHeight < 0) dy -= (texture->GetScaledHeightDouble()/2.0)-texture->GetScaledTopOffsetDouble();
else dy -= forceHeight*(0.5-(texture->GetScaledTopOffsetDouble()/texture->GetScaledHeightDouble()));
}
dx += xOffset;
dy += yOffset;
double w, h;
if(!fullScreenOffsets)
{
double tmp = 0;
dx += ST_X;
dy += ST_Y - (Scaled ? script->resH : 200) + script->height;
w = forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth;
h = forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight;
double dcx = clip[0] == 0 ? 0 : dx + clip[0] - texture->GetScaledLeftOffsetDouble();
double dcy = clip[1] == 0 ? 0 : dy + clip[1] - texture->GetScaledTopOffsetDouble();
double dcr = clip[2] == 0 ? INT_MAX : dx + w - clip[2] - texture->GetScaledLeftOffsetDouble();
double dcb = clip[3] == 0 ? INT_MAX : dy + h - clip[3] - texture->GetScaledTopOffsetDouble();
if(Scaled)
{
if(clip[0] != 0 || clip[1] != 0)
{
screen->VirtualToRealCoords(dcx, dcy, tmp, tmp, script->resW, script->resH, true);
if (clip[0] == 0) dcx = 0;
if (clip[1] == 0) dcy = 0;
}
if(clip[2] != 0 || clip[3] != 0 || clearDontDraw)
screen->VirtualToRealCoords(dcr, dcb, tmp, tmp, script->resW, script->resH, true);
screen->VirtualToRealCoords(dx, dy, w, h, script->resW, script->resH, true);
}
else
{
dy += 200 - script->resH;
dcy += 200 - script->resH;
dcb += 200 - script->resH;
}
if(clearDontDraw)
screen->Clear(static_cast<int>(MAX<double>(dx, dcx)), static_cast<int>(MAX<double>(dy, dcy)), static_cast<int>(MIN<double>(dcr,w+MAX<double>(dx, dcx))), static_cast<int>(MIN<double>(dcb,MAX<double>(dy, dcy)+h)), GPalette.BlackIndex, 0);
else
{
if(alphaMap)
{
screen->DrawTexture(texture, dx, dy,
DTA_DestWidthF, w,
DTA_DestHeightF, h,
DTA_ClipLeft, static_cast<int>(dcx),
DTA_ClipTop, static_cast<int>(dcy),
DTA_ClipRight, static_cast<int>(MIN<double>(INT_MAX, dcr)),
DTA_ClipBottom, static_cast<int>(MIN<double>(INT_MAX, dcb)),
DTA_Translation, translate ? GetTranslation() : 0,
DTA_ColorOverlay, dim ? DIM_OVERLAY : 0,
DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM,
DTA_AlphaF, Alpha,
DTA_AlphaChannel, alphaMap,
DTA_FillColor, 0,
TAG_DONE);
}
else
{
screen->DrawTexture(texture, dx, dy,
DTA_DestWidthF, w,
DTA_DestHeightF, h,
DTA_ClipLeft, static_cast<int>(dcx),
DTA_ClipTop, static_cast<int>(dcy),
DTA_ClipRight, static_cast<int>(MIN<double>(INT_MAX, dcr)),
DTA_ClipBottom, static_cast<int>(MIN<double>(INT_MAX, dcb)),
DTA_Translation, translate ? GetTranslation() : 0,
DTA_ColorOverlay, dim ? DIM_OVERLAY : 0,
DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM,
DTA_AlphaF, Alpha,
TAG_DONE);
}
}
}
else
{
double rx, ry, rcx=0, rcy=0, rcr=INT_MAX, rcb=INT_MAX;
double xScale = !hud_scale ? 1 : script->cleanX;
double yScale = !hud_scale ? 1 : script->cleanY;
adjustRelCenter(x.RelCenter(), y.RelCenter(), dx, dy, rx, ry, xScale, yScale);
// We can't use DTA_HUDRules since it forces a width and height.
// Translation: No high res.
bool xright = *x < 0 && !x.RelCenter();
bool ybot = *y < 0 && !y.RelCenter();
w = (forceWidth < 0 ? texture->GetScaledWidthDouble() : forceWidth);
h = (forceHeight < 0 ? texture->GetScaledHeightDouble() : forceHeight);
if(vid_fps && rx < 0 && ry >= 0)
ry += 10;
if(hud_scale)
{
rx *= xScale;
ry *= yScale;
w *= xScale;
h *= yScale;
}
if(xright)
rx = SCREENWIDTH + rx;
if(ybot)
ry = SCREENHEIGHT + ry;
// Check for clipping
if(clip[0] != 0 || clip[1] != 0 || clip[2] != 0 || clip[3] != 0)
{
rcx = clip[0] == 0 ? 0 : rx+((clip[0] - texture->GetScaledLeftOffsetDouble())*xScale);
rcy = clip[1] == 0 ? 0 : ry+((clip[1] - texture->GetScaledTopOffsetDouble())*yScale);
rcr = clip[2] == 0 ? INT_MAX : rx+w-((clip[2] + texture->GetScaledLeftOffsetDouble())*xScale);
rcb = clip[3] == 0 ? INT_MAX : ry+h-((clip[3] + texture->GetScaledTopOffsetDouble())*yScale);
}
if(clearDontDraw)
screen->Clear(static_cast<int>(rcx), static_cast<int>(rcy), static_cast<int>(MIN<double>(rcr, rcx+w)), static_cast<int>(MIN<double>(rcb, rcy+h)), GPalette.BlackIndex, 0);
else
{
if(alphaMap)
{
screen->DrawTexture(texture, rx, ry,
DTA_DestWidthF, w,
DTA_DestHeightF, h,
DTA_ClipLeft, static_cast<int>(rcx),
DTA_ClipTop, static_cast<int>(rcy),
DTA_ClipRight, static_cast<int>(rcr),
DTA_ClipBottom, static_cast<int>(rcb),
DTA_Translation, translate ? GetTranslation() : 0,
DTA_ColorOverlay, dim ? DIM_OVERLAY : 0,
DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM,
DTA_AlphaF, Alpha,
DTA_AlphaChannel, alphaMap,
DTA_FillColor, 0,
TAG_DONE);
}
else
{
screen->DrawTexture(texture, rx, ry,
DTA_DestWidthF, w,
DTA_DestHeightF, h,
DTA_ClipLeft, static_cast<int>(rcx),
DTA_ClipTop, static_cast<int>(rcy),
DTA_ClipRight, static_cast<int>(rcr),
DTA_ClipBottom, static_cast<int>(rcb),
DTA_Translation, translate ? GetTranslation() : 0,
DTA_ColorOverlay, dim ? DIM_OVERLAY : 0,
DTA_CenterBottomOffset, (offsetflags & SBarInfoCommand::CENTER_BOTTOM) == SBarInfoCommand::CENTER_BOTTOM,
DTA_AlphaF, Alpha,
TAG_DONE);
}
}
}
}
void DrawString(FFont *font, const char* cstring, SBarInfoCoordinate x, SBarInfoCoordinate y, int xOffset, int yOffset, double Alpha, bool fullScreenOffsets, EColorRange translation, int spacing=0, bool drawshadow=false, int shadowX=2, int shadowY=2) const
{
x += spacing;
double ax = *x;
double ay = *y;
double xScale = 1.0;
double yScale = 1.0;
const BYTE* str = (const BYTE*) cstring;
const EColorRange boldTranslation = EColorRange(translation ? translation - 1 : NumTextColors - 1);
FRemapTable *remap = font->GetColorTranslation(translation);
if(fullScreenOffsets)
{
if(hud_scale)
{
xScale = script->cleanX;
yScale = script->cleanY;
}
adjustRelCenter(x.RelCenter(), y.RelCenter(), *x, *y, ax, ay, xScale, yScale);
}
while(*str != '\0')
{
if(*str == ' ')
{
if(script->spacingCharacter == '\0')
ax += font->GetSpaceWidth();
else
ax += font->GetCharWidth((unsigned char) script->spacingCharacter);
str++;
continue;
}
else if(*str == TEXTCOLOR_ESCAPE)
{
EColorRange newColor = V_ParseFontColor(++str, translation, boldTranslation);
if(newColor != CR_UNDEFINED)
remap = font->GetColorTranslation(newColor);
continue;
}
int width;
if(script->spacingCharacter == '\0') //No monospace?
width = font->GetCharWidth((unsigned char) *str);
else
width = font->GetCharWidth((unsigned char) script->spacingCharacter);
FTexture* character = font->GetChar((unsigned char) *str, &width);
if(character == NULL) //missing character.
{
str++;
continue;
}
if(script->spacingCharacter == '\0') //If we are monospaced lets use the offset
ax += (character->LeftOffset+1); //ignore x offsets since we adapt to character size
double rx, ry, rw, rh;
rx = ax + xOffset;
ry = ay + yOffset;
rw = character->GetScaledWidthDouble();
rh = character->GetScaledHeightDouble();
if(script->spacingCharacter != '\0')
{
double spacingSize = font->GetCharWidth((unsigned char) script->spacingCharacter);
switch(script->spacingAlignment)
{
default:
break;
case SBarInfo::ALIGN_CENTER:
rx += (spacingSize/2)-(rw/2);
break;
case SBarInfo::ALIGN_RIGHT:
rx += spacingSize-rw;
break;
}
}
if(!fullScreenOffsets)
{
rx += ST_X;
ry += ST_Y - (Scaled ? script->resH : 200) + script->height;
if(Scaled)
screen->VirtualToRealCoords(rx, ry, rw, rh, script->resW, script->resH, true);
else
{
ry += (200 - script->resH);
}
}
else
{
if(vid_fps && ax < 0 && ay >= 0)
ry += 10;
bool xright = rx < 0;
bool ybot = ry < 0;
if(hud_scale)
{
rx *= xScale;
ry *= yScale;
rw *= xScale;
rh *= yScale;
}
if(xright)
rx = SCREENWIDTH + rx;
if(ybot)
ry = SCREENHEIGHT + ry;
}
if(drawshadow)
{
double salpha = (Alpha *HR_SHADOW);
double srx = rx + (shadowX*xScale);
double sry = ry + (shadowY*yScale);
screen->DrawTexture(character, srx, sry,
DTA_DestWidthF, rw,
DTA_DestHeightF, rh,
DTA_AlphaF, salpha,
DTA_FillColor, 0,
TAG_DONE);
}
screen->DrawTexture(character, rx, ry,
DTA_DestWidthF, rw,
DTA_DestHeightF, rh,
DTA_Translation, remap,
DTA_AlphaF, Alpha,
TAG_DONE);
if(script->spacingCharacter == '\0')
ax += width + spacing - (character->LeftOffset+1);
else //width gets changed at the call to GetChar()
ax += font->GetCharWidth((unsigned char) script->spacingCharacter) + spacing;
str++;
}
}
FRemapTable* GetTranslation() const
{
if(gameinfo.gametype & GAME_Raven)
return translationtables[TRANSLATION_PlayersExtra][int(CPlayer - players)];
return translationtables[TRANSLATION_Players][int(CPlayer - players)];
}
AAmmo *ammo1, *ammo2;
int ammocount1, ammocount2;
ABasicArmor *armor;
FImageCollection Images;
unsigned int invBarOffset;
private:
SBarInfo *script;
int pendingPopup;
int currentPopup;
int lastHud;
bool scalingWasForced;
SBarInfoMainBlock *lastInventoryBar;
SBarInfoMainBlock *lastPopup;
};
IMPLEMENT_CLASS(DSBarInfo, false, true, false, false)
IMPLEMENT_POINTERS_START(DSBarInfo)
IMPLEMENT_POINTER(ammo1)
IMPLEMENT_POINTER(ammo2)
IMPLEMENT_POINTER(armor)
IMPLEMENT_POINTERS_END
DBaseStatusBar *CreateCustomStatusBar (int script)
{
if(SBarInfoScript[script] == NULL)
I_FatalError("Tried to create a status bar with no script!");
return new DSBarInfo(SBarInfoScript[script]);
}
void SBarInfoMainBlock::DrawAux(const SBarInfoMainBlock *block, DSBarInfo *statusBar, int xOffset, int yOffset, double alpha)
{
// Popups can also be forced to scale
bool rescale = false;
if(ForceScaled())
{
if(FullScreenOffsets())
{
if(!hud_scale)
{
rescale = true;
hud_scale = true;
}
}
else if(!statusBar->Scaled)
{
rescale = true;
statusBar->SetScaled(true, true);
}
}
Draw(block, statusBar, xOffset, yOffset, alpha);
if(rescale)
{
if(FullScreenOffsets())
hud_scale = false;
else
statusBar->SetScaled(false);
}
}
#include "sbarinfo_commands.cpp"