added optional button to restart game (door, panel and touch variants)
[rocksndiamonds.git] / src / game.c
index ea30528996c15d9b1a706d899d0d6f0853dd5bd7..fd943b996e0155de3c0509a8498cc9e7a445e29a 100644 (file)
@@ -1017,19 +1017,22 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_SAVE              5
 #define GAME_CTRL_ID_PAUSE2            6
 #define GAME_CTRL_ID_LOAD              7
-#define GAME_CTRL_ID_PANEL_STOP                8
-#define GAME_CTRL_ID_PANEL_PAUSE       9
-#define GAME_CTRL_ID_PANEL_PLAY                10
-#define GAME_CTRL_ID_TOUCH_STOP                11
-#define GAME_CTRL_ID_TOUCH_PAUSE       12
-#define SOUND_CTRL_ID_MUSIC            13
-#define SOUND_CTRL_ID_LOOPS            14
-#define SOUND_CTRL_ID_SIMPLE           15
-#define SOUND_CTRL_ID_PANEL_MUSIC      16
-#define SOUND_CTRL_ID_PANEL_LOOPS      17
-#define SOUND_CTRL_ID_PANEL_SIMPLE     18
-
-#define NUM_GAME_BUTTONS               19
+#define GAME_CTRL_ID_RESTART           8
+#define GAME_CTRL_ID_PANEL_STOP                9
+#define GAME_CTRL_ID_PANEL_PAUSE       10
+#define GAME_CTRL_ID_PANEL_PLAY                11
+#define GAME_CTRL_ID_PANEL_RESTART     12
+#define GAME_CTRL_ID_TOUCH_STOP                13
+#define GAME_CTRL_ID_TOUCH_PAUSE       14
+#define GAME_CTRL_ID_TOUCH_RESTART     15
+#define SOUND_CTRL_ID_MUSIC            16
+#define SOUND_CTRL_ID_LOOPS            17
+#define SOUND_CTRL_ID_SIMPLE           18
+#define SOUND_CTRL_ID_PANEL_MUSIC      19
+#define SOUND_CTRL_ID_PANEL_LOOPS      20
+#define SOUND_CTRL_ID_PANEL_SIMPLE     21
+
+#define NUM_GAME_BUTTONS               22
 
 
 // forward declaration for internal use
@@ -3095,6 +3098,9 @@ static void InitGameEngine(void)
   game_em.use_single_button =
     (game.engine_version > VERSION_IDENT(4,0,0,2));
 
+  game_em.use_push_delay =
+    (game.engine_version > VERSION_IDENT(4,3,7,1));
+
   game_em.use_snap_key_bug =
     (game.engine_version < VERSION_IDENT(4,0,1,0));
 
@@ -3279,6 +3285,8 @@ static void InitGameEngine(void)
       change->actual_trigger_side = CH_SIDE_NONE;
       change->actual_trigger_ce_value = 0;
       change->actual_trigger_ce_score = 0;
+      change->actual_trigger_x = -1;
+      change->actual_trigger_y = -1;
     }
   }
 
@@ -3474,8 +3482,9 @@ static void InitGameEngine(void)
      level.game_engine_type == GAME_ENGINE_TYPE_EM &&
      !setup.forced_scroll_delay           ? 0 :
      setup.scroll_delay                   ? setup.scroll_delay_value       : 0);
-  game.scroll_delay_value =
-    MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
+  if (game.forced_scroll_delay_value == -1)
+    game.scroll_delay_value =
+      MIN(MAX(MIN_SCROLL_DELAY, game.scroll_delay_value), MAX_SCROLL_DELAY);
 
   // ---------- initialize game engine snapshots ------------------------------
   for (i = 0; i < MAX_PLAYERS; i++)
@@ -3864,6 +3873,8 @@ void InitGame(void)
   game.LevelSolved_CountingScore = 0;
   game.LevelSolved_CountingHealth = 0;
 
+  game.RestartGameRequested = FALSE;
+
   game.panel.active = TRUE;
 
   game.no_level_time_limit = (level.time == 0);
@@ -3970,6 +3981,10 @@ void InitGame(void)
 
   InitBeltMovement();
 
+  // required if level does not contain any "empty space" element
+  if (element_info[EL_EMPTY].use_gfx_element)
+    game.use_masked_elements = TRUE;
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     struct PlayerInfo *player = &stored_player[i];
@@ -4444,6 +4459,11 @@ void InitGame(void)
     scroll_y = SCROLL_POSITION_Y(local_player->jy);
   }
 
