fixed bug with not updating game panel when leaving invisible warp mode
[rocksndiamonds.git] / src / game.c
index 8b77ddef674f65d232e0c968c5e4c5ef9529870f..d8a3d16270af01b54ed8dcff09072d13f79aff4a 100644 (file)
@@ -1119,6 +1119,8 @@ void ExitPlayer(struct PlayerInfo *);
 static int getInvisibleActiveFromInvisibleElement(int);
 static int getInvisibleFromInvisibleActiveElement(int);
 
+static void TestFieldAfterSnapping(int, int, int, int, int);
+
 static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
 
 // for detection of endless loops, caused by custom element programming
@@ -2251,6 +2253,7 @@ static void UpdateGameControlValues(void)
                level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                MM_HEALTH(game_mm.laser_overload_value) :
                game.health);
+  int sync_random_frame = INIT_GFX_RANDOM();   // random, but synchronized
 
   UpdatePlayfieldElementCount();
 
@@ -2325,6 +2328,63 @@ static void UpdateGameControlValues(void)
       stored_player[player_nr].num_white_keys;
   }
 
+  // re-arrange keys on game panel, if needed or if defined by style settings
+  for (i = 0; i < MAX_NUM_KEYS + 1; i++)       // all normal keys + white key
+  {
+    int nr = GAME_PANEL_KEY_1 + i;
+    struct GamePanelControlInfo *gpc = &game_panel_controls[nr];
+    struct TextPosInfo *pos = gpc->pos;
+
+    // skip check if key is not in the player's inventory
+    if (gpc->value == EL_EMPTY)
+      continue;
+
+    // check if keys should be arranged on panel from left to right
+    if (pos->style == STYLE_LEFTMOST_POSITION)
+    {
+      // check previous key positions (left from current key)
+      for (k = 0; k < i; k++)
+      {
+       int nr_new = GAME_PANEL_KEY_1 + k;
+
+       if (game_panel_controls[nr_new].value == EL_EMPTY)
+       {
+         game_panel_controls[nr_new].value = gpc->value;
+         gpc->value = EL_EMPTY;
+
+         break;
+       }
+      }
+    }
+
+    // check if "undefined" keys can be placed at some other position
+    if (pos->x == -1 && pos->y == -1)
+    {
+      int nr_new = GAME_PANEL_KEY_1 + i % STD_NUM_KEYS;
+
+      // 1st try: display key at the same position as normal or EM keys
+      if (game_panel_controls[nr_new].value == EL_EMPTY)
+      {
+       game_panel_controls[nr_new].value = gpc->value;
+      }
+      else
+      {
+       // 2nd try: display key at the next free position in the key panel
+       for (k = 0; k < STD_NUM_KEYS; k++)
+       {
+         nr_new = GAME_PANEL_KEY_1 + k;
+
+         if (game_panel_controls[nr_new].value == EL_EMPTY)
+         {
+           game_panel_controls[nr_new].value = gpc->value;
+
+           break;
+         }
+       }
+      }
+    }
+  }
+
   for (i = 0; i < NUM_PANEL_INVENTORY; i++)
   {
     game_panel_controls[GAME_PANEL_INVENTORY_FIRST_1 + i].value =
@@ -2478,11 +2538,13 @@ static void UpdateGameControlValues(void)
        int last_anim_random_frame = gfx.anim_random_frame;
        int element = gpc->value;
        int graphic = el2panelimg(element);
+       int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+                              sync_random_frame : INIT_GFX_RANDOM());
 
        if (gpc->value != gpc->last_value)
        {
          gpc->gfx_frame = 0;
-         gpc->gfx_random = INIT_GFX_RANDOM();
+         gpc->gfx_random = init_gfx_random;
        }
        else
        {
@@ -2490,7 +2552,7 @@ static void UpdateGameControlValues(void)
 
          if (ANIM_MODE(graphic) == ANIM_RANDOM &&
              IS_NEXT_FRAME(gpc->gfx_frame, graphic))
-           gpc->gfx_random = INIT_GFX_RANDOM();
+           gpc->gfx_random = init_gfx_random;
        }
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
@@ -2499,8 +2561,7 @@ static void UpdateGameControlValues(void)
        if (ANIM_MODE(graphic) == ANIM_CE_SCORE)
          gpc->gfx_frame = element_info[element].collect_score;
 
-       gpc->frame = getGraphicAnimationFrame(el2panelimg(gpc->value),
-                                             gpc->gfx_frame);
+       gpc->frame = getGraphicAnimationFrame(graphic, gpc->gfx_frame);
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
          gfx.anim_random_frame = last_anim_random_frame;
