updated contact info in source file headers
[rocksndiamonds.git] / src / game.c
index 0468be7fa87a4be79be812213f84ecf3a8e9fd0b..28905e562b1ef07e942ca56e23be27b128cfade2 100644 (file)
@@ -1,15 +1,13 @@
-/***********************************************************
-* Rocks'n'Diamonds -- McDuffin Strikes Back!               *
-*----------------------------------------------------------*
-* (c) 1995-2006 Artsoft Entertainment                      *
-*               Holger Schemel                             *
-*               Detmolder Strasse 189                      *
-*               33604 Bielefeld                            *
-*               Germany                                    *
-*               e-mail: info@artsoft.org                   *
-*----------------------------------------------------------*
-* game.c                                                   *
-***********************************************************/
+// ============================================================================
+// Rocks'n'Diamonds - McDuffin Strikes Back!
+// ----------------------------------------------------------------------------
+// (c) 1995-2014 by Artsoft Entertainment
+//                         Holger Schemel
+//                 info@artsoft.org
+//                 http://www.artsoft.org/
+// ----------------------------------------------------------------------------
+// game.c
+// ============================================================================
 
 #include "libgame/libgame.h"
 
 #include "tape.h"
 #include "network.h"
 
+
+/* DEBUG SETTINGS */
+#define DEBUG_INIT_PLAYER      1
+#define DEBUG_PLAYER_ACTIONS   0
+
 /* EXPERIMENTAL STUFF */
 #define USE_NEW_AMOEBA_CODE    FALSE
 
@@ -1009,8 +1012,10 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define SOUND_CTRL_ID_MUSIC            3
 #define SOUND_CTRL_ID_LOOPS            4
 #define SOUND_CTRL_ID_SIMPLE           5
+#define GAME_CTRL_ID_SAVE              6
+#define GAME_CTRL_ID_LOAD              7
 
-#define NUM_GAME_BUTTONS               6
+#define NUM_GAME_BUTTONS               8
 
 
 /* forward declaration for internal use */
@@ -1077,7 +1082,6 @@ static void PlayLevelSoundActionIfLoop(int, int, int);
 static void StopLevelSoundActionIfLoop(int, int, int);
 static void PlayLevelMusic();
 
-static void MapGameButtons();
 static void HandleGameButtons(struct GadgetInfo *);
 
 int AmoebeNachbarNr(int, int);
@@ -1740,14 +1744,16 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
 
       StorePlayer[x][y] = Feld[x][y];
 
+#if DEBUG_INIT_PLAYER
       if (options.debug)
       {
-       printf("Player %d activated.\n", player->element_nr);
-       printf("[Local player is %d and currently %s.]\n",
+       printf("- player element %d activated", player->element_nr);
+       printf(" (local player is %d and currently %s)\n",
               local_player->element_nr,
               local_player->active ? "active" : "not active");
       }
     }
+#endif
 
     Feld[x][y] = EL_EMPTY;
 
@@ -3023,6 +3029,21 @@ static void InitGameEngine()
   game.engine_version = (tape.playing ? tape.engine_version :
                         level.game_version);
 
+  /* set single or multi-player game mode (needed for re-playing tapes) */
+  game.team_mode = setup.team_mode;
+
+  if (tape.playing)
+  {
+    int num_players = 0;
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (tape.player_participates[i])
+       num_players++;
+
+    /* multi-player tapes contain input data for more than one player */
+    game.team_mode = (num_players > 1);
+  }
+
   /* ---------------------------------------------------------------------- */
   /* set flags for bugs and changes according to active game engine version */
   /* ---------------------------------------------------------------------- */