+  if (game.forced_scroll_x != ARG_UNDEFINED_VALUE)
+    scroll_x = game.forced_scroll_x;
+  if (game.forced_scroll_y != ARG_UNDEFINED_VALUE)
+    scroll_y = game.forced_scroll_y;
+
   // !!! FIX THIS (START) !!!
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
@@ -4534,9 +4554,7 @@ void InitGame(void)
   }
 
   game.restart_level = FALSE;
-
   game.request_active = FALSE;
-  game.request_active_or_moving = FALSE;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
     InitGameActions_MM();
@@ -5674,14 +5692,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y,
   {
     // relocation _without_ centering of screen
 
-    int center_scroll_x = SCROLL_POSITION_X(old_x);
-    int center_scroll_y = SCROLL_POSITION_Y(old_y);
-    int offset_x = x + (scroll_x - center_scroll_x);
-    int offset_y = y + (scroll_y - center_scroll_y);
+    // apply distance between old and new player position to scroll position
+    int shifted_scroll_x = scroll_x + (x - old_x);
+    int shifted_scroll_y = scroll_y + (y - old_y);
 
-    // for new screen position, apply previous offset to center position
-    new_scroll_x = SCROLL_POSITION_X(offset_x);
-    new_scroll_y = SCROLL_POSITION_Y(offset_y);
+    // make sure that shifted scroll position does not scroll beyond screen
+    new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX);
+    new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY);
+
+    // special case for teleporting from one end of the playfield to the other
+    // (this kludge prevents the destination area to be shifted by half a tile
+    // against the source destination for even screen width or screen height;
+    // probably most useful when used with high "game.forced_scroll_delay_value"
+    // in combination with "game.forced_scroll_x" and "game.forced_scroll_y")
+    if (quick_relocation)
+    {
+      if (EVEN(SCR_FIELDX))
+      {
+       // relocate (teleport) between left and right border (half or full)
+       if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1)
+         new_scroll_x = SBX_Right;
+       else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right)
+         new_scroll_x = SBX_Right - 1;
+       else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1)
+         new_scroll_x = SBX_Left;
+       else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left)
+         new_scroll_x = SBX_Left + 1;
+      }
+
+      if (EVEN(SCR_FIELDY))
+      {
+       // relocate (teleport) between top and bottom border (half or full)
+       if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1)
+         new_scroll_y = SBY_Lower;
+       else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower)
+         new_scroll_y = SBY_Lower - 1;
+       else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1)
+         new_scroll_y = SBY_Upper;
+       else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper)
+         new_scroll_y = SBY_Upper + 1;
+      }
+    }
   }
 
   if (quick_relocation)
@@ -10642,13 +10693,22 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
     if (GFX_CRUMBLED(new_element))
       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
+    if (old_element == EL_EXPLOSION)
+    {
+      Store[x][y] = Store2[x][y] = 0;
+
+      // check if new element replaces an exploding player, requiring cleanup
+      if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
+       StorePlayer[x][y] = 0;
+    }
+
     // check if element under the player changes from accessible to unaccessible
     // (needed for special case of dropping element which then changes)
     // (must be checked after creating new element for walkable group elements)
     if (IS_PLAYER(x, y) && !player_explosion_protected &&
        IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element))
     {
-      Bang(x, y);
+      KillPlayer(PLAYERINFO(x, y));
 
       return;
     }
@@ -10711,6 +10771,8 @@ static boolean ChangeElement(int x, int y, int element, int page)
     change->actual_trigger_side = CH_SIDE_NONE;
     change->actual_trigger_ce_value = 0;
     change->actual_trigger_ce_score = 0;
+    change->actual_trigger_x = -1;
+    change->actual_trigger_y = -1;
   }
 
   // do not change elements more than a specified maximum number of changes
@@ -10720,7 +10782,9 @@ static boolean ChangeElement(int x, int y, int element, int page)
   ChangeCount[x][y]++;         // count number of changes in the same frame
 
   if (ei->has_anim_event)