@@ -2512,11 +2573,13 @@ static void UpdateGameControlValues(void)
       {
        int last_anim_random_frame = gfx.anim_random_frame;
        int graphic = gpc->graphic;
+       int init_gfx_random = (graphic_info[graphic].anim_global_sync ?
+                              sync_random_frame : INIT_GFX_RANDOM());
 
        if (gpc->value != gpc->last_value)
        {
          gpc->gfx_frame = 0;
-         gpc->gfx_random = INIT_GFX_RANDOM();
+         gpc->gfx_random = init_gfx_random;
        }
        else
        {
@@ -2524,7 +2587,7 @@ static void UpdateGameControlValues(void)
 
          if (ANIM_MODE(graphic) == ANIM_RANDOM &&
              IS_NEXT_FRAME(gpc->gfx_frame, graphic))
-           gpc->gfx_random = INIT_GFX_RANDOM();
+           gpc->gfx_random = init_gfx_random;
        }
 
        if (ANIM_MODE(graphic) == ANIM_RANDOM)
@@ -2589,6 +2652,10 @@ static void DisplayGameControlValues(void)
     if (PANEL_DEACTIVATED(pos))
       continue;
 
+    if (pos->class == get_hash_from_key("extra_panel_items") &&
+       !setup.prefer_extra_panel_items)
+      continue;
+
     gpc->last_value = value;
     gpc->last_frame = frame;
 
@@ -2802,12 +2869,10 @@ void UpdateAndDisplayGameControlValues(void)
   DisplayGameControlValues();
 }
 
-#if 0
-static void UpdateGameDoorValues(void)
+void UpdateGameDoorValues(void)
 {
   UpdateGameControlValues();
 }
