//------------------------------------------------------------------------- /* 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 "ns.h" #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 #include #include #include #include "typedefs.h" BEGIN_PS_NS 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; return nStatusAnim; } void RefreshStatus() { short nLives = nPlayerLives[nLocalPlayer]; if (nLives < 0 || nLives > kMaxPlayerLives) { I_Error("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; snprintf(coordBuf, 50, "X %d", (int)sprite[nSprite].x.cast()); printext(x, nViewTop + 1, coordBuf, kTile159, 255); snprintf(coordBuf, 50, "Y %d", sprite[nSprite].y.cast()); printext(x, nViewTop + 10, coordBuf, kTile159, 255); } if (bHolly) { snprintf(message_text, 80, "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); } } // I'm not sure this really needs to be saved. static SavegameHelper sgh("status", SV(nMaskY), SV(nAnimsFree), SV(message_timer), SV(magicperline), SV(airperline), SV(healthperline), SV(nAirFrames), SV(nCounter), SV(nCounterDest), SV(nStatusSeqOffset), SV(nItemFrames), SV(laststatusx), SV(laststatusy), SV(nItemSeq), SV(nMagicFrames), SV(nHealthLevel), SV(nItemFrame), SV(nMeterRange), SV(nMagicLevel), SV(nHealthFrame), SV(nMagicFrame), SV(statusx), SV(statusy), SV(nHealthFrames), SV(airframe), SV(nFirstAnim), SV(nLastAnim), SV(nItemAltSeq), SV(airpages), SV(ammodelay), SV(nCounterBullet), SA(statusmask), SA(message_text), SA(nDigit), SA(StatusAnim), SA(StatusAnimsFree), SA(StatusAnimFlags), SA(nItemSeqOffset), SA(word_9AD54), SA(dword_9AD64), nullptr); END_PS_NS