raze/source/exhumed/src/exhumed.cpp

2070 lines
45 KiB
C++
Raw Normal View History

//-------------------------------------------------------------------------
/*
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 "compat.h"
#include "baselayer.h"
#include "common.h"
#include "engine.h"
#include "exhumed.h"
#include "sequence.h"
#include "names.h"
#include "menu.h"
#include "player.h"
2019-11-24 09:03:19 +00:00
#include "ps_input.h"
#include "sound.h"
#include "view.h"
#include "status.h"
#include "version.h"
#include "aistuff.h"
#include "mapinfo.h"
#include <string.h>
#include <cstdio> // for printf
#include <cstdlib>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>
#include <assert.h>
#include "gamecvars.h"
#include "savegamehelp.h"
#include "c_dispatch.h"
#include "raze_sound.h"
#include "gamestate.h"
#include "screenjob.h"
#include "core/menu/menu.h"
BEGIN_PS_NS
2019-08-27 06:08:18 +00:00
extern const char* s_buildRev;
extern const char* s_buildTimestamp;
void uploadCinemaPalettes();
int32_t registerosdcommands(void);
void InitFonts();
int htimer = 0;
bool EndLevel = false;
/* these are XORed in the original game executable then XORed back to normal when the game first starts. Here they are normally */
const char *gString[] =
{
2019-08-27 06:08:18 +00:00
"HOLLY",
"KIMBERLY",
"LOBOCOP",
"LOBODEITY",
"LOBOLITE",
"LOBOPICK",
"LOBOSLIP",
"LOBOSNAKE",
"LOBOSPHERE",
"LOBOSWAG",
"LOBOXY",
};
//////////
enum gametokens
{
T_INCLUDE = 0,
T_INTERFACE = 0,
T_LOADGRP = 1,
T_MODE = 1,
T_CACHESIZE = 2,
T_ALLOW = 2,
T_NOAUTOLOAD,
T_INCLUDEDEFAULT,
T_SOUND,
T_FILE,
T_CUTSCENE,
T_ANIMSOUNDS,
T_NOFLOORPALRANGE,
T_ID,
T_MINPITCH,
T_MAXPITCH,
T_PRIORITY,
T_TYPE,
T_DISTANCE,
T_VOLUME,
T_DELAY,
T_RENAMEFILE,
T_GLOBALGAMEFLAGS,
T_ASPECT,
T_FORCEFILTER,
T_FORCENOFILTER,
T_TEXTUREFILTER,
T_NEWGAMECHOICES,
T_CHOICE,
T_NAME,
T_LOCKED,
T_HIDDEN,
T_USERCONTENT,
};
PlayerInput localInput;
////////
void ResetEngine()
{
EraseScreen(-1);
resettiming();
totalclock = 0;
ototalclock = totalclock;
localclock = (int)totalclock;
numframes = 0;
}
void InstallEngine()
{
TileFiles.LoadArtSet("tiles%03d.art");
if (engineInit())
{
G_FatalEngineError();
}
uploadCinemaPalettes();
LoadPaletteLookups();
InitFonts();
videoInit();
enginecompatibility_mode = ENGINECOMPATIBILITY_19950829;
}
void RemoveEngine()
{
engineUnInit();
}
2019-11-24 15:34:23 +00:00
const uint32_t kSpiritX = 106;
const uint32_t kSpiritY = 97;
short cPupData[300];
//int worktile[97 * 106] = { 0 };
2019-11-24 15:34:23 +00:00
uint8_t *Worktile;
const uint32_t WorktileSize = kSpiritX * 2 * kSpiritY * 2;
int lHeadStartClock;
short *pPupData;
int lNextStateChange;
int nPixels;
int nHeadTimeStart;
short curx[97 * 106];
short cury[97 * 106];
int8_t destvelx[97 * 106];
int8_t destvely[97 * 106];
uint8_t pixelval[97 * 106];
int8_t origy[97 * 106];
int8_t origx[97 * 106];
int8_t velx[97 * 106];
int8_t vely[97 * 106];
short nMouthTile;
short nPupData = 0;
short word_964E8 = 0;
short word_964EA = 0;
short word_964EC = 10;
short nSpiritRepeatX;
short nSpiritRepeatY;
short nPixelsToShow;
void CopyTileToBitmap(short nSrcTile, short nDestTile, int xPos, int yPos);
void DoTitle();
// void TestSaveLoad();
void EraseScreen(int nVal);
void LoadStatus();
int FindGString(const char *str);
void MySetView(int x1, int y1, int x2, int y2);
void mysetbrightness(char al);
void FadeIn();
char sHollyStr[40];
short nFontFirstChar;
short nBackgroundPic;
short nShadowPic;
short nCreaturesLeft = 0;
short nFreeze;
short nSnakeCam = -1;
short nBestLevel;
short nSpiritSprite;
short nLocalSpr;
short levelnew = 1;
int nNetPlayerCount = 0;
short nClockVal;
short fps;
short nRedTicks;
short lastlevel;
2019-08-31 11:44:02 +00:00
volatile short bInMove;
short nAlarmTicks;
short nButtonColor;
short nEnergyChan;
short bModemPlay = false;
int lCountDown = 0;
short nEnergyTowers = 0;
short nHeadStage;
short nCfgNetPlayers = 0;
FILE *vcrfp = NULL;
short nTalkTime = 0;
short forcelevel = -1;
int lLocalButtons = 0;
int lLocalCodes = 0;
short bCoordinates = false;
int nNetTime = -1;
short nCodeMin = 0;
short nCodeMax = 0;
short nCodeIndex = 0;
short levelnum = -1;
2019-08-27 06:08:18 +00:00
//short nScreenWidth = 320;
//short nScreenHeight = 200;
int moveframes;
int flash;
int localclock;
int totalmoves;
short nCurBodyNum = 0;
short nBodyTotal = 0;
short lastfps;
short nMapMode = 0;
short nTotalPlayers = 1;
// TODO: Rename this (or make it static) so it doesn't conflict with library function
//short socket = 0;
short nFirstPassword = 0;
short nFirstPassInfo = 0;
short nPasswordCount = 0;
short bSnakeCam = false;
short bRecord = false;
short bPlayback = false;
short bInDemo = false;
short bSlipMode = false;
short bDoFlashes = true;
short bHolly = false;
short besttarget;
short scan_char = 0;
int nStartLevel;
int nTimeLimit;
2019-10-27 16:36:25 +00:00
int bVanilla = 0;
short wConsoleNode; // TODO - move me into network file
2019-11-14 16:08:50 +00:00
ClockTicks tclocks, tclocks2;
void DebugOut(const char *fmt, ...)
{
#ifdef _DEBUG
2019-08-27 06:08:18 +00:00
va_list args;
va_start(args, fmt);
2019-11-24 09:03:19 +00:00
VPrintf(PRINT_HIGH, fmt, args);
#endif
}
2019-08-27 06:08:18 +00:00
void ShutDown(void)
{
StopCD();
RemoveEngine();
//UnInitFX();
2019-08-27 06:08:18 +00:00
}
void timerhandler()
{
UpdateSounds();
2019-08-27 06:08:18 +00:00
scan_char++;
if (scan_char == kTimerTicks)
{
scan_char = 0;
lastfps = fps;
fps = 0;
}
if (!bInMove) {
C_RunDelayedCommands();
}
}
extern int MenuExitCondition;
2019-08-31 15:04:06 +00:00
void HandleAsync()
{
MenuExitCondition = -2;
2019-08-31 15:04:06 +00:00
handleevents();
2019-08-31 15:04:06 +00:00
}
void DoPassword(int nPassword)
{
2019-08-27 06:08:18 +00:00
if (nNetPlayerCount) {
return;
}
const char *str = gString[nFirstPassInfo + nPassword];
if (str[0] != '\0') {
StatusMessage(750, str);
}
switch (nPassword)
{
case 0:
{
if (!nNetPlayerCount) {
bHolly = true;
2019-08-27 06:08:18 +00:00
}
break;
}
2020-06-09 23:08:47 +00:00
case 1: // KIMBERLY
{
break;
}
2019-08-27 06:08:18 +00:00
case 2: // LOBOCOP
{
lLocalCodes |= kButtonCheatGuns;
2019-08-27 06:08:18 +00:00
break;
}
case 3: // LOBODEITY
{
lLocalCodes |= kButtonCheatGodMode;
2019-08-27 06:08:18 +00:00
break;
}
2020-06-09 23:08:47 +00:00
case 4: // LOBOLITE
2019-08-27 06:08:18 +00:00
{
if (bDoFlashes == false)
2020-06-09 23:08:47 +00:00
{
bDoFlashes = true;
2019-08-27 06:08:18 +00:00
}
else {
bDoFlashes = false;
2019-08-27 06:08:18 +00:00
}
break;
}
2020-06-09 23:08:47 +00:00
case 5: // LOBOPICK
2019-08-27 06:08:18 +00:00
{
lLocalCodes |= kButtonCheatKeys;
2019-08-27 06:08:18 +00:00
break;
}
2020-06-09 23:08:47 +00:00
case 6: // LOBOSLIP
2019-08-27 06:08:18 +00:00
{
if (!nNetPlayerCount)
{
if (bSlipMode == false)
2019-08-27 06:08:18 +00:00
{
bSlipMode = true;
2020-06-09 23:08:47 +00:00
StatusMessage(300, "Slip mode ON");
2019-08-27 06:08:18 +00:00
}
else {
bSlipMode = false;
2020-06-09 23:08:47 +00:00
StatusMessage(300, "Slip mode OFF");
2019-08-27 06:08:18 +00:00
}
}
break;
}
2020-06-09 23:08:47 +00:00
case 7: // LOBOSNAKE
2019-08-27 06:08:18 +00:00
{
if (!nNetPlayerCount)
{
if (bSnakeCam == false)
2020-06-09 23:08:47 +00:00
{
bSnakeCam = true;
2020-06-09 23:08:47 +00:00
StatusMessage(750, "SNAKE CAM ENABLED");
2019-08-27 06:08:18 +00:00
}
2020-06-09 23:08:47 +00:00
else
{
bSnakeCam = false;
2020-06-09 23:08:47 +00:00
StatusMessage(750, "SNAKE CAM DISABLED");
2019-08-27 06:08:18 +00:00
}
}
break;
}
2020-06-09 23:08:47 +00:00
case 8: // LOBOSPHERE
2019-08-27 06:08:18 +00:00
{
GrabMap();
bShowTowers = true;
2019-08-27 06:08:18 +00:00
break;
}
2020-06-09 23:08:47 +00:00
case 9: // LOBOSWAG
2019-08-27 06:08:18 +00:00
{
2020-06-09 23:08:47 +00:00
lLocalCodes |= kButtonCheatItems;
2019-08-27 06:08:18 +00:00
break;
}
2020-06-09 23:08:47 +00:00
case 10: // LOBOXY
2019-08-27 06:08:18 +00:00
{
if (bCoordinates == false) {
bCoordinates = true;
2019-08-27 06:08:18 +00:00
}
else {
bCoordinates = false;
2019-08-27 06:08:18 +00:00
}
break;
}
default:
return;
}
}
void mysetbrightness(char nBrightness)
{
2019-08-27 06:08:18 +00:00
g_visibility = 2048 - (nBrightness << 9);
}
// Replicate original DOS EXE behaviour when pointer is null
static const char *safeStrtok(char *s, const char *d)
{
const char *r = strtok(s, d);
return r ? r : "";
}
void CheckKeys()
{
if (buttonMap.ButtonDown(gamefunc_Enlarge_Screen))
{
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Enlarge_Screen);
if (!nMapMode)
{
if (!SHIFTS_IS_PRESSED)
{
G_ChangeHudLayout(1);
}
else
{
hud_scale = hud_scale + 4;
}
}
2019-08-27 06:08:18 +00:00
}
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Shrink_Screen))
2019-08-27 06:08:18 +00:00
{
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Shrink_Screen);
if (!nMapMode)
{
if (!SHIFTS_IS_PRESSED)
{
G_ChangeHudLayout(-1);
}
else
{
hud_scale = hud_scale - 4;
}
}
2019-08-27 06:08:18 +00:00
}
// go to 3rd person view?
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Third_Person_View))
2019-08-27 06:08:18 +00:00
{
if (!nFreeze)
{
if (bCamera) {
bCamera = false;
2019-08-27 06:08:18 +00:00
}
else {
bCamera = true;
2019-08-27 06:08:18 +00:00
}
if (bCamera)
GrabPalette();
}
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Third_Person_View);
return;
2019-08-27 06:08:18 +00:00
}
2020-05-29 01:15:01 +00:00
if (paused)
2019-08-27 06:08:18 +00:00
{
return;
}
// Handle cheat codes
2019-11-24 09:03:19 +00:00
if (!bInDemo && inputState.keyBufferWaiting())
2019-08-27 06:08:18 +00:00
{
2019-11-24 09:03:19 +00:00
char ch = inputState.keyGetChar();
2019-08-27 06:08:18 +00:00
if (isalpha(ch))
{
ch = toupper(ch);
int ecx = nCodeMin;
int ebx = nCodeMin;
int edx = nCodeMin - 1;
while (ebx <= nCodeMax)
{
if (ch == gString[ecx][nCodeIndex])
{
nCodeMin = ebx;
nCodeIndex++;
if (gString[ecx][nCodeIndex] == 0)
{
ebx -= nFirstPassword;
DoPassword(ebx);
}
break;
}
else if (gString[ecx][nCodeIndex] < ch)
{
nCodeMin = ebx + 1;
}
else if (gString[ecx][nCodeIndex] > ch)
{
nCodeMax = edx;
}
ecx++;
edx++;
ebx++;
}
if (nCodeMin > nCodeMax) {
ResetPassword();
}
}
}
}
void FinishLevel()
{
2019-08-27 06:08:18 +00:00
if (levelnum > nBestLevel) {
nBestLevel = levelnum - 1;
}
levelnew = levelnum + 1;
StopAllSounds();
bCamera = false;
nMapMode = 0;
2019-08-27 06:08:18 +00:00
if (levelnum != kMap20)
{
EraseScreen(4);
PlayLocalSound(StaticSound[59], 0, true, CHANF_UI);
2019-08-27 06:08:18 +00:00
videoNextPage();
//WaitTicks(12);
2019-08-27 06:08:18 +00:00
WaitVBL();
DrawView(65536);
videoNextPage();
2019-08-27 06:08:18 +00:00
}
FadeOut(1);
EraseScreen(overscanindex);
if (levelnum == 0)
{
nPlayerLives[0] = 0;
levelnew = 100;
}
else
{
DoAfterCinemaScene(levelnum);
if (levelnum == kMap20)
{
2020-08-22 17:50:04 +00:00
//DoCredits();
2019-08-27 06:08:18 +00:00
nPlayerLives[0] = 0;
}
}
}
void DoClockBeep()
{
2019-08-27 06:08:18 +00:00
for (int i = headspritestat[407]; i != -1; i = nextspritestat[i]) {
2019-09-21 15:47:55 +00:00
PlayFX2(StaticSound[kSound74], i);
2019-08-27 06:08:18 +00:00
}
}
void DoRedAlert(int nVal)
{
2019-08-27 06:08:18 +00:00
if (nVal)
{
nAlarmTicks = 69;
nRedTicks = 30;
}
for (int i = headspritestat[405]; i != -1; i = nextspritestat[i])
{
if (nVal)
{
2019-09-21 15:47:55 +00:00
PlayFXAtXYZ(StaticSound[kSoundAlarm], sprite[i].x, sprite[i].y, sprite[i].z, sprite[i].sectnum);
2019-08-27 06:08:18 +00:00
AddFlash(sprite[i].sectnum, sprite[i].x, sprite[i].y, sprite[i].z, 192);
}
}
}
void LockEnergyTiles()
{
2019-08-27 06:08:18 +00:00
// old loadtilelockmode = 1;
tileLoad(kTile3603);
tileLoad(kEnergy1);
tileLoad(kEnergy2);
2019-08-27 06:08:18 +00:00
// old loadtilelockmode = 0;
}
void DrawClock()
{
2019-08-27 06:08:18 +00:00
int ebp = 49;
auto pixels = TileFiles.tileMakeWritable(kTile3603);
2020-04-11 22:04:02 +00:00
memset(pixels, TRANSPARENT_INDEX, 4096);
2019-08-27 06:08:18 +00:00
if (lCountDown / 30 != nClockVal)
{
nClockVal = lCountDown / 30;
DoClockBeep();
}
2019-08-27 06:08:18 +00:00
int nVal = nClockVal;
2019-08-27 06:08:18 +00:00
while (nVal)
{
int v2 = nVal & 0xF;
int yPos = 32 - tilesiz[v2 + kClockSymbol1].y / 2;
CopyTileToBitmap(v2 + kClockSymbol1, kTile3603, ebp - tilesiz[v2 + kClockSymbol1].x / 2, yPos);
2019-08-27 06:08:18 +00:00
ebp -= 15;
2019-08-27 06:08:18 +00:00
nVal /= 16;
}
2019-08-27 06:08:18 +00:00
DoEnergyTile();
}
void M32RunScript(const char* s) { UNREFERENCED_PARAMETER(s); }
void app_crashhandler(void)
{
ShutDown();
}
void G_Polymer_UnInit(void) { }
static inline int32_t calc_smoothratio(ClockTicks totalclk, ClockTicks ototalclk)
{
2020-05-29 01:15:01 +00:00
if (bRecord || bPlayback || nFreeze != 0 || bCamera || paused)
return 65536;
return CalcSmoothRatio(totalclk, ototalclk, 30);
}
2019-11-15 06:48:40 +00:00
#define LOW_FPS ((videoGetRenderMode() == REND_CLASSIC) ? 35 : 50)
#define SLOW_FRAME_TIME 20
#if defined GEKKO
# define FPS_YOFFSET 16
#else
# define FPS_YOFFSET 0
#endif
FString GameInterface::statFPS()
2019-11-15 06:48:40 +00:00
{
FString out;
2019-11-15 06:48:40 +00:00
static int32_t frameCount;
static double cumulativeFrameDelay;
static double lastFrameTime;
static float lastFPS; // , minFPS = std::numeric_limits<float>::max(), maxFPS;
//static double minGameUpdate = std::numeric_limits<double>::max(), maxGameUpdate;
2019-11-15 06:48:40 +00:00
double frameTime = timerGetHiTicks();
double frameDelay = frameTime - lastFrameTime;
cumulativeFrameDelay += frameDelay;
if (frameDelay >= 0)
{
out.Format("%.1f ms, %5.1f fps", frameDelay, lastFPS);
2019-11-15 06:48:40 +00:00
if (cumulativeFrameDelay >= 1000.0)
{
lastFPS = 1000.f * frameCount / cumulativeFrameDelay;
// g_frameRate = Blrintf(lastFPS);
frameCount = 0;
cumulativeFrameDelay = 0.0;
}
frameCount++;
}
lastFrameTime = frameTime;
return out;
2019-11-15 06:48:40 +00:00
}
static void GameDisplay(void)
{
// End Section B
SetView1();
if (levelnum == kMap20)
{
LockEnergyTiles();
DoEnergyTile();
DrawClock();
}
auto smoothRatio = calc_smoothratio(totalclock, tclocks);
DrawView(smoothRatio);
DrawStatusBar();
2020-05-29 01:15:01 +00:00
if (paused && !M_Active())
2019-11-14 16:08:50 +00:00
{
auto tex = GStrings("TXTB_PAUSED");
int nStringWidth = SmallFont->StringWidth(tex);
DrawText(twod, SmallFont, CR_UNTRANSLATED, 160 - nStringWidth / 2, 100, tex, DTA_FullscreenScale, FSMode_ScaleToFit43, DTA_VirtualWidth, 320, DTA_VirtualHeight, 200, TAG_DONE);
2019-11-14 16:08:50 +00:00
}
if (M_Active())
{
D_ProcessEvents();
}
2019-11-14 16:08:50 +00:00
videoNextPage();
}
static void GameMove(void)
{
FixPalette();
if (levelnum == kMap20)
{
if (lCountDown <= 0)
{
for (int i = 0; i < nTotalPlayers; i++) {
nPlayerLives[i] = 0;
}
DoFailedFinalScene();
levelnew = 100;
return;
}
// Pink section
lCountDown--;
DrawClock();
if (nRedTicks)
{
nRedTicks--;
if (nRedTicks <= 0) {
DoRedAlert(0);
}
}
nAlarmTicks--;
nButtonColor--;
if (nAlarmTicks <= 0) {
DoRedAlert(1);
}
}
// YELLOW SECTION
MoveThings();
2019-11-08 16:55:26 +00:00
obobangle = bobangle;
if (totalvel[nLocalPlayer] == 0)
{
bobangle = 0;
}
else
{
bobangle += 56;
bobangle &= kAngleMask;
}
UpdateCreepySounds();
// loc_120E9:
totalmoves++;
moveframes--;
}
int32_t r_maxfpsoffset = 0;
void ExitGame()
{
ShutDown();
2020-04-11 21:50:43 +00:00
throw CExitEvent(0);
}
static int32_t nonsharedtimer;
static const char* actions[] =
{
"Move_Forward",
"Move_Backward",
"Turn_Left",
"Turn_Right",
"Strafe",
"Fire",
"Open",
"Run",
"Alt_Fire", // Duke3D", Blood
"Jump",
"Crouch",
"Look_Up",
"Look_Down",
"Look_Left",
"Look_Right",
"Strafe_Left",
"Strafe_Right",
"Aim_Up",
"Aim_Down",
"Weapon_1",
"Weapon_2",
"Weapon_3",
"Weapon_4",
"Weapon_5",
"Weapon_6",
"Weapon_7",
"Weapon_8",
"Weapon_9",
"Weapon_10",
"Inventory",
"Inventory_Left",
"Inventory_Right",
"TurnAround",
"SendMessage",
"Map",
"Shrink_Screen",
"Enlarge_Screen",
"Center_View",
"Holster_Weapon",
"Show_Opponents_Weapon",
"Map_Follow_Mode",
"See_Coop_View",
"Mouse_Aiming",
"Toggle_Crosshair",
"Next_Weapon",
"Previous_Weapon",
"Dpad_Select",
"Dpad_Aiming",
"Last_Weapon",
"Alt_Weapon",
"Third_Person_View",
"Toggle_Crouch", // This is the last one used by EDuke32.
"Zoom_In", // Map controls should not pollute the global button namespace.
"Zoom_Out",
};
void InitTimer()
{
htimer = 1;
timerInit(kTimerTicks);
timerSetCallback(timerhandler);
}
int SyncScreenJob();
void DoTitle(CompletionFunc completion);
int GameInterface::app_main()
{
int i;
//int esi = 1;
//int edi = esi;
int doTitle = true; // REVERT true;
int stopTitle = false;
levelnew = 1;
buttonMap.SetButtons(actions, NUM_ACTIONS);
help_disabled = true;
// Create the global level table. Parts of the engine need it, even though the game itself does not.
for (int i = 0; i <= 32; i++)
{
auto mi = &mapList[i];
mi->fileName.Format("LEV%d.MAP", i);
mi->labelName.Format("LEV%d", i);
mi->name.Format("$TXT_EX_MAP%02d", i);
int nTrack = i;
if (nTrack != 0) nTrack--;
mi->cdSongId = (nTrack % 8) + 11;
}
// REVERT - change back to true
// short bDoTitle = false;
wConsoleNode = 0;
int nMenu = 0; // TEMP
2019-08-27 06:08:18 +00:00
if (nNetPlayerCount && forcelevel == -1) {
forcelevel = 1;
}
2019-08-27 06:08:18 +00:00
// loc_115F5:
nFirstPassword = FindGString("PASSWORDS");
nFirstPassInfo = FindGString("PASSINFO");
2019-08-27 06:08:18 +00:00
// count the number of passwords available
2019-08-31 14:07:47 +00:00
for (nPasswordCount = 0; strlen(gString[nFirstPassword+nPasswordCount]) != 0; nPasswordCount++)
2019-08-27 06:08:18 +00:00
{
}
ResetPassword();
registerosdcommands();
2019-08-27 06:08:18 +00:00
if (nNetPlayerCount == -1)
{
nNetPlayerCount = nCfgNetPlayers - 1;
nTotalPlayers += nNetPlayerCount;
}
// loc_116A5:
#if 0
2019-08-27 06:08:18 +00:00
if (nNetPlayerCount)
{
InitInput();
forcelevel = nStartLevel;
nNetTime = 1800 * nTimeLimit;
if (nNetTime == 0) {
nNetTime = -1;
}
}
#endif
2019-08-27 06:08:18 +00:00
// temp - moving InstallEngine(); before FadeOut as we use nextpage() in FadeOut
InstallEngine();
2019-11-21 12:51:27 +00:00
const char *defsfile = G_DefFile();
uint32_t stime = timerGetTicks();
if (!loaddefinitionsfile(defsfile))
{
uint32_t etime = timerGetTicks();
Printf("Definitions file \"%s\" loaded in %d ms.\n", defsfile, etime-stime);
2019-11-21 12:51:27 +00:00
}
enginePostInit();
2019-08-31 14:07:47 +00:00
2019-08-27 06:08:18 +00:00
InitView();
2019-09-19 13:38:28 +00:00
InitFX();
2019-08-27 06:08:18 +00:00
seq_LoadSequences();
InitStatus();
InitTimer();
for (i = 0; i < kMaxPlayers; i++) {
nPlayerLives[i] = kDefaultLives;
}
2019-08-27 06:08:18 +00:00
nBestLevel = 0;
2019-08-27 06:08:18 +00:00
UpdateScreenSize();
2019-08-27 06:08:18 +00:00
EraseScreen(overscanindex);
ResetEngine();
EraseScreen(overscanindex);
2019-08-27 06:08:18 +00:00
ResetView();
GrabPalette();
2019-08-31 14:07:47 +00:00
if (doTitle)
2019-08-27 06:08:18 +00:00
{
2019-08-31 14:07:47 +00:00
while (!stopTitle)
2019-08-27 06:08:18 +00:00
{
DoTitle([](bool) { gamestate = GS_MENUSCREEN; });
SyncScreenJob();
gamestate = GS_LEVEL;
stopTitle = true;
2019-08-27 06:08:18 +00:00
}
}
// loc_11811:
if (forcelevel > -1)
{
levelnew = forcelevel;
2019-08-31 14:07:47 +00:00
goto STARTGAME1;
2019-08-27 06:08:18 +00:00
}
2019-08-31 14:07:47 +00:00
MENU:
SavePosition = -1;
nMenu = menu_Menu(0);
2019-08-31 14:07:47 +00:00
switch (nMenu)
{
case -1:
goto MENU;
case 0:
goto EXITGAME;
case 3:
forcelevel = 0;
goto STARTGAME2;
case 6:
goto GAMELOOP;
2019-08-31 14:07:47 +00:00
case 9:
goto MENU;
2019-08-31 14:07:47 +00:00
}
STARTGAME1:
2019-08-27 06:08:18 +00:00
levelnew = 1;
levelnum = 1;
if (!nNetPlayerCount) {
FadeOut(0);
}
2019-08-31 14:07:47 +00:00
STARTGAME2:
bCamera = false;
2019-08-27 06:08:18 +00:00
ClearCinemaSeen();
PlayerCount = 0;
lastlevel = -1;
2019-08-27 06:08:18 +00:00
for (i = 0; i < nTotalPlayers; i++)
{
int nPlayer = GrabPlayer();
if (nPlayer < 0) {
2019-11-24 09:03:19 +00:00
I_Error("Can't create local player\n");
2019-08-27 06:08:18 +00:00
}
2019-08-27 06:08:18 +00:00
InitPlayerInventory(nPlayer);
2019-08-27 06:08:18 +00:00
if (i == wConsoleNode) {
PlayerList[nPlayer].someNetVal = -3;
}
else {
PlayerList[nPlayer].someNetVal = -4;
}
}
2019-08-27 06:08:18 +00:00
nNetMoves = 0;
2019-08-31 14:07:47 +00:00
if (forcelevel > -1)
2019-08-27 06:08:18 +00:00
{
2019-08-31 14:07:47 +00:00
// YELLOW SECTION
levelnew = forcelevel;
2019-08-27 06:08:18 +00:00
UpdateInputs();
2019-08-31 14:07:47 +00:00
forcelevel = -1;
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
goto LOOP3;
2019-08-27 06:08:18 +00:00
}
2019-08-31 14:07:47 +00:00
// PINK SECTION
2019-08-27 06:08:18 +00:00
UpdateInputs();
2019-08-31 14:07:47 +00:00
nNetMoves = 1;
2019-08-31 14:07:47 +00:00
if (nMenu == 2)
{
levelnew = 1;
levelnum = 1;
levelnew = menu_GameLoad(SavePosition);
lastlevel = -1;
}
2019-08-31 14:07:47 +00:00
nBestLevel = levelnew - 1;
LOOP1:
if (nPlayerLives[nLocalPlayer] <= 0) {
goto MENU;
}
if (levelnew > 99) {
goto EXITGAME;
}
if (!bInDemo && levelnew > nBestLevel && levelnew != 0 && levelnew <= kMap20 && SavePosition > -1) {
menu_GameSave(SavePosition);
}
LOOP2:
if (!nNetPlayerCount && !bPlayback && levelnew > 0 && levelnew <= kMap20) {
levelnew = showmap(levelnum, levelnew, nBestLevel);
}
2019-08-31 14:07:47 +00:00
if (levelnew > nBestLevel) {
nBestLevel = levelnew;
}
LOOP3:
2019-08-27 06:08:18 +00:00
while (levelnew != -1)
{
// BLUE
if (CDplaying()) {
fadecdaudio();
}
if (levelnew == kMap20)
{
lCountDown = 81000;
nAlarmTicks = 30;
nRedTicks = 0;
nClockVal = 0;
nEnergyTowers = 0;
}
if (!LoadLevel(levelnew)) {
// TODO "Can't load level %d...\n", nMap;
2019-08-31 14:07:47 +00:00
goto EXITGAME;
2019-08-27 06:08:18 +00:00
}
2019-08-31 14:07:47 +00:00
levelnew = -1;
2019-08-27 06:08:18 +00:00
}
/* don't restore mid level savepoint if re-entering just completed level
2019-08-31 14:07:47 +00:00
if (nNetPlayerCount == 0 && lastlevel == levelnum)
2019-08-27 06:08:18 +00:00
{
2019-08-31 14:07:47 +00:00
RestoreSavePoint(nLocalPlayer, &initx, &inity, &initz, &initsect, &inita);
}
*/
2019-08-31 14:07:47 +00:00
lastlevel = levelnum;
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
for (i = 0; i < nTotalPlayers; i++)
{
SetSavePoint(i, initx, inity, initz, initsect, inita);
RestartPlayer(i);
InitPlayerKeys(i);
}
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
UpdateScreenSize();
fps = 0;
lastfps = 0;
InitStatus();
ResetView();
ResetEngine();
totalmoves = 0;
GrabPalette();
ResetMoveFifo();
moveframes = 0;
bInMove = false;
tclocks = totalclock;
2019-08-31 14:07:47 +00:00
nPlayerDAng = 0;
lPlayerXVel = 0;
lPlayerYVel = 0;
movefifopos = movefifoend;
2019-08-31 14:07:47 +00:00
RefreshStatus();
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
//int edi = totalclock;
tclocks2 = totalclock;
2019-08-31 14:07:47 +00:00
// Game Loop
GAMELOOP:
2019-08-31 14:07:47 +00:00
while (1)
{
if (levelnew >= 0)
2019-08-31 15:04:06 +00:00
{
2019-08-31 14:07:47 +00:00
goto LOOP1;
2019-08-31 15:04:06 +00:00
}
HandleAsync();
C_RunDelayedCommands();
2019-08-31 14:07:47 +00:00
// Section B
if (!CDplaying() && !nFreeze && !nNetPlayerCount)
2019-08-31 14:07:47 +00:00
{
int nTrack = levelnum;
if (nTrack != 0) {
nTrack--;
}
playCDtrack((nTrack % 8) + 11, true);
2019-08-27 06:08:18 +00:00
}
// TODO CONTROL_GetButtonInput();
2020-05-29 01:15:01 +00:00
updatePauseStatus();
2019-08-31 14:07:47 +00:00
CheckKeys();
2019-08-27 06:08:18 +00:00
bInMove = true;
2019-08-27 06:08:18 +00:00
if (paused)
{
tclocks = totalclock - 4;
buttonMap.ResetButtonStates();
}
else
{
while ((totalclock - ototalclock) >= 1 || !bInMove)
2019-08-27 06:08:18 +00:00
{
ototalclock = ototalclock + 1;
2019-08-27 06:08:18 +00:00
if (!((int)ototalclock&3) && moveframes < 4)
moveframes++;
2019-08-31 14:07:47 +00:00
GetLocalInput();
PlayerInterruptKeys();
nPlayerDAng = fix16_sadd(nPlayerDAng, localInput.nAngle);
inita &= kAngleMask;
lPlayerXVel += localInput.yVel * Cos(inita) + localInput.xVel * Sin(inita);
lPlayerYVel += localInput.yVel * Sin(inita) - localInput.xVel * Cos(inita);
lPlayerXVel -= (lPlayerXVel >> 5) + (lPlayerXVel >> 6);
lPlayerYVel -= (lPlayerYVel >> 5) + (lPlayerYVel >> 6);
2019-08-27 06:08:18 +00:00
sPlayerInput[nLocalPlayer].xVel = lPlayerXVel;
sPlayerInput[nLocalPlayer].yVel = lPlayerYVel;
sPlayerInput[nLocalPlayer].buttons = lLocalButtons | lLocalCodes;
sPlayerInput[nLocalPlayer].nAngle = nPlayerDAng;
sPlayerInput[nLocalPlayer].nTarget = besttarget;
2019-08-27 06:08:18 +00:00
Ra[nLocalPlayer].nTarget = besttarget;
2019-08-27 06:08:18 +00:00
lLocalCodes = 0;
nPlayerDAng = 0;
2019-08-27 06:08:18 +00:00
sPlayerInput[nLocalPlayer].horizon = PlayerList[nLocalPlayer].q16horiz;
2019-08-27 06:08:18 +00:00
while (levelnew < 0 && totalclock >= tclocks + 4)
2019-08-27 06:08:18 +00:00
{
tclocks += 4;
GameMove();
if (EndLevel)
{
goto getoutofhere;
}
2019-08-27 06:08:18 +00:00
}
}
}
getoutofhere:
bInMove = false;
PlayerInterruptKeys();
if (G_FPSLimit())
{
GameDisplay();
}
if (!EndLevel)
2019-08-31 14:07:47 +00:00
{
nMenu = MenuExitCondition;
if (nMenu != -2)
2019-08-31 14:07:47 +00:00
{
MenuExitCondition = -2;
// MENU2:
bInMove = true;
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
switch (nMenu)
2019-08-27 06:08:18 +00:00
{
2019-08-31 14:07:47 +00:00
case 0:
goto EXITGAME;
case 1:
goto STARTGAME1;
case 2:
levelnum = levelnew = menu_GameLoad(SavePosition);
lastlevel = -1;
nBestLevel = levelnew - 1;
goto LOOP2;
case 3:
forcelevel = 0;
goto STARTGAME2;
case 6:
goto GAMELOOP;
2019-08-27 06:08:18 +00:00
}
2019-11-13 18:26:29 +00:00
totalclock = ototalclock = tclocks;
bInMove = false;
2019-08-31 14:07:47 +00:00
RefreshStatus();
2019-08-27 06:08:18 +00:00
}
2019-11-24 09:03:19 +00:00
else if (buttonMap.ButtonDown(gamefunc_Map)) // e.g. TAB (to show 2D map)
2019-08-31 14:07:47 +00:00
{
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Map);
2019-08-27 06:08:18 +00:00
2019-08-31 14:07:47 +00:00
if (!nFreeze) {
nMapMode = (nMapMode+1)%3;
2019-08-31 14:07:47 +00:00
}
}
if (nMapMode != 0)
2019-08-27 06:08:18 +00:00
{
int const timerOffset = ((int) totalclock - nonsharedtimer);
nonsharedtimer += timerOffset;
2019-08-31 14:07:47 +00:00
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Zoom_In))
lMapZoom += mulscale6(timerOffset, max<int>(lMapZoom, 256));
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Zoom_Out))
lMapZoom -= mulscale6(timerOffset, max<int>(lMapZoom, 256));
lMapZoom = clamp(lMapZoom, 48, 2048);
2019-08-27 06:08:18 +00:00
}
2019-08-31 14:07:47 +00:00
if (PlayerList[nLocalPlayer].nHealth > 0)
2019-08-27 06:08:18 +00:00
{
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Inventory_Left))
2019-08-27 06:08:18 +00:00
{
SetPrevItem(nLocalPlayer);
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Inventory_Left);
2019-08-27 06:08:18 +00:00
}
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Inventory_Right))
2019-08-27 06:08:18 +00:00
{
SetNextItem(nLocalPlayer);
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Inventory_Right);
2019-08-27 06:08:18 +00:00
}
2019-11-24 09:03:19 +00:00
if (buttonMap.ButtonDown(gamefunc_Inventory))
2019-08-27 06:08:18 +00:00
{
UseCurItem(nLocalPlayer);
2019-11-24 09:03:19 +00:00
buttonMap.ClearButton(gamefunc_Inventory);
2019-08-27 06:08:18 +00:00
}
}
else {
2019-08-31 14:07:47 +00:00
SetAirFrame();
}
2019-08-27 06:08:18 +00:00
}
else
{
EndLevel = false;
FinishLevel();
}
2019-08-31 14:07:47 +00:00
fps++;
2019-08-27 06:08:18 +00:00
}
2019-08-31 14:07:47 +00:00
EXITGAME:
2019-08-27 06:08:18 +00:00
ExitGame();
2019-08-27 06:08:18 +00:00
return 0;
}
void mychangespritesect(int nSprite, int nSector)
{
2019-08-27 06:08:18 +00:00
DoKenTest();
changespritesect(nSprite, nSector);
DoKenTest();
}
void mydeletesprite(int nSprite)
{
2019-08-27 06:08:18 +00:00
if (nSprite < 0 || nSprite > kMaxSprites) {
2019-11-24 09:03:19 +00:00
I_Error("bad sprite value %d handed to mydeletesprite", nSprite);
2019-08-27 06:08:18 +00:00
}
2019-08-27 06:08:18 +00:00
deletesprite(nSprite);
2019-08-27 06:08:18 +00:00
if (nSprite == besttarget) {
besttarget = -1;
}
}
extern int currentCinemaPalette;
void DoGameOverScene()
{
2019-08-27 06:08:18 +00:00
FadeOut(0);
2020-01-01 10:35:47 +00:00
inputState.ClearAllInput();
2019-08-27 06:08:18 +00:00
NoClip();
overwritesprite(0, 0, kTile3591, 0, 2, kPalNormal, 16);
videoNextPage();
2019-09-21 15:47:55 +00:00
PlayGameOverSound();
//WaitAnyKey(3);
2019-08-27 06:08:18 +00:00
FadeOut(0);
}
void CopyTileToBitmap(short nSrcTile, short nDestTile, int xPos, int yPos)
{
2019-08-27 06:08:18 +00:00
int nOffs = tilesiz[nDestTile].y * xPos;
auto pixels = TileFiles.tileMakeWritable(nDestTile);
uint8_t *pDest = pixels + nOffs + yPos;
uint8_t *pDestB = pDest;
2019-08-27 06:08:18 +00:00
tileLoad(nSrcTile);
int destYSize = tilesiz[nDestTile].y;
int srcYSize = tilesiz[nSrcTile].y;
const uint8_t *pSrc = tilePtr(nSrcTile);
2019-08-27 06:08:18 +00:00
for (int x = 0; x < tilesiz[nSrcTile].x; x++)
{
pDest += destYSize;
for (int y = 0; y < srcYSize; y++)
{
uint8_t val = *pSrc;
2020-04-11 22:04:02 +00:00
if (val != TRANSPARENT_INDEX) {
2019-08-27 06:08:18 +00:00
*pDestB = val;
}
pDestB++;
pSrc++;
}
// reset pDestB
pDestB = pDest;
}
2019-11-05 06:15:21 +00:00
TileFiles.InvalidateTile(nDestTile);
}
void EraseScreen(int nVal)
{
2020-04-12 05:44:55 +00:00
// There's no other values than 0 ever coming through here.
twod->ClearScreen();
}
void InitSpiritHead()
{
2019-08-27 06:08:18 +00:00
char filename[20];
2019-08-27 06:08:18 +00:00
nPixels = 0;
nSpiritRepeatX = sprite[nSpiritSprite].xrepeat;
nSpiritRepeatY = sprite[nSpiritSprite].yrepeat;
tileLoad(kTileRamsesNormal); // Ramses Normal Head
2019-08-27 06:08:18 +00:00
for (int i = 0; i < kMaxSprites; i++)
{
if (sprite[i].statnum)
{
sprite[i].cstat |= 0x8000;
}
}
auto pTile = tilePtr(kTileRamsesNormal); // Ramses Normal Head
auto pGold = tilePtr(kTileRamsesGold);
2019-08-27 06:08:18 +00:00
for (int x = 0; x < 97; x++)
{
for (int y = 0; y < 106; y++)
{
2020-04-11 22:04:02 +00:00
if (*pTile != TRANSPARENT_INDEX)
2019-08-27 06:08:18 +00:00
{
pixelval[nPixels] = *(pGold + x * 106 + y);
2019-08-27 06:08:18 +00:00
origx[nPixels] = x - 48;
origy[nPixels] = y - 53;
curx[nPixels] = 0;
cury[nPixels] = 0;
vely[nPixels] = 0;
velx[nPixels] = 0;
destvelx[nPixels] = RandomSize(2) + 1;
if (curx[nPixels] > 0) {
destvelx[nPixels] = -destvelx[nPixels];
}
destvely[nPixels] = RandomSize(2) + 1;
if (cury[nPixels] > 0) {
destvely[nPixels] = -destvely[nPixels];
}
nPixels++;
}
pTile++;
}
}
2019-08-27 06:08:18 +00:00
sprite[nSpiritSprite].yrepeat = 140;
sprite[nSpiritSprite].xrepeat = 140;
sprite[nSpiritSprite].picnum = kTileRamsesWorkTile;
2019-08-27 06:08:18 +00:00
nHeadStage = 0;
2019-08-27 06:08:18 +00:00
// work tile is twice as big as the normal head size
2019-11-24 15:37:31 +00:00
Worktile = TileFiles.tileCreate(kTileRamsesWorkTile, kSpiritX * 2, kSpiritY * 2);
2019-08-27 06:08:18 +00:00
sprite[nSpiritSprite].cstat &= 0x7FFF;
2019-09-06 05:18:12 +00:00
nHeadTimeStart = (int)totalclock;
2020-04-11 22:04:02 +00:00
memset(Worktile, TRANSPARENT_INDEX, WorktileSize);
TileFiles.InvalidateTile(kTileRamsesWorkTile);
2019-08-27 06:08:18 +00:00
nPixelsToShow = 0;
2019-08-27 06:08:18 +00:00
fadecdaudio();
2019-08-27 06:08:18 +00:00
int nTrack;
2019-08-27 06:08:18 +00:00
if (levelnum == 1)
{
nTrack = 3;
}
else
{
nTrack = 7;
}
bSubTitles = playCDtrack(nTrack, false) == 0;
2019-08-27 06:08:18 +00:00
StartSwirlies();
2019-08-27 06:08:18 +00:00
sprintf(filename, "LEV%d.PUP", levelnum);
2019-09-06 05:18:12 +00:00
lNextStateChange = (int)totalclock;
lHeadStartClock = (int)totalclock;
2020-04-11 21:54:33 +00:00
auto headfd = fileSystem.OpenFileReader(filename); // 512??
if (!headfd.isOpen())
{
memset(cPupData, 0, sizeof(cPupData));
}
else
{
nPupData = headfd.Read(cPupData, sizeof(cPupData));
pPupData = cPupData;
}
2019-08-27 06:08:18 +00:00
nMouthTile = 0;
nTalkTime = 1;
}
void DimSector(short nSector)
{
short startwall = sector[nSector].wallptr;
short nWalls = sector[nSector].wallnum;
for (int i = 0; i < nWalls; i++)
{
if (wall[startwall+i].shade < 40) {
wall[startwall+i].shade++;
}
}
if (sector[nSector].floorshade < 40) {
sector[nSector].floorshade++;
}
if (sector[nSector].ceilingshade < 40) {
sector[nSector].ceilingshade++;
}
}
void CopyHeadToWorkTile(short nTile)
{
const uint8_t* pSrc = tilePtr(nTile);
2019-11-24 15:34:23 +00:00
uint8_t *pDest = &Worktile[212 * 49 + 53];
2019-08-27 06:08:18 +00:00
for (int i = 0; i < 97; i++)
{
memcpy(pDest, pSrc, 106);
2019-08-27 06:08:18 +00:00
pDest += 212;
pSrc += 106;
}
}
int DoSpiritHead()
{
2019-08-27 06:08:18 +00:00
static short word_964E6 = 0;
PlayerList[0].q16horiz = fix16_sadd(PlayerList[0].q16horiz, fix16_sdiv(fix16_ssub(nDestVertPan[0], PlayerList[0].q16horiz), fix16_from_int(4)));
2019-08-27 06:08:18 +00:00
TileFiles.InvalidateTile(kTileRamsesWorkTile);
2019-11-05 06:15:21 +00:00
2019-08-27 06:08:18 +00:00
if (nHeadStage < 2)
{
2020-04-11 22:04:02 +00:00
memset(Worktile, TRANSPARENT_INDEX, WorktileSize);
2019-08-27 06:08:18 +00:00
}
if (nHeadStage < 2 || nHeadStage != 5)
{
2019-09-06 05:18:12 +00:00
nPixelsToShow = ((int)totalclock - nHeadTimeStart) * 15;
2019-08-27 06:08:18 +00:00
if (nPixelsToShow > nPixels) {
nPixelsToShow = nPixels;
}
if (nHeadStage < 3)
{
UpdateSwirlies();
if (sprite[nSpiritSprite].shade > -127) {
sprite[nSpiritSprite].shade--;
}
word_964E6--;
if (word_964E6 < 0)
{
DimSector(sprite[nSpiritSprite].sectnum);
word_964E6 = 5;
}
if (!nHeadStage)
{
2019-09-06 05:18:12 +00:00
if (((int)totalclock - nHeadTimeStart) > 480)
2019-08-27 06:08:18 +00:00
{
nHeadStage = 1;
2019-09-06 05:18:12 +00:00
nHeadTimeStart = (int)totalclock + 480;
2019-08-27 06:08:18 +00:00
}
2019-08-27 06:08:18 +00:00
for (int i = 0; i < nPixelsToShow; i++)
{
if (destvely[i] >= 0)
{
vely[i]++;
if (vely[i] >= destvely[i])
{
destvely[i] = -(RandomSize(2) + 1);
}
}
else
{
vely[i]--;
if (vely[i] <= destvely[i])
{
destvely[i] = RandomSize(2) + 1;
}
}
// loc_13541
if (destvelx[i] >= 0)
{
velx[i]++;
if (velx[i] >= destvelx[i])
{
destvelx[i] = -(RandomSize(2) + 1);
}
}
else
{
velx[i]--;
if (velx[i] <= destvelx[i])
{
destvelx[i] = RandomSize(2) + 1;
}
}
int esi = vely[i] + (cury[i] >> 8);
if (esi < 106)
{
if (esi < -105)
{
vely[i] = 0;
esi = 0;
}
}
else
{
vely[i] = 0;
esi = 0;
}
int ebx = velx[i] + (curx[i] >> 8);
if (ebx < 97)
{
if (ebx < -96)
{
velx[i] = 0;
ebx = 0;
}
}
else
{
velx[i] = 0;
ebx = 0;
}
2019-08-31 10:36:26 +00:00
curx[i] = ebx * 256;
cury[i] = esi * 256;
2019-08-27 06:08:18 +00:00
esi += (ebx + 97) * 212;
2019-11-24 15:34:23 +00:00
Worktile[106 + esi] = pixelval[i];
2019-08-27 06:08:18 +00:00
}
return 1;
}
else
{
if (nHeadStage != 1) {
return 1;
}
uint8_t nXRepeat = sprite[nSpiritSprite].xrepeat;
2019-08-27 06:08:18 +00:00
if (nXRepeat > nSpiritRepeatX)
{
sprite[nSpiritSprite].xrepeat -= 2;
nXRepeat = sprite[nSpiritSprite].xrepeat;
if (nXRepeat < nSpiritRepeatX)
{
sprite[nSpiritSprite].xrepeat = nSpiritRepeatX;
}
}
uint8_t nYRepeat = sprite[nSpiritSprite].yrepeat;
2019-08-27 06:08:18 +00:00
if (nYRepeat > nSpiritRepeatY)
{
sprite[nSpiritSprite].yrepeat -= 2;
nYRepeat = sprite[nSpiritSprite].yrepeat;
if (nYRepeat < nSpiritRepeatY)
{
sprite[nSpiritSprite].yrepeat = nSpiritRepeatY;
}
}
int esi = 0;
2019-08-27 06:08:18 +00:00
for (int i = 0; i < nPixels; i++)
{
int eax = (origx[i] << 8) - curx[i];
int ecx = eax;
if (eax)
{
if (eax < 0) {
eax = -eax;
}
if (eax < 8)
{
curx[i] = origx[i] << 8;
ecx = 0;
}
else {
ecx >>= 3;
}
}
else
{
ecx >>= 3;
}
int var_1C = (origy[i] << 8) - cury[i];
int ebp = var_1C;
if (var_1C)
{
eax = ebp;
if (eax < 0) {
eax = -eax;
}
if (eax < 8)
{
cury[i] = origy[i] << 8;
var_1C = 0;
}
else
{
var_1C >>= 3;
}
}
else
{
var_1C >>= 3;
}
if (var_1C || ecx)
{
curx[i] += ecx;
cury[i] += var_1C;
esi++;
}
ecx = (((curx[i] >> 8) + 97) * 212) + (cury[i] >> 8);
2019-11-24 15:34:23 +00:00
Worktile[106 + ecx] = pixelval[i];
2019-08-27 06:08:18 +00:00
}
2019-09-06 05:18:12 +00:00
if (((int)totalclock - lHeadStartClock) > 600) {
2019-08-27 06:08:18 +00:00
CopyHeadToWorkTile(kTileRamsesGold);
}
int eax = ((nPixels << 4) - nPixels) / 16;
if (esi < eax)
{
2019-09-21 15:47:55 +00:00
SoundBigEntrance();
2019-08-27 06:08:18 +00:00
AddGlow(sprite[nSpiritSprite].sectnum, 20);
AddFlash(
sprite[nSpiritSprite].sectnum,
sprite[nSpiritSprite].x,
sprite[nSpiritSprite].y,
sprite[nSpiritSprite].z,
128);
nHeadStage = 3;
TintPalette(255, 255, 255);
2019-08-27 06:08:18 +00:00
CopyHeadToWorkTile(kTileRamsesNormal);
}
return 1;
}
}
else
{
FixPalette();
if (!nPalDiff)
{
nFreeze = 2;
nHeadStage++;
}
return 0;
}
}
else
{
2019-09-06 05:18:12 +00:00
if (lNextStateChange <= (int)totalclock)
2019-08-27 06:08:18 +00:00
{
if (nPupData)
{
short nPupVal = *pPupData;
pPupData++;
nPupData -= 2;
if (nPupData > 0)
{
lNextStateChange = (nPupVal + lHeadStartClock) - 10;
nTalkTime = !nTalkTime;
}
else
{
nTalkTime = 0;
nPupData = 0;
}
}
else if (!bSubTitles)
{
if (!CDplaying())
{
levelnew = levelnum + 1;
fadecdaudio();
}
}
}
word_964E8--;
if (word_964E8 <= 0)
{
word_964EA = RandomBit() * 2;
word_964E8 = RandomSize(5) + 4;
}
int ebx = 592;
word_964EC--;
if (word_964EC < 3)
{
ebx = 593;
if (word_964EC <= 0) {
word_964EC = RandomSize(6) + 4;
}
}
ebx += word_964EA;
2019-11-24 15:34:23 +00:00
uint8_t *pDest = &Worktile[10441];
const uint8_t* pSrc = tilePtr(ebx);
2019-08-27 06:08:18 +00:00
for (int i = 0; i < 97; i++)
{
memcpy(pDest, pSrc, 106);
2019-08-27 06:08:18 +00:00
pDest += 212;
pSrc += 106;
}
if (nTalkTime)
{
if (nMouthTile < 2) {
nMouthTile++;
}
}
else if (nMouthTile != 0)
{
nMouthTile--;
}
if (nMouthTile)
{
short nTileSizeX = tilesiz[nMouthTile + 598].x;
short nTileSizeY = tilesiz[nMouthTile + 598].y;
2019-11-24 15:34:23 +00:00
uint8_t *pDest = &Worktile[212 * (97 - nTileSizeX / 2)] + (159 - nTileSizeY);
const uint8_t *pSrc = tilePtr(nMouthTile + 598);
2019-08-27 06:08:18 +00:00
while (nTileSizeX > 0)
{
memcpy(pDest, pSrc, nTileSizeY);
2019-08-27 06:08:18 +00:00
nTileSizeX--;
pDest += 212;
pSrc += nTileSizeY;
}
}
2019-08-27 06:08:18 +00:00
return 1;
}
2019-08-27 06:08:18 +00:00
// TEMP FIXME - temporary return value. what to return here? 1?
2019-08-27 06:08:18 +00:00
return 0;
}
2019-12-26 18:41:42 +00:00
bool GameInterface::CanSave()
{
2020-05-29 01:15:01 +00:00
return !bRecord && !bPlayback && !paused && !bInDemo && nTotalPlayers == 1;
2019-12-26 18:41:42 +00:00
}
::GameInterface* CreateInterface()
{
return new GameInterface;
}
// This is only the static global data.
static SavegameHelper sgh("exhumed",
SA(cPupData),
SV(nPupData),
SV(nPixels),
SV(besttarget),
SA(curx),
SA(cury),
SA(destvelx),
SA(destvely),
SA(pixelval),
SA(origy),
SA(origx),
SA(velx),
SA(vely),
SV(nMouthTile),
SV(nSpiritSprite),
SV(word_964E8),
SV(word_964EA),
SV(word_964EC),
SV(nSpiritRepeatX),
SV(nSpiritRepeatY),
SV(nPixelsToShow),
SV(nCreaturesLeft), // todo: also maintain a total counter.
SV(nFreeze),
SV(nSnakeCam),
SV(nLocalSpr),
SV(levelnew),
SV(nClockVal), // kTile3603
SV(nRedTicks),
SV(nAlarmTicks),
SV(nButtonColor),
SV(nEnergyChan),
SV(lCountDown),
SV(nEnergyTowers),
SV(nHeadStage),
SV(nTalkTime),
SV(levelnum),
SV(moveframes),
SV(totalmoves),
SV(nCurBodyNum),
SV(nBodyTotal),
SV(bSnakeCam),
SV(bSlipMode),
SV(lHeadStartClock),
SV(lNextStateChange),
SV(nHeadTimeStart),
SV(localclock),
SV(tclocks),
SV(tclocks2),
SV(totalclock),
nullptr);
void SaveTextureState()
{
auto fw = WriteSavegameChunk("texture");
int pupOffset = pPupData? int(pPupData - cPupData) : -1;
// There is really no good way to restore these tiles, so it's probably best to save them as well, so that they can be reloaded with the exact state they were left in
fw->Write(&pupOffset, 4);
uint8_t loaded = !!Worktile;
fw->Write(&loaded, 1);
if (Worktile) fw->Write(Worktile, WorktileSize);
auto pixels = TileFiles.tileMakeWritable(kTile3603);
fw->Write(pixels, tilesiz[kTile3603].x * tilesiz[kTile3603].y);
pixels = TileFiles.tileMakeWritable(kEnergy1);
fw->Write(pixels, tilesiz[kEnergy1].x * tilesiz[kEnergy1].y);
pixels = TileFiles.tileMakeWritable(kEnergy2);
fw->Write(pixels, tilesiz[kEnergy2].x * tilesiz[kEnergy2].y);
}
void LoadTextureState()
{
auto fr = ReadSavegameChunk("texture");
int pofs;
fr.Read(&pofs, 4);
pPupData = pofs == -1 ? nullptr : cPupData + pofs;
uint8_t loaded;
fr.Read(&loaded, 1);
if (loaded)
{
Worktile = TileFiles.tileCreate(kTileRamsesWorkTile, kSpiritX * 2, kSpiritY * 2);
fr.Read(Worktile, WorktileSize);
}
auto pixels = TileFiles.tileMakeWritable(kTile3603);
fr.Read(pixels, tilesiz[kTile3603].x * tilesiz[kTile3603].y);
pixels = TileFiles.tileMakeWritable(kEnergy1);
fr.Read(pixels, tilesiz[kEnergy1].x * tilesiz[kEnergy1].y);
pixels = TileFiles.tileMakeWritable(kEnergy2);
fr.Read(pixels, tilesiz[kEnergy2].x * tilesiz[kEnergy2].y);
TileFiles.InvalidateTile(kTileRamsesWorkTile);
TileFiles.InvalidateTile(kTile3603);
TileFiles.InvalidateTile(kEnergy1);
TileFiles.InvalidateTile(kEnergy2);
}
END_PS_NS