@@ -3497,6 +3518,9 @@ int get_num_special_action(int element, int action_first, int action_last)
 
 void InitGame()
 {
+  int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
+  int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+
   boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
   boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
   boolean emulate_sp = TRUE;   /* unless non-SUPAPLEX    elements found */
@@ -3510,7 +3534,46 @@ void InitGame()
 #endif
   int i, j, x, y;
 
+#if 1
   game_status = GAME_MODE_PLAYING;
+#endif
+
+#if 1
+
+  StopAnimation();
+
+  if (!game.restart_level)
+    CloseDoor(DOOR_CLOSE_1);
+
+#if 1
+  if (level_editor_test_game)
+    FadeSkipNextFadeIn();
+  else
+    FadeSetEnterScreen();
+#else
+  if (level_editor_test_game)
+    fading = fading_none;
+  else
+    fading = menu.destination;
+#endif
+
+#if 1
+  FadeOut(REDRAW_FIELD);
+#else
+  if (do_fading)
+    FadeOut(REDRAW_FIELD);
+#endif
+
+#endif
+
+#if 0
+  printf("::: FADING OUT: DONE\n");
+  Delay(1000);
+#endif
+
+#if 0
+  game_status = GAME_MODE_PLAYING;
+#endif
 
 #if 1
   /* needed if different viewport properties defined for playing */
@@ -3753,6 +3816,13 @@ void InitGame()
   for (i = 0; i < MAX_NUM_AMOEBA; i++)
     AmoebaCnt[i] = AmoebaCnt2[i] = 0;
 
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status at level initialization:\n");
+  }
+#endif
+
   SCAN_PLAYFIELD(x, y)
   {
     Feld[x][y] = level.field[x][y];
@@ -3886,16 +3956,25 @@ void InitGame()
   local_player->connected = TRUE;
   /* !!! SAME AS init.c:InitPlayerInfo() -- FIX THIS !!! */
 
+#if 0
+  printf("::: TEAM MODE: %d\n", game.team_mode);
+#endif
+
   if (tape.playing)
   {
+#if 1
+    for (i = 0; i < MAX_PLAYERS; i++)
+      stored_player[i].connected = tape.player_participates[i];
+#else
     /* try to guess locally connected team mode players (needed for correct
        assignment of player figures from level to locally playing players) */
 
     for (i = 0; i < MAX_PLAYERS; i++)
       if (tape.player_participates[i])
        stored_player[i].connected = TRUE;
+#endif
   }
-  else if (setup.team_mode && !options.network)
+  else if (game.team_mode && !options.network)
   {
     /* try to guess locally connected team mode players (needed for correct
        assignment of player figures from level to locally playing players) */
@@ -3906,14 +3985,32 @@ void InitGame()
        stored_player[i].connected = TRUE;
   }
 
-#if 0
-  for (i = 0; i < MAX_PLAYERS; i++)
-    printf("::: player %d: %s\n", i,
-          (stored_player[i].connected ? "connected" : "not connected"));
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status after level initialization:\n");
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    printf("::: player %d: %s\n", i,
-          (stored_player[i].present ? "present" : "not present"));
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
+            player->present,
+            player->connected,
+            player->active);
+
+      if (local_player == player)
+       printf(" (local player)");
+
+      printf("\n");
+    }
+  }
+#endif
+
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+    printf("Reassigning players ...\n");
 #endif
 
   /* check if any connected player was not found in playfield */
@@ -3925,12 +4022,29 @@ void InitGame()
     {
       struct PlayerInfo *field_player = NULL;
 
-#if 0
-      printf("::: looking for field player for player %d ...\n", i);
+#if DEBUG_INIT_PLAYER
+      if (options.debug)
+       printf("- looking for field player for player %d ...\n", i + 1);
 #endif
 
       /* assign first free player found that is present in the playfield */
 
+#if 1
+      /* first try: look for unmapped playfield player that is not connected */
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped &&
+           !stored_player[j].connected)
+         field_player = &stored_player[j];
+
+      /* second try: look for *any* unmapped playfield player */
+      for (j = 0; j < MAX_PLAYERS; j++)
+       if (field_player == NULL &&
+           stored_player[j].present &&
+           !stored_player[j].mapped)
+         field_player = &stored_player[j];
+#else
       /* first try: look for unmapped playfield player that is not connected */
       if (field_player == NULL)
        for (j = 0; j < MAX_PLAYERS; j++)
@@ -3945,13 +4059,15 @@ void InitGame()
          if (stored_player[j].present &&
              !stored_player[j].mapped)
            field_player = &stored_player[j];
+#endif
 
       if (field_player != NULL)
       {
        int jx = field_player->jx, jy = field_player->jy;
 
-#if 0
-       printf("::: found player figure %d\n", field_player->index_nr);
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- found player %d\n", field_player->index_nr + 1);
 #endif
 
        player->present = FALSE;
@@ -3980,9 +4096,10 @@ void InitGame()
 
        field_player->mapped = TRUE;
 
-#if 0
-       printf("::: map_player_action[%d] == %d\n",
-              field_player->index_nr, i);
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("- map_player_action[%d] == %d\n",
+                field_player->index_nr + 1, i + 1);
 #endif
       }
     }
