raze/wadsrc/static/zscript/games/exhumed/ui/sbar.zs
Mitchell Richters 42d791e7c2 - Exhumed: Fix clip calculation setup.
* Since game maintains its own clip capacities, we need to use them instead of the generic handler for the games that do not.
* Clean up the game-side code in `CheckClip()` to use `min()`.
* Repair issue with pistol clip calculation that was using modulo when it shouldn't. A full weapon with 300 will divide into 6 with no remainder, therefore nothing in the clip.
* Fixes #906.
2023-03-27 22:05:35 +11:00

881 lines
20 KiB
Text

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT
Copyright (C) 2020-2021 Christoph Oelckers
This file is part of Raze.
This is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//-------------------------------------------------------------------------
struct ChunkFrame // this wraps the internal (mis-)representation of the chunk data.
{
TextureID tex;
int x, y;
int flags;
native void GetChunkFrame(int nFrameBase);
}
class ExhumedStatusBar : RazeStatusBar
{
HUDFont textfont, numberFont;
int keyanims[4];
int airframe, lungframe;
int nSelectedItem;
int nHealthLevel;
int nMagicLevel;
int nMeterRange;
int nHurt;
int nHealthFrame;
int nMagicFrame;
int nItemAltSeq;
int nItemSeq;
int nItemFrames;
int nItemFrame;
int totalmoves;
int nCounter;
int nCounterDest;
int nDigit[3];
int ammodelay;
int nLastWeapon;
enum EConst
{
KeySeq = 36,
}
override int, int GetReservedScreenSpace(int viewsize)
{
return 0, 24;
}
override void Init()
{
textfont = HUDFont.Create(SmallFont, 1, Mono_Off, 1, 1);
numberFont = HUDFont.Create(BigFont, 0, Mono_Off, 1, 1);
let nPicNum = GetStatusSequencePic(0, 0);
let siz = TexMan.GetScaledSize(nPicNum);
nMeterRange = siz.Y;
Reset();
}
override void Reset()
{
airframe = lungframe = nHurt = nHealthFrame = nMagicFrame = nItemAltSeq = nItemFrames = nItemFrame = nCounter = 0;
nDigit[0] = nDigit[1] = nDigit[2] = 0;
nHealthLevel = -1;
nMagicLevel = -1;
nSelectedItem = -1;
nItemSeq = -1;
ammodelay = 3;
nLastWeapon = -1;
totalmoves = 0;
nCounterDest = 0;
}
//---------------------------------------------------------------------------
//
// draws a sequence animation to the status bar
//
//---------------------------------------------------------------------------
void DrawStatusSequence(int nSequence, int frameindex, double yoffset, double xoffset = 0, bool trueadjust = false)
{
int nFrameBase, nFrameSize;
[nFrameBase, nFrameSize] = Exhumed.GetStatusSequence(nSequence, frameindex);
for(; nFrameSize > 0; nFrameSize--, nFrameBase++)
{
int flags = 0;
ChunkFrame chunk;
chunk.GetChunkFrame(nFrameBase);
double x = chunk.x + xoffset;
double y = chunk.y + yoffset;
if (hud_size <= Hud_StbarOverlay)
{
x += 161;
y += 100;
}
else
{
if (x < 0)
{
x += 160;
flags |= DI_SCREEN_LEFT_BOTTOM;
}
else if (x > 0)
{
x -= 159; // graphics do not match up precisely.
flags |= DI_SCREEN_RIGHT_BOTTOM;
}
y -= 100;
}
if (chunk.flags & 3)
{
// This is hard to align with bad offsets, so skip that treatment for mirrored elements.
flags |= DI_ITEM_RELCENTER;
}
else
{
let tsiz = TexMan.GetScaledSize(chunk.tex);
if (trueadjust)
{
x -= tsiz.x * 0.5;
y -= tsiz.y * 0.5;
}
else
{
x -= int(tsiz.x) / 2;
y -= int(tsiz.y) / 2;
}
flags |= DI_ITEM_OFFSETS;
}
if (chunk.flags & 1)
flags |= DI_MIRROR;
if (chunk.flags & 2)
flags |= DI_MIRRORY;
DrawTexture(chunk.tex, (x, y), flags);
}
}
//---------------------------------------------------------------------------
//
// draws a sequence animation to the status bar
//
//---------------------------------------------------------------------------
TextureID GetStatusSequencePic(int nSequence, int frameindex)
{
int nFrameBase = Exhumed.GetStatusSequence(nSequence, frameindex);
ChunkFrame chunk;
chunk.GetChunkFrame(nFrameBase);
return chunk.tex;
}
String GetStatusSequenceName(int nSequence, int frameindex)
{
let texid = GetStatusSequencePic(nSequence, frameindex);
return TexMan.GetName(texid);
}
//---------------------------------------------------------------------------
//
// Frag display - very ugly and may have to be redone if multiplayer support gets added.
//
//---------------------------------------------------------------------------
void DrawMulti()
{
/*
char stringBuf[30];
if (netgame)
{
BeginHUD(1, false, 320, 200);
int shade;
if ((PlayClock / 120) & 1)
shade = -100;
else
shade = 127;
int nTile = 3593;
int xx = 320 / (nTotalPlayers + 1);
int x = xx - 160;
for (int i = 0; i < nTotalPlayers; i++)
{
DrawImage(String.Format("#%05d", nTile)), (x, 7), DI_ITEM_CENTER);
if (i != nLocalPlayer) {
shade = -100;
}
sprintf(stringBuf, "%d", nPlayerScore[i]);
DrawString(this, textfont, stringBuf, x, 0, DI_ITEM_TOP | DI_TEXT_ALIGN_CENTER, i != nLocalPlayer ? CR_UNTRANSLATED : CR_GOLD, 1, -1, 0, 1, 1);
x += xx;
nTile++;
}
}
*/
}
//==========================================================================
//
// Fullscreen HUD variant #1
//
//==========================================================================
int ItemTimer(ExhumedPlayer pPlayer, int num)
{
switch (num) {
case 1: //Scarab item
return (pPlayer.invincibility * 100) / 900;
case 3: //Hand item
return (pPlayer.nDouble * 100) / 1350;
case 5: //Mask
return (pPlayer.nMaskAmount * 100) / 1350;
case 4: //Invisible
return (pPlayer.nInvisible * 100) / 900;
case 2: //Torch
return (pPlayer.nTorch * 100) / 900;
}
return -1;
}
void DrawHUD2(ExhumedPlayer pp)
{
BeginHUD(1, false, 320, 200);
String format;
TextureID img;
double imgScale;
double baseScale = numberFont.mFont.GetHeight() * 0.75;
//
// Health
//
img = GetStatusSequencePic(125, 0);
let size = TexMan.GetScaledSize(img);
imgScale = baseScale / size.Y;
DrawTexture(img, (1.5, -1), DI_ITEM_LEFT_BOTTOM, scale:(imgScale, imgScale));
if (!althud_flashing || pp.nHealth > 150 || (PlayClock & 32))
{
int s = -8;
if (althud_flashing && pp.nHealth > 800)
s += Raze.bsin(PlayClock << 5) >> 10;
int intens = clamp(255 - 4 * s, 0, 255);
format = String.Format("%d", pp.nHealth >> 3);
DrawString(numberFont, format, (13, -numberFont.mFont.GetHeight() + 3), DI_TEXT_ALIGN_LEFT, Font.CR_UNTRANSLATED, intens / 255.);
}
//
// Air
//
if (pp.isUnderwater())
{
img = GetStatusSequencePic(133, airframe);
let size = TexMan.GetScaledSize(img);
imgScale = baseScale / size.Y;
DrawTexture(img, (-4, -22), DI_ITEM_RIGHT_BOTTOM, scale:(imgScale, imgScale));
}
//
// Magic
//
if (nItemSeq >= 0)
{
img = GetStatusSequencePic(nItemSeq, nItemFrame);
//int tile = GetStatusSequenceTile(nItemSeq, nItemFrame);
//int tile2 = tile;
//if (tile2 > 744 && tile2 < 751) tile2 = 744;
let size = TexMan.GetScaledSize(img);
imgScale = baseScale / size.Y;
DrawTexture(img, (70, -1), DI_ITEM_CENTER_BOTTOM, scale:(imgScale, imgScale));
format = String.Format("%d", pp.nMagic / 10);
int nItem = pp.nItem;
int timer = ItemTimer(pp, nItem);
if (timer > 0)
{
format.AppendFormat("/%d", timer);
}
DrawString(numberFont, format, (79.5, -numberFont.mFont.GetHeight() + 3), DI_TEXT_ALIGN_LEFT);
}
//
// Weapon
//
int weapon = pp.nCurrentWeapon;
int ammo = nCounterDest;
if (ammo > 0) // wicon > 0
{
if (weapon == kWeaponPistol && cl_showmagamt)
{
int clip = Exhumed.GetPistolClip();
format = String.Format("%d/%d", clip, ammo - clip);
}
else if (weapon == kWeaponM60 && cl_showmagamt)
{
int clip = Exhumed.GetPlayerClip();
format = String.Format("%d/%d", clip, ammo - clip);
}
else
{
format = String.Format("%d", ammo);
}
/* non-implemented weapon icon.
int wicon = 0;// ammo_sprites[weapon];
img = ammo_sprites[weapon];
imgScale = baseScale / img.GetDisplayHeight();
let imgX = 21.125;
let strlen = format.Len();
if (strlen > 1)
{
imgX += (imgX * 0.855) * (strlen - 1);
}
*/
if ((!althud_flashing || PlayClock & 32 || ammo > 10))// (DamageData[weapon].max_ammo / 10)))
{
DrawString(numberFont, format, (-3, -numberFont.mFont.GetHeight() + 3), DI_TEXT_ALIGN_RIGHT);
}
//DrawGraphic(img, (-imgX, -1), DI_ITEM_RIGHT_BOTTOM, scale:(imgScale, imgScale));
}
//
// keys
//
int nKeys = pp.keys;
int x = -164;
for (int i = 0; i < 4; i++)
{
if (nKeys & (0x1000 << i))
{
DrawImage(String.Format("KeyIcon%d", i+1), (x, -2), DI_ITEM_LEFT_BOTTOM);
}
x += 20;
}
}
//---------------------------------------------------------------------------
//
// draw the full status bar
//
//---------------------------------------------------------------------------
void DrawStatus(ExhumedPlayer pp)
{
if (hud_size <= Hud_StbarOverlay)
{
// draw the main bar itself
BeginStatusBar(false, 320, 200, 40);
if (hud_size == Hud_StbarOverlay) Set43ClipRect();
DrawImage("TileStatusBar", (160, 200), DI_ITEM_CENTER_BOTTOM);
screen.ClearClipRect();
}
else if (hud_size == Hud_Mini)
{
BeginHUD(1, false, 320, 200);
DrawImage("hud_l", (0, 0), DI_ITEM_LEFT_BOTTOM | DI_SCREEN_LEFT_BOTTOM);
DrawImage("hud_r", (0, 0), DI_ITEM_RIGHT_BOTTOM | DI_SCREEN_RIGHT_BOTTOM);
}
else if (hud_size == Hud_Full)
{
DrawHUD2(pp);
return;
}
for (int i = 0; i < 4; i++)
{
if (pp.keys & (4096 << i))
{
DrawStatusSequence(KeySeq + 2 * i, keyanims[i], 0.5, 0.5, true);
}
}
//if (/*!bFullScreen &&*/ nNetTime)
{
DrawStatusSequence(127, 0, 0);
DrawStatusSequence(129, nMagicFrame, nMagicLevel);
DrawStatusSequence(131, 0, 0); // magic pool frame (bottom)
DrawStatusSequence(128, 0, 0);
DrawStatusSequence(1, nHealthFrame, nHealthLevel);
DrawStatusSequence(125, 0, 0); // draw ankh on health pool
DrawStatusSequence(130, 0, 0); // draw health pool frame (top)
// Item on the magic pool
if (nItemSeq >= 0) DrawStatusSequence(nItemSeq, nItemFrame, 1);
// draw the blue air level meter when underwater
if (pp.isUnderwater()) DrawStatusSequence(133, airframe, 0, 0.5);
else DrawStatusSequence(132, lungframe, 0);
// item count dots.
if (nSelectedItem >= 0)
{
int count = pp.items[nSelectedItem];
DrawStatusSequence(156 + 2 * count, 0, 0);
}
// life dots.
DrawStatusSequence(145 + (2 * pp.nLives), 0, 0);
if (nHurt > 0) DrawStatusSequence(4, nHurt - 1, 0);
// draw compass
if (hud_size <= Hud_StbarOverlay) DrawStatusSequence(35, ((pp.GetAngle() + 128) & Raze.kAngleMask) >> 8, 0, 0.5, true);
//if (hud_size < Hud_Full)
{
// draw ammo count
DrawStatusSequence(44, nDigit[2], 0, 0.5, true);
DrawStatusSequence(45, nDigit[1], 0, 0.5, true);
DrawStatusSequence(46, nDigit[0], 0, 0.5, true);
}
}
DrawMulti();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void DoLevelStats(int bottomy, Summaryinfo summary)
{
StatsPrintInfo stats;
stats.fontscale = 1.;
stats.altspacing = stats.spacing = SmallFont.GetHeight();
stats.screenbottomspace = bottomy;
stats.letterColor = TEXTCOLOR_RED;
stats.standardColor = TEXTCOLOR_UNTRANSLATED;
int y = -1;
int mask = 1;
if (automapMode == am_full)
{
stats.statfont = Raze.PickSmallFont();
y = PrintAutomapInfo(stats, summary, true);
mask = 2;
}
if (hud_stats & mask)
{
stats.statfont = SmallFont;
stats.completeColor = TEXTCOLOR_DARKGREEN;
PrintLevelStats(stats, summary, y);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetItemSeq(ExhumedPlayer pp)
{
static const int nItemSeqOffset[] = { 91, 72, 76, 79, 68, 87, 83 };
static const int nItemMagic[] = { 500, 1000, 100, 500, 400, 200, 700, 0 };
int nItem = pp.nItem;
if (nItem < 0)
{
nItemSeq = -1;
return;
}
if (nItemMagic[nItem] <= pp.nMagic) nItemAltSeq = 0;
else nItemAltSeq = 2;
nItemFrame = 0;
nItemSeq = nItemSeqOffset[nItem] + nItemAltSeq;
nItemFrames = Exhumed.SizeofStatusSequence(nItemSeq);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetMagicFrame(ExhumedPlayer pp)
{
int magicperline = 1000 / nMeterRange;
int newMagicLevel = (1000 - pp.nMagic) / magicperline;
newMagicLevel = clamp(newMagicLevel, 0, nMeterRange - 1);
if (newMagicLevel != nMagicLevel || nItemFrames == 0) SetItemSeq(pp);
nMagicLevel = newMagicLevel;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetHealthFrame(ExhumedPlayer pp)
{
if (nHurt)
{
nHurt++;
if (nHurt > Exhumed.SizeofStatusSequence(4)) nHurt = 0;
}
int healthperline = 800 / nMeterRange;
int newHealthLevel = (800 - pp.nHealth) / healthperline;
newHealthLevel = clamp(newHealthLevel, 0, nMeterRange - 1);
if (newHealthLevel > nHealthLevel) nHurt = 1;
nHealthLevel = newHealthLevel;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetCounter(int nVal)
{
nCounterDest = clamp(nVal, 0, 999);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetCounterImmediate(int nVal)
{
SetCounter(nVal);
nCounter = nCounterDest;
SetCounterDigits();
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetCounterDigits()
{
nDigit[2] = 3 * (nCounter / 100 % 10);
nDigit[1] = 3 * (nCounter / 10 % 10);
nDigit[0] = 3 * (nCounter % 10);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void UpdateCounter(ExhumedPlayer pp)
{
int nWeapon = pp.nCurrentWeapon;
if (nWeapon < 0)
{
SetCounterImmediate(0);
}
else
{
int thiscount;
if (nWeapon >= kWeaponSword && nWeapon <= kWeaponRing)
thiscount = pp.nAmmo[nWeapon];
else
thiscount = 0;
if (nWeapon != nLastWeapon) SetCounterImmediate(thiscount);
else SetCounter(thiscount);
}
nLastWeapon = nWeapon;
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void MoveStatus(ExhumedPlayer pp)
{
if (nItemSeq >= 0)
{
nItemFrame++;
if (nItemFrame >= nItemFrames)
{
if (nItemSeq == 76) {
SetItemSeq(pp);
}
else
{
nItemSeq -= nItemAltSeq;
if (nItemAltSeq || (totalmoves & 0x1F))
{
if (nItemSeq < 2) {
nItemAltSeq = 0;
}
}
else
{
nItemAltSeq = 1;
}
nItemFrame = 0;
nItemSeq += nItemAltSeq;
nItemFrames = Exhumed.SizeofStatusSequence(nItemSeq);
}
}
}
nHealthFrame++;
if (nHealthFrame >= Exhumed.SizeofStatusSequence(1)) nHealthFrame = 0;
nMagicFrame++;
if (nMagicFrame >= Exhumed.SizeofStatusSequence(129)) nMagicFrame = 0;
if (nCounter == nCounterDest)
{
nCounter = nCounterDest;
ammodelay = 3;
return;
}
else
{
ammodelay--;
if (ammodelay > 0)
return;
}
int eax = nCounterDest - nCounter;
if (eax <= 0)
{
if (eax >= -30)
{
for (int i = 0; i < 3; i++)
{
nDigit[i]--;
if (nDigit[i] < 0)
{
nDigit[i] += 30;
}
if (nDigit[i] < 27) {
break;
}
}
}
else
{
nCounter += (nCounterDest - nCounter) >> 1;
SetCounterDigits();
return;
}
}
else
{
if (eax <= 30)
{
for (int i = 0; i < 3; i++)
{
nDigit[i]++;
if (nDigit[i] <= 27)
break;
if (nDigit[i] >= 30)
nDigit[i] -= 30;
}
}
else
{
nCounter += (nCounterDest - nCounter) >> 1;
SetCounterDigits();
return;
}
}
if (!(nDigit[0] % 3))
nCounter = nDigit[0] / 3 + 100 * (nDigit[2] / 3) + 10 * (nDigit[1] / 3);
eax = nCounterDest - nCounter;
if (eax < 0)
eax = -eax;
ammodelay = max(1, 4 - (eax >> 1));
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void SetPlayerItem(ExhumedPlayer pp)
{
if (nSelectedItem != pp.nItem)
{
nSelectedItem = pp.nItem;
SetItemSeq(pp);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override void Tick()
{
totalmoves++;
let pp = Exhumed.GetViewPlayer();
SetMagicFrame(pp);
SetHealthFrame(pp);
SetPlayerItem(pp);
UpdateCounter(pp);
MoveStatus(pp);
for (int i = 0; i < 4; i++)
{
int seq = KeySeq + 2 * i;
if (pp.keys & (4096 << i))
{
if (keyanims[i] < Exhumed.SizeofStatusSequence(seq) - 1)
{
Exhumed.MoveStatusSequence(seq, 0); // this plays the pickup sound.
keyanims[i]++;
}
}
else
{
keyanims[i] = 0;
}
}
if (pp.isUnderwater())
{
int nAirFrames = Exhumed.SizeofStatusSequence(133);
int airperline = 100 / nAirFrames;
airframe = pp.nAir / airperline;
if (airframe >= nAirFrames)
{
airframe = nAirFrames - 1;
}
else if (airframe < 0)
{
airframe = 0;
}
lungframe = 0;
}
else
{
int size = Exhumed.SizeofStatusSequence(132);
if (++lungframe == size) lungframe = 0;
}
}
override void UpdateStatusBar(SummaryInfo info)
{
if (hud_size <= Hud_Full)
{
DrawStatus(Exhumed.GetViewPlayer());
}
DoLevelStats(hud_size == Hud_Nothing ? 0 : hud_size == Hud_Full ? 20 : 45, info);
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
override void GetAllStats(HudStats stats)
{
stats.Clear();
stats.info.fontscale = 1;
stats.info.spacing = 8;
stats.info.letterColor = Font.TEXTCOLOR_RED;
stats.info.standardColor = Font.TEXTCOLOR_UNTRANSLATED;
stats.info.completeColor = Font.TEXTCOLOR_DARKGREEN;
stats.info.statfont = SmallFont;
let pp = Exhumed.GetViewPlayer();
stats.healthicon = GetStatusSequenceName(125, 0);
stats.healthvalue = pp.nHealth >> 3;
SetMagicFrame(pp);
stats.armoricons.Push(GetStatusSequenceName(nItemSeq, nItemFrame));
stats.armorvalues.Push(pp.nMagic / 10);
if (pp.isUnderwater())
{
let img = GetStatusSequenceName(133, airframe);
stats.armoricons.Push(img);
stats.armorvalues.Push(-1);
}
int nKeys = pp.keys;
for (int i = 0; i < 4; i++)
{
if (nKeys & (0x1000 << i))
{
stats.keyicons.Push(String.Format("KeyIcon%d", i+1));
}
}
static const string weaponicons[] = { "", "WeaponSpriteMagnum", "WeaponSpriteM60", "WeaponSpriteFlamethrower", "", "WeaponSpriteCobra", "" /* sprite for ring?*/};
for (int i = 0; i < weaponicons.Size(); i++)
{
if (pp.nPlayerWeapons & (1 << i) && weaponicons[i] != "")
{
if (pp.nCurrentWeapon == i) stats.weaponselect = stats.weaponicons.Size();
stats.weaponicons.Push(weaponicons[i]);
}
}
static const string ammoicons[] = { "", "AmmoSpriteMagnum", "AmmoSpriteM60_1", "AmmoSpriteFuel", "AmmoSpriteGrenade", "AmmoSpriteCobra", "-" /* sprite for ring?*/};
for (int i = 0; i < ammoicons.Size(); i++)
{
if (pp.nCurrentWeapon == i) stats.ammoselect = stats.ammoicons.Size();
stats.ammoicons.Push(ammoicons[i]);
stats.ammovalues.Push(pp.nAmmo[i]);
stats.ammomaxvalues.Push(300);
}
}
}