raze/source/exhumed/src/status.cpp
2019-11-22 00:13:44 +01:00

849 lines
18 KiB
C++

//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 sirlemonhead, Nuke.YKT
This file is part of PCExhumed.
PCExhumed 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.
*/
//-------------------------------------------------------------------------
#include "engine.h"
#include "player.h"
#include "anims.h"
#include "status.h"
#include "exhumed.h"
#include "sequence.h"
#include "init.h"
#include "names.h"
#include "items.h"
#include "view.h"
#include "trigdat.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include "typedefs.h"
short nMaskY;
static short nAnimsFree = 0;
short statusmask[MAXXDIM];
short message_timer = 0;
char message_text[80];
int magicperline;
int airperline;
int healthperline;
int nAirFrames;
int nCounter;
int nCounterDest;
short nStatusSeqOffset;
short nItemFrames;
int laststatusx;
int laststatusy;
int16_t nItemSeq;
short nDigit[3];
short nMagicFrames;
short nHealthLevel;
short nItemFrame;
short nMeterRange;
short nMagicLevel;
short nHealthFrame;
short nMagicFrame;
short statusx;
short statusy;
short nHealthFrames;
short airframe;
int16_t nFirstAnim;
int16_t nLastAnim;
short nItemAltSeq;
short airpages = 0;
short ammodelay = 3;
short nCounterBullet = -1;
// 8 bytes
struct statusAnim
{
int16_t s1;
int16_t s2;
// int16_t nPage;
int8_t nPrevAnim;
int8_t nNextAnim;
};
#define kMaxStatusAnims 50
statusAnim StatusAnim[kMaxStatusAnims];
uint8_t StatusAnimsFree[kMaxStatusAnims];
uint8_t StatusAnimFlags[kMaxStatusAnims];
short nItemSeqOffset[] = {91, 72, 76, 79, 68, 87, 83};
short word_9AD54[kMaxPlayers] = {0, 0, 0, 0, 0, 0, 0, 0};
int dword_9AD64[kMaxPlayers] = {0, 0, 0, 0, 0, 0, 0, 0};
void SetCounterDigits();
void SetItemSeq();
void SetItemSeq2(int nSeqOffset);
void DestroyStatusAnim(short nAnim);
int BuildStatusAnim(int val, int nFlags)
{
// destroy this anim if it already exists
for (int i = nFirstAnim; i >= 0; i = StatusAnim[i].nPrevAnim)
{
if (StatusAnim[i].s1 == val) {
DestroyStatusAnim(i);
break;
}
}
if (nAnimsFree <= 0) {
return -1;
}
nAnimsFree--;
uint8_t nStatusAnim = StatusAnimsFree[nAnimsFree];
StatusAnim[nStatusAnim].nPrevAnim = -1;
StatusAnim[nStatusAnim].nNextAnim = nLastAnim;
if (nLastAnim < 0) {
nFirstAnim = nStatusAnim;
}
else {
StatusAnim[nLastAnim].nPrevAnim = nStatusAnim;
}
nLastAnim = nStatusAnim;
StatusAnim[nStatusAnim].s1 = val;
StatusAnim[nStatusAnim].s2 = 0;
StatusAnimFlags[nStatusAnim] = nFlags;
// StatusAnim[nStatusAnim].nPage = numpages;
return nStatusAnim;
}
void RefreshStatus()
{
short nLives = nPlayerLives[nLocalPlayer];
if (nLives < 0 || nLives > kMaxPlayerLives) {
bail2dos("illegal value for nPlayerLives #%d\n", nLocalPlayer);
}
// draws the red dots that indicate the lives amount
BuildStatusAnim(145 + (2 * nLives), 0);
uint16_t nKeys = PlayerList[nLocalPlayer].keys;
int val = 37;
for (int i = 0; i < 4; i++)
{
if (nKeys & 0x1000) {
BuildStatusAnim(val, 0);
}
nKeys >>= 1;
val += 2;
}
SetPlayerItem(nLocalPlayer, nPlayerItem[nLocalPlayer]);
SetHealthFrame(0);
SetMagicFrame();
SetAirFrame();
}
void InitStatus()
{
nStatusSeqOffset = SeqOffsets[kSeqStatus];
nHealthFrames = SeqSize[nStatusSeqOffset + 1];
int nPicNum = seq_GetSeqPicnum(kSeqStatus, 1, 0);
nMagicFrames = SeqSize[nStatusSeqOffset + 129];
nHealthFrame = 0;
nMagicFrame = 0;
nHealthLevel = 0;
nMagicLevel = 0;
nMeterRange = tilesiz[nPicNum].y;
magicperline = 1000 / nMeterRange;
healthperline = 800 / nMeterRange;
nAirFrames = SeqSize[nStatusSeqOffset + 133];
airperline = 100 / nAirFrames;
nCounter = 0;
nCounterDest = 0;
memset(nDigit, 0, sizeof(nDigit));
SetCounter(0);
SetHealthFrame(0);
SetMagicFrame();
for (int i = 0; i < kMaxStatusAnims; i++) {
StatusAnimsFree[i] = i;
}
nLastAnim = -1;
nFirstAnim = -1;
nItemSeq = -1;
nAnimsFree = kMaxStatusAnims;
statusx = xdim - 320;
textpages = 0;
message_timer = 0;
statusy = ydim - 200;
}
void MoveStatusAnims()
{
for (int i = nFirstAnim; i >= 0; i = StatusAnim[i].nPrevAnim)
{
seq_MoveSequence(-1, nStatusSeqOffset + StatusAnim[i].s1, StatusAnim[i].s2);
StatusAnim[i].s2++;
short nSize = SeqSize[nStatusSeqOffset + StatusAnim[i].s1];
if (StatusAnim[i].s2 >= nSize)
{
if (StatusAnimFlags[i] & 0x10) {
StatusAnim[i].s2 = 0;
}
else {
StatusAnim[i].s2 = nSize - 1; // restart it
}
}
}
}
void DestroyStatusAnim(short nAnim)
{
int8_t nPrev = StatusAnim[nAnim].nPrevAnim;
int8_t nNext = StatusAnim[nAnim].nNextAnim;
if (nNext >= 0) {
StatusAnim[nNext].nPrevAnim = nPrev;
}
if (nPrev >= 0) {
StatusAnim[nPrev].nNextAnim = nNext;
}
if (nAnim == nFirstAnim) {
nFirstAnim = nPrev;
}
if (nAnim == nLastAnim) {
nLastAnim = nNext;
}
StatusAnimsFree[nAnimsFree] = (uint8_t)nAnim;
nAnimsFree++;
}
void DrawStatusAnims()
{
for (int i = nFirstAnim; i >= 0; i = StatusAnim[i].nPrevAnim)
{
int nSequence = nStatusSeqOffset + StatusAnim[i].s1;
seq_DrawStatusSequence(nSequence, StatusAnim[i].s2, 0);
/*
if (StatusAnim[nAnim].s2 >= (SeqSize[nSequence] - 1))
{
if (!(StatusAnimFlags[nAnim] & 0x10))
{
StatusAnim[nAnim].nPage--;
if (StatusAnim[nAnim].nPage <= 0) {
DestroyStatusAnim(nAnim);
}
}
}
*/
}
}
void SetMagicFrame()
{
nMagicLevel = (1000 - PlayerList[nLocalPlayer].nMagic) / magicperline;
if (nMagicLevel >= nMeterRange) {
nMagicLevel = nMeterRange - 1;
}
if (nMagicLevel < 0) {
nMagicLevel = 0;
}
SetItemSeq();
}
void SetHealthFrame(short nVal)
{
nHealthLevel = (800 - PlayerList[nLocalPlayer].nHealth) / healthperline;
if (nHealthLevel >= nMeterRange ) {
nHealthLevel = nMeterRange - 1;
}
if (nHealthLevel < 0) {
nHealthLevel = 0;
}
if (nVal < 0) {
BuildStatusAnim(4, 0);
}
}
void SetAirFrame()
{
airframe = PlayerList[nLocalPlayer].nAir / airperline;
if (airframe >= nAirFrames)
{
airframe = nAirFrames - 1;
}
else if (airframe < 0)
{
airframe = 0;
}
}
void SetCounter(short nVal)
{
if (nVal <= 999)
{
if (nVal < 0) {
nVal = 0;
}
}
else {
nVal = 999;
}
nCounterDest = nVal;
}
void SetCounterImmediate(short 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 SetItemSeq()
{
short nItem = nPlayerItem[nLocalPlayer];
if (nItem < 0)
{
nItemSeq = -1;
return;
}
short nOffset = nItemSeqOffset[nItem];
SetItemSeq2(nOffset);
}
void SetItemSeq2(int nSeqOffset)
{
short nItem = nPlayerItem[nLocalPlayer];
if (nItemMagic[nItem] <= PlayerList[nLocalPlayer].nMagic) {
nItemAltSeq = 0;
}
else {
nItemAltSeq = 2;
}
nItemFrame = 0;
nItemSeq = nSeqOffset + nItemAltSeq;
nItemFrames = SeqSize[nItemSeq + nStatusSeqOffset];
}
void SetPlayerItem(short nPlayer, short nItem)
{
nPlayerItem[nPlayer] = nItem;
if (nPlayer == nLocalPlayer)
{
SetItemSeq();
if (nItem >= 0) {
BuildStatusAnim(156 + (2 * PlayerList[nLocalPlayer].items[nItem]), 0);
}
}
}
void SetNextItem(int nPlayer)
{
short nItem = nPlayerItem[nPlayer];
int i;
for (i = 6; i > 0; i--)
{
nItem++;
if (nItem == 6)
nItem = 0;
if (PlayerList[nPlayer].items[nItem] != 0)
break;
}
if (i > 0) {
SetPlayerItem(nPlayer, nItem);
}
}
void SetPrevItem(int nPlayer)
{
if (nPlayerItem[nPlayer] == -1)
return;
int nItem = nPlayerItem[nPlayer];
int i;
for (i = 6; i > 0; i--)
{
nItem--;
if (nItem < 0)
nItem = 5;
if (PlayerList[nPlayer].items[nItem] != 0)
break;
}
if (i > 0) {
SetPlayerItem(nPlayer, nItem);
}
}
void MoveStatus()
{
if (nItemSeq >= 0)
{
nItemFrame++;
if (nItemFrame >= nItemFrames)
{
if (nItemSeq == 67) {
SetItemSeq();
}
else
{
nItemSeq -= nItemAltSeq;
if (nItemAltSeq || totalmoves & 0x1F)
{
if (nItemSeq < 2) {
nItemAltSeq = 0;
}
}
else
{
nItemAltSeq = 1;
}
nItemFrame = 0;
nItemSeq += nItemAltSeq;
nItemFrames = SeqSize[nStatusSeqOffset + nItemSeq];
}
}
}
if (message_timer)
{
message_timer -= 4;
if (message_timer <= 0)
{
if (screensize > 0) {
textpages = numpages;
}
message_timer = 0;
}
}
MoveStatusAnims();
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 = 4 - (eax >> 1);
if (ammodelay < 1) {
ammodelay = 1;
}
}
void UnMaskStatus()
{
#if 0
for (int i = 0; i < xdim; i++) {
startdmost[i] = ydim;
}
#endif
}
void MaskStatus()
{
#if 0
for (int i = 0; i < xdim; i++)
{
short bx = startdmost[i];
short cx = statusmask[i];
if (bx > cx) {
startdmost[i] = cx;
}
}
#endif
}
void LoadStatus()
{
#if 0
int i;
short nSize;
short tmp;
short buffer[1024];
// memset(buffer, 0, sizeof(buffer)); // bjd - added by me
for (i = 0; i < xdim; i++) {
statusmask[i] = ydim;
}
nMaskY = ydim;
int hStatus = kopen4load("status.msk", 1);
if (!hStatus) {
return;
}
kread(hStatus, &nSize, sizeof(nSize));
int nCount = nSize >> 1;
kread(hStatus, &tmp, sizeof(tmp));
kread(hStatus, buffer, nSize);
kclose(hStatus);
short *pStatusMask = statusmask;
for (i = 0; i < nCount; i++)
{
int v8 = ydim - ((ydim * buffer[i]) / 200);
*pStatusMask++ = ydim - v8;
if (bHiRes) {
*pStatusMask++ = ydim - v8;
}
if (ydim - v8 < nMaskY) {
nMaskY = ydim - v8;
}
}
#endif
}
void StatusMessage(int messageTime, const char *fmt, ...)
{
message_timer = messageTime;
va_list args;
va_start(args, fmt);
vsprintf(message_text, fmt, args);
if (screensize > 0) {
textpages = numpages;
}
}
void DrawStatus()
{
char numberBuf[10] = {0};
char stringBuf[20] = {0};
char coordBuf[50] = {0}; // not sure of the size for this?
if (!bFullScreen && nNetTime)
{
// bjd - commenting out this check seems to fix the black status bar at 320x200 resolution
// if (bHiRes) {
NoClip();
// }
// draw the main bar itself
seq_DrawStatusSequence(nStatusSeqOffset, 0, 0);
seq_DrawStatusSequence(nStatusSeqOffset + 128, 0, 0);
seq_DrawStatusSequence(nStatusSeqOffset + 127, 0, 0);
seq_DrawStatusSequence(nStatusSeqOffset + 1, nHealthFrame, nHealthLevel);
seq_DrawStatusSequence(nStatusSeqOffset + 129, nMagicFrame, nMagicLevel);
nHealthFrame++;
if (nHealthFrame >= nHealthFrames) {
nHealthFrame = 0;
}
nMagicFrame++;
if (nMagicFrame >= nMagicFrames) {
nMagicFrame = 0;
}
seq_DrawStatusSequence(nStatusSeqOffset + 125, 0, 0); // draw ankh on health pool
seq_DrawStatusSequence(nStatusSeqOffset + 130, 0, 0); // draw health pool frame (top)
seq_DrawStatusSequence(nStatusSeqOffset + 131, 0, 0); // magic pool frame (bottom)
if (nItemSeq >= 0) {
seq_DrawStatusSequence(nItemSeq + nStatusSeqOffset, nItemFrame, 0);
}
// draws health level dots, animates breathing lungs and other things
DrawStatusAnims();
// draw the blue air level meter when underwater (but not responsible for animating the breathing lungs otherwise)
if (airpages)
{
seq_DrawStatusSequence(nStatusSeqOffset + 133, airframe, 0);
// airpages--;
}
// draw compass
seq_DrawStatusSequence(nStatusSeqOffset + 35, ((inita + 128) & kAngleMask) >> 8, 0);
if (bCoordinates)
{
sprintf(numberBuf, "%i", lastfps);
// char *cFPS = itoa(lastfps, numberBuf, 10);
printext(xdim - 20, nViewTop, numberBuf, kTile159, -1);
}
// draw ammo count
seq_DrawStatusSequence(nStatusSeqOffset + 44, nDigit[2], 0);
seq_DrawStatusSequence(nStatusSeqOffset + 45, nDigit[1], 0);
seq_DrawStatusSequence(nStatusSeqOffset + 46, nDigit[0], 0);
// bjd - commenting out this check seems to fix the black status bar at 320x200 resolution
// if (bHiRes) {
Clip();
// }
}
if (nNetPlayerCount)
{
NoClip();
int shade;
if ((int)totalclock / kTimerTicks & 1) {
shade = -100;
}
else {
shade = 127;
}
int nTile = kTile3593;
int x = 320 / (nTotalPlayers + 1);
for (int i = 0; i < nTotalPlayers; i++)
{
int nScore = nPlayerScore[i];
if (word_9AD54[i] == nScore)
{
int v9 = dword_9AD64[i];
if (v9 && v9 <= (int)totalclock) {
dword_9AD64[i] = 0;
}
}
else
{
word_9AD54[i] = nScore;
dword_9AD64[i] = (int)totalclock + 30;
}
overwritesprite(x, 7, nTile, 0, 3, kPalNormal);
if (i != nLocalPlayer) {
shade = -100;
}
sprintf(stringBuf, "%d", nPlayerScore[i]);
int nStringLen = MyGetStringWidth(stringBuf);
myprintext(x - (nStringLen / 2), 4, stringBuf, shade);
x *= 2;
nTile++;
}
if (nNetTime >= 0)
{
int y = nViewTop;
if (nNetTime)
{
int v12 = (nNetTime + 29) / 30 % 60;
int v13 = (nNetTime + 29) / 1800;
nNetTime += 29;
sprintf(stringBuf, "%d.%02d", v13, v12);
if (bHiRes) {
y = nViewTop / 2;
}
if (nViewTop <= 0) {
y += 20;
}
else {
y += 15;
}
nNetTime -= 29;
}
else
{
y = 100;
strcpy(stringBuf, "GAME OVER");
}
int nLenString = MyGetStringWidth(stringBuf);
myprintext((320 - nLenString) / 2, y, stringBuf, 0);
}
Clip();
}
if (bCoordinates)
{
int nSprite = PlayerList[nLocalPlayer].nSprite;
int x = (nViewLeft + nViewRight) / 2;
sprintf(coordBuf, "X %d", (int)sprite[nSprite].x);
printext(x, nViewTop + 1, coordBuf, kTile159, 255);
sprintf(coordBuf, "Y %d", sprite[nSprite].y);
printext(x, nViewTop + 10, coordBuf, kTile159, 255);
}
if (bHolly)
{
sprintf(message_text, "HOLLY: %s", sHollyStr);
printext(0, 0, message_text, kTile159, 255);
}
else if (nSnakeCam < 0)
{
if (message_timer) {
printext(0, 0, message_text, kTile159, 255);
}
}
else
{
printext(0, 0, "S E R P E N T C A M", kTile159, 255);
}
}