@@ -3991,6 +4108,29 @@ void InitGame()
       player->mapped = TRUE;
   }
 
+#if DEBUG_INIT_PLAYER
+  if (options.debug)
+  {
+    printf("Player status after player assignment (first stage):\n");
+
+    for (i = 0; i < MAX_PLAYERS; i++)
+    {
+      struct PlayerInfo *player = &stored_player[i];
+
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
+            player->present,
+            player->connected,
+            player->active);
+
+      if (local_player == player)
+       printf(" (local player)");
+
+      printf("\n");
+    }
+  }
+#endif
+
 #else
 
   /* check if any connected player was not found in playfield */
@@ -4041,6 +4181,11 @@ void InitGame()
     /* when playing a tape, eliminate all players who do not participate */
 
 #if USE_NEW_PLAYER_ASSIGNMENTS
+
+#if 1
+    if (!game.team_mode)
+#endif
+
     for (i = 0; i < MAX_PLAYERS; i++)
     {
       if (stored_player[i].active &&
@@ -4049,12 +4194,19 @@ void InitGame()
        struct PlayerInfo *player = &stored_player[i];
        int jx = player->jx, jy = player->jy;
 
+#if DEBUG_INIT_PLAYER
+       if (options.debug)
+         printf("Removing player %d at (%d, %d)\n", i + 1, jx, jy);
+#endif
+
        player->active = FALSE;
        StorePlayer[jx][jy] = 0;
        Feld[jx][jy] = EL_EMPTY;
       }
     }
+
 #else
+
     for (i = 0; i < MAX_PLAYERS; i++)
     {
       if (stored_player[i].active &&
@@ -4070,7 +4222,7 @@ void InitGame()
     }
 #endif
   }
-  else if (!options.network && !setup.team_mode)       /* && !tape.playing */
+  else if (!options.network && !game.team_mode)                /* && !tape.playing */
   {
     /* when in single player mode, eliminate all but the first active player */
 
@@ -4110,21 +4262,28 @@ void InitGame()
 #endif
   }
 
