From 148d0ad7e7ac1a97eb50de7ecbb642f36c877311 Mon Sep 17 00:00:00 2001 From: Holger Schemel Date: Sun, 11 Oct 2009 22:18:38 +0200 Subject: [PATCH] rnd-20091011-1-src * 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 ;-) ) --- ChangeLog | 5 + src/conftime.h | 2 +- src/files.c | 179 ++++++++++++++-- src/game_sp/ASM.c | 20 +- src/game_sp/ASM.h | 4 +- src/game_sp/Globals.c | 32 ++- src/game_sp/MainForm.c | 30 +++ src/game_sp/MainGameLoop.c | 237 +++++++++++++++++++++ src/game_sp/MainGameLoop.h | 2 + src/game_sp/Makefile | 2 + src/game_sp/Murphy.c | 6 + src/game_sp/export.h | 74 ++++++- src/game_sp/file.c | 405 ++++++++++++++++++++++++++++++++++++ src/game_sp/main.c | 2 + src/game_sp/modAnimations.c | 4 + src/main.h | 1 + 16 files changed, 970 insertions(+), 35 deletions(-) create mode 100644 src/game_sp/file.c diff --git a/ChangeLog b/ChangeLog index 1e307c07..39a8bcd8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +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) diff --git a/src/conftime.h b/src/conftime.h index 847d17c6..a00952ee 100644 --- a/src/conftime.h +++ b/src/conftime.h @@ -1 +1 @@ -#define COMPILE_DATE_STRING "2009-09-25 20:53" +#define COMPILE_DATE_STRING "2009-10-11 22:17" diff --git a/src/files.c b/src/files.c index 863ac203..85a4b23d 100644 --- a/src/files.c +++ b/src/files.c @@ -1587,8 +1587,10 @@ static void setLevelInfoToDefaults(struct LevelInfo *level) *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; @@ -3799,30 +3801,13 @@ void CopyNativeLevel_EM_to_RND(struct LevelInfo *level) } } -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 @@ -4168,6 +4153,121 @@ static void LoadLevelFromFileInfo_SP(struct LevelInfo *level, *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 @@ -6138,6 +6238,47 @@ static void LoadLevelFromFileInfo_DC(struct LevelInfo *level, #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 */ /* ------------------------------------------------------------------------- */ diff --git a/src/game_sp/ASM.c b/src/game_sp/ASM.c index 24d4c2f6..9cad09d5 100644 --- a/src/game_sp/ASM.c +++ b/src/game_sp/ASM.c @@ -129,30 +129,30 @@ void MySub(int *A, int B) *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; @@ -160,14 +160,14 @@ int SHL(int Var, int Count) 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; } } diff --git a/src/game_sp/ASM.h b/src/game_sp/ASM.h index 3b277322..09bb3938 100644 --- a/src/game_sp/ASM.h +++ b/src/game_sp/ASM.h @@ -28,8 +28,8 @@ extern void MovHighByte(int *Var, int Val); 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); diff --git a/src/game_sp/Globals.c b/src/game_sp/Globals.c index 9dbe3138..e73a7d53 100644 --- a/src/game_sp/Globals.c +++ b/src/game_sp/Globals.c @@ -329,7 +329,7 @@ int GetStretchY(int si) return GetStretchY; } -void ReadLevel() +void OLD_ReadLevel() { #if 1 static char CurPathTEST[1024]; @@ -580,6 +580,18 @@ static void ReadDemo() 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 @@ -607,3 +619,21 @@ ReadDemoEH: Close(); #endif } + +void ReadLevel() +{ +#if 0 + OLD_ReadLevel(); + + // return; +#endif + + copyInternalEngineVars_SP(); + + LevelNumber = level_nr; + + if (!DemoFlag || !DemoAvailable) + subRandomize(); + + LevelLoaded = True; +} diff --git a/src/game_sp/MainForm.c b/src/game_sp/MainForm.c index c3d7476e..1358ab02 100644 --- a/src/game_sp/MainForm.c +++ b/src/game_sp/MainForm.c @@ -2523,6 +2523,8 @@ void menPlay_Click() } +#if 0 + // static void menPlayDemo_Click() void menPlayDemo_Click() { @@ -2535,6 +2537,10 @@ void menPlayDemo_Click() menPlay_Click(); +#if 1 + return; +#endif + #if 0 if (LevelStatus != 1) lblStatus = "Demo Failed"; @@ -2543,6 +2549,30 @@ void menPlayDemo_Click() 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() diff --git a/src/game_sp/MainGameLoop.c b/src/game_sp/MainGameLoop.c index ddf3d4e2..99c5dbe4 100644 --- a/src/game_sp/MainGameLoop.c +++ b/src/game_sp/MainGameLoop.c @@ -22,6 +22,243 @@ boolean AutoScrollFlag; // 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; diff --git a/src/game_sp/MainGameLoop.h b/src/game_sp/MainGameLoop.h index 71fd4301..a686bcaf 100644 --- a/src/game_sp/MainGameLoop.h +++ b/src/game_sp/MainGameLoop.h @@ -14,6 +14,8 @@ extern int subCalculateScreenScrollPos(); extern int subMainGameLoop(); +extern int subMainGameLoop_Init(); +extern int subMainGameLoop_Main(); extern void subUpdatePlayingTime(); extern boolean AutoScrollFlag; diff --git a/src/game_sp/Makefile b/src/game_sp/Makefile index 97311dc0..13a7a917 100644 --- a/src/game_sp/Makefile +++ b/src/game_sp/Makefile @@ -13,6 +13,7 @@ # ----------------------------------------------------------------------------- SRCS = init.c \ + file.c \ main.c \ vb_lib.c \ vb_vars.c \ @@ -57,6 +58,7 @@ SRCS = init.c \ modMPX.c OBJS = init.o \ + file.o \ main.o \ vb_lib.o \ vb_vars.o \ diff --git a/src/game_sp/Murphy.c b/src/game_sp/Murphy.c index bfe6854a..bcd61200 100644 --- a/src/game_sp/Murphy.c +++ b/src/game_sp/Murphy.c @@ -2333,8 +2333,14 @@ int subSpPortTest(int si) 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) { diff --git a/src/game_sp/export.h b/src/game_sp/export.h index 37af76fb..ffd78783 100644 --- a/src/game_sp/export.h +++ b/src/game_sp/export.h @@ -9,11 +9,71 @@ /* 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 { }; @@ -24,7 +84,16 @@ struct GameInfo_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 @@ -70,7 +139,8 @@ extern void GameActions_SP(byte *, boolean); 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 *); diff --git a/src/game_sp/file.c b/src/game_sp/file.c new file mode 100644 index 00000000..cd183a24 --- /dev/null +++ b/src/game_sp/file.c @@ -0,0 +1,405 @@ + +#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; +} diff --git a/src/game_sp/main.c b/src/game_sp/main.c index ce409eb4..702cb7d3 100644 --- a/src/game_sp/main.c +++ b/src/game_sp/main.c @@ -3,6 +3,8 @@ #include "global.h" +struct LevelInfo_SP native_sp_level; + void InitGameEngine_SP() { #if 0 diff --git a/src/game_sp/modAnimations.c b/src/game_sp/modAnimations.c index 4b42b65a..3f9a1e88 100644 --- a/src/game_sp/modAnimations.c +++ b/src/game_sp/modAnimations.c @@ -70,5 +70,9 @@ void GoPlay() // Call subFetchAndInitLevelB EndFlag = False; +#if 0 + subMainGameLoop_Init(); +#else subMainGameLoop(); +#endif } diff --git a/src/main.h b/src/main.h index 38a5bcbf..2614bbb6 100644 --- a/src/main.h +++ b/src/main.h @@ -2293,6 +2293,7 @@ struct LevelInfo /* 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 */ -- 2.34.1