-#endif
 
 void DrawGameDoorValues(void)
 {
@@ -3511,11 +3576,15 @@ void InitGame(void)
   InitGameEngine();
   InitGameControlValues();
 
-  // initialize tape actions from game when recording tape
   if (tape.recording)
   {
+    // initialize tape actions from game when recording tape
     tape.use_key_actions   = game.use_key_actions;
     tape.use_mouse_actions = game.use_mouse_actions;
+
+    // initialize visible playfield size when recording tape (for team mode)
+    tape.scr_fieldx = SCR_FIELDX;
+    tape.scr_fieldy = SCR_FIELDY;
   }
 
   // don't play tapes over network
@@ -3648,6 +3717,8 @@ void InitGame(void)
     player->shield_normal_time_left = 0;
     player->shield_deadly_time_left = 0;
 
+    player->last_removed_element = EL_UNDEFINED;
+
     player->inventory_infinite_element = EL_UNDEFINED;
     player->inventory_size = 0;
 
@@ -4382,7 +4453,9 @@ void InitGame(void)
 
   game.restart_level = FALSE;
   game.restart_game_message = NULL;
+
   game.request_active = FALSE;
+  game.request_active_or_moving = FALSE;
 
   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
     InitGameActions_MM();
@@ -4643,7 +4716,7 @@ void GameWon(void)
 {
   static int time_count_steps;
   static int time, time_final;
-  static int score, score_final;
+  static float score, score_final; // needed for time score < 10 for 10 seconds
   static int health, health_final;
   static int game_over_delay_1 = 0;
   static int game_over_delay_2 = 0;
@@ -4651,6 +4724,8 @@ void GameWon(void)
   int game_over_delay_value_1 = 50;
   int game_over_delay_value_2 = 25;
   int game_over_delay_value_3 = 50;
+  int time_score_base = MIN(MAX(1, level.time_score_base), 10);
+  float time_score = (float)level.score[SC_TIME_BONUS] / time_score_base;
 
   if (!game.LevelSolved_GameWon)
   {
@@ -4684,19 +4759,23 @@ void GameWon(void)
     score = score_final = game.score_final;
     health = health_final = game.health_final;
 
-    if (level.score[SC_TIME_BONUS] > 0)
+    if (time_score > 0)
     {
+      int time_frames = 0;
+
       if (TimeLeft > 0)
       {
        time_final = 0;
-       score_final += TimeLeft * level.score[SC_TIME_BONUS];
+       time_frames = TimeLeft * FRAMES_PER_SECOND - TimeFrames;
       }
       else if (game.no_time_limit && TimePlayed < 999)
       {
        time_final = 999;
-       score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS];
+       time_frames = (999 - TimePlayed) * FRAMES_PER_SECOND - TimeFrames;
       }
 
+      score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5;
+
       time_count_steps = MAX(1, ABS(time_final - time) / 100);
 
       game_over_delay_1 = game_over_delay_value_1;
@@ -4704,7 +4783,7 @@ void GameWon(void)
       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
       {
        health_final = 0;
-       score_final += health * level.score[SC_TIME_BONUS];
+       score_final += health * time_score;
 
        game_over_delay_2 = game_over_delay_value_2;
       }
@@ -4793,7 +4872,11 @@ void GameWon(void)
       time_count_steps = 1;
 
     time  += time_count_steps * time_count_dir;
-    score += time_count_steps * level.score[SC_TIME_BONUS];
+    score += time_count_steps * time_score;
+
+    // set final score to correct rounding differences after counting score
+    if (time == time_final)
+      score = score_final;
 
     game.LevelSolved_CountingTime = time;
     game.LevelSolved_CountingScore = score;
@@ -4825,7 +4908,7 @@ void GameWon(void)
     int health_count_dir = (health < health_final ? +1 : -1);
 
     health += health_count_dir;
-    score  += level.score[SC_TIME_BONUS];
+    score  += time_score;
 
     game.LevelSolved_CountingHealth = health;
     game.LevelSolved_CountingScore = score;
@@ -11215,13 +11298,14 @@ static void CheckSingleStepMode(struct PlayerInfo *player)
 {
   if (tape.single_step && tape.recording && !tape.pausing)
   {
-    /* as it is called "single step mode", just return to pause mode when the
-       player stopped moving after one tile (or never starts moving at all) */
-    if (!player->is_moving &&
-       !player->is_pushing &&
-       !player->is_dropping_pressed &&
-       !player->effective_mouse_action.button)
-      TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+    // as it is called "single step mode", just return to pause mode when the
+    // player stopped moving after one tile (or never starts moving at all)
+    // (reverse logic needed here in case single step mode used in team mode)
+    if (player->is_moving ||
+       player->is_pushing ||
+       player->is_dropping_pressed ||
+       player->effective_mouse_action.button)
+      game.enter_single_step_mode = FALSE;
   }
 
   CheckSaveEngineSnapshot(player);
@@ -11884,6 +11968,10 @@ void GameActions_RND(void)
     DrawGameDoorValues();
   }
 
+  // check single step mode (set flag and clear again if any player is active)
+  game.enter_single_step_mode =
+    (tape.single_step && tape.recording && !tape.pausing);
+
   for (i = 0; i < MAX_PLAYERS; i++)
   {
     int actual_player_action = stored_player[i].effective_action;
@@ -11908,6 +11996,10 @@ void GameActions_RND(void)
     ScrollPlayer(&stored_player[i], SCROLL_GO_ON);
   }
 
+  // single step pause mode may already have been toggled by "ScrollPlayer()"
+  if (game.enter_single_step_mode && !tape.pausing)
+    TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
   ScrollScreen(NULL, SCROLL_GO_ON);
 
   /* for backwards compatibility, the following code emulates a fixed bug that
@@ -11960,10 +12052,17 @@ void GameActions_RND(void)
       MovDelay[x][y]--;
       if (MovDelay[x][y] <= 0)
       {
+       int element = Store[x][y];
+       int move_direction = MovDir[x][y];
+       int player_index_bit = Store2[x][y];
+
+       Store[x][y] = 0;
+       Store2[x][y] = 0;
+
        RemoveField(x, y);
        TEST_DrawLevelField(x, y);
 
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
       }
     }
 
@@ -12381,6 +12480,8 @@ void GameActions_RND(void)
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
 {
   int min_x = x, min_y = y, max_x = x, max_y = y;
+  int scr_fieldx = getScreenFieldSizeX();
+  int scr_fieldy = getScreenFieldSizeY();
   int i;
 
   for (i = 0; i < MAX_PLAYERS; i++)
@@ -12396,7 +12497,7 @@ static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
     max_y = MAX(max_y, jy);
   }
 
-  return (max_x - min_x < SCR_FIELDX && max_y - min_y < SCR_FIELDY);
+  return (max_x - min_x < scr_fieldx && max_y - min_y < scr_fieldy);
 }
 
 static boolean AllPlayersInVisibleScreen(void)
@@ -12962,6 +13063,21 @@ void ScrollPlayer(struct PlayerInfo *player, int mode)
       if (!player->is_pushing)
        TestIfElementTouchesCustomElement(jx, jy);      // for empty space
 
+      if (level.finish_dig_collect &&
+         (player->is_digging || player->is_collecting))
+      {
+       int last_element = player->last_removed_element;
+       int move_direction = player->MovDir;
+       int enter_side = MV_DIR_OPPOSITE(move_direction);
+       int change_event = (player->is_digging ? CE_PLAYER_DIGS_X :
+                           CE_PLAYER_COLLECTS_X);
+
+       CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event,
+                                           player->index_bit, enter_side);
+
+       player->last_removed_element = EL_UNDEFINED;
+      }
+
       if (!player->active)
        RemovePlayer(player);
     }
@@ -13696,7 +13812,8 @@ void ExitPlayer(struct PlayerInfo *player)
     game.players_still_needed--;
 }
 
-static void setFieldForSnapping(int x, int y, int element, int direction)
+static void SetFieldForSnapping(int x, int y, int element, int direction,
+                               int player_index_bit)
 {
   struct ElementInfo *ei = &element_info[element];
   int direction_bit = MV_DIR_TO_BIT(direction);
@@ -13706,6 +13823,9 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
 
   Tile[x][y] = EL_ELEMENT_SNAPPING;
   MovDelay[x][y] = MOVE_DELAY_NORMAL_SPEED + 1 - 1;
+  MovDir[x][y] = direction;
+  Store[x][y] = element;
+  Store2[x][y] = player_index_bit;
 
   ResetGfxAnimation(x, y);
 
@@ -13715,6 +13835,20 @@ static void setFieldForSnapping(int x, int y, int element, int direction)
   GfxFrame[x][y] = -1;
 }
 
+static void TestFieldAfterSnapping(int x, int y, int element, int direction,
+                                  int player_index_bit)
+{
+  TestIfElementTouchesCustomElement(x, y);     // for empty space
+
+  if (level.finish_dig_collect)
+  {
+    int dig_side = MV_DIR_OPPOSITE(direction);
+
+    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                       player_index_bit, dig_side);
+  }
+}
+
 /*
   =============================================================================
   checkDiagonalPushing()
@@ -13994,18 +14128,28 @@ static int DigField(struct PlayerInfo *player,
 
     PlayLevelSoundElementAction(x, y, element, ACTION_DIGGING);
 
-    CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
-                                       player->index_bit, dig_side);
+    // use old behaviour for old levels (digging)
+    if (!level.finish_dig_collect)
+    {
+      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_DIGS_X,
+                                         player->index_bit, dig_side);
+
+      // if digging triggered player relocation, finish digging tile
+      if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
+    }
 
     if (mode == DF_SNAP)
     {
       if (level.block_snap_field)
-       setFieldForSnapping(x, y, element, move_direction);
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
       else
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
 
-      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
-                                         player->index_bit, dig_side);
+      // use old behaviour for old levels (snapping)
+      if (!level.finish_dig_collect)
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                           player->index_bit, dig_side);
     }
   }
   else if (player_can_move_or_snap && IS_COLLECTIBLE(element))
@@ -14117,19 +14261,28 @@ static int DigField(struct PlayerInfo *player,
     RaiseScoreElement(element);
     PlayLevelSoundElementAction(x, y, element, ACTION_COLLECTING);
 
-    if (is_player)
+    // use old behaviour for old levels (collecting)
+    if (!level.finish_dig_collect && is_player)
+    {
       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_COLLECTS_X,
                                          player->index_bit, dig_side);
 
+      // if collecting triggered player relocation, finish collecting tile
+      if (mode == DF_DIG && (player->jx != jx || player->jy != jy))
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
+    }
+
     if (mode == DF_SNAP)
     {
       if (level.block_snap_field)
-       setFieldForSnapping(x, y, element, move_direction);
+       SetFieldForSnapping(x, y, element, move_direction, player->index_bit);
       else
-       TestIfElementTouchesCustomElement(x, y);        // for empty space
+       TestFieldAfterSnapping(x, y, element, move_direction, player->index_bit);
 
-      CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
-                                         player->index_bit, dig_side);
+      // use old behaviour for old levels (snapping)
+      if (!level.finish_dig_collect)
+       CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
+                                           player->index_bit, dig_side);
     }
   }
   else if (player_can_move_or_snap && IS_PUSHABLE(element))
@@ -14467,6 +14620,8 @@ static int DigField(struct PlayerInfo *player,
     {
       player->is_collecting = !player->is_digging;
       player->is_active = TRUE;
+
+      player->last_removed_element = element;
     }
   }
 
@@ -15242,6 +15397,9 @@ void RequestRestartGame(char *message)
   }
   else
   {
+    // needed in case of envelope request to close game panel
+    CloseDoor(DOOR_CLOSE_1);
+
     SetGameStatus(GAME_MODE_MAIN);
 
     DrawMainMenu();