+#if DEBUG_INIT_PLAYER
   if (options.debug)
   {
+    printf("Player status after player assignment (final stage):\n");
+
     for (i = 0; i < MAX_PLAYERS; i++)
     {
       struct PlayerInfo *player = &stored_player[i];
 
-      printf("Player %d: present == %d, connected == %d, active == %d.\n",
-            i+1,
+      printf("- player %d: present == %d, connected == %d, active == %d",
+            i + 1,
             player->present,
             player->connected,
             player->active);
+
       if (local_player == player)
-       printf("Player  %d is local player.\n", i+1);
+       printf(" (local player)");
+
+      printf("\n");
     }
   }
+#endif
 
   if (BorderElement == EL_EMPTY)
   {
@@ -4143,16 +4302,44 @@ void InitGame()
 
 #if NEW_TILESIZE
 
+  // printf("::: START-0: %d, %d\n", lev_fieldx, SCR_FIELDX);
+  // printf("::: START-1: %d, %d\n", SBX_Left, SBX_Right);
+
+#if 1
+  if (full_lev_fieldx <= SCR_FIELDX)
+    SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
+
+  if (full_lev_fieldy <= SCR_FIELDY)
+    SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
+#else
   if (lev_fieldx + (SBX_Left < 0 ? 2 : 0) <= SCR_FIELDX)
     SBX_Left = SBX_Right = -1 * (SCR_FIELDX - lev_fieldx) / 2;
 
   if (lev_fieldy + (SBY_Upper < 0 ? 2 : 0) <= SCR_FIELDY)
     SBY_Upper = SBY_Lower = -1 * (SCR_FIELDY - lev_fieldy) / 2;
+#endif
 
+  /*
+  printf("::: START-2: %d, %d (%d)\n", SBX_Left, SBX_Right,
+        SBX_Right - SBX_Left + 1);
+  */
+
+#if 1
+  if (EVEN(SCR_FIELDX) && full_lev_fieldx > SCR_FIELDX)
+    SBX_Left--;
+  if (EVEN(SCR_FIELDY) && full_lev_fieldy > SCR_FIELDY)
+    SBY_Upper--;
+#else
   if (EVEN(SCR_FIELDX))
     SBX_Left--;
   if (EVEN(SCR_FIELDY))
     SBY_Upper--;
+#endif
+
+#if 0
+  printf("::: START-3: %d, %d\n", SBX_Left, SBX_Right);
+  printf("\n");
+#endif
 
 #else
 
@@ -4281,6 +4468,8 @@ void InitGame()
   game_status = GAME_MODE_MAIN;
 #endif
 
+#if 0
+
   StopAnimation();
 
   if (!game.restart_level)
@@ -4305,6 +4494,8 @@ void InitGame()
     FadeOut(REDRAW_FIELD);
 #endif
 
+#endif
+
 #if 0
   game_status = GAME_MODE_PLAYING;
 #endif
@@ -4314,35 +4505,49 @@ void InitGame()
   {
     InitGameEngine_EM();
 
+#if 0
     /* blit playfield from scroll buffer to normal back buffer for fading in */
     BlitScreenToBitmap_EM(backbuffer);
+#endif
   }
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
   {
     InitGameEngine_SP();
 
+#if 0
     /* blit playfield from scroll buffer to normal back buffer for fading in */
     BlitScreenToBitmap_SP(backbuffer);
+#endif
   }
   else
   {
-    DrawLevel();
+    DrawLevel(REDRAW_FIELD);
     DrawAllPlayers();
 
     /* after drawing the level, correct some elements */
     if (game.timegate_time_left == 0)
       CloseAllOpenTimegates();
 
+#if 0
+    /* blit playfield from scroll buffer to normal back buffer for fading in */
 #if NEW_TILESIZE
     BlitScreenToBitmap(backbuffer);
 #else
-    /* blit playfield from scroll buffer to normal back buffer for fading in */
     if (setup.soft_scrolling)
       BlitBitmap(fieldbuffer, backbuffer, FX, FY, SXSIZE, SYSIZE, SX, SY);
 #endif
+#endif
 
+#if 0
     redraw_mask |= REDRAW_FROM_BACKBUFFER;
+#endif
   }
+#if 1
+  /* blit playfield from scroll buffer to normal back buffer for fading in */
+  BlitScreenToBitmap(backbuffer);
+
+  redraw_mask |= REDRAW_FROM_BACKBUFFER;
+#endif
   /* !!! FIX THIS (END) !!! */
 
 #if 1
@@ -4398,8 +4603,12 @@ void InitGame()
     MapTapeButtons();
 
     /* copy actual game door content to door double buffer for OpenDoor() */
+#if 1
+    BlitBitmap(drawto, bitmap_db_door_1, DX, DY, DXSIZE, DYSIZE, 0, 0);
+#else
     BlitBitmap(drawto, bitmap_db_door,
               DX, DY, DXSIZE, DYSIZE, DOOR_GFX_PAGEX1, DOOR_GFX_PAGEY1);
+#endif
 
     OpenDoor(DOOR_OPEN_ALL);
 
@@ -4410,12 +4619,28 @@ void InitGame()
 
     KeyboardAutoRepeatOffUnlessAutoplay();
 
+#if DEBUG_INIT_PLAYER
     if (options.debug)
     {
+      printf("Player status (final):\n");
+
       for (i = 0; i < MAX_PLAYERS; i++)
-       printf("Player %d %sactive.\n",
-              i + 1, (stored_player[i].active ? "" : "not "));
+      {
+       struct PlayerInfo *player = &stored_player[i];
+
+       printf("- player %d: present == %d, connected == %d, active == %d",
+              i + 1,
+              player->present,
+              player->connected,
+              player->active);
+
+       if (local_player == player)
+         printf(" (local player)");
+
+       printf("\n");
+      }
     }
+#endif
   }
 
 #if 1
@@ -11872,7 +12097,6 @@ static void CheckSingleStepMode(struct PlayerInfo *player)
 
 static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 {
-  boolean moved = FALSE, snapped = FALSE, dropped = FALSE;
   int left     = player_action & JOY_LEFT;
   int right    = player_action & JOY_RIGHT;
   int up       = player_action & JOY_UP;
@@ -11888,13 +12112,13 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
   if (player_action)
   {
     if (button1)
-      snapped = SnapField(player, dx, dy);
+      SnapField(player, dx, dy);
     else
     {
       if (button2)
-       dropped = DropElement(player);
+       DropElement(player);
 
-      moved = MovePlayer(player, dx, dy);
+      MovePlayer(player, dx, dy);
     }
 
     CheckSingleStepMode(player);
@@ -12127,9 +12351,9 @@ void GameActions()
   /* detect endless loops, caused by custom element programming */
   if (recursion_loop_detected && recursion_loop_depth == 0)
   {
-    char *message = getStringCat3("Internal Error ! Element ",
+    char *message = getStringCat3("Internal Error! Element ",
                                  EL_NAME(recursion_loop_element),
-                                 " caused endless loop ! Quit the game ?");
+                                 " caused endless loop! Quit the game?");
 
     Error(ERR_WARN, "element '%s' caused endless loop in game engine",
          EL_NAME(recursion_loop_element));
@@ -12244,8 +12468,13 @@ void GameActions()
   {
     summarized_player_action |= stored_player[i].action;
 
+#if 1
+    if (!network_playing && (game.team_mode || tape.playing))
+      stored_player[i].effective_action = stored_player[i].action;
+#else
     if (!network_playing)
       stored_player[i].effective_action = stored_player[i].action;
+#endif
   }
 
 #if defined(NETWORK_AVALIABLE)
@@ -12253,10 +12482,13 @@ void GameActions()
     SendToServer_MovePlayer(summarized_player_action);
 #endif
 
-  if (!options.network && !setup.team_mode)
+  if (!options.network && !game.team_mode)
     local_player->effective_action = summarized_player_action;
 
-  if (setup.team_mode && setup.input_on_focus && game.centered_player_nr != -1)
+  if (tape.recording &&
+      setup.team_mode &&
+      setup.input_on_focus &&
+      game.centered_player_nr != -1)
   {
     for (i = 0; i < MAX_PLAYERS; i++)
       stored_player[i].effective_action =
@@ -12271,9 +12503,19 @@ void GameActions()
   {
     tape_action[i] = stored_player[i].effective_action;
 
+#if 1
+    /* (this may happen in the RND game engine if a player was not present on
+       the playfield on level start, but appeared later from a custom element */
+    if (tape.recording &&
+       setup.team_mode &&
+       tape_action[i] &&
+       !tape.player_participates[i])
+      tape.player_participates[i] = TRUE;
+#else
     /* (this can only happen in the R'n'D game engine) */
     if (tape.recording && tape_action[i] && !tape.player_participates[i])
       tape.player_participates[i] = TRUE;    /* player just appeared from CE */
+#endif
   }
 
   /* only record actions from input devices, but not programmed actions */
@@ -12281,16 +12523,76 @@ void GameActions()
     TapeRecordAction(tape_action);
 
 #if USE_NEW_PLAYER_ASSIGNMENTS
+#if 1
+  if (game.team_mode)
+#endif
   {
     byte mapped_action[MAX_PLAYERS];
 
+#if DEBUG_PLAYER_ACTIONS
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+#endif
+
     for (i = 0; i < MAX_PLAYERS; i++)
       mapped_action[i] = stored_player[map_player_action[i]].effective_action;
 
     for (i = 0; i < MAX_PLAYERS; i++)
       stored_player[i].effective_action = mapped_action[i];
+
+#if DEBUG_PLAYER_ACTIONS
+    printf(" =>");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
+#endif
+  }
+#if DEBUG_PLAYER_ACTIONS
+  else
+  {
+    printf(":::");
+    for (i = 0; i < MAX_PLAYERS; i++)
+      printf(" %d, ", stored_player[i].effective_action);
+    printf("\n");
   }
 #endif
+#endif
+
+#if 0
+  printf("::: summarized_player_action == %d\n",
+        local_player->effective_action);
+#endif
+
+
+
+
+#if 0
+#if DEBUG_INIT_PLAYER
+    if (options.debug)
+    {
+      printf("Player status (final):\n");
+
+      for (i = 0; i < MAX_PLAYERS; i++)
+      {
+       struct PlayerInfo *player = &stored_player[i];
+
+       printf("- player %d: present == %d, connected == %d, active == %d",
+              i + 1,
+              player->present,
+              player->connected,
+              player->active);
+
+       if (local_player == player)
+         printf(" (local player)");
+
+       printf("\n");
+      }
+    }
+#endif
+#endif
+
+
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
@@ -15803,14 +16105,19 @@ static boolean DropElement(struct PlayerInfo *player)
   if (IS_CUSTOM_ELEMENT(new_element) && CAN_MOVE(new_element) &&
       element_info[new_element].move_pattern == MV_WHEN_DROPPED)
   {
-    int move_direction, nextx, nexty;
+#if 0
+    int move_direction;
+    int nextx, nexty;
+#endif
 
     if (element_info[new_element].move_direction_initial == MV_START_AUTOMATIC)
       MovDir[dropx][dropy] = drop_direction;
 
+#if 0
     move_direction = MovDir[dropx][dropy];
     nextx = dropx + GET_DX_FROM_DIR(move_direction);
     nexty = dropy + GET_DY_FROM_DIR(move_direction);
+#endif
 
     ChangeCount[dropx][dropy] = 0;     /* allow at least one more change */
 
@@ -16224,6 +16531,12 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 {
   if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
   {
+#if 1
+    /* closing door required in case of envelope style request dialogs */
+    if (!skip_request)
+      CloseDoor(DOOR_CLOSE_1);
+#endif
+
 #if defined(NETWORK_AVALIABLE)
     if (options.network)
       SendToServer_StopPlaying(NETWORK_STOP_BY_PLAYER);
@@ -16282,7 +16595,7 @@ void RequestQuitGame(boolean ask_if_really_quit)
   boolean skip_request = AllPlayersGone || quick_quit;
 
   RequestQuitGameExt(skip_request, quick_quit,
-                    "Do you really want to quit the game ?");
+                    "Do you really want to quit the game?");
 }
 
 
@@ -16427,6 +16740,14 @@ static void LoadEngineSnapshotValues_RND()
   }
 }
 
+void FreeEngineSnapshot()
+{
+  FreeEngineSnapshotBuffers();
+
+  setString(&snapshot_level_identifier, NULL);
+  snapshot_level_nr = -1;
+}
+
 void SaveEngineSnapshot()
 {
   /* do not save snapshots from editor */
@@ -16436,6 +16757,25 @@ void SaveEngineSnapshot()
   /* free previous snapshot buffers, if needed */
   FreeEngineSnapshotBuffers();
 
+#if 1
+  /* copy some special values to a structure better suited for the snapshot */
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveEngineSnapshotValues_SP();
+
+  /* save values stored in special snapshot structure */
+
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
+#else
   /* copy some special values to a structure better suited for the snapshot */
 
   SaveEngineSnapshotValues_RND();
@@ -16447,6 +16787,7 @@ void SaveEngineSnapshot()
   SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd));
   SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em));
   SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp));
