qzdoom/src/g_shared/sbarinfo_commands.cpp

3019 lines
90 KiB
C++

/*
** 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 SBarInfoCommand
{
public:
CommandDrawImage(SBarInfo *script) : SBarInfoCommand(script),
translatable(false), type(NORMAL_IMAGE), image(-1), offset(static_cast<Offset> (TOP|LEFT)),
texture(NULL), alpha(FRACUNIT)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(texture == NULL)
return;
// We must calculate this per frame in order to prevent glitches with cl_capfps true.
fixed_t frameAlpha = block->Alpha();
if(alpha != FRACUNIT)
frameAlpha = fixed_t(((double) block->Alpha() / (double) FRACUNIT) * ((double) alpha / (double) OPAQUE) * FRACUNIT);
statusBar->DrawGraphic(texture, imgx, imgy, block->XOffset(), block->YOffset(), frameAlpha, block->FullScreenOffsets(),
translatable, false, offset);
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
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"))
{
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("Unkown 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 || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory
{
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();
}
sc.MustGetToken(',');
GetCoordinates(sc, fullScreenOffsets, imgx, imgy);
if(sc.CheckToken(','))
{
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);
}
sc.MustGetToken(';');
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
texture = NULL;
alpha = FRACUNIT;
if(type == PLAYERICON)
texture = TexMan[statusBar->CPlayer->mo->ScoreIcon];
else if(type == AMMO1)
{
if(statusBar->ammo1 != NULL)
texture = TexMan[statusBar->ammo1->Icon];
}
else if(type == AMMO2)
{
if(statusBar->ammo2 != NULL)
texture = TexMan[statusBar->ammo2->Icon];
}
else if(type == ARMOR)
{
if(statusBar->armor != NULL && statusBar->armor->Amount != 0)
texture = TexMan(statusBar->armor->Icon);
}
else if(type == WEAPONICON)
{
AWeapon *weapon = statusBar->CPlayer->ReadyWeapon;
if(weapon != NULL && weapon->Icon.isValid())
{
texture = TexMan[weapon->Icon];
}
}
else if(type == SIGIL)
{
AInventory *item = statusBar->CPlayer->mo->FindInventory<ASigil>();
if (item != NULL)
texture = TexMan[item->Icon];
}
else if(type == HEXENARMOR_ARMOR || type == HEXENARMOR_SHIELD || type == HEXENARMOR_HELM || type == HEXENARMOR_AMULET)
{
int armorType = type - HEXENARMOR_ARMOR;
AHexenArmor *harmor = statusBar->CPlayer->mo->FindInventory<AHexenArmor>();
if (harmor != NULL)
{
if (harmor->Slots[armorType] > 0 && harmor->SlotsIncrement[armorType] > 0)
{
//combine the alpha values
alpha = fixed_t(((double) alpha / (double) FRACUNIT) * ((double) MIN<fixed_t> (OPAQUE, Scale(harmor->Slots[armorType], OPAQUE, harmor->SlotsIncrement[armorType])) / (double) OPAQUE) * FRACUNIT);
texture = statusBar->Images[image];
}
else
return;
}
}
else if(type == INVENTORYICON)
texture = TexMan[sprite];
else if(type == SELECTEDINVENTORYICON && statusBar->CPlayer->mo->InvSel != NULL)
texture = TexMan(statusBar->CPlayer->mo->InvSel->Icon);
else if(image >= 0)
texture = statusBar->Images[image];
}
protected:
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;
// 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;
int alpha;
};
////////////////////////////////////////////////////////////////////////////////
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;
}
}
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)
{
sc.MustGetToken(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;
}
else if(sc.Compare("armortype"))
{
condition = ARMORTYPE;
sc.MustGetToken(TK_Identifier);
armorType[0] = FName(sc.String).GetIndex();
GetOperation(sc, conditionalOperator[0], conditionalValue[0]);
}
else
{
inventoryItem[0] = sc.String;
const PClass* item = PClass::FindClass(sc.String);
if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory
{
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;
}
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 || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory
{
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++)
{
const PClass *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)
{
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(RUNTIME_CLASS(AKey)))
{
int keynum = static_cast<AKey *>(item)->KeyNumber;
if(keynum == conditionalValue[0])
found1 = true;
if(conditionAnd && keynum == conditionalValue[1]) // two keys
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)
{
ABasicArmor *armor = (ABasicArmor *) statusBar->CPlayer->mo->FindInventory(NAME_BasicArmor);
if(armor != NULL)
{
bool matches1 = armor->ArmorType.GetIndex() == armorType[0] && EvaluateOperation(conditionalOperator[0], conditionalValue[0], armor->Amount);
bool matches2 = armor->ArmorType.GetIndex() == armorType[1] && EvaluateOperation(conditionalOperator[1], conditionalValue[1], armor->Amount);
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(PClass::FindClass(inventoryItem[0]));
if(item == NULL || !EvaluateOperation(conditionalOperator[0], conditionalValue[0], item->Amount))
drawAlt = 1;
if(conditionAnd)
{
item = statusBar->CPlayer->mo->FindInventory(PClass::FindClass(inventoryItem[1]));
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 = FRACUNIT;
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];
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawString : public SBarInfoCommand
{
public:
CommandDrawString(SBarInfo *script) : SBarInfoCommand(script),
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)
{
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)
{
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(',');
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("globalvar"))
{
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;
}
else if(sc.Compare("globalarray"))
{
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;
}
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;
}
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
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|') && !sc.CheckToken(',')) break;
}
}
}
sc.MustGetToken(';');
RealignString();
}
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;
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.PlayerClass != cache)
{
cache = statusBar->CPlayer->userinfo.PlayerClass;
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.netname;
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;
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,
CONSTANT
};
bool shadow;
int shadowX;
int shadowY;
int spacing;
FFont *font;
EColorRange translation;
SBarInfoCoordinate startX;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
int cache; /// General purpose cache.
StringValueType strValue;
int valueArgument;
FString str;
StringAlignment alignment;
private:
void SetStringToTag(AActor *actor)
{
if(actor != NULL)
{
if(actor->GetClass()->ClassIndex != cache)
{
cache = actor->GetClass()->ClassIndex;
str = actor->GetTag();
RealignString();
}
}
else
{
cache = -1;
str = "";
}
}
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawNumber : public CommandDrawString
{
public:
CommandDrawNumber(SBarInfo *script) : CommandDrawString(script),
fillZeros(false), whenNotZero(false), interpolationSpeed(0), drawValue(0),
length(3), lowValue(-1), lowTranslation(CR_UNTRANSLATED), highValue(-1),
highTranslation(CR_UNTRANSLATED), value(CONSTANT),
inventoryItem(NULL)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_IntConst);
length = sc.Number;
sc.MustGetToken(',');
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
{
sc.MustGetToken(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("score"))
value = SCORE;
else if(sc.Compare("ammo")) //request the next string to be an ammo type
{
value = AMMO;
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
inventoryItem = RUNTIME_CLASS(AAmmo);
}
}
else if(sc.Compare("ammocapacity"))
{
value = AMMOCAPACITY;
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
inventoryItem = RUNTIME_CLASS(AAmmo);
}
}
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("airtime"))
value = AIRTIME;
else if(sc.Compare("globalvar"))
{
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;
}
else if(sc.Compare("globalarray")) //acts like variable[playernumber()]
{
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;
}
else if(sc.Compare("poweruptime"))
{
value = POWERUPTIME;
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(APowerupGiver)->IsAncestorOf(inventoryItem))
{
sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String);
inventoryItem = RUNTIME_CLASS(APowerupGiver);
}
}
else
{
value = INVENTORY;
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of ammo
{
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("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
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)
{
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 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:
{
AHexenArmor *harmor = statusBar->CPlayer->mo->FindInventory<AHexenArmor>();
if(harmor != NULL)
{
num = harmor->Slots[0] + harmor->Slots[1] +
harmor->Slots[2] + harmor->Slots[3] + harmor->Slots[4];
}
//Hexen counts basic armor also so we should too.
if(statusBar->armor != NULL)
{
num += statusBar->armor->SavePercent;
}
num /= (5*FRACUNIT);
break;
}
case GLOBALVAR:
num = ACS_GlobalVars[valueArgument];
break;
case GLOBALARRAY:
num = ACS_GlobalArrays[valueArgument][consoleplayer];
break;
case POWERUPTIME:
{
//Get the PowerupType and check to see if the player has any in inventory.
const PClass* powerupType = ((APowerupGiver*) GetDefaultByType(inventoryItem))->PowerupType;
APowerup* powerup = (APowerup*) statusBar->CPlayer->mo->FindInventory(powerupType);
if(powerup != NULL)
num = powerup->EffectTics / TICRATE + 1;
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;
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;
// 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);
str.Format("%d", drawValue);
if(fillZeros)
{
if(drawValue < 0) //We don't want the negative just yet
str.Format("%d", -drawValue);
while(str.Len() < (unsigned int) length)
{
if(drawValue < 0 && str.Len() == (unsigned int) (length-1))
str.Insert(0, "-");
else
str.Insert(0, "0");
}
}
RealignString();
}
protected:
enum ValueType
{
HEALTH,
ARMOR,
AMMO1,
AMMO2,
AMMO,
AMMOCAPACITY,
FRAGS,
INVENTORY,
KILLS,
MONSTERS,
ITEMS,
TOTALITEMS,
SECRETS,
TOTALSECRETS,
ARMORCLASS,
GLOBALVAR,
GLOBALARRAY,
POWERUPTIME,
AIRTIME,
SELECTEDINVENTORY,
SCORE,
CONSTANT
};
bool fillZeros;
bool whenNotZero;
int interpolationSpeed;
int drawValue;
int length;
int lowValue;
EColorRange lowTranslation;
int highValue;
EColorRange highTranslation;
EColorRange normalTranslation;
ValueType value;
const PClass *inventoryItem;
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 = script->MugShot.GetFace(statusBar->CPlayer, defaultFace, accuracy, stateFlags);
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))
{
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
sc.ScriptError("Unknown flag '%s'.", sc.String);
if(!sc.CheckToken('|'))
sc.MustGetToken(',');
}
GetCoordinates(sc, fullScreenOffsets, x, y);
sc.MustGetToken(';');
}
void Reset()
{
script->MugShot.Reset();
}
protected:
FString defaultFace; //Deprecated
int accuracy;
FMugShot::StateFlags stateFlags;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
};
////////////////////////////////////////////////////////////////////////////////
class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private CommandDrawImage, private CommandDrawNumber
{
public:
CommandDrawSelectedInventory(SBarInfo *script) : SBarInfoCommandFlowControl(script),
CommandDrawImage(script), CommandDrawNumber(script), alternateOnEmpty(false),
artiflash(false), alwaysShowCounter(false)
{
}
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
{
if(alternateOnEmpty)
SBarInfoCommandFlowControl::Draw(block, statusBar);
if(statusBar->CPlayer->mo->InvSel != NULL && !(level.flags & LEVEL_NOINVENTORYBAR))
{
if(artiflash && artiflashTick)
{
statusBar->DrawGraphic(statusBar->Images[ARTIFLASH_OFFSET+(4-artiflashTick)], imgx, imgy, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(),
translatable, false, offset);
}
else
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)
{
sc.MustGetToken(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("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
{
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);
if(artiflashTick > 0)
artiflashTick--;
SetTruth(statusBar->CPlayer->mo->InvSel == NULL || (level.flags & LEVEL_NOINVENTORYBAR), block, statusBar);
CommandDrawImage::Tick(block, statusBar, hudChanged);
CommandDrawNumber::Tick(block, statusBar, hudChanged);
}
static void Flash() { artiflashTick = 4; }
protected:
bool alternateOnEmpty;
bool artiflash;
bool alwaysShowCounter;
static int artiflashTick;
};
int CommandDrawSelectedInventory::artiflashTick = 4;
void DSBarInfo::FlashItem(const PClass *itemtype)
{
CommandDrawSelectedInventory::Flash();
}
////////////////////////////////////////////////////////////////////////////////
class CommandGameMode : public SBarInfoCommandFlowControl
{
public:
CommandGameMode(SBarInfo *script) : SBarInfoCommandFlowControl(script),
modes(static_cast<GameModes> (0))
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
do
{
sc.MustGetToken(TK_Identifier);
modes |= static_cast<GameModes> (1<<sc.MustMatchString(modeNames));
}
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 SBarInfoCommandFlowControl
{
public:
CommandUsesAmmo(SBarInfo *script) : SBarInfoCommandFlowControl(script),
negate(false)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
if(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("not"))
negate = true;
else
sc.ScriptError("Expected 'not', but got '%s' instead.", sc.String);
}
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth((statusBar->CPlayer->ReadyWeapon != NULL && (statusBar->CPlayer->ReadyWeapon->AmmoType1 != NULL || statusBar->CPlayer->ReadyWeapon->AmmoType2 != NULL)) ^ negate, block, statusBar);
}
protected:
bool negate;
};
////////////////////////////////////////////////////////////////////////////////
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) ^ negate, block, statusBar);
}
};
////////////////////////////////////////////////////////////////////////////////
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("5:4"))
ratio = ASPECTRATIO_5_4;
else
sc.ScriptError("Unkown aspect ratio: %s", sc.String);
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
SetTruth(CheckRatio(screen->GetWidth(), screen->GetHeight()) == ratio, block, statusBar);
}
protected:
enum Ratio
{
ASPECTRATIO_4_3 = 0,
ASPECTRATIO_16_9 = 1,
ASPECTRATIO_16_10 = 2,
ASPECTRATIO_5_4 = 4
};
Ratio ratio;
};
////////////////////////////////////////////////////////////////////////////////
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);
}
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(';');
}
protected:
bool vertical;
bool reverse;
unsigned int width;
unsigned int height;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
private:
class FBarShader : public FTexture
{
public:
FBarShader(bool vertical, bool reverse)
{
int i;
Width = vertical ? 2 : 256;
Height = vertical ? 256 : 2;
CalcBitSize();
// Fill the column/row with shading values.
// Vertical shaders have have minimum alpha at the top
// and maximum alpha at the bottom, unless flipped by
// setting reverse to true. Horizontal shaders are just
// the opposite.
if (vertical)
{
if (!reverse)
{
for (i = 0; i < 256; ++i)
{
Pixels[i] = i;
Pixels[256+i] = i;
}
}
else
{
for (i = 0; i < 256; ++i)
{
Pixels[i] = 255 - i;
Pixels[256+i] = 255 -i;
}
}
}
else
{
if (!reverse)
{
for (i = 0; i < 256; ++i)
{
Pixels[i*2] = 255 - i;
Pixels[i*2+1] = 255 - i;
}
}
else
{
for (i = 0; i < 256; ++i)
{
Pixels[i*2] = i;
Pixels[i*2+1] = i;
}
}
}
DummySpan[0].TopOffset = 0;
DummySpan[0].Length = vertical ? 256 : 2;
DummySpan[1].TopOffset = 0;
DummySpan[1].Length = 0;
}
const BYTE *GetColumn(unsigned int column, const Span **spans_out)
{
if (spans_out != NULL)
{
*spans_out = DummySpan;
}
return Pixels + ((column & WidthMask) << HeightBits);
}
const BYTE *GetPixels() { return Pixels; }
void Unload() {}
private:
BYTE Pixels[512];
Span DummySpan[2];
};
static FBarShader shaders[4];
};
CommandDrawShader::FBarShader CommandDrawShader::shaders[4] =
{
FBarShader(false, false), FBarShader(false, true),
FBarShader(true, false), FBarShader(true, true)
};
////////////////////////////////////////////////////////////////////////////////
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), 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 = 0;
if(!vertical)
spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1;
else
spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1;
int bgalpha = block->Alpha();
if(translucent)
bgalpha = fixed_t((((double) block->Alpha() / (double) FRACUNIT) * ((double) HX_SHADOW / (double) FRACUNIT)) * FRACUNIT);
AInventory *item;
unsigned int i = 0;
// If the player has no artifacts, don't draw the bar
statusBar->CPlayer->mo->InvFirst = statusBar->ValidateInvFirst(size);
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[!(gametic & 4) ?
statusBar->invBarOffset + imgINVLFGEM1 : statusBar->invBarOffset + imgINVLFGEM2], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
}
// 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[!(gametic & 4) ?
statusBar->invBarOffset + imgINVRTGEM1 : statusBar->invBarOffset + imgINVRTGEM2], x + (!vertical ? offset : yOffset), y + (vertical ? offset : yOffset), block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets());
}
}
}
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("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(',');
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(';');
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
// Make the counters if need be.
if(counters == NULL)
{
int spacing = 0;
if(!vertical)
spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledWidth() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledWidth() - 1;
else
spacing = (style != STYLE_Strife) ? statusBar->Images[statusBar->invBarOffset + imgARTIBOX]->GetScaledHeight() + 1 : statusBar->Images[statusBar->invBarOffset + imgCURSOR]->GetScaledHeight() - 1;
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 = 3;
}
}
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;
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(RUNTIME_CLASS(AKey)))
{
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;
}
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;
}
// 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);
else
slotOffset += (reverse ? -1 : 1) * (TexMan[item->Icon]->GetScaledHeight() + 2);
}
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), inventoryItem(NULL), interpolationSpeed(0), drawValue(0)
{
}
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;
fixed_t value = drawValue;
if(border != 0)
{
value = FRACUNIT - value; //invert since the new drawing method requires drawing the bg on the fg.
//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, 0, 0, 0, 0, true);
}
// {cx, cy, cr, cb}
fixed_t clip[4] = {0, 0, 0, 0};
fixed_t sizeOfImage = (horizontal ? fg->GetScaledWidth()-border*2 : fg->GetScaledHeight()-border*2)<<FRACBITS;
clip[(!horizontal)|((horizontal ? !reverse : reverse)<<1)] = sizeOfImage - FixedMul(sizeOfImage, value);
// Draw background
if(border != 0)
{
for(unsigned int i = 0;i < 4;i++)
clip[i] += border<<FRACBITS;
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[0], clip[1], clip[2], clip[3]);
else
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, clip[0], clip[1], clip[2], clip[3], true);
}
else
statusBar->DrawGraphic(fg, this->x, this->y, block->XOffset(), block->YOffset(), block->Alpha(), block->FullScreenOffsets(), false, false, 0, false, -1, -1, clip[0], clip[1], clip[2], clip[3]);
}
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;
if(sc.CheckToken(TK_Identifier)) //comparing reference
{
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of inventory
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
inventoryItem = RUNTIME_CLASS(AInventory);
}
}
}
else if(sc.Compare("armor"))
{
type = ARMOR;
if(sc.CheckToken(TK_Identifier))
{
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem)) //must be a kind of inventory
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
inventoryItem = RUNTIME_CLASS(AInventory);
}
}
}
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
{
sc.MustGetToken(TK_Identifier);
type = AMMO;
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(inventoryItem)) //must be a kind of ammo
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
inventoryItem = RUNTIME_CLASS(AAmmo);
}
}
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("poweruptime"))
{
type = POWERUPTIME;
sc.MustGetToken(TK_Identifier);
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(APowerupGiver)->IsAncestorOf(inventoryItem))
{
sc.ScriptMessage("'%s' is not a type of PowerupGiver.", sc.String);
inventoryItem = RUNTIME_CLASS(APowerupGiver);
}
}
else
{
type = INVENTORY;
inventoryItem = PClass::FindClass(sc.String);
if(inventoryItem == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(inventoryItem))
{
sc.ScriptMessage("'%s' is not a type of inventory item.", sc.String);
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)
{
fixed_t value = 0;
int max = 0;
switch(type)
{
case HEALTH:
value = statusBar->CPlayer->mo->health;
if(value < 0) //health shouldn't display negatives
value = 0;
if(inventoryItem != NULL)
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(inventoryItem); //max comparer
if(item != NULL)
max = item->Amount;
else
max = 0;
}
else //default to the class's health
max = statusBar->CPlayer->mo->GetMaxHealth() + statusBar->CPlayer->stamina;
break;
case ARMOR:
value = statusBar->armor != NULL ? statusBar->armor->Amount : 0;
if(inventoryItem != NULL)
{
AInventory *item = statusBar->CPlayer->mo->FindInventory(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(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(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:
{
//Get the PowerupType and check to see if the player has any in inventory.
APowerupGiver *powerupGiver = (APowerupGiver*) GetDefaultByType(inventoryItem);
const PClass *powerupType = powerupGiver->PowerupType;
APowerup *powerup = (APowerup*) statusBar->CPlayer->mo->FindInventory(powerupType);
if(powerup != NULL && powerupType != NULL && powerupGiver != NULL)
{
value = powerup->EffectTics + 1;
if(powerupGiver->EffectTics == 0) //if 0 we need to get the default from the powerup
max = ((APowerup*) GetDefaultByType(powerupType))->EffectTics + 1;
else
max = powerupGiver->EffectTics + 1;
}
break;
}
default: return;
}
if(max != 0 && value > 0)
{
value = (value << FRACBITS) / max;
if(value > FRACUNIT)
value = FRACUNIT;
}
else
value = 0;
if(interpolationSpeed != 0 && (!hudChanged || level.time == 1))
{
if(value < drawValue)
drawValue -= clamp<fixed_t>((drawValue - value) >> 2, 1, FixedDiv(interpolationSpeed<<FRACBITS, FRACUNIT*100));
else if(drawValue < value)
drawValue += clamp<fixed_t>((value - drawValue) >> 2, 1, FixedDiv(interpolationSpeed<<FRACBITS, FRACUNIT*100));
}
else
drawValue = value;
}
protected:
enum ValueType
{
HEALTH,
ARMOR,
AMMO1,
AMMO2,
AMMO,
FRAGS,
INVENTORY,
KILLS,
ITEMS,
SECRETS,
ARMORCLASS,
POWERUPTIME,
AIRTIME
};
unsigned int border;
bool horizontal;
bool reverse;
int foreground;
int background;
ValueType type;
const PClass *inventoryItem;
SBarInfoCoordinate x;
SBarInfoCoordinate y;
int interpolationSpeed;
fixed_t drawValue;
};
////////////////////////////////////////////////////////////////////////////////
class CommandIsSelected : public SBarInfoCommandFlowControl
{
public:
CommandIsSelected(SBarInfo *script) : SBarInfoCommandFlowControl(script),
negate(false)
{
weapon[0] = NULL;
weapon[1] = NULL;
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
//Using StringConst instead of Identifieres is deperecated!
if(sc.CheckToken(TK_Identifier))
{
if(sc.Compare("not"))
{
negate = true;
if(!sc.CheckToken(TK_StringConst))
sc.MustGetToken(TK_Identifier);
}
}
else
sc.MustGetToken(TK_StringConst);
for(int i = 0;i < 2;i++)
{
weapon[i] = PClass::FindClass(sc.String);
if(weapon[i] == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon[i]))
{
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;
}
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
if(statusBar->CPlayer->ReadyWeapon != NULL)
{
const PClass *readyWeapon = statusBar->CPlayer->ReadyWeapon->GetClass();
SetTruth(((weapon[1] != NULL) &&
((negate && (weapon[0] != readyWeapon && weapon[1] != readyWeapon)) ||
(!negate && (weapon[0] == readyWeapon || weapon[1] == readyWeapon)))) ||
((weapon[1] == NULL) &&
((!negate && weapon[0] == readyWeapon) || (negate && weapon[0] != readyWeapon))), block, statusBar);
}
}
protected:
bool negate;
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->Meta.GetMetaString(APMETA_DisplayName)) == 0)
{
foundClass = true;
classes.Push(PlayerClasses[c].Type->ClassIndex);
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
int spawnClass = statusBar->CPlayer->cls->ClassIndex;
for(unsigned int i = 0;i < classes.Size();i++)
{
if(classes[i] == spawnClass)
{
SetTruth(true, block, statusBar);
return;
}
}
SetTruth(false, block, statusBar);
}
protected:
TArray<int> classes;
};
////////////////////////////////////////////////////////////////////////////////
class CommandHasWeaponPiece : public SBarInfoCommandFlowControl
{
public:
CommandHasWeaponPiece(SBarInfo *script) : SBarInfoCommandFlowControl(script),
weapon(NULL), piece(1)
{
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_Identifier);
weapon = PClass::FindClass(sc.String);
if(weapon == NULL || !RUNTIME_CLASS(AWeapon)->IsAncestorOf(weapon)) //must be a weapon
{
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)
{
if(inv->IsKindOf(RUNTIME_CLASS(AWeaponHolder)))
{
AWeaponHolder *hold = static_cast<AWeaponHolder*>(inv);
if(hold->PieceWeapon == weapon)
{
if(hold->PieceMask & (1 << (piece-1)))
SetTruth(true, block, statusBar);
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->GetWidth();
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->Amount : statusBar->CPlayer->mo->health;
int max = armor ? 100 : statusBar->CPlayer->mo->GetMaxHealth() + statusBar->CPlayer->stamina;
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 SBarInfoCommandFlowControl
{
public:
CommandWeaponAmmo(SBarInfo *script) : SBarInfoCommandFlowControl(script),
conditionAnd(false), negate(false)
{
ammo[0] = NULL;
ammo[1] = NULL;
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("not"))
{
negate = true;
sc.MustGetToken(TK_Identifier);
}
for(int i = 0;i < 2;i++)
{
ammo[i] = PClass::FindClass(sc.String);
if(ammo[i] == NULL || !RUNTIME_CLASS(AAmmo)->IsAncestorOf(ammo[i])) //must be a kind of ammo
{
sc.ScriptMessage("'%s' is not a type of ammo.", sc.String);
ammo[i] = RUNTIME_CLASS(AAmmo);
}
if(sc.CheckToken(TK_OrOr))
{
conditionAnd = false;
sc.MustGetToken(TK_Identifier);
}
else if(sc.CheckToken(TK_AndAnd))
{
conditionAnd = true;
sc.MustGetToken(TK_Identifier);
}
else
break;
}
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
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(negate && !usesammo1 && !usesammo2) //if the weapon doesn't use ammo don't go though the trouble.
{
SetTruth(true, block, statusBar);
return;
}
//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)))
{
if(!negate)
{
SetTruth(true, block, statusBar);
return;
}
}
else if(negate)
{
SetTruth(true, block, statusBar);
return;
}
}
else //Every thing here could probably be one long if statement but then it would be more confusing.
{
if((usesammo1 && (AmmoType1 == ammo[0])) || (usesammo2 && (AmmoType2 == ammo[0])))
{
if(!negate)
{
SetTruth(true, block, statusBar);
return;
}
}
else if(negate)
{
SetTruth(true, block, statusBar);
return;
}
}
}
SetTruth(false, block, statusBar);
}
protected:
bool conditionAnd;
bool negate;
const PClass *ammo[2];
};
////////////////////////////////////////////////////////////////////////////////
class CommandInInventory : public SBarInfoCommandFlowControl
{
public:
CommandInInventory(SBarInfo *script) : SBarInfoCommandFlowControl(script),
conditionAnd(false), negate(false)
{
item[0] = item[1] = NULL;
amount[0] = amount[1] = 0;
}
void Parse(FScanner &sc, bool fullScreenOffsets)
{
sc.MustGetToken(TK_Identifier);
if(sc.Compare("not"))
{
negate = true;
sc.MustGetToken(TK_Identifier);
}
for(int i = 0;i < 2;i++)
{
item[i] = PClass::FindClass(sc.String);
if(item[i] == NULL || !RUNTIME_CLASS(AInventory)->IsAncestorOf(item[i]))
{
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;
sc.MustGetToken(TK_Identifier);
}
else if(sc.CheckToken(TK_AndAnd))
{
conditionAnd = true;
sc.MustGetToken(TK_Identifier);
}
else
break;
}
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
{
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
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(invItem[1] != NULL && conditionAnd)
{
if((invItem[0] != NULL && invItem[1] != NULL) && !negate)
{
SetTruth(true, block, statusBar);
return;
}
else if((invItem[0] == NULL || invItem[1] == NULL) && negate)
{
SetTruth(true, block, statusBar);
return;
}
}
else if(invItem[1] != NULL && !conditionAnd)
{
if((invItem[0] != NULL || invItem[1] != NULL) && !negate)
{
SetTruth(true, block, statusBar);
return;
}
else if((invItem[0] == NULL && invItem[1] == NULL) && negate)
{
SetTruth(true, block, statusBar);
return;
}
}
else if((invItem[0] != NULL) && !negate)
{
SetTruth(true, block, statusBar);
return;
}
else if((invItem[0] == NULL) && negate)
{
SetTruth(true, block, statusBar);
return;
}
SetTruth(false, block, statusBar);
}
protected:
bool conditionAnd;
bool negate;
const PClass *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 = fixed_t(FRACUNIT * sc.Float);
// We don't want to allow all the options of a regular main block
// so skip to the SBarInfoCommandFlowControl.
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
}
};
////////////////////////////////////////////////////////////////////////////////
static const char *SBarInfoCommandNames[] =
{
"drawimage", "drawnumber", "drawswitchableimage",
"drawmugshot", "drawselectedinventory",
"drawinventorybar", "drawbar", "drawgem",
"drawshader", "drawstring", "drawkeybar",
"gamemode", "playerclass", "aspectratio",
"isselected", "usesammo", "usessecondaryammo",
"hasweaponpiece", "inventorybarnotvisible",
"weaponammo", "ininventory", "alpha",
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_ASPECTRATIO,
SBARINFO_ISSELECTED, SBARINFO_USESAMMO, SBARINFO_USESSECONDARYAMMO,
SBARINFO_HASWEAPONPIECE, SBARINFO_INVENTORYBARNOTVISIBLE,
SBARINFO_WEAPONAMMO, SBARINFO_ININVENTORY, SBARINFO_ALPHA,
};
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 reinterpret_cast<CommandDrawImage *> (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_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);
}
sc.ScriptError("Unknown command '%s'.\n", sc.String);
return NULL;
}
sc.MustGetToken('}');
return NULL;
}