-    HandleGlobalAnimEventByElementChange(element, page, x, y);
+    HandleGlobalAnimEventByElementChange(element, page, x, y,
+                                        change->actual_trigger_x,
+                                        change->actual_trigger_y);
 
   if (change->explode)
   {
@@ -11050,6 +11114,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y,
        change->actual_trigger_side = trigger_side;
        change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+       change->actual_trigger_x = trigger_x;
+       change->actual_trigger_y = trigger_y;
 
        if ((change->can_change && !change_done) || change->has_action)
        {
@@ -11164,6 +11230,8 @@ static boolean CheckElementChangeExt(int x, int y,
       change->actual_trigger_side = trigger_side;
       change->actual_trigger_ce_value = CustomValue[x][y];
       change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+      change->actual_trigger_x = x;
+      change->actual_trigger_y = y;
 
       // special case: trigger element not at (x,y) position for some events
       if (check_trigger_element)
@@ -11187,6 +11255,8 @@ static boolean CheckElementChangeExt(int x, int y,
 
        change->actual_trigger_ce_value = CustomValue[xx][yy];
        change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element);
+       change->actual_trigger_x = xx;
+       change->actual_trigger_y = yy;
       }
 
       if (change->can_change && !change_done)
@@ -15737,10 +15807,14 @@ boolean CheckRestartGame(void)
     return FALSE;
   }
 
-  // do not handle game over if request dialog is already active
+  // do not ask to play again if request dialog is already active
   if (game.request_active)
     return FALSE;
 
+  // do not ask to play again if request dialog already handled
+  if (game.RestartGameRequested)
+    return FALSE;
+
   // do not ask to play again if game was never actually played
   if (!game.GamePlayed)
     return FALSE;
@@ -15749,6 +15823,8 @@ boolean CheckRestartGame(void)
   if (!setup.ask_on_game_over)
     return FALSE;
 
+  game.RestartGameRequested = TRUE;
+
   RequestRestartGame();
 
   return TRUE;
@@ -16173,6 +16249,11 @@ static struct
     GAME_CTRL_ID_LOAD,                         NULL,
     TRUE, FALSE,                               "load game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_RESTART,               &game.button.restart,
+    GAME_CTRL_ID_RESTART,                      NULL,
+    TRUE, FALSE,                               "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_PANEL_STOP,            &game.button.panel_stop,
     GAME_CTRL_ID_PANEL_STOP,                   NULL,
@@ -16188,6 +16269,11 @@ static struct
     GAME_CTRL_ID_PANEL_PLAY,                   NULL,
     FALSE, FALSE,                              "play game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_PANEL_RESTART,         &game.button.panel_restart,
+    GAME_CTRL_ID_PANEL_RESTART,                        NULL,
+    FALSE, FALSE,                              "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_TOUCH_STOP,            &game.button.touch_stop,
     GAME_CTRL_ID_TOUCH_STOP,                   NULL,
@@ -16198,6 +16284,11 @@ static struct
     GAME_CTRL_ID_TOUCH_PAUSE,                  NULL,
     FALSE, TRUE,                               "pause game"
   },
+  {
+    IMG_GFX_GAME_BUTTON_TOUCH_RESTART,         &game.button.touch_restart,
+    GAME_CTRL_ID_TOUCH_RESTART,                        NULL,
+    FALSE, TRUE,                               "restart game"
+  },
   {
     IMG_GFX_GAME_BUTTON_SOUND_MUSIC,           &game.button.sound_music,
     SOUND_CTRL_ID_MUSIC,                       &setup.sound_music,
@@ -16277,7 +16368,10 @@ void CreateGameButtons(void)
        id == GAME_CTRL_ID_PLAY ||
        id == GAME_CTRL_ID_PANEL_PLAY ||
        id == GAME_CTRL_ID_SAVE ||
-       id == GAME_CTRL_ID_LOAD)
+       id == GAME_CTRL_ID_LOAD ||
+       id == GAME_CTRL_ID_RESTART ||
+       id == GAME_CTRL_ID_PANEL_RESTART ||
+       id == GAME_CTRL_ID_TOUCH_RESTART)
     {
       button_type = GD_TYPE_NORMAL_BUTTON;
       checked = FALSE;
@@ -16400,6 +16494,10 @@ void ModifyPauseButtons(void)
   };
   int i;
 
+  // do not redraw pause button on closed door (may happen when restarting game)
+  if (!(GetDoorState() & DOOR_OPEN_1))
+    return;
+
   for (i = 0; ids[i] > -1; i++)
     ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END);
 }
@@ -16621,6 +16719,13 @@ static void HandleGameButtonsExt(int id, int button)
       TapeQuickLoad();
       break;
 
+    case GAME_CTRL_ID_RESTART:
+    case GAME_CTRL_ID_PANEL_RESTART:
+    case GAME_CTRL_ID_TOUCH_RESTART:
+      TapeRestartGame();
+
+      break;
+
     case SOUND_CTRL_ID_MUSIC:
     case SOUND_CTRL_ID_PANEL_MUSIC:
       if (setup.sound_music)