+#endif
 
   /* save further RND engine values */
 
@@ -16542,9 +16883,40 @@ void LoadEngineSnapshot()
 
   /* restore special values from snapshot structure */
 
+#if 1
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+    LoadEngineSnapshotValues_RND();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
+    LoadEngineSnapshotValues_EM();
+  if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
+    LoadEngineSnapshotValues_SP();
+#else
   LoadEngineSnapshotValues_RND();
   LoadEngineSnapshotValues_EM();
   LoadEngineSnapshotValues_SP();
+#endif
+
+#if 0
+  printf("::: %d, %d (LoadEngineSnapshot / 1)\n", scroll_x, scroll_y);
+#endif
+
+#if 0
+  // needed if tile size was different when saving and loading engine snapshot
+  if (local_player->present)
+  {
+    scroll_x = (local_player->jx < SBX_Left  + MIDPOSX ? SBX_Left :
+               local_player->jx > SBX_Right + MIDPOSX ? SBX_Right :
+               local_player->jx - MIDPOSX);
+
+    scroll_y = (local_player->jy < SBY_Upper + MIDPOSY ? SBY_Upper :
+               local_player->jy > SBY_Lower + MIDPOSY ? SBY_Lower :
+               local_player->jy - MIDPOSY);
+  }
+#endif
+
+#if 0
+  printf("::: %d, %d (LoadEngineSnapshot / 1)\n", scroll_x, scroll_y);
+#endif
 }
 
 boolean CheckEngineSnapshot()
