+2009-09-25
+ * fixed another translation problem from VisualBasic to C (where "int"
+ should be "short") causing unsolvable demos with bugs and terminals
+ ("bugs" being related to the Supaplex "buggy base" element here ;-) )
+
2009-09-23
* fixed bug when reading Supaplex single level files (preventing loader
from seeking to level position like in Supaplex level package files)
-#define COMPILE_DATE_STRING "2009-09-25 20:53"
+#define COMPILE_DATE_STRING "2009-10-11 22:17"
*level = li; /* copy temporary buffer back to level data */
setLevelInfoToDefaults_EM();
+ setLevelInfoToDefaults_SP();
level->native_em_level = &native_em_level;
+ level->native_sp_level = &native_sp_level;
level->file_version = FILE_VERSION_ACTUAL;
level->game_version = GAME_VERSION_ACTUAL;
}
}
-static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
- struct LevelFileInfo *level_file_info)
-{
- if (!LoadNativeLevel_EM(level_file_info->filename))
- level->no_valid_file = TRUE;
-}
-
-void CopyNativeLevel_RND_to_Native(struct LevelInfo *level)
-{
- if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
- CopyNativeLevel_RND_to_EM(level);
-}
-
-void CopyNativeLevel_Native_to_RND(struct LevelInfo *level)
-{
- if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
- CopyNativeLevel_EM_to_RND(level);
-}
-
/* ------------------------------------------------------------------------- */
/* functions for loading SP level */
/* ------------------------------------------------------------------------- */
+#if 0
+
#define NUM_SUPAPLEX_LEVELS_PER_PACKAGE 111
#define SP_LEVEL_SIZE 1536
#define SP_LEVEL_XSIZE 60
*level = multipart_level;
}
+#endif
+
+void CopyNativeLevel_RND_to_SP(struct LevelInfo *level)
+{
+ /* ... yet to be written ... */
+}
+
+void CopyNativeLevel_SP_to_RND(struct LevelInfo *level)
+{
+ LevelInfoType *header = &native_sp_level.header;
+ int i, x, y;
+
+ level->fieldx = native_sp_level.width;
+ level->fieldy = native_sp_level.height;
+
+ for (x = 0; x < level->fieldx; x++)
+ {
+ for (y = 0; y < level->fieldy; y++)
+ {
+ int element_old = native_sp_level.playfield[x][y];
+ int element_new;
+
+ if (element_old <= 0x27)
+ element_new = getMappedElement(EL_SP_START + element_old);
+ else if (element_old == 0x28)
+ element_new = EL_INVISIBLE_WALL;
+ else
+ {
+ Error(ERR_WARN, "invalid element %d at position %d, %d",
+ element_old, x, y);
+
+ element_new = EL_UNKNOWN;
+ }
+
+ level->field[x][y] = element_new;
+ }
+ }
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ level->initial_player_gravity[i] =
+ (header->InitialGravity == 1 ? TRUE : FALSE);
+
+ for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
+ level->name[i] = header->LevelTitle[i];
+ level->name[SP_LEVEL_NAME_LEN] = '\0';
+
+ level->gems_needed = header->InfotronsNeeded;
+
+ for (i = 0; i < header->SpecialPortCount; i++)
+ {
+ SpecialPortType *port = &header->SpecialPort[i];
+ int port_location = port->PortLocation;
+ int gravity = port->Gravity;
+ int port_x, port_y, port_element;
+
+ port_x = (port_location / 2) % level->fieldx;
+ port_y = (port_location / 2) / level->fieldx;
+
+ if (port_x < 0 || port_x >= level->fieldx ||
+ port_y < 0 || port_y >= level->fieldy)
+ {
+ Error(ERR_WARN, "special port position (%d, %d) out of bounds",
+ port_x, port_y);
+
+ continue;
+ }
+
+ port_element = level->field[port_x][port_y];
+
+ if (port_element < EL_SP_GRAVITY_PORT_RIGHT ||
+ port_element > EL_SP_GRAVITY_PORT_UP)
+ {
+ Error(ERR_WARN, "no special port at position (%d, %d)", port_x, port_y);
+
+ continue;
+ }
+
+ /* change previous (wrong) gravity inverting special port to either
+ gravity enabling special port or gravity disabling special port */
+ level->field[port_x][port_y] +=
+ (gravity == 1 ? EL_SP_GRAVITY_ON_PORT_RIGHT :
+ EL_SP_GRAVITY_OFF_PORT_RIGHT) - EL_SP_GRAVITY_PORT_RIGHT;
+ }
+
+ /* change special gravity ports without database entries to normal ports */
+ for (x = 0; x < level->fieldx; x++)
+ for (y = 0; y < level->fieldy; y++)
+ if (level->field[x][y] >= EL_SP_GRAVITY_PORT_RIGHT &&
+ level->field[x][y] <= EL_SP_GRAVITY_PORT_UP)
+ level->field[x][y] += EL_SP_PORT_RIGHT - EL_SP_GRAVITY_PORT_RIGHT;
+
+ level->time = 0; /* no time limit */
+ level->amoeba_speed = 0;
+ level->time_magic_wall = 0;
+ level->time_wheel = 0;
+ level->amoeba_content = EL_EMPTY;
+
+#if 1
+ /* original Supaplex does not use score values -- use default values */
+#else
+ for (i = 0; i < LEVEL_SCORE_ELEMENTS; i++)
+ level->score[i] = 0;
+#endif
+
+ /* there are no yamyams in supaplex levels */
+ for (i = 0; i < level->num_yamyam_contents; i++)
+ for (x = 0; x < 3; x++)
+ for (y = 0; y < 3; y++)
+ level->yamyam_content[i].e[x][y] = EL_EMPTY;
+}
+
+
+/* ------------------------------------------------------------------------- */
+/* functions for loading DC level */
+/* ------------------------------------------------------------------------- */
#define DC_LEVEL_HEADER_SIZE 344
#endif
+/* ------------------------------------------------------------------------- */
+/* functions for handling native levels */
+/* ------------------------------------------------------------------------- */
+
+static void LoadLevelFromFileInfo_EM(struct LevelInfo *level,
+ struct LevelFileInfo *level_file_info)
+{
+ if (!LoadNativeLevel_EM(level_file_info->filename))
+ level->no_valid_file = TRUE;
+}
+
+static void LoadLevelFromFileInfo_SP(struct LevelInfo *level,
+ struct LevelFileInfo *level_file_info)
+{
+ int pos = 0;
+
+ /* determine position of requested level inside level package */
+ if (level_file_info->packed)
+ pos = level_file_info->nr - leveldir_current->first_level;
+
+ if (!LoadNativeLevel_SP(level_file_info->filename, pos))
+ level->no_valid_file = TRUE;
+}
+
+void CopyNativeLevel_RND_to_Native(struct LevelInfo *level)
+{
+ if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
+ CopyNativeLevel_RND_to_EM(level);
+ else if (level->game_engine_type == GAME_ENGINE_TYPE_SP)
+ CopyNativeLevel_RND_to_SP(level);
+}
+
+void CopyNativeLevel_Native_to_RND(struct LevelInfo *level)
+{
+ if (level->game_engine_type == GAME_ENGINE_TYPE_EM)
+ CopyNativeLevel_EM_to_RND(level);
+ else if (level->game_engine_type == GAME_ENGINE_TYPE_SP)
+ CopyNativeLevel_SP_to_RND(level);
+}
+
+
/* ------------------------------------------------------------------------- */
/* functions for loading generic level */
/* ------------------------------------------------------------------------- */
*A = *A - B;
}
-int SHR(int Var, int Count)
+int SHR(int *Var, int Count)
{
int SHR;
int i;
- if (Var & 0x8000)
+ if (*Var & 0x8000)
{
- Var = ((Var & 0x7FFF) / 2) | 0x4000;
+ *Var = ((*Var & 0x7FFF) / 2) | 0x4000;
}
else
{
- Var = Var / 2;
+ *Var = *Var / 2;
}
for (i = 2; i <= Count; i++)
{
- Var = Var / 2;
+ *Var = *Var / 2;
}
return SHR;
}
-int SHL(int Var, int Count)
+int SHL(int *Var, int Count)
{
int SHL;
for (i = 1; i <= Count; i++)
{
- Var = Var & 0x7FFF;
- if ((Var & 0x4000) != 0)
+ *Var = *Var & 0x7FFF;
+ if ((*Var & 0x4000) != 0)
{
- Var = (2 * (Var & 0x3FFF)) | 0x8000;
+ *Var = (2 * (*Var & 0x3FFF)) | 0x8000;
}
else
{
- Var = 2 * Var;
+ *Var = 2 * *Var;
}
}
extern void MovLowByte(int *Var, int Val);
extern void MySub(int *A, int B);
extern void Neg(int *Val);
-extern int SHL(int Var, int Count);
-extern int SHR(int Var, int Count);
+extern int SHL(int *Var, int Count);
+extern int SHR(int *Var, int Count);
extern int SgnHighByte(int Var);
extern void XCHG(int A, int B);
return GetStretchY;
}
-void ReadLevel()
+void OLD_ReadLevel()
{
#if 1
static char CurPathTEST[1024];
printf("::: LInfo.DemoRandomSeed == %d\n", LInfo.DemoRandomSeed);
#endif
+#if 0
+ printf("::: LInfo.SpecialPortCount == %d\n", LInfo.SpecialPortCount);
+ for (i = 0; i < LInfo.SpecialPortCount; i++)
+ {
+ int port_x = (LInfo.SpecialPort[i].PortLocation / 2) % FieldWidth;
+ int port_y = (LInfo.SpecialPort[i].PortLocation / 2) / FieldWidth;
+
+ printf("::: %d: port_location == %d => (%d, %d)\n",
+ i, LInfo.SpecialPort[i].PortLocation, port_x, port_y);
+ }
+#endif
+
RandomSeed = LInfo.DemoRandomSeed;
#if 0
Close();
#endif
}
+
+void ReadLevel()
+{
+#if 0
+ OLD_ReadLevel();
+
+ // return;
+#endif
+
+ copyInternalEngineVars_SP();
+
+ LevelNumber = level_nr;
+
+ if (!DemoFlag || !DemoAvailable)
+ subRandomize();
+
+ LevelLoaded = True;
+}
}
+#if 0
+
// static void menPlayDemo_Click()
void menPlayDemo_Click()
{
menPlay_Click();
+#if 1
+ return;
+#endif
+
#if 0
if (LevelStatus != 1)
lblStatus = "Demo Failed";
DemoFlag = 0;
}
+#else
+
+// static void menPlayDemo_Click()
+void menPlayDemo_Click()
+{
+ DemoFlag = 1;
+ RecordDemoFlag = 0;
+
+#if 0
+ lblStatus = "Demo Playback";
+#endif
+
+ menPlay_Click();
+
+#if 0
+ if (LevelStatus != 1)
+ lblStatus = "Demo Failed";
+#endif
+
+ DemoFlag = 0;
+}
+
+#endif
+
#if 0
static void menRec_Click()
// Play a game/demo
// ==========================================================================
+int subMainGameLoop_Init()
+{
+ int subMainGameLoop;
+
+ // int al, bx;
+ // int bx;
+#if 0
+ TickCountObject Clock;
+ currency LastFrame;
+#endif
+
+ if (DemoFlag != 0)
+ {
+#if 1
+ printf("::: playing demo ...\n");
+#endif
+
+ // EP set level success byte: demo, not game
+ WasDemoFlag = 1;
+ EP_GameDemoVar0DAA = 0; // demo
+ }
+ else // loc_g_1836:
+ {
+#if 1
+ printf("::: playing game ...\n");
+#endif
+
+ // EP set level success byte: game, not demo
+ WasDemoFlag = 0;
+ EP_GameDemoVar0DAA = 1; // game
+ }
+
+ // RestartGameLoop:
+ // If RecordDemoFlag = 1 Then
+ // RecordDemoFlag = 0 ' clear Demo Recording flag
+ // Call subDisplayPlayingTime ' playing time on screen
+ // ' Record key still pressed?' >= (Ctrl-)F1 and <= (Ctrl-)F10
+ // While &H3B <= KeyScanCode7 And KeyScanCode7 <= &H44
+ // ' yes -> wait until released
+ // ' should we DoEvents here???? ... depends on how ... but yes!
+ // ' ...or we can rather poll the keyboardstate inside this loop???
+ // Wend
+ // Call subInitGameConditions ' Init game conditions (vars)
+ // If MusicOnFlag = 0 Then Call subMusicInit
+ // WasDemoFlag = 0 ' no demo anymore
+ // EP_GameDemoVar0DAA = 1 ' force game
+ // End If
+
+ // This was a bug in the original Supaplex: sometimes red disks could not
+ // be released. This happened If Murphy was killed DURING a red disk release
+ // and the next try started.
+
+ RedDiskReleasePhase = 0; // (re-)enable red disk release
+ UpdatedFlag = 0;
+ GameLoopRunning = 1;
+ LevelStatus = 0;
+
+ return subMainGameLoop;
+}
+
+int subMainGameLoop_Main()
+{
+ int subMainGameLoop;
+ int bx;
+
+ // ----------------------------------------------------------------------------
+ // --------------------- START OF GAME-BUSY LOOP ------------------------------
+ // ----------------------------------------------------------------------------
+
+locRepeatMainGameLoop: // start repeating game loop
+
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ // FS synchronization
+ while (PauseMode != 0)
+ {
+ DoEvents();
+ }
+
+ do
+ {
+ DoEvents(); // user may klick on menus or move the window here ...
+ }
+#if 1
+ while (0);
+#else
+ while (Clock.TickDiffUS(LastFrame) < DeltaT); // wait till its time for the next frame
+#endif
+
+ // never any additional code between here!
+#if 0
+ LastFrame = Clock.TickNow(); // store the frame time
+#endif
+ // never any additional code between here!
+ if (! NoDisplayFlag) // copy the BackBuffer(=Stage) to visible screen
+ Stage.Blt();
+
+ // FS end of synchronization
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ if (EndFlag)
+ goto locExitMainGameLoop;
+
+ // If DemoFlag = 0 Then Call subCheckJoystick ' check joystick
+ // bx = subCheckRightMouseButton() ' check (right) mouse button
+ // If bx = 2 And LeadOutCounter < 1 Then KillMurphyFlag = 1 ' lead-out busy after quit? -> kill Murphy!
+
+ // If DebugVersionFlag <> 0 Then ' debug mode on?
+ // If Data_SubRest <> 0 Then Data_SubRest = Data_SubRest - 1
+ // If keyEnter <> 0 Then GoTo loc_g_186F ' Was it the Enter key? -> yes--skip! No mouse!
+ // ' fixes ENTER bug If no mouse driver!
+ // If bx <> 1 Then GoTo loc_g_186F ' Left button=Init game field
+ // ' Also Enter If no mouse!
+ // If Data_SubRest <> 0 Then GoTo loc_g_186F
+ // Data_SubRest = 10
+ // Call subRestoreFancy
+ // Call subDisplayLevel ' Paint (Init) game field
+ // Call subConvertToEasySymbols ' Convert to easy symbols
+ // End If
+
+ // loc_g_186F:
+
+ subProcessKeyboardInput(); // Check keyboard, act on keys
+
+ // 'HACK:
+ // TimerVar = TimerVar + 1
+ // DoEvents
+ // GoTo loc_g_186F
+ // 'END HACK
+ // If RecordDemoFlag = 1 Then GoTo RestartGameLoop
+
+ // ----------------------------------------------------------------------------
+ //
+
+#if 0
+ printf("::: >>>>>>>>>> MainGameLoop.c: subDoGameStuff() START\n");
+#endif
+
+ subDoGameStuff(); // do all game stuff
+
+#if 0
+ printf("::: <<<<<<<<<< MainGameLoop.c: subDoGameStuff() END\n");
+#endif
+
+ //
+ // ----------------------------------------------------------------------------
+
+ // Call subDisplayPlayingTime ' playing time on screen
+
+ subCheckRestoreRedDiskCountDisplay(); // Restore panel: red-disk hole
+
+ subRedDiskReleaseExplosion(); // Red Disk release and explode
+ subFollowUpExplosions(); // every explosion may cause up to 8 following explosions
+
+ bx = subCalculateScreenScrollPos(); // calculate screen start addrs
+
+ ScreenPosition = bx;
+
+ // Now new X and new Y are calculated, and bx = screen position = ScreenPosition
+ data_h_Ytmp = ScreenScrollYPos; // copy Y for next soft scroll
+ data_h_Xtmp = ScreenScrollXPos; // copy X for next soft scroll
+ if ((! UserDragFlag) && AutoScrollFlag)
+ {
+#if 0
+ printf("::: MainGameLoop.c: subMainGameLoop(): %d, %d\n", ScreenScrollXPos, ScreenScrollYPos);
+#endif
+
+ ScrollTowards(ScreenScrollXPos, ScreenScrollYPos);
+ }
+
+ if (ForcedExitFlag != 0) // Forced Exit?' yes--exit!
+ goto locExitMainGameLoop;
+
+ TimerVar = TimerVar + 1;
+
+#if 0
+ if (bCapturePane)
+ MainForm.SaveSnapshot(TimerVar);
+#endif
+
+ // If Not NoDisplayFlag Then
+ // With MainForm.lblFrameCount
+ // .Caption = TimerVar
+ // .Refresh
+ // End With
+ // End If
+ if (ExitToMenuFlag == 1)
+ goto locExitMainGameLoop;
+
+ if (LeadOutCounter == 0) // no lead-out: game busy
+ goto locRepeatMainGameLoop;
+
+ // ----------------------------------------------------------------------------
+ // ---------------------- END OF GAME-BUSY LOOP -------------------------------
+ // ----------------------------------------------------------------------------
+ LeadOutCounter = LeadOutCounter - 1; // do more lead-out after quit
+ if (LeadOutCounter != 0) // lead-out not ready: more
+ goto locRepeatMainGameLoop;
+
+ // lead-out done: exit now
+ // ---------------------- END OF GAME-BUSY LOOP (including lead-out) ----------
+
+locExitMainGameLoop:
+ do
+ {
+ DoEvents(); // user may klick on menus or move the window here ...
+ }
+#if 1
+ while (0);
+#else
+ while (Clock.TickDiffUS(LastFrame) < DeltaT); // wait till its time for the next frame
+#endif
+
+ Stage.Blt(); // blit the last frame
+ GameLoopRunning = 0;
+
+#if 0
+ MainForm.menStop_Click();
+ MainForm.PanelVisible = True;
+#endif
+
+ // If DemoRecordingFlag <> 0 Then Call subCloseDemoRecordingFile ' Demo recording on? -> close opened demo file (w)
+ if (SavedGameFlag != 0) // after savegame: no update!
+ {
+ SavedGameFlag = 0;
+ return subMainGameLoop;
+ }
+
+ SavedGameFlag = 0;
+ if (UpdateTimeFlag == 0) // update time?
+ return subMainGameLoop;
+
+ if (UpdatedFlag == 0) // update playing time
+ subUpdatePlayingTime();
+
+
+ return subMainGameLoop;
+} // subMainGameLoop
+
int subMainGameLoop()
{
int subMainGameLoop;
extern int subCalculateScreenScrollPos();
extern int subMainGameLoop();
+extern int subMainGameLoop_Init();
+extern int subMainGameLoop_Main();
extern void subUpdatePlayingTime();
extern boolean AutoScrollFlag;
# -----------------------------------------------------------------------------
SRCS = init.c \
+ file.c \
main.c \
vb_lib.c \
vb_vars.c \
modMPX.c
OBJS = init.o \
+ file.o \
main.o \
vb_lib.o \
vb_vars.o \
for (i = 0; i < cx; i++)
{
+#if 1
+ /* this assumes that PortLocation is stored as big endian */
+ bx = LInfo.SpecialPort[i].PortLocation;
+#else
+ /* this assumes that PortLocation is stored as little endian */
bx = HighByte(LInfo.SpecialPort[i].PortLocation);
MovHighByte(&bx, LowByte(LInfo.SpecialPort[i].PortLocation));
+#endif
if (bx / 2 == si)
{
/* constant definitions */
/* ------------------------------------------------------------------------- */
+#define SP_MAX_PLAYFIELD_WIDTH MAX_PLAYFIELD_WIDTH
+#define SP_MAX_PLAYFIELD_HEIGHT MAX_PLAYFIELD_HEIGHT
+
+#define SP_NUM_LEVELS_PER_PACKAGE 111
+
+#define SP_PLAYFIELD_WIDTH 60
+#define SP_PLAYFIELD_HEIGHT 24
+#define SP_LEVEL_NAME_LEN 23
+#define SP_MAX_SPECIAL_PORTS 10
+
+#define SP_HEADER_SIZE 96
+#define SP_PLAYFIELD_SIZE (SP_PLAYFIELD_WIDTH * \
+ SP_PLAYFIELD_HEIGHT)
+#define SP_LEVEL_SIZE (SP_HEADER_SIZE + SP_PLAYFIELD_SIZE)
+
+#define SP_FRAMES_PER_SECOND 35
+#define SP_MAX_TAPE_LEN 64010 /* (see "spfix63.doc") */
+
/* ------------------------------------------------------------------------- */
/* data structure definitions */
/* ------------------------------------------------------------------------- */
+#ifndef HAS_SpecialPortType
+typedef struct
+{
+#if 1
+ short PortLocation; // = 2*(x+(y*60)) /* big endian format */
+#else
+ int PortLocation; // = 2*(x+(y*60))
+#endif
+ byte Gravity; // 1 = turn on, anything else (0) = turn off
+ byte FreezeZonks; // 2 = turn on, anything else (0) = turn off (1=off!)
+ byte FreezeEnemies; // 1 = turn on, anything else (0) = turn off
+ byte UnUsed;
+} SpecialPortType;
+#define HAS_SpecialPortType
+#endif
+
+#ifndef HAS_LevelInfoType
+typedef struct
+{
+ byte UnUsed[4];
+ byte InitialGravity; // 1=on, anything else (0) = off
+ byte Version; // SpeedFixVersion XOR &H20
+ char LevelTitle[23];
+ byte InitialFreezeZonks; // 2=on, anything else (0) = off. (1=off too!)
+ byte InfotronsNeeded;
+
+ // Number of Infotrons needed. 0 means that Supaplex will count the total
+ // amount of Infotrons in the level, and use the low byte of that number.
+ // (A multiple of 256 Infotrons will then result in 0-to-eat, etc.!)
+ byte SpecialPortCount; // Maximum 10 allowed!
+ SpecialPortType SpecialPort[10];
+ byte SpeedByte; // = Speed XOR Highbyte(RandomSeed)
+ byte CheckSumByte; // = CheckSum XOR SpeedByte
+#if 1
+ short DemoRandomSeed; /* little endian format */
+#else
+ int DemoRandomSeed;
+#endif
+} LevelInfoType;
+#define HAS_LevelInfoType
+#endif
+
struct GlobalInfo_SP
{
};
struct LevelInfo_SP
{
- int file_version;
+ LevelInfoType header;
+
+ byte playfield[SP_MAX_PLAYFIELD_WIDTH][SP_MAX_PLAYFIELD_HEIGHT];
+
+ int width, height;
+
+ boolean demo_available;
+
+ byte demo[SP_MAX_TAPE_LEN];
+ int demo_length;
};
struct GraphicInfo_SP
extern unsigned int InitEngineRandom_SP(long);
extern void setLevelInfoToDefaults_SP();
-extern boolean LoadNativeLevel_SP(char *);
+extern void copyInternalEngineVars_SP();
+extern boolean LoadNativeLevel_SP(char *, int);
extern void BackToFront_SP(void);
extern void BlitScreenToBitmap_SP(Bitmap *);
--- /dev/null
+
+#include "main_sp.h"
+#include "global.h"
+
+
+/* ------------------------------------------------------------------------- */
+/* functions for loading Supaplex level */
+/* ------------------------------------------------------------------------- */
+
+void setLevelInfoToDefaults_SP()
+{
+ LevelInfoType *header = &native_sp_level.header;
+ char *empty_title = "-------- EMPTY --------";
+ int i, x, y;
+
+ native_sp_level.width = SP_PLAYFIELD_WIDTH;
+ native_sp_level.height = SP_PLAYFIELD_HEIGHT;
+
+ for (x = 0; x < native_sp_level.width; x++)
+ for (y = 0; y < native_sp_level.height; y++)
+ native_sp_level.playfield[x][y] = fiSpace;
+
+ /* copy string (without terminating '\0' character!) */
+ for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
+ header->LevelTitle[i] = empty_title[i];
+
+ header->InitialGravity = 0;
+ header->Version = 0;
+ header->InitialFreezeZonks = 0;
+ header->InfotronsNeeded = 0;
+ header->SpecialPortCount = 0;
+ header->SpeedByte = 0;
+ header->CheckSumByte = 0;
+ header->DemoRandomSeed = 0;
+
+ for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
+ {
+ SpecialPortType *port = &header->SpecialPort[i];
+
+ port->PortLocation = 0;
+ port->Gravity = 0;
+ port->FreezeZonks = 0;
+ port->FreezeEnemies = 0;
+ }
+
+ native_sp_level.demo_available = FALSE;
+ native_sp_level.demo_length = 0;
+}
+
+void copyInternalEngineVars_SP()
+{
+ int i, x, y;
+
+ LInfo = native_sp_level.header;
+
+ FieldWidth = native_sp_level.width;
+ FieldHeight = native_sp_level.height;
+ HeaderSize = 96;
+
+ FieldMax = (FieldWidth * FieldHeight) + HeaderSize - 1;
+ LevelMax = (FieldWidth * FieldHeight) - 1;
+
+ FileMax = FieldMax + native_sp_level.demo_length;
+
+ PlayField8 = REDIM_1D(sizeof(byte), 0, FileMax + 1 - 1);
+ DisPlayField = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
+ PlayField16 = REDIM_1D(sizeof(int), -FieldWidth, FieldMax);
+
+ for (i = 0, y = 0; y < native_sp_level.height; y++)
+ {
+ for (x = 0; x < native_sp_level.width; x++)
+ {
+ PlayField8[i] = native_sp_level.playfield[x][y];
+
+ PlayField16[i] = PlayField8[i];
+ DisPlayField[i] = PlayField8[i];
+ PlayField8[i] = 0;
+
+ i++;
+ }
+ }
+
+ if (native_sp_level.demo_available)
+ {
+ DemoAvailable = True;
+
+ for (i = 0; i < native_sp_level.demo_length; i++)
+ PlayField8[FieldMax + i + 1] = native_sp_level.demo[i];
+ }
+
+ AnimationPosTable = REDIM_1D(sizeof(int), 0, LevelMax - 2 * FieldWidth);
+ AnimationSubTable = REDIM_1D(sizeof(byte), 0, LevelMax - 2 * FieldWidth);
+ TerminalState = REDIM_1D(sizeof(byte), 0, FieldMax + 1 - 1);
+
+ DemoPointer = FieldMax + 1;
+ DemoOffset = DemoPointer;
+ DemoKeyRepeatCounter = 0;
+
+ GravityFlag = LInfo.InitialGravity;
+ FreezeZonks = LInfo.InitialFreezeZonks;
+
+ RandomSeed = LInfo.DemoRandomSeed;
+
+ LevelLoaded = True;
+}
+
+static void LoadNativeLevelFromFileStream_SP(FILE *file, boolean demo_available)
+{
+ LevelInfoType *header = &native_sp_level.header;
+ int i, x, y;
+
+ /* for details of the Supaplex level format, see Herman Perk's Supaplex
+ documentation file "SPFIX63.DOC" from his Supaplex "SpeedFix" package */
+
+ native_sp_level.width = SP_PLAYFIELD_WIDTH;
+ native_sp_level.height = SP_PLAYFIELD_HEIGHT;
+
+ /* read level playfield (width * height == 60 * 24 tiles == 1440 bytes) */
+ for (y = 0; y < native_sp_level.height; y++)
+ for (x = 0; x < native_sp_level.width; x++)
+ native_sp_level.playfield[x][y] = getFile8Bit(file);
+
+ /* read level header (96 bytes) */
+
+ ReadUnusedBytesFromFile(file, 4); /* (not used by Supaplex engine) */
+
+ /* initial gravity: 1 == "on", anything else (0) == "off" */
+ header->InitialGravity = getFile8Bit(file);
+
+ /* SpeedFixVersion XOR 0x20 */
+ header->Version = getFile8Bit(file);
+
+ /* level title in uppercase letters, padded with dashes ("-") (23 bytes) */
+ for (i = 0; i < SP_LEVEL_NAME_LEN; i++)
+ header->LevelTitle[i] = getFile8Bit(file);
+
+ /* initial "freeze zonks": 2 == "on", anything else (0, 1) == "off" */
+ header->InitialFreezeZonks = getFile8Bit(file);
+
+ /* number of infotrons needed; 0 means that Supaplex will count the total
+ amount of infotrons in the level and use the low byte of that number
+ (a multiple of 256 infotrons will result in "0 infotrons needed"!) */
+ header->InfotronsNeeded = getFile8Bit(file);
+
+ /* number of special ("gravity") port entries below (maximum 10 allowed) */
+ header->SpecialPortCount = getFile8Bit(file);
+
+#if 0
+ printf("::: num_special_ports == %d\n", header->SpecialPortCount);
+#endif
+
+ /* database of properties of up to 10 special ports (6 bytes per port) */
+ for (i = 0; i < SP_MAX_SPECIAL_PORTS; i++)
+ {
+ SpecialPortType *port = &header->SpecialPort[i];
+
+ /* high and low byte of the location of a special port; if (x, y) are the
+ coordinates of a port in the field and (0, 0) is the top-left corner,
+ the 16 bit value here calculates as 2 * (x + (y * 60)) (this is twice
+ of what may be expected: Supaplex works with a game field in memory
+ which is 2 bytes per tile) */
+ port->PortLocation = getFile16BitBE(file);
+
+#if 0
+ {
+ int port_x = (port->PortLocation / 2) % SP_PLAYFIELD_WIDTH;
+ int port_y = (port->PortLocation / 2) / SP_PLAYFIELD_WIDTH;
+
+ printf("::: %d: port_location == %d => (%d, %d)\n",
+ i, port->PortLocation, port_x, port_y);
+ }
+#endif
+
+ /* change gravity: 1 == "turn on", anything else (0) == "turn off" */
+ port->Gravity = getFile8Bit(file);
+
+ /* "freeze zonks": 2 == "turn on", anything else (0, 1) == "turn off" */
+ port->FreezeZonks = getFile8Bit(file);
+
+ /* "freeze enemies": 1 == "turn on", anything else (0) == "turn off" */
+ port->FreezeEnemies = getFile8Bit(file);
+
+ ReadUnusedBytesFromFile(file, 1); /* (not used by Supaplex engine) */
+ }
+
+ /* SpeedByte XOR Highbyte(RandomSeed) */
+ header->SpeedByte = getFile8Bit(file);
+
+ /* CheckSum XOR SpeedByte */
+ header->CheckSumByte = getFile8Bit(file);
+
+ /* random seed used for recorded demos */
+ header->DemoRandomSeed = getFile16BitLE(file);
+
+#if 1
+ printf("::: file.c: DemoRandomSeed == %d\n", header->DemoRandomSeed);
+#endif
+
+ /* auto-determine number of infotrons if it was stored as "0" -- see above */
+ if (header->InfotronsNeeded == 0)
+ {
+ for (x = 0; x < native_sp_level.width; x++)
+ for (y = 0; y < native_sp_level.height; y++)
+ if (native_sp_level.playfield[x][y] == fiInfotron)
+ header->InfotronsNeeded++;
+
+ header->InfotronsNeeded &= 0xff; /* only use low byte -- see above */
+ }
+
+ /* also load demo tape, if available */
+
+ if (demo_available)
+ {
+ for (i = 0; i < SP_MAX_TAPE_LEN && !feof(file); i++)
+ {
+ native_sp_level.demo[i] = getFile8Bit(file);
+
+ if (native_sp_level.demo[i] == 0xff) /* "end of demo" byte */
+ {
+ i++;
+
+ break;
+ }
+ }
+
+ native_sp_level.demo_length = i;
+ native_sp_level.demo_available = (native_sp_level.demo_length > 0);
+ }
+}
+
+boolean LoadNativeLevel_SP(char *filename, int pos)
+{
+ FILE *file;
+ int i, l, x, y;
+ char name_first, name_last;
+ struct LevelInfo_SP multipart_level;
+ int multipart_xpos, multipart_ypos;
+ boolean is_multipart_level;
+ boolean is_first_part;
+ boolean reading_multipart_level = FALSE;
+ boolean use_empty_level = FALSE;
+ LevelInfoType *header = &native_sp_level.header;
+ boolean demo_available = (strSuffix(filename, ".sp") ||
+ strSuffix(filename, ".SP"));
+
+ /* always start with reliable default values */
+ setLevelInfoToDefaults_SP();
+ copyInternalEngineVars_SP();
+
+ if (!(file = fopen(filename, MODE_READ)))
+ {
+ Error(ERR_WARN, "cannot open level '%s' -- using empty level", filename);
+
+ return FALSE;
+ }
+
+ /* position file stream to the requested level (in case of level package) */
+ if (fseek(file, pos * SP_LEVEL_SIZE, SEEK_SET) != 0)
+ {
+ Error(ERR_WARN, "cannot fseek in file '%s' -- using empty level", filename);
+
+ return FALSE;
+ }
+
+ /* there exist Supaplex level package files with multi-part levels which
+ can be detected as follows: instead of leading and trailing dashes ('-')
+ to pad the level name, they have leading and trailing numbers which are
+ the x and y coordinations of the current part of the multi-part level;
+ if there are '?' characters instead of numbers on the left or right side
+ of the level name, the multi-part level consists of only horizontal or
+ vertical parts */
+
+ for (l = pos; l < SP_NUM_LEVELS_PER_PACKAGE; l++)
+ {
+ LoadNativeLevelFromFileStream_SP(file, demo_available);
+
+ /* check if this level is a part of a bigger multi-part level */
+
+ name_first = header->LevelTitle[0];
+ name_last = header->LevelTitle[SP_LEVEL_NAME_LEN - 1];
+
+ is_multipart_level =
+ ((name_first == '?' || (name_first >= '0' && name_first <= '9')) &&
+ (name_last == '?' || (name_last >= '0' && name_last <= '9')));
+
+ is_first_part =
+ ((name_first == '?' || name_first == '1') &&
+ (name_last == '?' || name_last == '1'));
+
+ /* correct leading multipart level meta information in level name */
+ for (i = 0; i < SP_LEVEL_NAME_LEN && header->LevelTitle[i] == name_first; i++)
+ header->LevelTitle[i] = '-';
+
+ /* correct trailing multipart level meta information in level name */
+ for (i = SP_LEVEL_NAME_LEN - 1; i >= 0 && header->LevelTitle[i] == name_last; i--)
+ header->LevelTitle[i] = '-';
+
+ /* ---------- check for normal single level ---------- */
+
+ if (!reading_multipart_level && !is_multipart_level)
+ {
+ /* the current level is simply a normal single-part level, and we are
+ not reading a multi-part level yet, so return the level as it is */
+
+ break;
+ }
+
+ /* ---------- check for empty level (unused multi-part) ---------- */
+
+ if (!reading_multipart_level && is_multipart_level && !is_first_part)
+ {
+ /* this is a part of a multi-part level, but not the first part
+ (and we are not already reading parts of a multi-part level);
+ in this case, use an empty level instead of the single part */
+
+ use_empty_level = TRUE;
+
+ break;
+ }
+
+ /* ---------- check for finished multi-part level ---------- */
+
+ if (reading_multipart_level &&
+ (!is_multipart_level ||
+ !strEqualN(header->LevelTitle, multipart_level.header.LevelTitle,
+ SP_LEVEL_NAME_LEN)))
+ {
+ /* we are already reading parts of a multi-part level, but this level is
+ either not a multi-part level, or a part of a different multi-part
+ level; in both cases, the multi-part level seems to be complete */
+
+ break;
+ }
+
+ /* ---------- here we have one part of a multi-part level ---------- */
+
+ reading_multipart_level = TRUE;
+
+ if (is_first_part) /* start with first part of new multi-part level */
+ {
+ /* copy level info structure from first part */
+ multipart_level = native_sp_level;
+
+ /* clear playfield of new multi-part level */
+ for (x = 0; x < SP_MAX_PLAYFIELD_WIDTH; x++)
+ for (y = 0; y < SP_MAX_PLAYFIELD_HEIGHT; y++)
+ multipart_level.playfield[x][y] = fiSpace;
+ }
+
+ if (name_first == '?')
+ name_first = '1';
+ if (name_last == '?')
+ name_last = '1';
+
+ multipart_xpos = (int)(name_first - '0');
+ multipart_ypos = (int)(name_last - '0');
+
+#if 0
+ printf("----------> part (%d/%d) of multi-part level '%s'\n",
+ multipart_xpos, multipart_ypos, multipart_level.header.LevelTitle);
+#endif
+
+ if (multipart_xpos * SP_PLAYFIELD_WIDTH > SP_MAX_PLAYFIELD_WIDTH ||
+ multipart_ypos * SP_PLAYFIELD_HEIGHT > SP_MAX_PLAYFIELD_HEIGHT)
+ {
+ Error(ERR_WARN, "multi-part level is too big -- ignoring part of it");
+
+ break;
+ }
+
+ multipart_level.width = MAX(multipart_level.width,
+ multipart_xpos * SP_PLAYFIELD_WIDTH);
+ multipart_level.height = MAX(multipart_level.height,
+ multipart_ypos * SP_PLAYFIELD_HEIGHT);
+
+ /* copy level part at the right position of multi-part level */
+ for (x = 0; x < SP_PLAYFIELD_WIDTH; x++)
+ {
+ for (y = 0; y < SP_PLAYFIELD_HEIGHT; y++)
+ {
+ int start_x = (multipart_xpos - 1) * SP_PLAYFIELD_WIDTH;
+ int start_y = (multipart_ypos - 1) * SP_PLAYFIELD_HEIGHT;
+
+ multipart_level.playfield[start_x + x][start_y + y] =
+ native_sp_level.playfield[x][y];
+ }
+ }
+ }
+
+ fclose(file);
+
+ if (use_empty_level)
+ {
+ setLevelInfoToDefaults_SP();
+
+ Error(ERR_WARN, "single part of multi-part level -- using empty level");
+ }
+
+ if (reading_multipart_level)
+ native_sp_level = multipart_level;
+
+ copyInternalEngineVars_SP();
+
+ return TRUE;
+}
#include "global.h"
+struct LevelInfo_SP native_sp_level;
+
void InitGameEngine_SP()
{
#if 0
// Call subFetchAndInitLevelB
EndFlag = False;
+#if 0
+ subMainGameLoop_Init();
+#else
subMainGameLoop();
+#endif
}
/* level stored in native format for the alternative native game engines */
struct LevelInfo_EM *native_em_level;
+ struct LevelInfo_SP *native_sp_level;
int file_version; /* file format version the level is stored with */
int game_version; /* game release version the level was created with */