gzdoom-gles/src/g_statusbar/sbarinfo_commands.cpp

3600 lines
102 KiB
C++
Raw Normal View History

2016-03-01 15:47:10 +00:00
/*
** sbarinfo_commands.cpp
**
** This is an extension to the sbarinfo.cpp file. This contains all of the
** commands.
**---------------------------------------------------------------------------
** 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.
**---------------------------------------------------------------------------
**
*/
// [BL] Do note that this file is included by sbarinfo.cpp. This is done so
// that all the code can be more or less in one spot.
//
// At the bottom of this file is a function that belongs to
// SBarInfoCommandFlowControl which creates the instances of the following
// classes.
////////////////////////////////////////////////////////////////////////////////
class CommandDrawImage : public SBarInfoCommandFlowControl
{
public:
CommandDrawImage(SBarInfo *script) : SBarInfoCommandFlowControl(script),
translatable(false), type(NORMAL_IMAGE), image(-1), maxwidth(-1),
maxheight(-1), spawnScaleX(1.0f), spawnScaleY(1.0f), flags(0),
applyscale(false), offset(static_cast<Offset> (TOP|LEFT)),
texture(NULL), alpha(1.)
2016-03-01 15:47:10 +00:00
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(flags & DI_ALTERNATEONFAIL)
SBarInfoCommandFlowControl::Draw(block, statusBar);
if(texture == NULL)
return;
int w = maxwidth, h = maxheight;
// We must calculate this per frame in order to prevent glitches with cl_capfps true.
double frameAlpha = block->Alpha() * alpha;
2016-03-01 15:47:10 +00:00
if(flags & DI_DRAWINBOX)
{
double scale1, scale2;
scale1 = scale2 = 1.0f;
double texwidth = (int) (texture->GetScaledWidthDouble()*spawnScaleX);
double texheight = (int) (texture->GetScaledHeightDouble()*spawnScaleY);
if (w != -1 && (w<texwidth || (flags & DI_FORCESCALE)))
{
scale1 = w/texwidth;
}
if (h != -1 && (h<texheight || (flags & DI_FORCESCALE)))
{
scale2 = h/texheight;
}
if (flags & DI_FORCESCALE)
{
if (w == -1 || (h != -1 && scale2<scale1))
scale1=scale2;
}
else if (scale2<scale1) scale1=scale2;
w=(int)(texwidth*scale1);
h=(int)(texheight*scale1);
}
else if (applyscale)
{
w=(int) (texture->GetScaledWidthDouble()*spawnScaleX);
h=(int) (texture->GetScaledHeightDouble()*spawnScaleY);
}
statusBar->DrawGraphic(texture, imgx, imgy, block->XOffset(), block->YOffset(), frameAlpha, block->FullScreenOffsets(),
translatable, false, offset, false, w, h);
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
bool parenthesized = false;
bool getImage = true;
if(sc.CheckToken(TK_Identifier))
{
getImage = false;
if(sc.Compare("playericon"))
type = PLAYERICON;
else if(sc.Compare("ammoicon1"))
type = AMMO1;
else if(sc.Compare("ammoicon2"))
type = AMMO2;
else if(sc.Compare("armoricon"))
type = ARMOR;
else if(sc.Compare("weaponicon"))
type = WEAPONICON;
else if(sc.Compare("sigil"))
type = SIGIL;
else if(sc.Compare("hexenarmor"))
{
parenthesized = sc.CheckToken('(');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("armor"))
type = HEXENARMOR_ARMOR;
else if(sc.Compare("shield"))
type = HEXENARMOR_SHIELD;
else if(sc.Compare("helm"))
type = HEXENARMOR_HELM;
else if(sc.Compare("amulet"))
type = HEXENARMOR_AMULET;
else
{
sc.ScriptMessage("Unknown armor type: '%s'", sc.String);
type = HEXENARMOR_ARMOR;
}
sc.MustGetToken(',');
getImage = true;
}
else if(sc.Compare("translatable"))
{
translatable = true;
getImage = true;
}
else
{
type = INVENTORYICON;
const PClass* item = PClass::FindClass(sc.String);
if(item == NULL || !item->IsDescendantOf(NAME_Inventory)) //must be a kind of Inventory
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
}
else
{
sprite = ((AInventory *)GetDefaultByType(item))->Icon;
}
image = -1;
}
}
if(getImage)
{
sc.MustGetToken(TK_StringConst);
image = script->newImage(sc.String);
sprite.SetInvalid();
if(parenthesized) sc.MustGetToken(')');
}
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, imgx, imgy);
if(sc.CheckToken(','))
{
// Use none instead of topleft in case we decide we want to use
// alignments to remove the offset from images.
if(!sc.CheckToken(TK_None))
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("center"))
offset = CENTER;
else if(sc.Compare("centerbottom"))
offset = static_cast<Offset> (HMIDDLE|BOTTOM);
else
sc.ScriptError("'%s' is not a valid alignment.", sc.String);
}
}
if(sc.CheckToken(','))
{
sc.MustGetToken(TK_IntConst);
if((maxwidth = sc.Number) > 0)
flags |= DI_DRAWINBOX;
else
maxwidth = -1;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
if ((maxheight = sc.Number) > 0)
flags |= DI_DRAWINBOX;
else
maxheight = -1;
}
if(sc.CheckToken(','))
{
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("skipicon"))
flags |= DI_SKIPICON;
else if(sc.Compare("skipalticon"))
flags |= DI_SKIPALTICON;
else if(sc.Compare("skipspawn"))
flags |= DI_SKIPSPAWN;
else if(sc.Compare("skipready"))
flags |= DI_SKIPREADY;
else if(sc.Compare("alticonfirst"))
flags |= DI_ALTICONFIRST;
else if(sc.Compare("forcescale"))
{
if(flags & DI_DRAWINBOX)
flags |= DI_FORCESCALE;
}
else if(sc.Compare("alternateonfail"))
flags |= DI_ALTERNATEONFAIL;
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|') && !sc.CheckToken(',')) break;
}
}
if(flags & DI_ALTERNATEONFAIL)
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
else
sc.MustGetToken(';');
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
texture = NULL;
alpha = 1.;
2016-03-01 15:47:10 +00:00
if (applyscale)
{
spawnScaleX = spawnScaleY = 1.0f;
applyscale = false;
}
if(type == PLAYERICON)
texture = TexMan(statusBar->CPlayer->mo->ScoreIcon);
2016-03-01 15:47:10 +00:00
else if(type == AMMO1)
{
2017-01-18 13:18:17 +00:00
auto ammo = statusBar->ammo1;
2016-03-01 15:47:10 +00:00
if(ammo != NULL)
GetIcon(ammo);
}
else if(type == AMMO2)
{
2017-01-18 13:18:17 +00:00
auto ammo = statusBar->ammo2;
2016-03-01 15:47:10 +00:00
if(ammo != NULL)
GetIcon(ammo);
}
else if(type == ARMOR)
{
2017-01-18 13:18:17 +00:00
auto armor = statusBar->armor;
2016-03-01 15:47:10 +00:00
if(armor != NULL && armor->Amount != 0)
GetIcon(armor);
}
else if(type == WEAPONICON)
{
2017-01-18 13:18:17 +00:00
auto weapon = statusBar->CPlayer->ReadyWeapon;
2016-03-01 15:47:10 +00:00
if(weapon != NULL)
GetIcon(weapon);
}
else if(type == SIGIL)
{
auto item = statusBar->CPlayer->mo->FindInventory(NAME_Sigil);
2016-03-01 15:47:10 +00:00
if (item != NULL)
texture = TexMan(item->Icon);
2016-03-01 15:47:10 +00:00
}
else if(type == HEXENARMOR_ARMOR || type == HEXENARMOR_SHIELD || type == HEXENARMOR_HELM || type == HEXENARMOR_AMULET)
{
int armorType = type - HEXENARMOR_ARMOR;
2017-01-18 22:42:08 +00:00
auto harmor = statusBar->CPlayer->mo->FindInventory(NAME_HexenArmor);
2016-03-01 15:47:10 +00:00
if (harmor != NULL)
{
2017-01-18 22:42:08 +00:00
double *Slots = (double*)harmor->ScriptVar(NAME_Slots, nullptr);
double *SlotsIncrement = (double*)harmor->ScriptVar(NAME_SlotsIncrement, nullptr);
if (Slots[armorType] > 0 && SlotsIncrement[armorType] > 0)
2016-03-01 15:47:10 +00:00
{
//combine the alpha values
2017-01-18 22:42:08 +00:00
alpha *= MIN(1., Slots[armorType] / SlotsIncrement[armorType]);
2016-03-01 15:47:10 +00:00
texture = statusBar->Images[image];
}
else
return;
}
}
else if(type == INVENTORYICON)
texture = TexMan(sprite);
2016-03-01 15:47:10 +00:00
else if(type == SELECTEDINVENTORYICON && statusBar->CPlayer->mo->InvSel != NULL)
texture = TexMan(statusBar->CPlayer->mo->InvSel->Icon);
else if(image >= 0)
texture = statusBar->Images[image];
if (flags & DI_ALTERNATEONFAIL)
{
SetTruth(texture == NULL || texture->UseType == ETextureType::Null, block, statusBar);
2016-03-01 15:47:10 +00:00
}
}
protected:
void GetIcon(AInventory *item)
{
FTextureID icon = GetInventoryIcon(item, flags, &applyscale);
if (applyscale)
{
spawnScaleX = item->Scale.X;
spawnScaleY = item->Scale.Y;
2016-03-01 15:47:10 +00:00
}
texture = TexMan(icon);
2016-03-01 15:47:10 +00:00
}
enum ImageType
{
PLAYERICON,
AMMO1,
AMMO2,
ARMOR,
WEAPONICON,
SIGIL,
HEXENARMOR_ARMOR,
HEXENARMOR_SHIELD,
HEXENARMOR_HELM,
HEXENARMOR_AMULET,
INVENTORYICON,
WEAPONSLOT,
SELECTEDINVENTORYICON,
NORMAL_IMAGE
};
bool translatable;
ImageType type;
int image;
FTextureID sprite;
int maxwidth;
int maxheight;
double spawnScaleX;
double spawnScaleY;
uint32_t flags;
2016-03-01 15:47:10 +00:00
bool applyscale; //Set remotely from from GetInventoryIcon when selected sprite comes from Spawn state
// I'm using imgx/imgy here so that I can inherit drawimage with drawnumber for some commands.
SBarInfoCoordinate imgx;
SBarInfoCoordinate imgy;
Offset offset;
FTexture *texture;
double alpha;
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawSwitchableImage : public CommandDrawImage
{
private:
enum Operator
{
EQUAL,
LESS,
GREATER,
LESSOREQUAL,
GREATEROREQUAL,
NOTEQUAL
};
static void GetOperation(FScanner &sc, Operator &op, int &value)
{
if(sc.CheckToken(TK_Eq))
op = EQUAL;
else if(sc.CheckToken('<'))
op = LESS;
else if(sc.CheckToken('>'))
op = GREATER;
else if(sc.CheckToken(TK_Leq))
op = LESSOREQUAL;
else if(sc.CheckToken(TK_Geq))
op = GREATEROREQUAL;
else if(sc.CheckToken(TK_Neq))
op = NOTEQUAL;
else
{ // Default
op = GREATER;
value = 0;
return;
}
sc.MustGetToken(TK_IntConst);
value = sc.Number;
}
static bool EvaluateOperation(const Operator &op, const int &value, const int &compare)
{
switch(op)
{
case EQUAL:
return compare == value;
case LESS:
return compare < value;
case GREATER:
default:
return compare > value;
case LESSOREQUAL:
return compare <= value;
case GREATEROREQUAL:
return compare >= value;
case NOTEQUAL:
return compare != value;
}
}
// Key species are used to allow alternates for existing key slots.
static FName LookupKeySpecies(int keynum)
{
for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); ++i)
{
PClassActor *cls = PClassActor::AllActorClasses[i];
if (cls->IsDescendantOf(NAME_Key))
2016-03-01 15:47:10 +00:00
{
auto key = GetDefaultByType(cls);
if (key->special1 == keynum)
2016-03-01 15:47:10 +00:00
return cls->TypeName;
}
}
return NAME_None;
}
public:
CommandDrawSwitchableImage(SBarInfo *script) : CommandDrawImage(script),
condition(INVENTORY), conditionAnd(false)
{
conditionalImage[0] = conditionalImage[1] = conditionalImage[2] = -1;
conditionalValue[0] = conditionalValue[1] = 0;
armorType[0] = armorType[1] = 0;
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
if(sc.TokenType == TK_Identifier)
{
if(sc.Compare("weaponslot"))
{
condition = WEAPONSLOT;
sc.MustGetToken(TK_IntConst);
conditionalValue[0] = sc.Number;
}
else if(sc.Compare("invulnerable"))
{
condition = INVULNERABILITY;
}
else if(sc.Compare("keyslot"))
{
condition = KEYSLOT;
sc.MustGetToken(TK_IntConst);
conditionalValue[0] = sc.Number;
keySpecies[0] = LookupKeySpecies(conditionalValue[0]);
}
else if(sc.Compare("armortype"))
{
condition = ARMORTYPE;
sc.MustGetToken(TK_Identifier);
armorType[0] = FName(sc.String).GetIndex();
GetOperation(sc, conditionalOperator[0], conditionalValue[0]);
}
}
if(condition == INVENTORY)
{
inventoryItem[0] = sc.String;
const PClass* item = PClass::FindClass(sc.String);
if(item == NULL || !item->IsDescendantOf(NAME_Inventory)) //must be a kind of Inventory
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
}
GetOperation(sc, conditionalOperator[0], conditionalValue[0]);
}
if(sc.CheckToken(TK_AndAnd) && condition != INVULNERABILITY)
{
conditionAnd = true;
if(condition == KEYSLOT || condition == WEAPONSLOT)
{
sc.MustGetToken(TK_IntConst);
conditionalValue[1] = sc.Number;
if(condition == KEYSLOT)
keySpecies[1] = LookupKeySpecies(conditionalValue[1]);
}
else if(condition == ARMORTYPE)
{
sc.MustGetToken(TK_Identifier);
armorType[1] = FName(sc.String).GetIndex();
GetOperation(sc, conditionalOperator[1], conditionalValue[1]);
}
else
{
sc.MustGetToken(TK_Identifier);
inventoryItem[1] = sc.String;
const PClass* item = PClass::FindClass(sc.String);
if(item == NULL || !item->IsDescendantOf(NAME_Inventory)) //must be a kind of Inventory
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
}
GetOperation(sc, conditionalOperator[1], conditionalValue[1]);
}
}
// [BL] I have word that MSVC++ wants this static_cast ;) Shut up MSVC!
for(unsigned int i = 0;i < (conditionAnd ? 3u : 1u);i++)
{
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst);
conditionalImage[i] = script->newImage(sc.String);
}
sc.MustGetToken(',');
CommandDrawImage::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
// DrawSwitchable image allows 2 or 4 images to be supplied.
// drawAlt toggles these:
// 1 = first image
// 2 = second image
// 3 = thrid image
// 0 = last image
int drawAlt = 0;
if(condition == WEAPONSLOT) //weaponslots
{
drawAlt = 1; //draw off state until we know we have something.
for (int i = 0; i < statusBar->CPlayer->weapons.Slots[conditionalValue[0]].Size(); i++)
{
PClassActor *weap = statusBar->CPlayer->weapons.Slots[conditionalValue[0]].GetWeapon(i);
if(weap == NULL)
{
continue;
}
else if(statusBar->CPlayer->mo->FindInventory(weap) != NULL)
{
drawAlt = 0;
break;
}
}
}
else if(condition == INVULNERABILITY)
{
if(statusBar->CPlayer->cheats&(CF_GODMODE|CF_GODMODE2))
{
drawAlt = 1;
}
}
else if(condition == KEYSLOT)
{
bool found1 = false;
bool found2 = false;
drawAlt = 1;
for(AInventory *item = statusBar->CPlayer->mo->Inventory;item != NULL;item = item->Inventory)
{
if(item->IsKindOf(NAME_Key))
2016-03-01 15:47:10 +00:00
{
int keynum = item->special1;
2016-03-01 15:47:10 +00:00
if(keynum)
{
if(keynum == conditionalValue[0])
found1 = true;
if(conditionAnd && keynum == conditionalValue[1]) // two keys
found2 = true;
}
else
{
FName species = item->GetSpecies();
if(species == keySpecies[0])
found1 = true;
if(conditionAnd && species == keySpecies[1])
found2 = true;
}
}
}
if(conditionAnd)
{
if(found1 && found2)
drawAlt = 0;
else if(found1)
drawAlt = 2;
else if(found2)
drawAlt = 3;
}
else
{
if(found1)
drawAlt = 0;
}
}
else if(condition == ARMORTYPE)
{
auto armor = statusBar->CPlayer->mo->FindInventory(NAME_BasicArmor);
2016-03-01 15:47:10 +00:00
if(armor != NULL)
{
bool matches1 = armor->NameVar(NAME_ArmorType).GetIndex() == armorType[0] && EvaluateOperation(conditionalOperator[0], conditionalValue[0], armor->Amount);
bool matches2 = armor->NameVar(NAME_ArmorType).GetIndex() == armorType[1] && EvaluateOperation(conditionalOperator[1], conditionalValue[1], armor->Amount);
2016-03-01 15:47:10 +00:00
drawAlt = 1;
if(conditionAnd)
{
if(matches1 && matches2)
drawAlt = 0;
else if(matches2)
drawAlt = 3;
else if(matches1)
drawAlt = 2;
}
else if(matches1)
drawAlt = 0;
}
}
else //check the inventory items and draw selected sprite
{
AInventory* item = statusBar->CPlayer->mo->FindInventory(inventoryItem[0]);
2016-03-01 15:47:10 +00:00
if(item == NULL || !EvaluateOperation(conditionalOperator[0], conditionalValue[0], item->Amount))
drawAlt = 1;
if(conditionAnd)
{
item = statusBar->CPlayer->mo->FindInventory(inventoryItem[1]);
2016-03-01 15:47:10 +00:00
bool secondCondition = item != NULL && EvaluateOperation(conditionalOperator[1], conditionalValue[1], item->Amount);
if((item != NULL && secondCondition) && drawAlt == 0) //both
{
drawAlt = 0;
}
else if((item != NULL && secondCondition) && drawAlt == 1) //2nd
{
drawAlt = 3;
}
else if((item == NULL || !secondCondition) && drawAlt == 0) //1st
{
drawAlt = 2;
}
}
}
if(drawAlt != 0) //draw 'off' image
{
texture = statusBar->Images[conditionalImage[drawAlt-1]];
// Since we're not going to call our parent's tick() method,
// be sure to set the alpha value properly.
alpha = 1.;
2016-03-01 15:47:10 +00:00
return;
}
CommandDrawImage::Tick(block, statusBar, hudChanged);
}
private:
enum Condition
{
WEAPONSLOT,
INVULNERABILITY,
KEYSLOT,
ARMORTYPE,
INVENTORY
};
Condition condition;
bool conditionAnd;
int conditionalImage[3];
int conditionalValue[2];
Operator conditionalOperator[2];
FString inventoryItem[2];
int armorType[2];
FName keySpecies[2] = { NAME_None, NAME_None };
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawString : public SBarInfoCommand
{
public:
CommandDrawString(SBarInfo *script) : SBarInfoCommand(script),
lineBreaks(false), breakWidth(320), shadow(false), shadowX(2),
shadowY(2), spacing(0), font(NULL), translation(CR_UNTRANSLATED),
cache(-1), strValue(CONSTANT), valueArgument(0), alignment(ALIGN_RIGHT)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(lineBreaks)
{
auto lines = V_BreakLines(font, breakWidth, str.GetChars());
for(unsigned i = 0; i < lines.Size();i++)
2016-03-01 15:47:10 +00:00
{
statusBar->DrawString(font, lines[i].Text, x, y+i*(font->GetHeight()+4), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow, shadowX, shadowY);
}
}
else
statusBar->DrawString(font, str.GetChars(), x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translation, spacing, shadow, shadowX, shadowY);
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
font = V_GetFont(sc.String);
if(font == NULL)
{
sc.ScriptMessage("Unknown font '%s'.", sc.String);
font = SmallFont;
}
sc.MustGetToken(',');
translation = GetTranslation(sc);
sc.MustGetToken(',');
ParseStringValue(sc);
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, startX, y);
if(sc.CheckToken(',')) //spacing
{
sc.MustGetToken(TK_IntConst);
spacing = sc.Number;
if(sc.CheckToken(',')) //[KS] flags? flags! SIX FLAGS!
{
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("alignment"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("right"))
alignment = ALIGN_RIGHT;
else if(sc.Compare("left"))
alignment = ALIGN_LEFT;
else if(sc.Compare("center"))
alignment = ALIGN_CENTER;
else
sc.ScriptError("Unknown alignment '%s'.", sc.String);
sc.MustGetToken(')');
}
else if(sc.Compare("drawshadow"))
{
if(sc.CheckToken('('))
{
sc.MustGetToken(TK_IntConst);
shadowX = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
shadowY = sc.Number;
sc.MustGetToken(')');
}
shadow = true;
}
else if(sc.Compare("linebreaks"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_IntConst);
breakWidth = sc.Number;
sc.MustGetToken(')');
lineBreaks = true;
}
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|') && !sc.CheckToken(',')) break;
}
}
}
sc.MustGetToken(';');
RealignString();
}
void ParseStringValue(FScanner &sc)
{
if(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("levelname"))
strValue = LEVELNAME;
else if(sc.Compare("levellump"))
strValue = LEVELLUMP;
else if(sc.Compare("skillname"))
strValue = SKILLNAME;
else if(sc.Compare("playerclass"))
strValue = PLAYERCLASS;
else if(sc.Compare("playername"))
strValue = PLAYERNAME;
else if(sc.Compare("ammo1tag"))
strValue = AMMO1TAG;
else if(sc.Compare("ammo2tag"))
strValue = AMMO2TAG;
else if(sc.Compare("weapontag"))
strValue = WEAPONTAG;
else if(sc.Compare("inventorytag"))
strValue = INVENTORYTAG;
else if(sc.Compare("time"))
strValue = TIME;
else if(sc.Compare("logtext"))
strValue = LOGTEXT;
else if(sc.Compare("globalvar"))
{
bool parenthesized = sc.CheckToken('(');
strValue = GLOBALVAR;
sc.MustGetToken(TK_IntConst);
if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS)
sc.ScriptError("Global variable number out of range: %d", sc.Number);
valueArgument = sc.Number;
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("globalarray"))
{
bool parenthesized = sc.CheckToken('(');
strValue = GLOBALARRAY;
sc.MustGetToken(TK_IntConst);
if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS)
sc.ScriptError("Global variable number out of range: %d", sc.Number);
valueArgument = sc.Number;
if(parenthesized) sc.MustGetToken(')');
}
else
sc.ScriptError("Unknown string '%s'.", sc.String);
}
else
{
strValue = CONSTANT;
sc.MustGetToken(TK_StringConst);
if(sc.String[0] == '$')
str = GStrings[sc.String+1];
else
str = sc.String;
}
}
void Reset()
{
switch(strValue)
{
case PLAYERCLASS:
// userinfo changes before the actual class change.
case SKILLNAME:
// Although it's not possible for the skill level to change
// midlevel, it is possible the level was restarted.
cache = -1;
break;
default:
break;
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
switch(strValue)
{
case LEVELNAME:
if(level.lumpnum != cache)
{
cache = level.lumpnum;
str = level.LevelName;
RealignString();
}
break;
case LEVELLUMP:
if(level.lumpnum != cache)
{
cache = level.lumpnum;
str = level.MapName;
str.ToUpper();
RealignString();
}
break;
case SKILLNAME:
if(level.lumpnum != cache) // Can only change skill between level.
{
cache = level.lumpnum;
str = G_SkillName();
RealignString();
}
break;
case PLAYERCLASS:
if(statusBar->CPlayer->userinfo.GetPlayerClassNum() != cache)
{
cache = statusBar->CPlayer->userinfo.GetPlayerClassNum();
str = GetPrintableDisplayName(statusBar->CPlayer->cls);
RealignString();
}
break;
case AMMO1TAG:
SetStringToTag(statusBar->ammo1);
break;
case AMMO2TAG:
SetStringToTag(statusBar->ammo2);
break;
case WEAPONTAG:
SetStringToTag(statusBar->CPlayer->ReadyWeapon);
break;
case INVENTORYTAG:
SetStringToTag(statusBar->CPlayer->mo->InvSel);
break;
case PLAYERNAME:
// Can't think of a good way to detect changes to this, so
// I guess copying it every tick will have to do.
str = statusBar->CPlayer->userinfo.GetName();
RealignString();
break;
case GLOBALVAR:
if(ACS_GlobalVars[valueArgument] != cache)
{
cache = ACS_GlobalVars[valueArgument];
str = FBehavior::StaticLookupString(ACS_GlobalVars[valueArgument]);
RealignString();
}
break;
case GLOBALARRAY:
if(ACS_GlobalArrays[valueArgument][consoleplayer] != cache)
{
cache = ACS_GlobalArrays[valueArgument][consoleplayer];
str = FBehavior::StaticLookupString(ACS_GlobalArrays[valueArgument][consoleplayer]);
RealignString();
}
break;
case TIME:
{
int sec = Tics2Seconds(level.time);
str.Format("%02d:%02d:%02d", sec / 3600, (sec % 3600) / 60, sec % 60);
break;
}
case LOGTEXT:
str = statusBar->CPlayer->LogText;
break;
default:
break;
}
}
protected:
enum StringAlignment
{
ALIGN_RIGHT,
ALIGN_LEFT,
ALIGN_CENTER,
};
void RealignString()
{
x = startX;
switch (alignment)
{
case ALIGN_LEFT:
break;
case ALIGN_RIGHT:
if(script->spacingCharacter == '\0')
x -= static_cast<int> (font->StringWidth(str)+(spacing * str.Len()));
else //monospaced, so just multiplay the character size
x -= static_cast<int> ((font->GetCharWidth((unsigned char) script->spacingCharacter) + spacing) * str.Len());
break;
case ALIGN_CENTER:
if(script->spacingCharacter == '\0')
x -= static_cast<int> ((font->StringWidth(str)+(spacing * str.Len())) / 2);
else
x -= static_cast<int> ((font->GetCharWidth((unsigned char) script->spacingCharacter) + spacing) * str.Len() / 2);
break;
}
}
enum StringValueType
{
LEVELNAME,
LEVELLUMP,
SKILLNAME,
PLAYERCLASS,
PLAYERNAME,
AMMO1TAG,
AMMO2TAG,
WEAPONTAG,
INVENTORYTAG,
GLOBALVAR,
GLOBALARRAY,
TIME,
LOGTEXT,
CONSTANT
};
bool lineBreaks;
int breakWidth;
bool shadow;
int shadowX;
int shadowY;
int spacing;
FFont *font;
EColorRange translation;
SBarInfoCoordinate startX;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
intptr_t cache; /// General purpose cache.
StringValueType strValue;
int valueArgument;
FString str;
StringAlignment alignment;
private:
void SetStringToTag(AActor *actor)
{
if (actor != NULL)
{
if ((intptr_t)actor->GetClass() != cache)
{
cache = (intptr_t)actor->GetClass();
str = actor->GetTag();
RealignString();
}
}
else
{
cache = -1;
str = "";
}
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawNumber : public CommandDrawString
{
public:
CommandDrawNumber(SBarInfo *script) : CommandDrawString(script),
fillZeros(false), whenNotZero(false), dontCap(false),
usePrefix(false), interpolationSpeed(0), drawValue(0), length(3),
lowValue(-1), lowTranslation(CR_UNTRANSLATED), highValue(-1),
highTranslation(CR_UNTRANSLATED), value(CONSTANT),
inventoryItem(NULL), cvarName(nullptr)
2016-03-01 15:47:10 +00:00
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_IntConst);
length = sc.Number;
sc.MustGetToken(',');
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
font = V_GetFont(sc.String);
if(font == NULL)
{
sc.ScriptMessage("Unknown font '%s'.", sc.String);
font = SmallFont;
}
sc.MustGetToken(',');
normalTranslation = GetTranslation(sc);
sc.MustGetToken(',');
if(sc.CheckToken(TK_IntConst))
{
value = CONSTANT;
valueArgument = sc.Number;
sc.MustGetToken(',');
}
else
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
value = INVENTORY;
if(sc.TokenType == TK_Identifier)
{
if(sc.Compare("health"))
value = HEALTH;
else if(sc.Compare("armor"))
value = ARMOR;
else if(sc.Compare("ammo1"))
value = AMMO1;
else if(sc.Compare("ammo2"))
value = AMMO2;
else if(sc.Compare("ammo1capacity"))
value = AMMO1CAPACITY;
else if(sc.Compare("ammo2capacity"))
value = AMMO2CAPACITY;
else if(sc.Compare("score"))
value = SCORE;
else if(sc.Compare("ammo")) //request the next string to be an ammo type
{
bool parenthesized = sc.CheckToken('(');
value = AMMO;
if(!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindActor(sc.String);
if(inventoryItem == NULL || !inventoryItem->IsDescendantOf(NAME_Ammo)) //must be a kind of ammo
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
2017-01-18 13:18:17 +00:00
inventoryItem = PClass::FindActor(NAME_Ammo);
2016-03-01 15:47:10 +00:00
}
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("ammocapacity"))
{
bool parenthesized = sc.CheckToken('(');
value = AMMOCAPACITY;
if(!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindActor(sc.String);
if (inventoryItem == NULL || !inventoryItem->IsDescendantOf(NAME_Ammo)) //must be a kind of ammo
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
2017-01-18 13:18:17 +00:00
inventoryItem = PClass::FindActor(NAME_Ammo);
2016-03-01 15:47:10 +00:00
}
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("frags"))
value = FRAGS;
else if(sc.Compare("kills"))
value = KILLS;
else if(sc.Compare("monsters"))
value = MONSTERS;
else if(sc.Compare("items"))
value = ITEMS;
else if(sc.Compare("totalitems"))
value = TOTALITEMS;
else if(sc.Compare("secrets"))
value = SECRETS;
else if(sc.Compare("totalsecrets"))
value = TOTALSECRETS;
else if(sc.Compare("armorclass"))
value = ARMORCLASS;
else if(sc.Compare("savepercent"))
value = SAVEPERCENT;
else if(sc.Compare("airtime"))
value = AIRTIME;
else if(sc.Compare("accuracy"))
value = ACCURACY;
else if(sc.Compare("stamina"))
value = STAMINA;
else if(sc.Compare("keys"))
value = KEYS;
else if(sc.Compare("globalvar"))
{
bool parenthesized = sc.CheckToken('(');
value = GLOBALVAR;
sc.MustGetToken(TK_IntConst);
if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS)
sc.ScriptError("Global variable number out of range: %d", sc.Number);
valueArgument = sc.Number;
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("globalarray")) //acts like variable[playernumber()]
{
bool parenthesized = sc.CheckToken('(');
value = GLOBALARRAY;
sc.MustGetToken(TK_IntConst);
if(sc.Number < 0 || sc.Number >= NUM_GLOBALVARS)
sc.ScriptError("Global variable number out of range: %d", sc.Number);
valueArgument = sc.Number;
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("poweruptime"))
{
bool parenthesized = sc.CheckToken('(');
value = POWERUPTIME;
if(!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindActor(sc.String);
if (inventoryItem == NULL || !inventoryItem->IsDescendantOf(NAME_PowerupGiver))
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String);
inventoryItem = PClass::FindActor(NAME_PowerupGiver);
2016-03-01 15:47:10 +00:00
}
if(parenthesized) sc.MustGetToken(')');
}
else if (sc.Compare("intcvar"))
{
bool parenthesized = sc.CheckToken('(');
value = INTCVAR;
if (!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
cvarName = sc.String;
// We have a name, but make sure it exists. If not, send notification so modders
// are aware of the situation.
FBaseCVar *CVar = FindCVar(cvarName, nullptr);
if (CVar != nullptr)
{
ECVarType cvartype = CVar->GetRealType();
if (!(cvartype == CVAR_Bool || cvartype == CVAR_Int))
{
2016-10-03 18:44:00 +00:00
sc.ScriptMessage("CVar '%s' is not an int or bool", cvarName.GetChars());
}
}
else
{
2016-10-03 18:44:00 +00:00
sc.ScriptMessage("CVar '%s' does not exist", cvarName.GetChars());
}
if (parenthesized) sc.MustGetToken(')');
}
2016-03-01 15:47:10 +00:00
}
if(value == INVENTORY)
{
inventoryItem = PClass::FindActor(sc.String);
if (inventoryItem == NULL || !inventoryItem->IsDescendantOf(NAME_Inventory))
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
inventoryItem = RUNTIME_CLASS(AInventory);
}
}
sc.MustGetToken(',');
}
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("fillzeros"))
fillZeros = true;
else if(sc.Compare("whennotzero"))
whenNotZero = true;
else if(sc.Compare("dontcap"))
dontCap = true;
else if(sc.Compare("drawshadow"))
{
if(sc.CheckToken('('))
{
sc.MustGetToken(TK_IntConst);
shadowX = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
shadowY = sc.Number;
sc.MustGetToken(')');
}
shadow = true;
}
else if(sc.Compare("interpolate"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_IntConst);
interpolationSpeed = sc.Number;
sc.MustGetToken(')');
}
else if(sc.Compare("alignment"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("right"))
alignment = ALIGN_RIGHT;
else if(sc.Compare("left"))
alignment = ALIGN_LEFT;
else if(sc.Compare("center"))
alignment = ALIGN_CENTER;
else
sc.ScriptError("Unknown alignment '%s'.", sc.String);
sc.MustGetToken(')');
}
else if(sc.Compare("prefix"))
{
usePrefix = true;
sc.MustGetToken('(');
ParseStringValue(sc);
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst);
prefixPadding = sc.String;
if(strValue == CommandDrawString::CONSTANT)
{
usePrefix = false; // Use prefix just determines if we tick the string.
prefixPadding = str + prefixPadding;
}
sc.MustGetToken(')');
}
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
GetCoordinates(sc, fullScreenOffsets, startX, y);
if(sc.CheckToken(','))
{
bool needsComma = false;
if(sc.CheckToken(TK_IntConst)) //font spacing
{
spacing = sc.Number;
needsComma = true;
}
if(!needsComma || sc.CheckToken(',')) //2nd coloring for "low-on" value
{
lowTranslation = GetTranslation(sc);
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
lowValue = sc.Number;
if(sc.CheckToken(',')) //3rd coloring for "high-on" value
{
highTranslation = GetTranslation(sc);
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
highValue = sc.Number;
}
}
}
sc.MustGetToken(';');
if(value == HEALTH)
interpolationSpeed = script->interpolateHealth ? script->interpolationSpeed : interpolationSpeed;
else if(value == ARMOR)
interpolationSpeed = script->interpolateArmor ? script->armorInterpolationSpeed : interpolationSpeed;
}
void Reset()
{
drawValue = 0;
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
if(usePrefix)
{
cache = -1; // Disable the cache since we are using the same variables.
CommandDrawString::Tick(block, statusBar, hudChanged);
}
int num = valueArgument;
switch(value)
{
case HEALTH:
num = statusBar->CPlayer->mo->health;
if(script->lowerHealthCap && num < 0) //health shouldn't display negatives
num = 0;
if(script->interpolateHealth)
interpolationSpeed = script->interpolationSpeed;
break;
case ARMOR:
num = statusBar->armor != NULL ? statusBar->armor->Amount : 0;
if(script->interpolateArmor)
interpolationSpeed = script->armorInterpolationSpeed;
break;
case AMMO1:
if(statusBar->ammo1 == NULL) //no ammo, do not draw
{
str = "";
return;
}
num = statusBar->ammocount1;
break;
case AMMO2:
if(statusBar->ammo2 == NULL) //no ammo, do not draw
{
str = "";
return;
}
num = statusBar->ammocount2;
break;
case AMMO:
{
AInventory* item = statusBar->CPlayer->mo->FindInventory(inventoryItem);
if(item != NULL)
num = item->Amount;
else
num = 0;
break;
}
case AMMO1CAPACITY:
if(statusBar->ammo1 == NULL) //no ammo, do not draw
{
str = "";
return;
}
else
num = statusBar->ammo1->MaxAmount;
break;
case AMMO2CAPACITY:
if(statusBar->ammo2 == NULL) //no ammo, do not draw
{
str = "";
return;
}
else
num = statusBar->ammo2->MaxAmount;
break;
case AMMOCAPACITY:
{
AInventory* item = statusBar->CPlayer->mo->FindInventory(inventoryItem);
if(item != NULL)
num = item->MaxAmount;
else
num = ((AInventory *)GetDefaultByType(inventoryItem))->MaxAmount;
break;
}
case FRAGS:
num = statusBar->CPlayer->fragcount;
break;
case KILLS:
num = level.killed_monsters;
break;
case MONSTERS:
num = level.total_monsters;
break;
case ITEMS:
num = level.found_items;
break;
case TOTALITEMS:
num = level.total_items;
break;
case SECRETS:
num = level.found_secrets;
break;
case SCORE:
num = statusBar->CPlayer->mo->Score;
break;
case TOTALSECRETS:
num = level.total_secrets;
break;
case ARMORCLASS:
case SAVEPERCENT:
{
double add = 0;
2017-01-18 22:42:08 +00:00
auto harmor = statusBar->CPlayer->mo->FindInventory(NAME_HexenArmor);
2016-03-01 15:47:10 +00:00
if(harmor != NULL)
{
2017-01-18 22:42:08 +00:00
double *Slots = (double*)harmor->ScriptVar(NAME_Slots, nullptr);
add = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];
2016-03-01 15:47:10 +00:00
}
//Hexen counts basic armor also so we should too.
if(statusBar->armor != NULL)
{
add += statusBar->armor->FloatVar(NAME_SavePercent) * 100;
2016-03-01 15:47:10 +00:00
}
if(value == ARMORCLASS)
add /= 5;
num = int(add);
2016-03-01 15:47:10 +00:00
break;
}
case GLOBALVAR:
num = ACS_GlobalVars[valueArgument];
break;
case GLOBALARRAY:
num = ACS_GlobalArrays[valueArgument][consoleplayer];
break;
case POWERUPTIME:
{
// num = statusBar.CPlayer.mo.GetEffectTicsForItem(inventoryItem) / TICRATE + 1;
static VMFunction *func = nullptr;
if (func == nullptr) PClass::FindFunction(&func, NAME_PlayerPawn, "GetEffectTicsForItem");
VMValue params[] = { statusBar->CPlayer->mo, inventoryItem };
int retv;
VMReturn ret(&retv);
VMCall(func, params, 2, &ret, 1);
num = retv < 0? 0 : retv / TICRATE + 1;
2016-03-01 15:47:10 +00:00
break;
}
case INVENTORY:
{
AInventory* item = statusBar->CPlayer->mo->FindInventory(inventoryItem);
if(item != NULL)
num = item->Amount;
else
num = 0;
break;
}
case AIRTIME:
{
if(statusBar->CPlayer->mo->waterlevel < 3)
num = level.airsupply/TICRATE;
else
num = clamp<int>((statusBar->CPlayer->air_finished - level.time + (TICRATE-1))/TICRATE, 0, INT_MAX);
break;
}
case SELECTEDINVENTORY:
if(statusBar->CPlayer->mo->InvSel != NULL)
num = statusBar->CPlayer->mo->InvSel->Amount;
break;
case ACCURACY:
num = statusBar->CPlayer->mo->accuracy;
break;
case STAMINA:
num = statusBar->CPlayer->mo->stamina;
break;
case KEYS:
num = 0;
for(AInventory *item = statusBar->CPlayer->mo->Inventory;item != NULL;item = item->Inventory)
{
if(item->IsKindOf(NAME_Key))
2016-03-01 15:47:10 +00:00
num++;
}
break;
case INTCVAR:
{
FBaseCVar *CVar = GetCVar(statusBar->CPlayer->mo, cvarName);
if (CVar != nullptr)
{
ECVarType cvartype = CVar->GetRealType();
if (cvartype == CVAR_Bool || cvartype == CVAR_Int)
{
num = CVar->GetGenericRep(CVAR_Int).Int;
break;
}
}
// Fallback in case of bad cvar/type. Unset can remove a cvar at will.
num = 0;
break;
}
2016-03-01 15:47:10 +00:00
default: break;
}
if(interpolationSpeed != 0 && (!hudChanged || level.time == 1))
{
if(num < drawValue)
drawValue -= clamp<int>((drawValue - num) >> 2, 1, interpolationSpeed);
else if(drawValue < num)
drawValue += clamp<int>((num - drawValue) >> 2, 1, interpolationSpeed);
}
else
drawValue = num;
if(whenNotZero && drawValue == 0)
{
str = "";
return;
}
translation = normalTranslation;
if(lowValue != -1 && drawValue <= lowValue) //low
translation = lowTranslation;
else if(highValue != -1 && drawValue >= highValue) //high
translation = highTranslation;
bool useFillZeros = fillZeros;
if(!dontCap)
{
// 10^9 is a largest we can hold in a 32-bit int. So if we go any larger we have to toss out the positions limit.
int maxval = length <= 9 ? (int) ceil(pow(10., length))-1 : INT_MAX;
if(!fillZeros || length == 1)
drawValue = clamp(drawValue, -maxval, maxval);
else //The community wanted negatives to take the last digit, but we can only do this if there is room
drawValue = clamp(drawValue, length <= 9 ? (int) -(ceil(pow(10., length-1))-1) : INT_MIN, maxval);
}
else if(length <= 9)
{
int limit = (int) ceil(pow(10., length > 1 && drawValue < 0 ? length - 1 : length));
if(drawValue >= limit)
useFillZeros = true;
drawValue = drawValue%limit;
}
if(useFillZeros)
str.Format("%s%s%0*d", usePrefix ? str.GetChars() : "", prefixPadding.GetChars(), drawValue < 0 ? length - 1 : length, drawValue);
else
str.Format("%s%s%d", usePrefix ? str.GetChars() : "", prefixPadding.GetChars(), drawValue);
RealignString();
}
protected:
enum ValueType
{
HEALTH,
ARMOR,
AMMO1,
AMMO2,
AMMO,
AMMO1CAPACITY,
AMMO2CAPACITY,
AMMOCAPACITY,
FRAGS,
INVENTORY,
KILLS,
MONSTERS,
ITEMS,
TOTALITEMS,
SECRETS,
TOTALSECRETS,
ARMORCLASS,
GLOBALVAR,
GLOBALARRAY,
POWERUPTIME,
AIRTIME,
SELECTEDINVENTORY,
SCORE,
SAVEPERCENT,
ACCURACY,
STAMINA,
KEYS,
INTCVAR,
2016-03-01 15:47:10 +00:00
CONSTANT
};
bool fillZeros;
bool whenNotZero;
bool dontCap;
bool usePrefix;
int interpolationSpeed;
int drawValue;
int length;
int lowValue;
EColorRange lowTranslation;
int highValue;
EColorRange highTranslation;
EColorRange normalTranslation;
ValueType value;
PClassActor *inventoryItem;
FString prefixPadding;
FString cvarName;
2016-03-01 15:47:10 +00:00
friend class CommandDrawInventoryBar;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawMugShot : public SBarInfoCommand
{
public:
CommandDrawMugShot(SBarInfo *script) : SBarInfoCommand(script),
accuracy(5), stateFlags(FMugShot::STANDARD)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
FTexture *face = statusBar->wrapper->mugshot.GetFace(statusBar->CPlayer, defaultFace, accuracy, stateFlags);
2016-03-01 15:47:10 +00:00
if (face != NULL)
statusBar->DrawGraphic(face, x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(sc.CheckToken(TK_StringConst))
{
defaultFace = sc.String;
if(defaultFace.Len() > 3)
sc.ScriptError("Default can not be longer than 3 characters.");
sc.MustGetToken(',');
}
sc.MustGetToken(TK_IntConst); //accuracy
if(sc.Number < 1 || sc.Number > 9)
sc.ScriptError("Expected a number between 1 and 9, got %d instead.", sc.Number);
accuracy = sc.Number;
sc.MustGetToken(',');
while (sc.CheckToken(TK_Identifier))
2016-03-01 15:47:10 +00:00
{
if (sc.Compare("xdeathface"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::XDEATHFACE);
else if (sc.Compare("animatedgodmode"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::ANIMATEDGODMODE);
else if (sc.Compare("disablegrin"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::DISABLEGRIN);
else if (sc.Compare("disableouch"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::DISABLEOUCH);
else if (sc.Compare("disablepain"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::DISABLEPAIN);
else if (sc.Compare("disablerampage"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::DISABLERAMPAGE);
else if (sc.Compare("custom"))
stateFlags = static_cast<FMugShot::StateFlags> (stateFlags | FMugShot::CUSTOM);
2016-03-01 15:47:10 +00:00
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if (!sc.CheckToken('|'))
2016-03-01 15:47:10 +00:00
sc.MustGetToken(',');
}
GetCoordinates(sc, fullScreenOffsets, x, y);
sc.MustGetToken(';');
}
void Reset()
{
}
protected:
FString defaultFace; //Deprecated
int accuracy;
FMugShot::StateFlags stateFlags;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawSelectedInventory : public CommandDrawImage, private CommandDrawNumber
{
public:
CommandDrawSelectedInventory(SBarInfo *script) : CommandDrawImage(script),
CommandDrawNumber(script), alternateOnEmpty(false),
artiflash(false), alwaysShowCounter(false), itemflash(false)
{
length = INT_MAX; // Counter size
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(alternateOnEmpty)
SBarInfoCommandFlowControl::Draw(block, statusBar);
if(statusBar->CPlayer->mo->InvSel != NULL && !(level.flags & LEVEL_NOINVENTORYBAR))
{
2017-03-27 19:01:40 +00:00
if(artiflash && statusBar->wrapper->artiflashTick)
2016-03-01 15:47:10 +00:00
{
2017-03-27 19:01:40 +00:00
statusBar->DrawGraphic(statusBar->Images[ARTIFLASH_OFFSET+(4- statusBar->wrapper->artiflashTick)], imgx, imgy, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(),
2016-03-01 15:47:10 +00:00
translatable, false, offset);
}
else
{
2017-03-27 19:01:40 +00:00
if(itemflash && statusBar->wrapper->itemflashFade != 0)
2016-03-01 15:47:10 +00:00
{
2017-03-27 19:01:40 +00:00
double flashAlpha = block->Alpha() * statusBar->wrapper->itemflashFade;
2016-03-01 15:47:10 +00:00
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgCURSOR], imgx-4, imgy+2, block->XOffset(), block->YOffset(), flashAlpha, block->FullScreenOffsets(),
translatable, false, offset);
}
CommandDrawImage::Draw(block, statusBar);
}
if(alwaysShowCounter || statusBar->CPlayer->mo->InvSel->Amount != 1)
CommandDrawNumber::Draw(block, statusBar);
}
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
type = SELECTEDINVENTORYICON;
value = SELECTEDINVENTORY;
while(true) //go until we get a font (non-flag)
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
bool isFont = sc.TokenType != TK_Identifier;
if(sc.TokenType == TK_Identifier)
{
if(sc.Compare("alternateonempty"))
alternateOnEmpty = true;
else if(sc.Compare("artiflash"))
artiflash = true;
else if(sc.Compare("alwaysshowcounter"))
alwaysShowCounter = true;
else if(sc.Compare("itemflash"))
itemflash = true;
else if(sc.Compare("center"))
offset = CENTER;
else if(sc.Compare("centerbottom"))
offset = static_cast<Offset> (HMIDDLE|BOTTOM);
else if(sc.Compare("drawshadow"))
{
if(sc.CheckToken('('))
{
sc.MustGetToken(TK_IntConst);
shadowX = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
shadowY = sc.Number;
sc.MustGetToken(')');
}
shadow = true;
}
else
isFont = true;
}
if(isFont)
{
font = V_GetFont(sc.String);
if(font == NULL)
{
sc.ScriptMessage("Unknown font '%s'.", sc.String);
font = SmallFont;
}
sc.MustGetToken(',');
break;
}
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
CommandDrawImage::GetCoordinates(sc, fullScreenOffsets, imgx, imgy);
startX = imgx + 30;
y = imgy + 24;
normalTranslation = CR_GOLD;
if(sc.CheckToken(',')) //more font information
{
CommandDrawNumber::GetCoordinates(sc, fullScreenOffsets, startX, y);
if(sc.CheckToken(','))
{
normalTranslation = CommandDrawNumber::GetTranslation(sc);
if(sc.CheckToken(','))
{
sc.MustGetToken(TK_IntConst);
spacing = sc.Number;
}
}
}
if(alternateOnEmpty)
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
else
sc.MustGetToken(';');
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(statusBar->CPlayer->mo->InvSel == NULL || (level.flags & LEVEL_NOINVENTORYBAR), block, statusBar);
CommandDrawImage::Tick(block, statusBar, hudChanged);
CommandDrawNumber::Tick(block, statusBar, hudChanged);
}
protected:
bool alternateOnEmpty;
bool artiflash;
bool alwaysShowCounter;
bool itemflash;
};
////////////////////////////////////////////////////////////////////////////////
class CommandGameMode : public SBarInfoCommandFlowControl
{
public:
CommandGameMode(SBarInfo *script) : SBarInfoCommandFlowControl(script),
modes(static_cast<GameModes> (0))
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
static bool warnUnknown = true;
do
{
sc.MustGetToken(TK_Identifier);
int mode = sc.MatchString(modeNames);
if(mode >= 0)
modes |= static_cast<GameModes> (1<<mode);
else if(warnUnknown)
{
// Only warn about unknowns for cross port compatibility
// Also only warn once to keep logs from getting messy.
warnUnknown = false;
FScriptPosition(sc).Message(MSG_WARNING, "Ignoring unknown gamemode %s (future cases will be silently ignored).", sc.String);
}
}
while(sc.CheckToken(','));
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth((!multiplayer && (modes & SINGLEPLAYER)) ||
(deathmatch && (modes & DEATHMATCH)) ||
(multiplayer && !deathmatch && (modes & COOPERATIVE)) ||
(teamplay && (modes & TEAMGAME)), block, statusBar);
}
protected:
static const char* const modeNames[];
enum GameModes
{
SINGLEPLAYER = 0x1,
COOPERATIVE = 0x2,
DEATHMATCH = 0x4,
TEAMGAME = 0x8
};
unsigned int modes;
};
const char* const CommandGameMode::modeNames[] =
{
"singleplayer",
"cooperative",
"deathmatch",
"teamgame",
NULL
};
////////////////////////////////////////////////////////////////////////////////
class CommandUsesAmmo : public SBarInfoNegatableFlowControl
2016-03-01 15:47:10 +00:00
{
public:
CommandUsesAmmo(SBarInfo *script) : SBarInfoNegatableFlowControl(script) {}
2016-03-01 15:47:10 +00:00
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
2016-03-01 15:47:10 +00:00
SetTruth(statusBar->CPlayer->ReadyWeapon != NULL && (statusBar->CPlayer->ReadyWeapon->AmmoType1 != NULL || statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL), block, statusBar);
2016-03-01 15:47:10 +00:00
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandUsesSecondaryAmmo : public CommandUsesAmmo
{
public:
CommandUsesSecondaryAmmo(SBarInfo *script) : CommandUsesAmmo(script)
{
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(statusBar->CPlayer->ReadyWeapon != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL && statusBar->CPlayer->ReadyWeapon->AmmoType1 != statusBar->CPlayer->ReadyWeapon->AmmoType2, block, statusBar);
2016-03-01 15:47:10 +00:00
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandInventoryBarNotVisible : public SBarInfoCommandFlowControl
{
public:
CommandInventoryBarNotVisible(SBarInfo *script) : SBarInfoCommandFlowControl(script)
{
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(statusBar->CPlayer->inventorytics <= 0 || (level.flags & LEVEL_NOINVENTORYBAR), block, statusBar);
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandAspectRatio : public SBarInfoCommandFlowControl
{
public:
CommandAspectRatio(SBarInfo *script) : SBarInfoCommandFlowControl(script),
ratio(ASPECTRATIO_4_3)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_StringConst);
if(sc.Compare("4:3"))
ratio = ASPECTRATIO_4_3;
else if(sc.Compare("16:9"))
ratio = ASPECTRATIO_16_9;
else if(sc.Compare("16:10"))
ratio = ASPECTRATIO_16_10;
else if(sc.Compare("17:10"))
ratio = ASPECTRATIO_17_10;
else if(sc.Compare("5:4"))
ratio = ASPECTRATIO_5_4;
else
sc.ScriptError("Unkown aspect ratio: %s", sc.String);
// Make this ratio known and map to itself
// In the future, should another aspect ratio get added, you'd want
// to also make any wider ratio remap to this one if suitable.
ratioMap[ratio] = ratio;
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(ratioMap[FindRatio()] == ratio, block, statusBar);
2016-03-01 15:47:10 +00:00
}
protected:
enum Ratio
{
ASPECTRATIO_4_3 = 0,
ASPECTRATIO_16_9 = 1,
ASPECTRATIO_16_10 = 2,
ASPECTRATIO_17_10 = 3,
ASPECTRATIO_5_4 = 4
};
// Since the number of aspect ratios may change at any time, we should
// track what aspect ratios the statusbar supports and use the widest
// or tallest available ratio.
static Ratio ratioMap[5];
Ratio ratio;
private:
int FindRatio()
{
float aspect = ActiveRatio(screen->GetWidth(), screen->GetHeight());
static std::pair<float, int> ratioTypes[] =
{
{ 21 / 9.0f , ASPECTRATIO_16_9 },
{ 16 / 9.0f , ASPECTRATIO_16_9 },
{ 17 / 10.0f , ASPECTRATIO_17_10 },
{ 16 / 10.0f , ASPECTRATIO_16_10 },
{ 4 / 3.0f , ASPECTRATIO_4_3 },
{ 5 / 4.0f , ASPECTRATIO_5_4 },
{ 0.0f, 0 }
};
int ratio = ratioTypes[0].second;
2016-09-12 16:44:04 +00:00
float distance = fabs(ratioTypes[0].first - aspect);
for (int i = 1; ratioTypes[i].first != 0.0f; i++)
{
2016-09-12 16:44:04 +00:00
float d = fabs(ratioTypes[i].first - aspect);
if (d < distance)
{
ratio = ratioTypes[i].second;
distance = d;
}
}
return ratio;
}
2016-03-01 15:47:10 +00:00
};
CommandAspectRatio::Ratio CommandAspectRatio::ratioMap[5] = {ASPECTRATIO_4_3,ASPECTRATIO_16_9,ASPECTRATIO_16_10,ASPECTRATIO_16_10,ASPECTRATIO_5_4};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawShader : public SBarInfoCommand
{
public:
CommandDrawShader(SBarInfo *script) : SBarInfoCommand(script),
vertical(false), reverse(false), width(1), height(1)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
statusBar->DrawGraphic(shaders[(vertical<<1) + reverse], x, y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, true, width, height);
2016-03-01 15:47:10 +00:00
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_IntConst);
width = sc.Number;
if(sc.Number < 1)
sc.ScriptError("Width must be greater than 1.");
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
height = sc.Number;
if(sc.Number < 1)
sc.ScriptError("Height must be greater than 1.");
sc.MustGetToken(',');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("vertical"))
vertical = true;
else if(!sc.Compare("horizontal"))
sc.ScriptError("Unknown direction '%s'.", sc.String);
sc.MustGetToken(',');
if(sc.CheckToken(TK_Identifier))
{
if(!sc.Compare("reverse"))
{
sc.ScriptError("Exspected 'reverse', got '%s' instead.", sc.String);
}
reverse = true;
sc.MustGetToken(',');
}
GetCoordinates(sc, fullScreenOffsets, x, y);
sc.MustGetToken(';');
shaders[0] = TexMan.FindTexture("BarShaderHF");
shaders[1] = TexMan.FindTexture("BarShaderHR");
shaders[2] = TexMan.FindTexture("BarShaderVF");
shaders[3] = TexMan.FindTexture("BarShaderVR");
2016-03-01 15:47:10 +00:00
}
protected:
bool vertical;
bool reverse;
unsigned int width;
unsigned int height;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
private:
FTexture *shaders[4];
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawInventoryBar : public SBarInfoCommand
{
public:
CommandDrawInventoryBar(SBarInfo *script) : SBarInfoCommand(script),
style(STYLE_Doom), size(7), alwaysShow(false), noArtibox(false),
noArrows(false), alwaysShowCounter(false), translucent(false),
vertical(false), shadow(false), shadowX(2), shadowY(2),
counters(NULL), font(NULL), translation(CR_GOLD),
fontSpacing(0)
{
}
~CommandDrawInventoryBar()
{
if(counters != NULL)
{
for(unsigned int i = 0;i < size;i++)
delete counters[i];
delete[] counters;
}
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
int spacing = GetCounterSpacing(statusBar);
double bgalpha = block->Alpha();
2016-03-01 15:47:10 +00:00
if(translucent)
bgalpha *= HX_SHADOW;
2016-03-01 15:47:10 +00:00
AInventory *item;
unsigned int i = 0;
// If the player has no artifacts, don't draw the bar
statusBar->CPlayer->mo->InvFirst = statusBar->wrapper->ValidateInvFirst(size);
2016-03-01 15:47:10 +00:00
if(statusBar->CPlayer->mo->InvFirst != NULL || alwaysShow)
{
for(item = statusBar->CPlayer->mo->InvFirst, i = 0; item != NULL && i < size; item = item->NextInv(), ++i)
{
SBarInfoCoordinate rx = x + (!vertical ? i*spacing : 0);
SBarInfoCoordinate ry = y + (vertical ? i*spacing : 0);
if(!noArtibox)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgARTIBOX], rx, ry, block->XOffset(), block->YOffset(), bgalpha, block->FullScreenOffsets());
if(style != STYLE_Strife) //Strife draws the cursor before the icons
statusBar->DrawGraphic(TexMan(item->Icon), rx - (style == STYLE_HexenStrict ? 2 : 0), ry - (style == STYLE_HexenStrict ? 1 : 0), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, item->Amount <= 0);
if(item == statusBar->CPlayer->mo->InvSel)
{
if(style == STYLE_Heretic)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry+29, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
else if(style == STYLE_Hexen)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry-1, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
else if(style == STYLE_HexenStrict)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx-1, ry-1, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
else if(style == STYLE_Strife)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgCURSOR], rx-6, ry-2, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
else
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgSELECTBOX], rx, ry, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
}
if(style == STYLE_Strife)
statusBar->DrawGraphic(TexMan(item->Icon), rx, ry, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, item->Amount <= 0);
if(counters != NULL && (alwaysShowCounter || item->Amount != 1))
{
counters[i]->valueArgument = item->Amount;
counters[i]->Draw(block, statusBar);
}
}
for (; i < size && !noArtibox; ++i)
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgARTIBOX], x + (!vertical ? (i*spacing) : 0), y + (vertical ? (i*spacing) : 0), block->XOffset(), block->YOffset(), bgalpha, block->FullScreenOffsets());
// Is there something to the left?
if (!noArrows && statusBar->CPlayer->mo->FirstInv() != statusBar->CPlayer->mo->InvFirst)
{
int offset = (style != STYLE_Strife ? (style != STYLE_HexenStrict ? -12 : -10) : 14);
int yOffset = style != STYLE_HexenStrict ? 0 : -1;
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgINVLFGEM1], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
2016-03-01 15:47:10 +00:00
}
// Is there something to the right?
if (!noArrows && item != NULL)
{
int offset = (style != STYLE_Strife ? (style != STYLE_HexenStrict ? size*31+2 : size*31) : size*35-4);
int yOffset = style != STYLE_HexenStrict ? 0 : -1;
statusBar->DrawGraphic(statusBar->Images[statusBar->invBarOffset + imgINVRTGEM1], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
2016-03-01 15:47:10 +00:00
}
}
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("Doom"))
style = STYLE_Doom;
else if(sc.Compare("Heretic"))
style = STYLE_Heretic;
else if(sc.Compare("Hexen"))
style = STYLE_Hexen;
else if(sc.Compare("HexenStrict"))
style = STYLE_HexenStrict;
else if(sc.Compare("Strife"))
style = STYLE_Strife;
else
sc.ScriptError("Unknown style '%s'.", sc.String);
sc.MustGetToken(',');
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("alwaysshow"))
alwaysShow = true;
else if(sc.Compare("drawshadow"))
{
if(sc.CheckToken('('))
{
sc.MustGetToken(TK_IntConst);
shadowX = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
shadowY = sc.Number;
sc.MustGetToken(')');
}
shadow = true;
}
else if(sc.Compare("noartibox"))
noArtibox = true;
else if(sc.Compare("noarrows"))
noArrows = true;
else if(sc.Compare("alwaysshowcounter"))
alwaysShowCounter = true;
else if(sc.Compare("translucent"))
translucent = true;
else if(sc.Compare("vertical"))
vertical = true;
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
sc.MustGetToken(TK_IntConst);
size = sc.Number;
sc.MustGetToken(',');
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
font = V_GetFont(sc.String);
if(font == NULL)
{
sc.ScriptError("Unknown font '%s'.", sc.String);
font = SmallFont;
}
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, x, y);
counterX = x + 26;
counterY = y + 22;
if(sc.CheckToken(',')) //more font information
{
GetCoordinates(sc, fullScreenOffsets, counterX, counterY);
if(sc.CheckToken(','))
{
translation = GetTranslation(sc);
if(sc.CheckToken(','))
{
sc.MustGetToken(TK_IntConst);
fontSpacing = sc.Number;
}
}
}
sc.MustGetToken(';');
}
int GetCounterSpacing(const DSBarInfo *statusBar) const
{
FTexture *box = (style != STYLE_Strife)
? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]
: statusBar->Images[statusBar->invBarOffset + imgCURSOR];
if (box == NULL)
{ // Don't crash without a graphic.
return 32;
}
else
{
int spacing;
if (!vertical)
{
spacing = box->GetScaledWidth();
}
else
{
spacing = box->GetScaledHeight();
}
return spacing + ((style != STYLE_Strife) ? 1 : -1);
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
// Make the counters if need be.
if(counters == NULL)
{
int spacing = GetCounterSpacing(statusBar);
counters = new CommandDrawNumber*[size];
for(unsigned int i = 0;i < size;i++)
{
counters[i] = new CommandDrawNumber(script);
counters[i]->startX = counterX + (!vertical ? spacing*i : 0);
counters[i]->y = counterY + (vertical ? spacing*i : 0);
counters[i]->normalTranslation = translation;
counters[i]->font = font;
counters[i]->spacing = fontSpacing;
counters[i]->whenNotZero = !alwaysShowCounter;
counters[i]->drawValue = counters[i]->value = CommandDrawNumber::CONSTANT;
counters[i]->length = INT_MAX;
counters[i]->shadow = shadow;
counters[i]->shadowX = shadowX;
counters[i]->shadowY = shadowY;
}
}
for(unsigned int i = 0;i < size;i++)
counters[i]->Tick(block, statusBar, hudChanged);
}
protected:
enum Styles
{
STYLE_Doom,
STYLE_Heretic,
STYLE_Hexen,
STYLE_HexenStrict,
STYLE_Strife
};
Styles style;
unsigned int size;
bool alwaysShow;
bool noArtibox;
bool noArrows;
bool alwaysShowCounter;
bool translucent;
bool vertical;
bool shadow;
int shadowX;
int shadowY;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
CommandDrawNumber* *counters;
private:
// This information will be transferred to counters as soon as possible.
FFont *font;
SBarInfoCoordinate counterX;
SBarInfoCoordinate counterY;
EColorRange translation;
int fontSpacing;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawKeyBar : public SBarInfoCommand
{
public:
CommandDrawKeyBar(SBarInfo *script) : SBarInfoCommand(script),
number(3), vertical(false), reverse(false), reverseRows(false),
iconSize(-1), rowIconSize(-1), keyOffset(0), rowSize(0)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
AInventory *item = statusBar->CPlayer->mo->Inventory;
if(item == NULL)
return;
int slotOffset = 0;
int rowOffset = 0;
int rowWidth = 0;
for(unsigned int i = 0;i < number+keyOffset;i++)
{
while(!item->Icon.isValid() || !item->IsKindOf(NAME_Key))
2016-03-01 15:47:10 +00:00
{
item = item->Inventory;
if(item == NULL)
return;
}
if(i >= keyOffset) //Should we start drawing?
{
if(!vertical)
{
statusBar->DrawGraphic(TexMan(item->Icon), x+slotOffset, y+rowOffset, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
rowWidth = rowIconSize == -1 ? TexMan(item->Icon)->GetScaledHeight()+2 : rowIconSize;
2016-03-01 15:47:10 +00:00
}
else
{
statusBar->DrawGraphic(TexMan(item->Icon), x+rowOffset, y+slotOffset, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
rowWidth = rowIconSize == -1 ? TexMan(item->Icon)->GetScaledWidth()+2 : rowIconSize;
2016-03-01 15:47:10 +00:00
}
// If cmd.special is -1 then the slot size is auto detected
if(iconSize == -1)
{
if(!vertical)
slotOffset += (reverse ? -1 : 1) * (TexMan(item->Icon)->GetScaledWidth() + 2);
2016-03-01 15:47:10 +00:00
else
slotOffset += (reverse ? -1 : 1) * (TexMan(item->Icon)->GetScaledHeight() + 2);
2016-03-01 15:47:10 +00:00
}
else
slotOffset += (reverse ? -iconSize : iconSize);
if(rowSize > 0 && (i % rowSize == rowSize-1))
{
if(reverseRows)
rowOffset -= rowWidth;
else
rowOffset += rowWidth;
rowWidth = 0;
slotOffset = 0;
}
}
item = item->Inventory;
if(item == NULL)
return;
}
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_IntConst);
number = sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("vertical"))
vertical = true;
else if(!sc.Compare("horizontal"))
sc.ScriptError("Unknown direction '%s'.", sc.String);
sc.MustGetToken(',');
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("reverserows"))
reverseRows = true;
else if(sc.Compare("reverse"))
reverse = true;
else
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
if(sc.CheckToken(TK_Auto))
iconSize = -1;
else
{
sc.MustGetToken(TK_IntConst);
iconSize = sc.Number;
}
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, x, y);
if(sc.CheckToken(','))
{
//key offset
sc.MustGetToken(TK_IntConst);
keyOffset = sc.Number;
if(sc.CheckToken(','))
{
//max per row/column
sc.MustGetToken(TK_IntConst);
rowSize = sc.Number;
sc.MustGetToken(',');
//row/column spacing (opposite of previous)
if(sc.CheckToken(TK_Auto))
rowIconSize = -1;
else
{
sc.MustGetToken(TK_IntConst);
rowIconSize = sc.Number;
}
}
}
sc.MustGetToken(';');
}
protected:
unsigned int number;
bool vertical;
bool reverse;
bool reverseRows;
int iconSize;
int rowIconSize;
unsigned int keyOffset;
unsigned int rowSize;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawBar : public SBarInfoCommand
{
public:
CommandDrawBar(SBarInfo *script) : SBarInfoCommand(script),
border(0), horizontal(false), reverse(false), foreground(-1),
background(-1), type(HEALTH), interpolationSpeed(0), drawValue(0),
pixel(-1)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(foreground == -1 || statusBar->Images[foreground] == NULL)
return; //don't draw anything.
assert(statusBar->Images[foreground] != NULL);
FTexture *fg = statusBar->Images[foreground];
FTexture *bg = (background != -1) ? statusBar->Images[background] : NULL;
double value = drawValue;
2016-03-01 15:47:10 +00:00
if(border != 0)
{
value = 1. - value; //invert since the new drawing method requires drawing the bg on the fg.
2016-03-01 15:47:10 +00:00
//Draw the whole foreground
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
}
else
{
// Draw background
if (bg != NULL && bg->GetScaledWidth() == fg->GetScaledWidth() && bg->GetScaledHeight() == fg->GetScaledHeight())
statusBar->DrawGraphic(bg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
else
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, nulclip, true);
2016-03-01 15:47:10 +00:00
}
// {cx, cy, cr, cb}
double Clip[4] = {0, 0, 0, 0};
2016-03-01 15:47:10 +00:00
int sizeOfImage = (horizontal ? fg->GetScaledWidth()-border*2 : fg->GetScaledHeight()-border*2);
Clip[(!horizontal)|((horizontal ? !reverse : reverse)<<1)] = sizeOfImage - sizeOfImage *value;
2016-03-01 15:47:10 +00:00
// Draw background
if(border != 0)
{
for(unsigned int i = 0;i < 4;i++)
Clip[i] += border;
2016-03-01 15:47:10 +00:00
if (bg != NULL && bg->GetScaledWidth() == fg->GetScaledWidth() && bg->GetScaledHeight() == fg->GetScaledHeight())
statusBar->DrawGraphic(bg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, Clip);
2016-03-01 15:47:10 +00:00
else
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, Clip, true);
2016-03-01 15:47:10 +00:00
}
else
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, Clip);
2016-03-01 15:47:10 +00:00
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_StringConst);
foreground = script->newImage(sc.String);
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst);
background = script->newImage(sc.String);
sc.MustGetToken(',');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("health"))
{
type = HEALTH;
ParseComparator(sc);
}
else if(sc.Compare("armor"))
{
type = ARMOR;
ParseComparator(sc);
}
else if(sc.Compare("ammo1"))
type = AMMO1;
else if(sc.Compare("ammo2"))
type = AMMO2;
else if(sc.Compare("ammo")) //request the next string to be an ammo type
{
bool parenthesized = sc.CheckToken('(');
if(!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
type = AMMO;
data.inventoryItem = PClass::FindActor(sc.String);
if (data.inventoryItem == NULL || !data.inventoryItem->IsDescendantOf(NAME_Ammo)) //must be a kind of ammo
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
2017-01-18 13:18:17 +00:00
data.inventoryItem = PClass::FindActor(NAME_Ammo);
2016-03-01 15:47:10 +00:00
}
if(parenthesized) sc.MustGetToken(')');
}
else if(sc.Compare("frags"))
type = FRAGS;
else if(sc.Compare("kills"))
type = KILLS;
else if(sc.Compare("items"))
type = ITEMS;
else if(sc.Compare("secrets"))
type = SECRETS;
else if(sc.Compare("airtime"))
type = AIRTIME;
else if(sc.Compare("savepercent"))
type = SAVEPERCENT;
else if(sc.Compare("poweruptime"))
{
bool parenthesized = sc.CheckToken('(');
type = POWERUPTIME;
if(!parenthesized || !sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
data.inventoryItem = PClass::FindActor(sc.String);
if(data.inventoryItem == NULL || !data.inventoryItem->IsDescendantOf(NAME_PowerupGiver))
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String);
data.inventoryItem = PClass::FindActor(NAME_PowerupGiver);
2016-03-01 15:47:10 +00:00
}
if(parenthesized) sc.MustGetToken(')');
}
else
{
type = INVENTORY;
data.inventoryItem = PClass::FindActor(sc.String);
if(data.inventoryItem == NULL || !data.inventoryItem->IsDescendantOf(NAME_Inventory))
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
data.inventoryItem = RUNTIME_CLASS(AInventory);
}
}
sc.MustGetToken(',');
sc.MustGetToken(TK_Identifier);
if(sc.Compare("horizontal"))
horizontal = true;
else if(!sc.Compare("vertical"))
sc.ScriptError("Unknown direction '%s'.", sc.String);
sc.MustGetToken(',');
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("reverse"))
reverse = true;
else if(sc.Compare("interpolate"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_IntConst);
interpolationSpeed = sc.Number;
sc.MustGetToken(')');
}
else
sc.ScriptError("Unkown flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
GetCoordinates(sc, fullScreenOffsets, x, y);
if(sc.CheckToken(',')) //border
{
sc.MustGetToken(TK_IntConst);
border = sc.Number;
// Flip the direction since it represents the area to clip
if(border != 0)
reverse = !reverse;
}
sc.MustGetToken(';');
if(type == HEALTH)
interpolationSpeed = script->interpolateHealth ? script->interpolationSpeed : interpolationSpeed;
else if(type == ARMOR)
interpolationSpeed = script->interpolateArmor ? script->armorInterpolationSpeed : interpolationSpeed;
}
void Reset()
{
drawValue = 0;
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
double value = 0;
2016-03-01 15:47:10 +00:00
int max = 0;
switch(type)
{
case HEALTH:
value = statusBar->CPlayer->mo->health;
if(value < 0) //health shouldn't display negatives
value = 0;
if(data.useMaximumConstant)
max = data.value;
else if(data.inventoryItem != NULL)
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(data.inventoryItem); //max comparer
if(item != NULL)
max = item->Amount;
else
max = 0;
}
else //default to the class's health
max = statusBar->CPlayer->mo->GetMaxHealth(true);
2016-03-01 15:47:10 +00:00
break;
case ARMOR:
value = statusBar->armor != NULL ? statusBar->armor->Amount : 0;
if(data.useMaximumConstant)
max = data.value;
else if(data.inventoryItem != NULL)
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(data.inventoryItem);
if(item != NULL)
max = item->Amount;
else
max = 0;
}
else
max = 100;
break;
case AMMO1:
value = statusBar->ammocount1;
if(statusBar->ammo1 == NULL) //no ammo, draw as empty
{
value = 0;
max = 1;
}
else
max = statusBar->ammo1->MaxAmount;
break;
case AMMO2:
value = statusBar->ammocount2;
if(statusBar->ammo2 == NULL) //no ammo, draw as empty
{
value = 0;
max = 1;
}
else
max = statusBar->ammo2->MaxAmount;
break;
case AMMO:
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(data.inventoryItem);
if(item != NULL)
{
value = item->Amount;
max = item->MaxAmount;
}
else
value = 0;
break;
}
case FRAGS:
value = statusBar->CPlayer->fragcount;
max = fraglimit;
break;
case KILLS:
value = level.killed_monsters;
max = level.total_monsters;
break;
case ITEMS:
value = level.found_items;
max = level.total_items;
break;
case SECRETS:
value = level.found_secrets;
max = level.total_secrets;
break;
case INVENTORY:
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(data.inventoryItem);
if(item != NULL)
{
value = item->Amount;
max = item->MaxAmount;
}
else
value = 0;
break;
}
case AIRTIME:
value = clamp<int>(statusBar->CPlayer->air_finished - level.time, 0, INT_MAX);
max = level.airsupply;
break;
case POWERUPTIME:
{
2017-01-18 13:18:17 +00:00
// [value, max] = statusBar.CPlayer.mo.GetEffectTicsForItem(inventoryItem);
// value++; max++;
static VMFunction *func = nullptr;
if (func == nullptr) PClass::FindFunction(&func, NAME_PlayerPawn, "GetEffectTicsForItem");
VMValue params[] = { statusBar->CPlayer->mo, data.inventoryItem };
VMReturn ret[2];
int ival;
ret[0].IntAt(&ival);
ret[1].IntAt(&max);
VMCall(func, params, 2, ret, 2);
value = ival + 1;
max++;
2016-03-01 15:47:10 +00:00
break;
}
case SAVEPERCENT:
{
double add = 0;
2017-01-18 22:42:08 +00:00
auto harmor = statusBar->CPlayer->mo->FindInventory(NAME_HexenArmor);
if (harmor != NULL)
2016-03-01 15:47:10 +00:00
{
2017-01-18 22:42:08 +00:00
double *Slots = (double*)harmor->ScriptVar(NAME_Slots, nullptr);
add = Slots[0] + Slots[1] + Slots[2] + Slots[3] + Slots[4];
2016-03-01 15:47:10 +00:00
}
2017-01-18 22:42:08 +00:00
2016-03-01 15:47:10 +00:00
//Hexen counts basic armor also so we should too.
if(statusBar->armor != NULL)
{
add += statusBar->armor->FloatVar(NAME_SavePercent) * 100;
2016-03-01 15:47:10 +00:00
}
value = int(add);
2016-03-01 15:47:10 +00:00
max = 100;
break;
}
default: return;
}
if(max != 0 && value > 0)
{
value = MIN(value / max, 1.);
2016-03-01 15:47:10 +00:00
}
else
value = 0;
if(interpolationSpeed != 0 && (!hudChanged || level.time == 1))
{
// [BL] Since we used a percentage (in order to get the most fluid animation)
// we need to establish a cut off point so the last pixel won't hang as the animation slows
if(pixel == -1 && statusBar->Images[foreground])
pixel = MAX(1 / 65536., 1./statusBar->Images[foreground]->GetWidth());
2016-03-01 15:47:10 +00:00
if(fabs(drawValue - value) < pixel)
2016-03-01 15:47:10 +00:00
drawValue = value;
else if (value < drawValue)
drawValue -= clamp<double>((drawValue - value) / 4, 1 / 65536., interpolationSpeed / 100.);
else if (drawValue < value)
drawValue += clamp<double>((value - drawValue) / 4, 1 / 65536., interpolationSpeed / 100.);
2016-03-01 15:47:10 +00:00
}
else
drawValue = value;
}
protected:
void ParseComparator(FScanner &sc)
{
bool extendedSyntax = sc.CheckToken('(');
if(sc.CheckToken(TK_Identifier) || (extendedSyntax && sc.CheckToken(TK_StringConst))) //comparing reference
{
data.inventoryItem = PClass::FindActor(sc.String);
if(data.inventoryItem == NULL || !data.inventoryItem->IsDescendantOf(NAME_Inventory)) //must be a kind of inventory
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
data.inventoryItem = RUNTIME_CLASS(AInventory);
}
}
else if(extendedSyntax && sc.CheckToken(TK_IntConst))
{
data.useMaximumConstant = true;
data.value = sc.Number;
}
if(extendedSyntax)
sc.MustGetToken(')');
}
enum ValueType
{
HEALTH,
ARMOR,
AMMO1,
AMMO2,
AMMO,
FRAGS,
INVENTORY,
KILLS,
ITEMS,
SECRETS,
ARMORCLASS,
POWERUPTIME,
AIRTIME,
SAVEPERCENT
};
struct AdditionalData
{
public:
AdditionalData() : useMaximumConstant(false)
{
inventoryItem = NULL;
}
bool useMaximumConstant;
union
{
PClassActor *inventoryItem;
int value;
};
};
unsigned int border;
bool horizontal;
bool reverse;
int foreground;
int background;
ValueType type;
AdditionalData data;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
int interpolationSpeed;
double drawValue;
double pixel;
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandIsSelected : public SBarInfoNegatableFlowControl
2016-03-01 15:47:10 +00:00
{
public:
CommandIsSelected(SBarInfo *script) : SBarInfoNegatableFlowControl(script)
2016-03-01 15:47:10 +00:00
{
weapon[0] = NULL;
weapon[1] = NULL;
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
2016-03-01 15:47:10 +00:00
{
if(!sc.CheckToken(TK_Identifier))
2016-03-01 15:47:10 +00:00
sc.MustGetToken(TK_StringConst);
for(int i = 0;i < 2;i++)
{
weapon[i] = PClass::FindClass(sc.String);
if(weapon[i] == NULL || !weapon[i]->IsDescendantOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of weapon.", sc.String);
weapon[i] = RUNTIME_CLASS(AWeapon);
}
if(sc.CheckToken(','))
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
else
break;
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
2016-03-01 15:47:10 +00:00
if(statusBar->CPlayer->ReadyWeapon != NULL)
{
const PClass *readyWeapon = statusBar->CPlayer->ReadyWeapon->GetClass();
SetTruth(weapon[0] == readyWeapon || (weapon[1] && weapon[1] == readyWeapon), block, statusBar);
2016-03-01 15:47:10 +00:00
}
}
protected:
const PClass *weapon[2];
};
////////////////////////////////////////////////////////////////////////////////
class CommandPlayerClass : public SBarInfoCommandFlowControl
{
public:
CommandPlayerClass(SBarInfo *script) : SBarInfoCommandFlowControl(script)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_Identifier);
do
{
bool foundClass = false;
for(unsigned int c = 0;c < PlayerClasses.Size();c++)
{
if(stricmp(sc.String, PlayerClasses[c].Type->GetDisplayName()) == 0)
2016-03-01 15:47:10 +00:00
{
foundClass = true;
classes.Push(PlayerClasses[c].Type);
break;
}
}
/*
if(!foundClass)
sc.ScriptError("Unkown PlayerClass '%s'.", sc.String);
*/
if(!sc.CheckToken(','))
break;
}
while(sc.CheckToken(TK_Identifier));
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
if(statusBar->CPlayer->cls == NULL)
return; //No class so we can not continue
PClass *spawnClass = statusBar->CPlayer->cls;
for(unsigned int i = 0;i < classes.Size();i++)
{
if(classes[i] == spawnClass)
{
SetTruth(true, block, statusBar);
return;
}
}
SetTruth(false, block, statusBar);
}
protected:
TArray<PClass*> classes;
};
////////////////////////////////////////////////////////////////////////////////
class CommandPlayerType : public SBarInfoCommandFlowControl
{
public:
CommandPlayerType(SBarInfo *script) : SBarInfoCommandFlowControl(script)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
do
{
bool foundClass = false;
const PClass *cls = PClass::FindClass(sc.String);
if (cls != NULL)
{
foundClass = true;
classes.Push(cls);
}
/*
if(!foundClass)
sc.ScriptError("Unkown PlayerClass '%s'.", sc.String);
*/
if(!sc.CheckToken(','))
break;
}
while(sc.CheckToken(TK_Identifier) || sc.CheckToken(TK_StringConst));
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
if(statusBar->CPlayer->cls == NULL)
return; //No class so we can not continue
for(unsigned int i = 0;i < classes.Size();i++)
{
if (statusBar->CPlayer->cls->IsDescendantOf(classes[i]))
{
SetTruth(true, block, statusBar);
return;
}
}
SetTruth(false, block, statusBar);
}
protected:
TArray<const PClass *> classes;
};
////////////////////////////////////////////////////////////////////////////////
class CommandHasWeaponPiece : public SBarInfoCommandFlowControl
{
public:
CommandHasWeaponPiece(SBarInfo *script) : SBarInfoCommandFlowControl(script),
weapon(NULL), piece(1)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
weapon = PClass::FindClass(sc.String);
if (weapon == NULL || !weapon->IsDescendantOf(NAME_Weapon)) //must be a weapon
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("%s is not a kind of weapon.", sc.String);
weapon = RUNTIME_CLASS(AWeapon);
}
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
if(sc.Number < 1)
sc.ScriptError("Weapon piece number can not be less than 1.");
piece = sc.Number;
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
for(AInventory *inv = statusBar->CPlayer->mo->Inventory;inv != NULL;inv=inv->Inventory)
{
auto hc = PClass::FindActor("WeaponHolder");
if(inv->IsKindOf(hc))
2016-03-01 15:47:10 +00:00
{
if(inv->PointerVar<PClass>("PieceWeapon") == weapon)
2016-03-01 15:47:10 +00:00
{
SetTruth(0 != (inv->IntVar("PieceMask") & (1 << (piece-1))), block, statusBar);
2016-03-01 15:47:10 +00:00
return;
}
}
}
SetTruth(false, block, statusBar);
}
protected:
const PClass *weapon;
unsigned int piece;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawGem : public SBarInfoCommand
{
public:
CommandDrawGem(SBarInfo *script) : SBarInfoCommand(script),
wiggle(false), translatable(false), armor(false), reverse(false),
chain(-1), gem(-1), leftPadding(0), rightPadding(0), chainSize(1),
interpolationSpeed(0), drawValue(0), goalValue(0), chainWiggle(0)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
FTexture *chainImg = statusBar->Images[chain];
FTexture *gemImg = statusBar->Images[gem];
if(chainImg == NULL)
return;
SBarInfoCoordinate drawY = y;
if(wiggle && drawValue != goalValue) // Should only wiggle when the value doesn't equal what is being drawn.
drawY += chainWiggle;
int chainWidth = chainImg->GetScaledWidth();
int offset = (int) (((double) (chainWidth-leftPadding-rightPadding)/100)*drawValue);
statusBar->DrawGraphic(chainImg, x+(offset%chainSize), drawY, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
if(gemImg != NULL)
statusBar->DrawGraphic(gemImg, x+leftPadding+offset, drawY, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), translatable);
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
while(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("wiggle"))
wiggle = true;
else if(sc.Compare("translatable"))
translatable = true;
else if(sc.Compare("armor"))
armor = true;
else if(sc.Compare("interpolate"))
{
sc.MustGetToken('(');
sc.MustGetToken(TK_IntConst);
interpolationSpeed = sc.Number;
sc.MustGetToken(')');
}
else if(sc.Compare("reverse"))
reverse = true;
else
sc.ScriptError("Unknown drawgem flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
sc.MustGetToken(TK_StringConst); //chain
chain = script->newImage(sc.String);
sc.MustGetToken(',');
sc.MustGetToken(TK_StringConst); //gem
gem = script->newImage(sc.String);
sc.MustGetToken(',');
bool negative = sc.CheckToken('-');
sc.MustGetToken(TK_IntConst);
leftPadding = (negative ? -1 : 1) * sc.Number;
sc.MustGetToken(',');
negative = sc.CheckToken('-');
sc.MustGetToken(TK_IntConst);
rightPadding = (negative ? -1 : 1) * sc.Number;
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
if(sc.Number < 0)
sc.ScriptError("Chain size must be a positive number.");
chainSize = sc.Number+1;
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, x, y);
sc.MustGetToken(';');
if(!armor)
interpolationSpeed = script->interpolateHealth ? script->interpolationSpeed : interpolationSpeed;
else
interpolationSpeed = script->interpolateArmor ? script->armorInterpolationSpeed : interpolationSpeed;
}
void Reset()
{
drawValue = 0;
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
goalValue = armor ? (statusBar->armor ? statusBar->armor->Amount : 0) : statusBar->CPlayer->mo->health;
int max = armor ? 100 : statusBar->CPlayer->mo->GetMaxHealth(true);
2016-03-01 15:47:10 +00:00
if(max != 0 && goalValue > 0)
{
goalValue = (goalValue*100)/max;
if(goalValue > 100)
goalValue = 100;
}
else
goalValue = 0;
goalValue = reverse ? 100 - goalValue : goalValue;
if(interpolationSpeed != 0 && (!hudChanged || level.time == 1)) // At the start force an animation
{
if(goalValue < drawValue)
drawValue -= clamp<int>((drawValue - goalValue) >> 2, 1, interpolationSpeed);
else if(drawValue < goalValue)
drawValue += clamp<int>((goalValue - drawValue) >> 2, 1, interpolationSpeed);
}
else
drawValue = goalValue;
if(wiggle && level.time & 1)
chainWiggle = pr_chainwiggle() & 1;
}
protected:
bool wiggle;
bool translatable;
bool armor;
bool reverse;
int chain;
int gem;
int leftPadding;
int rightPadding;
unsigned int chainSize;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
int interpolationSpeed;
int drawValue;
int goalValue;
private:
int chainWiggle;
static FRandom pr_chainwiggle;
};
FRandom CommandDrawGem::pr_chainwiggle; //use the same method of chain wiggling as heretic.
////////////////////////////////////////////////////////////////////////////////
class CommandWeaponAmmo : public SBarInfoNegatableFlowControl
2016-03-01 15:47:10 +00:00
{
public:
CommandWeaponAmmo(SBarInfo *script) : SBarInfoNegatableFlowControl(script),
conditionAnd(false)
2016-03-01 15:47:10 +00:00
{
ammo[0] = NULL;
ammo[1] = NULL;
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
2016-03-01 15:47:10 +00:00
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
for(int i = 0;i < 2;i++)
{
ammo[i] = PClass::FindClass(sc.String);
if(ammo[i] == NULL || !ammo[i]->IsDescendantOf(NAME_Ammo)) //must be a kind of ammo
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
2017-01-18 13:18:17 +00:00
ammo[i] = PClass::FindActor(NAME_Ammo);
2016-03-01 15:47:10 +00:00
}
if(sc.CheckToken(TK_OrOr))
{
conditionAnd = false;
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
else if(sc.CheckToken(TK_AndAnd))
{
conditionAnd = true;
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
else
break;
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
2016-03-01 15:47:10 +00:00
if(statusBar->CPlayer->ReadyWeapon != NULL)
{
const PClass *AmmoType1 = statusBar->CPlayer->ReadyWeapon->AmmoType1;
const PClass *AmmoType2 = statusBar->CPlayer->ReadyWeapon->AmmoType2;
bool usesammo1 = (AmmoType1 != NULL);
bool usesammo2 = (AmmoType2 != NULL);
//if(!usesammo1 && !usesammo2) //if the weapon doesn't use ammo don't go though the trouble.
//{
// SetTruth(false, block, statusBar);
// return;
//}
2016-03-01 15:47:10 +00:00
//Or means only 1 ammo type needs to match and means both need to match.
if(ammo[1] != NULL)
{
bool match1 = ((usesammo1 && (AmmoType1 == ammo[0] || AmmoType1 == ammo[1])) || !usesammo1);
bool match2 = ((usesammo2 && (AmmoType2 == ammo[0] || AmmoType2 == ammo[1])) || !usesammo2);
if((!conditionAnd && (match1 || match2)) || (conditionAnd && (match1 && match2)))
{
SetTruth(true, block, statusBar);
return;
}
}
else
2016-03-01 15:47:10 +00:00
{
if((usesammo1 && (AmmoType1 == ammo[0])) || (usesammo2 && (AmmoType2 == ammo[0])))
{
SetTruth(true, block, statusBar);
return;
}
}
}
SetTruth(false, block, statusBar);
}
protected:
const PClass *ammo[2];
bool conditionAnd;
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandInInventory : public SBarInfoNegatableFlowControl
2016-03-01 15:47:10 +00:00
{
public:
CommandInInventory(SBarInfo *script) : SBarInfoNegatableFlowControl(script),
conditionAnd(false)
2016-03-01 15:47:10 +00:00
{
item[0] = item[1] = NULL;
amount[0] = amount[1] = 0;
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
2016-03-01 15:47:10 +00:00
{
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
for(int i = 0;i < 2;i++)
{
item[i] = PClass::FindActor(sc.String);
if (item[i] == NULL || !item[i]->IsDescendantOf(NAME_Inventory)) //must be a kind of ammo
2016-03-01 15:47:10 +00:00
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
item[i] = RUNTIME_CLASS(AInventory);
}
if (sc.CheckToken(','))
{
sc.MustGetNumber();
amount[i] = sc.Number;
}
if(sc.CheckToken(TK_OrOr))
{
conditionAnd = false;
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
else if(sc.CheckToken(TK_AndAnd))
{
conditionAnd = true;
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
else
break;
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
2016-03-01 15:47:10 +00:00
AInventory *invItem[2] = { statusBar->CPlayer->mo->FindInventory(item[0]), statusBar->CPlayer->mo->FindInventory(item[1]) };
if (invItem[0] != NULL && amount[0] > 0 && invItem[0]->Amount < amount[0]) invItem[0] = NULL;
if (invItem[1] != NULL && amount[1] > 0 && invItem[1]->Amount < amount[1]) invItem[1] = NULL;
if (item[1])
{
if (conditionAnd)
SetTruth(invItem[0] && invItem[1], block, statusBar);
2016-03-01 15:47:10 +00:00
else
SetTruth(invItem[0] || invItem[1], block, statusBar);
2016-03-01 15:47:10 +00:00
}
else
SetTruth(invItem[0] != NULL, block, statusBar);
2016-03-01 15:47:10 +00:00
}
protected:
bool conditionAnd;
PClassActor *item[2];
int amount[2];
};
////////////////////////////////////////////////////////////////////////////////
class CommandAlpha : public SBarInfoMainBlock
{
public:
CommandAlpha(SBarInfo *script) : SBarInfoMainBlock(script)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
forceScaled = block->ForceScaled();
fullScreenOffsets = block->FullScreenOffsets();
SBarInfoMainBlock::Draw(block, statusBar, block->XOffset(), block->YOffset(), block->Alpha());
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_FloatConst);
alpha = sc.Float;
2016-03-01 15:47:10 +00:00
// We don't want to allow all the options of a regular main block
// so skip to the SBarInfoCommandFlowControl.
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandIfHealth : public SBarInfoNegatableFlowControl
2016-03-01 15:47:10 +00:00
{
public:
CommandIfHealth(SBarInfo *script) : SBarInfoNegatableFlowControl(script),
percentage(false)
2016-03-01 15:47:10 +00:00
{
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
2016-03-01 15:47:10 +00:00
{
sc.MustGetToken(TK_IntConst);
percentage = sc.CheckToken('%');
hpamount = sc.Number;
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
2016-03-01 15:47:10 +00:00
int phealth = percentage ? statusBar->CPlayer->mo->health * 100 / statusBar->CPlayer->mo->GetMaxHealth() : statusBar->CPlayer->mo->health;
SetTruth(phealth >= hpamount, block, statusBar);
2016-03-01 15:47:10 +00:00
}
protected:
int hpamount;
bool percentage;
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
class CommandIfInvulnerable : public SBarInfoNegatableFlowControl
{
public:
CommandIfInvulnerable(SBarInfo *script) : SBarInfoNegatableFlowControl(script)
{
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
SetTruth((statusBar->CPlayer->mo->flags2 & MF2_INVULNERABLE) || (statusBar->CPlayer->cheats & (CF_GODMODE | CF_GODMODE2)), block, statusBar);
}
2016-03-01 15:47:10 +00:00
};
////////////////////////////////////////////////////////////////////////////////
2016-07-05 13:15:39 +00:00
class CommandIfWaterLevel : public SBarInfoNegatableFlowControl
{
public:
CommandIfWaterLevel(SBarInfo *script) : SBarInfoNegatableFlowControl(script)
{
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_IntConst);
value = sc.Number;
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(statusBar->CPlayer->mo->waterlevel >= value, block, statusBar);
}
protected:
int value;
};
////////////////////////////////////////////////////////////////////////////////
2016-09-03 15:55:19 +00:00
class CommandIfCVarInt : public SBarInfoNegatableFlowControl
{
public:
CommandIfCVarInt(SBarInfo *script) : SBarInfoNegatableFlowControl(script),
equalcomp(false)
{
}
void ParseNegatable(FScanner &sc, bool fullScreenOffsets)
{
if(!sc.CheckToken(TK_StringConst))
{
sc.MustGetToken(TK_Identifier);
}
cvarname = sc.String;
cvar = FindCVar(cvarname, nullptr);
if (cvar != nullptr)
{
ECVarType cvartype = cvar->GetRealType();
if (cvartype == CVAR_Bool || cvartype == CVAR_Int)
{
sc.MustGetToken(',');
sc.MustGetToken(TK_IntConst);
value = sc.Number;
if (sc.CheckToken(','))
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("equal"))
{
equalcomp = true;
}
}
}
else
{
sc.ScriptError("Type mismatch: console variable '%s' is not of type 'bool' or 'int'.", cvarname.GetChars());
2016-09-03 15:55:19 +00:00
}
}
else
{
sc.ScriptError("Unknown console variable '%s'.", cvarname.GetChars());
2016-09-03 15:55:19 +00:00
}
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoNegatableFlowControl::Tick(block, statusBar, hudChanged);
bool result = false;
cvar = GetCVar(statusBar->CPlayer->mo, cvarname);
if (cvar != nullptr)
{
int cvarvalue = cvar->GetGenericRep(CVAR_Int).Int;
result = equalcomp ? cvarvalue == value : cvarvalue >= value;
}
SetTruth(result, block, statusBar);
}
protected:
FString cvarname;
FBaseCVar *cvar;
int value;
bool equalcomp;
};
////////////////////////////////////////////////////////////////////////////////
2016-03-01 15:47:10 +00:00
static const char *SBarInfoCommandNames[] =
{
"drawimage", "drawnumber", "drawswitchableimage",
"drawmugshot", "drawselectedinventory",
"drawinventorybar", "drawbar", "drawgem",
"drawshader", "drawstring", "drawkeybar",
"gamemode", "playerclass", "playertype", "aspectratio",
"isselected", "usesammo", "usessecondaryammo",
"hasweaponpiece", "inventorybarnotvisible",
"weaponammo", "ininventory", "alpha", "ifhealth",
2016-09-03 15:55:19 +00:00
"ifinvulnerable", "ifwaterlevel", "ifcvarint",
2016-03-01 15:47:10 +00:00
NULL
};
enum SBarInfoCommands
{
SBARINFO_DRAWIMAGE, SBARINFO_DRAWNUMBER, SBARINFO_DRAWSWITCHABLEIMAGE,
SBARINFO_DRAWMUGSHOT, SBARINFO_DRAWSELECTEDINVENTORY,
SBARINFO_DRAWINVENTORYBAR, SBARINFO_DRAWBAR, SBARINFO_DRAWGEM,
SBARINFO_DRAWSHADER, SBARINFO_DRAWSTRING, SBARINFO_DRAWKEYBAR,
SBARINFO_GAMEMODE, SBARINFO_PLAYERCLASS, SBARINFO_PLAYERTYPE, SBARINFO_ASPECTRATIO,
SBARINFO_ISSELECTED, SBARINFO_USESAMMO, SBARINFO_USESSECONDARYAMMO,
SBARINFO_HASWEAPONPIECE, SBARINFO_INVENTORYBARNOTVISIBLE,
SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA, SBARINFO_IFHEALTH,
2016-09-03 15:55:19 +00:00
SBARINFO_IFINVULNERABLE, SBARINFO_IFWATERLEVEL, SBARINFO_IFCVARINT,
2016-03-01 15:47:10 +00:00
};
SBarInfoCommand *SBarInfoCommandFlowControl::NextCommand(FScanner &sc)
{
if(sc.CheckToken(TK_Identifier))
{
switch(sc.MatchString(SBarInfoCommandNames))
{
default: break;
case SBARINFO_DRAWIMAGE: return new CommandDrawImage(script);
case SBARINFO_DRAWSWITCHABLEIMAGE: return new CommandDrawSwitchableImage(script);
case SBARINFO_DRAWSTRING: return new CommandDrawString(script);
case SBARINFO_DRAWNUMBER: return new CommandDrawNumber(script);
case SBARINFO_DRAWMUGSHOT: return new CommandDrawMugShot(script);
case SBARINFO_DRAWSELECTEDINVENTORY: return static_cast<SBarInfoCommandFlowControl *> (new CommandDrawSelectedInventory(script));
case SBARINFO_DRAWSHADER: return new CommandDrawShader(script);
case SBARINFO_DRAWINVENTORYBAR: return new CommandDrawInventoryBar(script);
case SBARINFO_DRAWKEYBAR: return new CommandDrawKeyBar(script);
case SBARINFO_DRAWBAR: return new CommandDrawBar(script);
case SBARINFO_DRAWGEM: return new CommandDrawGem(script);
case SBARINFO_GAMEMODE: return new CommandGameMode(script);
case SBARINFO_USESAMMO: return new CommandUsesAmmo(script);
case SBARINFO_USESSECONDARYAMMO: return new CommandUsesSecondaryAmmo(script);
case SBARINFO_INVENTORYBARNOTVISIBLE: return new CommandInventoryBarNotVisible(script);
case SBARINFO_ASPECTRATIO: return new CommandAspectRatio(script);
case SBARINFO_ISSELECTED: return new CommandIsSelected(script);
case SBARINFO_PLAYERCLASS: return new CommandPlayerClass(script);
case SBARINFO_PLAYERTYPE: return new CommandPlayerType(script);
case SBARINFO_HASWEAPONPIECE: return new CommandHasWeaponPiece(script);
case SBARINFO_WEAPONAMMO: return new CommandWeaponAmmo(script);
case SBARINFO_ININVENTORY: return new CommandInInventory(script);
case SBARINFO_ALPHA: return new CommandAlpha(script);
case SBARINFO_IFHEALTH: return new CommandIfHealth(script);
case SBARINFO_IFINVULNERABLE: return new CommandIfInvulnerable(script);
2016-07-05 13:15:39 +00:00
case SBARINFO_IFWATERLEVEL: return new CommandIfWaterLevel(script);
2016-09-03 15:55:19 +00:00
case SBARINFO_IFCVARINT: return new CommandIfCVarInt(script);
2016-03-01 15:47:10 +00:00
}
sc.ScriptError("Unknown command '%s'.\n", sc.String);
return NULL;
}
sc.MustGetToken('}');
return NULL;
}