@@ -16587,6 +16959,14 @@ static struct
   {
     IMG_GAME_BUTTON_GFX_SOUND_SIMPLE,  &game.button.sound_simple,
     SOUND_CTRL_ID_SIMPLE,              "normal sounds on/off"
+  },
+  {
+    IMG_GAME_BUTTON_GFX_SAVE,          &game.button.save,
+    GAME_CTRL_ID_SAVE,                 "save game"
+  },
+  {
+    IMG_GAME_BUTTON_GFX_LOAD,          &game.button.load,
+    GAME_CTRL_ID_LOAD,                 "load game"
   }
 };
 
@@ -16602,6 +16982,8 @@ void CreateGameButtons()
     int button_type;
     boolean checked;
     unsigned int event_mask;
+    int base_x = (tape.show_game_buttons ? VX : DX);
+    int base_y = (tape.show_game_buttons ? VY : DY);
     int gd_x   = gfx->src_x;
     int gd_y   = gfx->src_y;
     int gd_xp  = gfx->src_x + gfx->pressed_xoffset;
@@ -16612,9 +16994,18 @@ void CreateGameButtons()
     int gd_yap = gfx->src_y + gfx->active_yoffset + gfx->pressed_yoffset;
     int id = i;
 
+    if (gfx->bitmap == NULL)
+    {
+      game_gadget[id] = NULL;
+
+      continue;
+    }
+
     if (id == GAME_CTRL_ID_STOP ||
        id == GAME_CTRL_ID_PAUSE ||
-       id == GAME_CTRL_ID_PLAY)
+       id == GAME_CTRL_ID_PLAY ||
+       id == GAME_CTRL_ID_SAVE ||
+       id == GAME_CTRL_ID_LOAD)
     {
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
@@ -16632,8 +17023,8 @@ void CreateGameButtons()
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
                      GDI_INFO_TEXT, gamebutton_info[i].infotext,
-                     GDI_X, DX + pos->x,
-                     GDI_Y, DY + pos->y,
+                     GDI_X, base_x + GDI_ACTIVE_POS(pos->x),
+                     GDI_Y, base_y + GDI_ACTIVE_POS(pos->y),
                      GDI_WIDTH, gfx->width,
                      GDI_HEIGHT, gfx->height,
                      GDI_TYPE, button_type,
@@ -16663,7 +17054,7 @@ void FreeGameButtons()
     FreeGadget(game_gadget[i]);
 }
 
