qzdoom/src/g_strife/strife_sbar.cpp
Randy Heit 6e83d231fe - The co-op summary screen now has a totals row at the bottom (if it fits).
- Changed WI_drawPercent() when wi_percents is false so that the total
  display is optional, and it formats it like Heretic's intermission, with
  a slash and a fixed-width right column.
- Font is no longer a property of the screen object. Pass the font to
  DrawText and DrawChar directly instead.
- Doom's intermission characters are now collected together as a font
  so they can be colorized.


SVN r1294 (trunk)
2008-11-27 17:43:36 +00:00

854 lines
19 KiB
C++

#include "doomtype.h"
#include "doomstat.h"
#include "v_font.h"
#include "v_video.h"
#include "sbar.h"
#include "r_defs.h"
#include "w_wad.h"
#include "m_random.h"
#include "d_player.h"
#include "st_stuff.h"
#include "r_local.h"
#include "m_swap.h"
#include "templates.h"
#include "a_keys.h"
#include "a_strifeglobal.h"
#include "gi.h"
#include "g_level.h"
#include "colormatcher.h"
#include "v_palette.h"
// Number of tics to move the popscreen up and down.
#define POP_TIME (TICRATE/8)
// Popscreen height when fully extended
#define POP_HEIGHT 104
// Number of tics to scroll keys left
#define KEY_TIME (TICRATE/3)
class FHealthBar : public FTexture
{
public:
FHealthBar ();
const BYTE *GetColumn (unsigned int column, const Span **spans_out);
const BYTE *GetPixels ();
bool CheckModified ();
void Unload ();
void SetVial (int level);
protected:
BYTE Pixels[200*2];
BYTE Colors[8];
static const Span DummySpan[2];
int VialLevel;
bool NeedRefresh;
void MakeTexture ();
void FillBar (int min, int max, BYTE light, BYTE dark);
};
const FTexture::Span FHealthBar::DummySpan[2] = { { 0, 2 }, { 0, 0 } };
FHealthBar::FHealthBar ()
: VialLevel(0), NeedRefresh(false)
{
int i;
static const BYTE rgbs[8*3] =
{
180, 228, 128, // light green
128, 180, 80, // dark green
196, 204, 252, // light blue
148, 152, 200, // dark blue
224, 188, 0, // light gold
208, 128, 0, // dark gold
216, 44, 44, // light red
172, 28, 28 // dark red
};
Width = 200;
Height = 2;
WidthBits = 8;
HeightBits = 1;
WidthMask = 255;
for (i = 0; i < 8; ++i)
{
Colors[i] = ColorMatcher.Pick (rgbs[i*3], rgbs[i*3+1], rgbs[i*3+2]);
}
}
bool FHealthBar::CheckModified ()
{
return NeedRefresh;
}
void FHealthBar::Unload ()
{
}
const BYTE *FHealthBar::GetColumn (unsigned int column, const Span **spans_out)
{
if (NeedRefresh)
{
MakeTexture ();
}
if (column > 199)
{
column = 199;
}
if (spans_out != NULL)
{
*spans_out = &DummySpan[column >= (unsigned int)VialLevel*2];
}
return Pixels + column*2;
}
const BYTE *FHealthBar::GetPixels ()
{
if (NeedRefresh)
{
MakeTexture ();
}
return Pixels;
}
void FHealthBar::SetVial (int level)
{
if (level < 0)
{
level = 0;
}
else if (level > 200 && level != 999)
{
level = 200;
}
if (VialLevel != level)
{
VialLevel = level;
NeedRefresh = true;
}
}
void FHealthBar::MakeTexture ()
{
if (VialLevel == 999)
{
FillBar (0, 100, Colors[4], Colors[5]);
}
else
{
if (VialLevel <= 100)
{
if (VialLevel <= 10)
{
FillBar (0, VialLevel, Colors[6], Colors[7]);
}
else if (VialLevel <= 20)
{
FillBar (0, VialLevel, Colors[4], Colors[5]);
}
else
{
FillBar (0, VialLevel, Colors[0], Colors[1]);
}
FillBar (VialLevel, 100, 0, 0);
}
else
{
int stop = 100 - (VialLevel - 100);
FillBar (0, stop, Colors[0], Colors[1]);
FillBar (stop, 100, Colors[2], Colors[3]);
}
}
}
void FHealthBar::FillBar (int min, int max, BYTE light, BYTE dark)
{
#ifdef WORDS_BIGENDIAN
SDWORD fill = (light << 24) | (dark << 16) | (light << 8) | dark;
#else
SDWORD fill = light | (dark << 8) | (light << 16) | (dark << 24);
#endif
if (max > min)
{
clearbuf (&Pixels[min*4], max - min, fill);
}
}
class DStrifeStatusBar : public DBaseStatusBar
{
DECLARE_CLASS(DStrifeStatusBar, DBaseStatusBar)
public:
DStrifeStatusBar () : DBaseStatusBar (32)
{
static const char *sharedLumpNames[] =
{
NULL, NULL, "INVFONY0", "INVFONY1", "INVFONY2",
"INVFONY3", "INVFONY4", "INVFONY5", "INVFONY6", "INVFONY7",
"INVFONY8", "INVFONY9", NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, "INVFONG0", "INVFONG1",
"INVFONG2", "INVFONG3", "INVFONG4", "INVFONG5", "INVFONG6",
"INVFONG7", "INVFONG8", "INVFONG9"
};
DBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
DoCommonInit ();
}
~DStrifeStatusBar ()
{
}
void NewGame ()
{
Images.Uninit ();
DoCommonInit ();
if (CPlayer != NULL)
{
AttachToPlayer (CPlayer);
}
}
void Draw (EHudState state)
{
DBaseStatusBar::Draw (state);
if (state == HUD_Fullscreen)
{
SB_state = screen->GetPageCount ();
DrawFullScreenStuff ();
}
else if (state == HUD_StatusBar)
{
if (SB_state != 0)
{
SB_state--;
}
DrawMainBar ();
}
}
void ShowPop (int popnum)
{
DBaseStatusBar::ShowPop(popnum);
if (popnum == CurrentPop)
{
if (popnum == POP_Keys)
{
AInventory *item;
int i;
KeyPopPos += 10;
KeyPopScroll = 280;
for (item = CPlayer->mo->Inventory, i = 0;
item != NULL;
item = item->Inventory)
{
if (item->IsKindOf (RUNTIME_CLASS(AKey)))
{
if (i == KeyPopPos)
{
return;
}
i++;
}
}
}
PendingPop = POP_None;
// Do not scroll keys horizontally when dropping the popscreen
KeyPopScroll = 0;
KeyPopPos -= 10;
}
else
{
KeyPopPos = 0;
PendingPop = popnum;
}
}
bool MustDrawLog(EHudState state)
{
// Tell the base class to draw the log if the pop screen won't be displayed.
return (state == HUD_None);
}
private:
void DoCommonInit ()
{
static const char *strifeLumpNames[] =
{
"INVCURS", "CURSOR01", "INVBACK", "INVTOP", "INVPOP", "INVPOP2",
"INVPBAK", "INVPBAK2",
"INVFONG0", "INVFONG1", "INVFONG2", "INVFONG3", "INVFONG4",
"INVFONG5", "INVFONG6", "INVFONG7", "INVFONG8", "INVFONG9",
"INVFONG%",
"INVFONY0", "INVFONY1", "INVFONY2", "INVFONY3", "INVFONY4",
"INVFONY5", "INVFONY6", "INVFONY7", "INVFONY8", "INVFONY9",
"INVFONY%",
"I_COMM", "I_MDKT", "I_ARM1", "I_ARM2"
};
Images.Init (strifeLumpNames, NUM_STRIFESB_IMAGES);
CursorImage = Images[imgINVCURS] != NULL ? imgINVCURS : imgCURSOR01;
SB_state = screen->GetPageCount ();
CurrentPop = POP_None;
PendingPop = POP_NoChange;
PopHeight = 0;
KeyPopPos = 0;
KeyPopScroll = 0;
ItemFlash = 0;
}
void Tick ()
{
DBaseStatusBar::Tick ();
if (ItemFlash > 0)
{
ItemFlash -= FRACUNIT/14;
if (ItemFlash < 0)
{
ItemFlash = 0;
}
}
PopHeightChange = 0;
if (PendingPop != POP_NoChange)
{
if (PopHeight < 0)
{
PopHeightChange = POP_HEIGHT / POP_TIME;
PopHeight += POP_HEIGHT / POP_TIME;
}
else
{
CurrentPop = PendingPop;
PendingPop = POP_NoChange;
}
}
else
{
if (CurrentPop == POP_None)
{
PopHeight = 0;
}
else if (PopHeight > -POP_HEIGHT)
{
PopHeight -= POP_HEIGHT / POP_TIME;
if (PopHeight < -POP_HEIGHT)
{
PopHeight = -POP_HEIGHT;
}
else
{
PopHeightChange = -POP_HEIGHT / POP_TIME;
}
}
if (KeyPopScroll > 0)
{
KeyPopScroll -= 280 / KEY_TIME;
if (KeyPopScroll < 0)
{
KeyPopScroll = 0;
}
}
}
}
void FlashItem (const PClass *itemtype)
{
ItemFlash = FRACUNIT*3/4;
}
void DrawMainBar ()
{
AInventory *item;
int i;
// Pop screen (log, keys, and status)
if (CurrentPop != POP_None && PopHeight < 0)
{
DrawPopScreen (Scaled ? (ST_Y - 8) * screen->GetHeight() / 200 : ST_Y - 8);
}
DrawImage (Images[imgINVBACK], 0, 0);
DrawImage (Images[imgINVTOP], 0, -8);
// Health
DrINumber (CPlayer->health, 79, -6, imgFONG0);
if (CPlayer->cheats & CF_GODMODE)
{
HealthBar.SetVial (999);
}
else
{
HealthBar.SetVial (CPlayer->health);
}
DrawImage (&HealthBar, 49, 4);
DrawImage (&HealthBar, 49, 7);
// Armor
item = CPlayer->mo->FindInventory<ABasicArmor>();
if (item != NULL && item->Amount > 0)
{
DrawImage (TexMan(item->Icon), 2, 9);
DrINumber (item->Amount, 27, 23, imgFONY0);
}
// Ammo
AAmmo *ammo1, *ammo2;
int ammocount1, ammocount2;
GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2);
if (ammo1 != NULL)
{
DrINumber (ammo1->Amount, 311, -6, imgFONG0);
DrawImage (TexMan(ammo1->Icon), 290, 13);
if (ammo2 != NULL)
{
/* int y = MIN (-5 - BigHeight, -5 - TexMan(ammo1->Icon)->GetHeight());
screen->DrawTexture (TexMan(ammo2->Icon), -14, y,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
TAG_DONE);
DrBNumberOuterFont (ammo2->Amount, -67, y - BigHeight);
*/ }
}
// Sigil
item = CPlayer->mo->FindInventory<ASigil>();
if (item != NULL)
{
DrawImage (TexMan(item->Icon), 253, 7);
}
// Inventory
CPlayer->mo->InvFirst = ValidateInvFirst (6);
for (item = CPlayer->mo->InvFirst, i = 0; item != NULL && i < 6; item = item->NextInv(), ++i)
{
if (item == CPlayer->mo->InvSel)
{
screen->DrawTexture (Images[CursorImage],
42 + 35*i + ST_X, 12 + ST_Y,
DTA_Bottom320x200, Scaled,
DTA_Alpha, FRACUNIT - ItemFlash,
TAG_DONE);
}
if (item->Icon.isValid())
{
DrawDimImage (TexMan(item->Icon), 48 + 35*i, 14, item->Amount <= 0);
}
DrINumber (item->Amount, 74 + 35*i, 23, imgFONY0);
}
}
void DrawFullScreenStuff ()
{
// Draw health
DrINumberOuter (CPlayer->health, 4, -10, false, 7);
screen->DrawTexture (Images[imgMEDI], 14, -17,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
TAG_DONE);
// Draw armor
ABasicArmor *armor = CPlayer->mo->FindInventory<ABasicArmor>();
if (armor != NULL && armor->Amount != 0)
{
DrINumberOuter (armor->Amount, 35, -10, false, 7);
screen->DrawTexture (TexMan(armor->Icon), 45, -17,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
TAG_DONE);
}
// Draw ammo
AAmmo *ammo1, *ammo2;
int ammocount1, ammocount2;
GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2);
if (ammo1 != NULL)
{
// Draw primary ammo in the bottom-right corner
DrINumberOuter (ammo1->Amount, -23, -10, false, 7);
screen->DrawTexture (TexMan(ammo1->Icon), -14, -17,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
TAG_DONE);
if (ammo2 != NULL && ammo1!=ammo2)
{
// Draw secondary ammo just above the primary ammo
DrINumberOuter (ammo2->Amount, -23, -48, false, 7);
screen->DrawTexture (TexMan(ammo2->Icon), -14, -55,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
TAG_DONE);
}
}
if (deathmatch)
{ // Draw frags (in DM)
DrBNumberOuterFont (CPlayer->fragcount, -44, 1);
}
// Draw inventory
if (CPlayer->inventorytics == 0)
{
if (CPlayer->mo->InvSel != 0)
{
if (ItemFlash > 0)
{
FTexture *cursor = Images[CursorImage];
screen->DrawTexture (cursor, -28, -15,
DTA_HUDRules, HUD_Normal,
DTA_LeftOffset, cursor->GetWidth(),
DTA_TopOffset, cursor->GetHeight(),
DTA_Alpha, ItemFlash,
TAG_DONE);
}
DrINumberOuter (CPlayer->mo->InvSel->Amount, -51, -10, false, 7);
screen->DrawTexture (TexMan(CPlayer->mo->InvSel->Icon), -42, -17,
DTA_HUDRules, HUD_Normal,
DTA_CenterBottomOffset, true,
DTA_ColorOverlay, CPlayer->mo->InvSel->Amount > 0 ? 0 : DIM_OVERLAY,
TAG_DONE);
}
}
else
{
CPlayer->mo->InvFirst = ValidateInvFirst (6);
int i = 0;
AInventory *item;
if (CPlayer->mo->InvFirst != NULL)
{
for (item = CPlayer->mo->InvFirst; item != NULL && i < 6; item = item->NextInv(), ++i)
{
if (item == CPlayer->mo->InvSel)
{
screen->DrawTexture (Images[CursorImage], -100+i*35, -21,
DTA_HUDRules, HUD_HorizCenter,
DTA_Alpha, TRANSLUC75,
TAG_DONE);
}
if (item->Icon.isValid())
{
screen->DrawTexture (TexMan(item->Icon), -94 + i*35, -19,
DTA_HUDRules, HUD_HorizCenter,
DTA_ColorOverlay, CPlayer->mo->InvSel->Amount > 0 ? 0 : DIM_OVERLAY,
TAG_DONE);
}
DrINumberOuter (item->Amount, -89 + i*35, -10, true, 7);
}
}
}
// Draw pop screen (log, keys, and status)
if (CurrentPop != POP_None && PopHeight < 0)
{
DrawPopScreen (screen->GetHeight());
}
}
void DrawPopScreen (int bottom)
{
char buff[64];
const char *label;
int i;
AInventory *item;
int xscale, yscale, left, top;
int bars = (CurrentPop == POP_Status) ? imgINVPOP : imgINVPOP2;
int back = (CurrentPop == POP_Status) ? imgINVPBAK : imgINVPBAK2;
// Extrapolate the height of the popscreen for smoother movement
int height = clamp<int> (PopHeight + FixedMul (r_TicFrac, PopHeightChange), -POP_HEIGHT, 0);
xscale = CleanXfac;
yscale = CleanYfac;
left = screen->GetWidth()/2 - 160*CleanXfac;
top = bottom + height * yscale;
screen->DrawTexture (Images[back], left, top, DTA_CleanNoMove, true, DTA_Alpha, FRACUNIT*3/4, TAG_DONE);
screen->DrawTexture (Images[bars], left, top, DTA_CleanNoMove, true, TAG_DONE);
switch (CurrentPop)
{
case POP_Log:
// Draw the latest log message.
mysnprintf (buff, countof(buff), "%02d:%02d:%02d",
(level.time/TICRATE)/3600,
((level.time/TICRATE)%3600)/60,
(level.time/TICRATE)%60);
screen->DrawText (SmallFont2, CR_UNTRANSLATED, left+210*xscale, top+8*yscale, buff,
DTA_CleanNoMove, true, TAG_DONE);
if (CPlayer->LogText != NULL)
{
FBrokenLines *lines = V_BreakLines (SmallFont2, 272, CPlayer->LogText);
for (i = 0; lines[i].Width >= 0; ++i)
{
screen->DrawText (SmallFont2, CR_UNTRANSLATED, left+24*xscale, top+(18+i*12)*yscale,
lines[i].Text, DTA_CleanNoMove, true, TAG_DONE);
}
V_FreeBrokenLines (lines);
}
break;
case POP_Keys:
// List the keys the player has.
int pos, endpos, leftcol;
int clipleft, clipright;
pos = KeyPopPos;
endpos = pos + 10;
leftcol = 20;
clipleft = left + 17*xscale;
clipright = left + (320-17)*xscale;
if (KeyPopScroll > 0)
{
// Extrapolate the scroll position for smoother scrolling
int scroll = MAX<int> (0,KeyPopScroll - FixedMul (r_TicFrac, 280/KEY_TIME));
pos -= 10;
leftcol = leftcol - 280 + scroll;
}
for (i = 0, item = CPlayer->mo->Inventory;
i < endpos && item != NULL;
item = item->Inventory)
{
if (!item->IsKindOf (RUNTIME_CLASS(AKey)))
continue;
if (i < pos)
{
i++;
continue;
}
label = item->GetClass()->Meta.GetMetaString (AMETA_StrifeName);
if (label == NULL)
{
label = item->GetClass()->TypeName.GetChars();
}
int colnum = ((i-pos) / 5) & (KeyPopScroll > 0 ? 3 : 1);
int rownum = (i % 5) * 18;
screen->DrawTexture (TexMan(item->Icon),
left + (colnum * 140 + leftcol)*xscale,
top + (6 + rownum)*yscale,
DTA_CleanNoMove, true,
DTA_ClipLeft, clipleft,
DTA_ClipRight, clipright,
TAG_DONE);
screen->DrawText (SmallFont2, CR_UNTRANSLATED,
left + (colnum * 140 + leftcol + 17)*xscale,
top + (11 + rownum)*yscale,
label,
DTA_CleanNoMove, true,
DTA_ClipLeft, clipleft,
DTA_ClipRight, clipright,
TAG_DONE);
i++;
}
break;
case POP_Status:
// Show miscellaneous status items.
// Print stats
DrINumber2 (CPlayer->accuracy, left+268*xscale, top+28*yscale, 7*xscale, imgFONY0);
DrINumber2 (CPlayer->stamina, left+268*xscale, top+52*yscale, 7*xscale, imgFONY0);
// How many keys does the player have?
for (i = 0, item = CPlayer->mo->Inventory;
item != NULL;
item = item->Inventory)
{
if (item->IsKindOf (RUNTIME_CLASS(AKey)))
{
i++;
}
}
DrINumber2 (i, left+268*xscale, top+76*yscale, 7*xscale, imgFONY0);
// Does the player have a communicator?
item = CPlayer->mo->FindInventory (NAME_Communicator);
if (item != NULL)
{
screen->DrawTexture (TexMan(item->Icon),
left + 280*xscale,
top + 74*yscale,
DTA_CleanNoMove, true, TAG_DONE);
}
// How much ammo does the player have?
static const struct
{
ENamedName AmmoType;
int Y;
} AmmoList[7] =
{
{ NAME_ClipOfBullets, 19 },
{ NAME_PoisonBolts, 35 },
{ NAME_ElectricBolts, 43 },
{ NAME_HEGrenadeRounds, 59 },
{ NAME_PhosphorusGrenadeRounds, 67 },
{ NAME_MiniMissiles, 75 },
{ NAME_EnergyPod, 83 }
};
for (i = 0; i < 7; ++i)
{
const PClass *ammotype = PClass::FindClass(AmmoList[i].AmmoType);
item = CPlayer->mo->FindInventory (ammotype);
if (item == NULL)
{
DrINumber2 (0, left+206*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
DrINumber2 (((AInventory *)GetDefaultByType (ammotype))->MaxAmount,
left+239*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
}
else
{
DrINumber2 (item->Amount, left+206*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
DrINumber2 (item->MaxAmount, left+239*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
}
}
// What weapons does the player have?
static const struct
{
ENamedName TypeName;
int X, Y;
} WeaponList[6] =
{
{ NAME_StrifeCrossbow, 23, 19 },
{ NAME_AssaultGun, 21, 41 },
{ NAME_FlameThrower, 57, 50 },
{ NAME_MiniMissileLauncher, 20, 64 },
{ NAME_StrifeGrenadeLauncher, 55, 20 },
{ NAME_Mauler, 52, 75 },
};
for (i = 0; i < 6; ++i)
{
item = CPlayer->mo->FindInventory (WeaponList[i].TypeName);
if (item != NULL)
{
screen->DrawTexture (TexMan(item->Icon),
left + WeaponList[i].X*xscale,
top + WeaponList[i].Y*yscale,
DTA_CleanNoMove, true,
DTA_LeftOffset, 0,
DTA_TopOffset, 0,
TAG_DONE);
}
}
break;
}
}
void DrINumber (signed int val, int x, int y, int imgBase) const
{
x -= 7;
if (val == 0)
{
DrawImage (Images[imgBase], x, y);
}
else
{
while (val != 0)
{
DrawImage (Images[imgBase+val%10], x, y);
val /= 10;
x -= 7;
}
}
}
void DrINumber2 (signed int val, int x, int y, int width, int imgBase) const
{
x -= width;
if (val == 0)
{
screen->DrawTexture (Images[imgBase], x, y, DTA_CleanNoMove, true, TAG_DONE);
}
else
{
while (val != 0)
{
screen->DrawTexture (Images[imgBase+val%10], x, y, DTA_CleanNoMove, true, TAG_DONE);
val /= 10;
x -= width;
}
}
}
enum
{
imgINVCURS,
imgCURSOR01,
imgINVBACK,
imgINVTOP,
imgINVPOP,
imgINVPOP2,
imgINVPBAK,
imgINVPBAK2,
imgFONG0,
imgFONG1,
imgFONG2,
imgFONG3,
imgFONG4,
imgFONG5,
imgFONG6,
imgFONG7,
imgFONG8,
imgFONG9,
imgFONG_PERCENT,
imgFONY0,
imgFONY1,
imgFONY2,
imgFONY3,
imgFONY4,
imgFONY5,
imgFONY6,
imgFONY7,
imgFONY8,
imgFONY9,
imgFONY_PERCENT,
imgCOMM,
imgMEDI,
imgARM1,
imgARM2,
NUM_STRIFESB_IMAGES
};
FImageCollection Images;
FHealthBar HealthBar;
int CursorImage;
int CurrentPop, PendingPop, PopHeight, PopHeightChange;
int KeyPopPos, KeyPopScroll;
fixed_t ItemFlash;
};
IMPLEMENT_CLASS(DStrifeStatusBar);
DBaseStatusBar *CreateStrifeStatusBar ()
{
return new DStrifeStatusBar;
}