-static void MapGameButtons()
+void MapGameButtons()
 {
   int i;
 
@@ -16685,24 +17076,35 @@ void RedrawGameButtons()
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
     RedrawGadget(game_gadget[i]);
+
+  // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
+  redraw_mask &= ~REDRAW_ALL;
 }
 
 static void HandleGameButtonsExt(int id)
 {
-  if (game_status != GAME_MODE_PLAYING)
+  boolean handle_game_buttons =
+    (game_status == GAME_MODE_PLAYING ||
+     (game_status == GAME_MODE_MAIN && tape.show_game_buttons));
+
+  if (!handle_game_buttons)
     return;
 
   switch (id)
   {
     case GAME_CTRL_ID_STOP:
+      if (game_status == GAME_MODE_MAIN)
+       break;
+
       if (tape.playing)
        TapeStop();
       else
        RequestQuitGame(TRUE);
+
       break;
 
     case GAME_CTRL_ID_PAUSE:
-      if (options.network)
+      if (options.network && game_status == GAME_MODE_PLAYING)
       {
 #if defined(NETWORK_AVALIABLE)
        if (tape.pausing)
@@ -16716,7 +17118,11 @@ static void HandleGameButtonsExt(int id)
       break;
 
     case GAME_CTRL_ID_PLAY:
-      if (tape.pausing)
+      if (game_status == GAME_MODE_MAIN)
+      {
+        StartGameActions(options.network, setup.autorecord, level.random_seed);
+      }
+      else if (tape.pausing)
       {
 #if defined(NETWORK_AVALIABLE)
        if (options.network)
@@ -16769,6 +17175,14 @@ static void HandleGameButtonsExt(int id)
       }
       break;
 
+    case GAME_CTRL_ID_SAVE:
+      TapeQuickSave();
+      break;
+
+    case GAME_CTRL_ID_LOAD:
+      TapeQuickLoad();
+      break;
+
     default:
       break;
   }