changed "http" to "https" in URLs
[rocksndiamonds.git] / src / game.c
index 1f019a4e1dfba2e62d425d82906a7bcbf9b71b8d..803eebb327f93d0ca0f6cc636a4f388025ceafbb 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // game.c
 // ============================================================================
@@ -1785,7 +1785,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game)
     player->jy = player->last_jy = y;
   }
 
-  if (!init_game)
+  // always check if player was just killed and should be reanimated
   {
     int player_nr = GET_PLAYER_NR(element);
     struct PlayerInfo *player = &stored_player[player_nr];
@@ -1877,6 +1877,8 @@ static void InitField(int x, int y, boolean init_game)
     case EL_MOLE_RIGHT:
     case EL_MOLE_UP:
     case EL_MOLE_DOWN:
+    case EL_SPRING_LEFT:
+    case EL_SPRING_RIGHT:
       InitMovDir(x, y);
       break;
 
@@ -1963,12 +1965,12 @@ static void InitField(int x, int y, boolean init_game)
       break;
 
     case EL_EMC_MAGIC_BALL:
-      if (game.ball_state)
+      if (game.ball_active)
        Feld[x][y] = EL_EMC_MAGIC_BALL_ACTIVE;
       break;
 
     case EL_EMC_MAGIC_BALL_SWITCH:
-      if (game.ball_state)
+      if (game.ball_active)
        Feld[x][y] = EL_EMC_MAGIC_BALL_SWITCH_ACTIVE;
       break;
 
@@ -2126,9 +2128,9 @@ static int compareGamePanelOrderInfo(const void *object1, const void *object2)
 int getPlayerInventorySize(int player_nr)
 {
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-    return level.native_em_level->ply[player_nr]->dynamite;
+    return game_em.ply[player_nr]->dynamite;
   else if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
-    return level.native_sp_level->game_sp->red_disk_count;
+    return game_sp.red_disk_count;
   else
     return stored_player[player_nr].inventory_size;
 }
@@ -2212,32 +2214,32 @@ static void UpdateGameControlValues(void)
   int time = (game.LevelSolved ?
              game.LevelSolved_CountingTime :
              level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-             level.native_em_level->lev->time :
+             game_em.lev->time :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-             level.native_sp_level->game_sp->time_played :
+             game_sp.time_played :
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.energy_left :
              game.no_time_limit ? TimePlayed : TimeLeft);
   int score = (game.LevelSolved ?
               game.LevelSolved_CountingScore :
               level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-              level.native_em_level->lev->score :
+              game_em.lev->score :
               level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-              level.native_sp_level->game_sp->score :
+              game_sp.score :
               level.game_engine_type == GAME_ENGINE_TYPE_MM ?
               game_mm.score :
               game.score);
   int gems = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-             level.native_em_level->lev->required :
+             game_em.lev->gems_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-             level.native_sp_level->game_sp->infotrons_still_needed :
+             game_sp.infotrons_still_needed :
              level.game_engine_type == GAME_ENGINE_TYPE_MM ?
              game_mm.kettles_still_needed :
              game.gems_still_needed);
   int exit_closed = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                    level.native_em_level->lev->required > 0 :
+                    game_em.lev->gems_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_SP ?
-                    level.native_sp_level->game_sp->infotrons_still_needed > 0 :
+                    game_sp.infotrons_still_needed > 0 :
                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                     game_mm.kettles_still_needed > 0 ||
                     game_mm.lights_still_needed > 0 :
@@ -2277,7 +2279,7 @@ static void UpdateGameControlValues(void)
       {
        if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
        {
-         if (level.native_em_level->ply[i]->keys & (1 << k))
+         if (game_em.ply[i]->keys & (1 << k))
            game_panel_controls[GAME_PANEL_KEY_1 + k].value =
              get_key_element_from_nr(k);
        }
@@ -2305,7 +2307,7 @@ static void UpdateGameControlValues(void)
     {
       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
       {
-       if (level.native_em_level->ply[player_nr]->keys & (1 << k))
+       if (game_em.ply[player_nr]->keys & (1 << k))
          game_panel_controls[GAME_PANEL_KEY_1 + k].value =
            get_key_element_from_nr(k);
       }
@@ -2366,9 +2368,9 @@ static void UpdateGameControlValues(void)
     (exit_closed ? EL_EXIT_CLOSED : EL_EXIT_OPEN);
 
   game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL].value =
-    (game.ball_state ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
+    (game.ball_active ? EL_EMC_MAGIC_BALL_ACTIVE : EL_EMC_MAGIC_BALL);
   game_panel_controls[GAME_PANEL_EMC_MAGIC_BALL_SWITCH].value =
-    (game.ball_state ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
+    (game.ball_active ? EL_EMC_MAGIC_BALL_SWITCH_ACTIVE :
      EL_EMC_MAGIC_BALL_SWITCH);
 
   game_panel_controls[GAME_PANEL_LIGHT_SWITCH].value =
@@ -2840,10 +2842,84 @@ static void InitGameEngine(void)
     game.team_mode = (num_players > 1);
   }
 
+#if 0
+  printf("level %d: level.game_version  == %06d\n", level_nr,
+        level.game_version);
+  printf("          tape.file_version   == %06d\n",
+        tape.file_version);
+  printf("          tape.game_version   == %06d\n",
+        tape.game_version);
+  printf("          tape.engine_version == %06d\n",
+        tape.engine_version);
+  printf("       => game.engine_version == %06d [tape mode: %s]\n",
+        game.engine_version, (tape.playing ? "PLAYING" : "RECORDING"));
+#endif
+
   // --------------------------------------------------------------------------
   // set flags for bugs and changes according to active game engine version
   // --------------------------------------------------------------------------
 
+  /*
+    Summary of bugfix:
+    Fixed property "can fall" for run-time element "EL_AMOEBA_DROPPING"
+
+    Bug was introduced in version:
+    2.0.1
+
+    Bug was fixed in version:
+    4.1.4.2
+
+    Description:
+    In version 2.0.1, a new run-time element "EL_AMOEBA_DROPPING" was added,
+    but the property "can fall" was missing, which caused some levels to be
+    unsolvable. This was fixed in version 4.1.4.2.
+
+    Affected levels/tapes:
+    An example for a tape that was fixed by this bugfix is tape 029 from the
+    level set "rnd_sam_bateman".
+    The wrong behaviour will still be used for all levels or tapes that were
+    created/recorded with it. An example for this is tape 023 from the level
+    set "rnd_gerhard_haeusler", which was recorded with a buggy game engine.
+  */
+
+  boolean use_amoeba_dropping_cannot_fall_bug =
+    ((game.engine_version >= VERSION_IDENT(2,0,1,0) &&
+      game.engine_version <= VERSION_IDENT(4,1,4,1)) ||
+     (tape.playing &&
+      tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+      tape.game_version <= VERSION_IDENT(4,1,4,1)));
+
+  /*
+    Summary of bugfix/change:
+    Fixed move speed of elements entering or leaving magic wall.
+
+    Fixed/changed in version:
+    2.0.1
+
+    Description:
+    Before 2.0.1, move speed of elements entering or leaving magic wall was
+    twice as fast as it is now.
+    Since 2.0.1, this is set to a lower value by using move_stepsize_list[].
+
+    Affected levels/tapes:
+    The first condition is generally needed for all levels/tapes before version
+    2.0.1, which might use the old behaviour before it was changed; known tapes
+    that are affected: Tape 014 from the level set "rnd_conor_mancone".
+    The second condition is an exception from the above case and is needed for
+    the special case of tapes recorded with game (not engine!) version 2.0.1 or
+    above, but before it was known that this change would break tapes like the
+    above and was fixed in 4.1.4.2, so that the changed behaviour was active
+    although the engine version while recording maybe was before 2.0.1. There
+    are a lot of tapes that are affected by this exception, like tape 006 from
+    the level set "rnd_conor_mancone".
+  */
+
+  boolean use_old_move_stepsize_for_magic_wall =
+    (game.engine_version < VERSION_IDENT(2,0,1,0) &&
+     !(tape.playing &&
+       tape.game_version >= VERSION_IDENT(2,0,1,0) &&
+       tape.game_version <  VERSION_IDENT(4,1,4,2)));
+
   /*
     Summary of bugfix/change:
     Fixed handling for custom elements that change when pushed by the player.
@@ -2906,12 +2982,29 @@ static void InitGameEngine(void)
   game.use_block_last_field_bug =
     (game.engine_version < VERSION_IDENT(3,1,1,0));
 
+  /* various special flags and settings for native Emerald Mine game engine */
+
   game_em.use_single_button =
     (game.engine_version > VERSION_IDENT(4,0,0,2));
 
   game_em.use_snap_key_bug =
     (game.engine_version < VERSION_IDENT(4,0,1,0));
 
+  game_em.use_old_explosions =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_android =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_push_elements =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_old_push_into_acid =
+    (game.engine_version < VERSION_IDENT(4,1,4,2));
+
+  game_em.use_wrap_around =
+    (game.engine_version > VERSION_IDENT(4,1,4,1));
+
   // --------------------------------------------------------------------------
 
   // set maximal allowed number of custom element changes per game frame
@@ -2923,13 +3016,11 @@ static void InitGameEngine(void)
   // dynamically adjust element properties according to game engine version
   InitElementPropertiesEngine(game.engine_version);
 
-#if 0
-  printf("level %d: level version == %06d\n", level_nr, level.game_version);
-  printf("          tape version == %06d [%s] [file: %06d]\n",
-        tape.engine_version, (tape.playing ? "PLAYING" : "RECORDING"),
-        tape.file_version);
-  printf("       => game.engine_version == %06d\n", game.engine_version);
-#endif
+  // ---------- initialize special element properties -------------------------
+
+  // "EL_AMOEBA_DROPPING" missed property "can fall" between 2.0.1 and 4.1.4.1
+  if (use_amoeba_dropping_cannot_fall_bug)
+    SET_PROPERTY(EL_AMOEBA_DROPPING, EP_CAN_FALL, FALSE);
 
   // ---------- initialize player's initial move delay ------------------------
 
@@ -3176,6 +3267,16 @@ static void InitGameEngine(void)
     int e = move_stepsize_list[i].element;
 
     element_info[e].move_stepsize = move_stepsize_list[i].move_stepsize;
+
+    // set move stepsize value for certain elements for older engine versions
+    if (use_old_move_stepsize_for_magic_wall)
+    {
+      if (e == EL_MAGIC_WALL_FILLING ||
+         e == EL_MAGIC_WALL_EMPTYING ||
+         e == EL_BD_MAGIC_WALL_FILLING ||
+         e == EL_BD_MAGIC_WALL_EMPTYING)
+       element_info[e].move_stepsize *= 2;
+    }
   }
 
   // ---------- initialize collect score --------------------------------------
@@ -3250,6 +3351,8 @@ static void InitGameEngine(void)
   // ---------- initialize graphics engine ------------------------------------
   game.scroll_delay_value =
     (game.forced_scroll_delay_value != -1 ? game.forced_scroll_delay_value :
+     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);
@@ -3272,6 +3375,34 @@ static void InitGameEngine(void)
   // Supaplex levels with time limit currently unsupported -- should be added
   if (level.game_engine_type == GAME_ENGINE_TYPE_SP)
     level.time = 0;
+
+  // ---------- initialize flags for handling game actions --------------------
+
+  // set flags for game actions to default values
+  game.use_key_actions = TRUE;
+  game.use_mouse_actions = FALSE;
+
+  // when using Mirror Magic game engine, handle mouse events only
+  if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
+  {
+    game.use_key_actions = FALSE;
+    game.use_mouse_actions = TRUE;
+  }
+
+  // check for custom elements with mouse click events
+  if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
+  {
+    for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
+    {
+      int element = EL_CUSTOM_START + i;
+
+      if (HAS_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) ||
+         HAS_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) ||
+         HAS_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) ||
+         HAS_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X))
+       game.use_mouse_actions = TRUE;
+    }
+  }
 }
 
 static int get_num_special_action(int element, int action_first,
@@ -3384,6 +3515,13 @@ void InitGame(void)
   InitGameEngine();
   InitGameControlValues();
 
+  // initialize tape actions from game when recording tape
+  if (tape.recording)
+  {
+    tape.use_key_actions   = game.use_key_actions;
+    tape.use_mouse_actions = game.use_mouse_actions;
+  }
+
   // don't play tapes over network
   network_playing = (network.enabled && !tape.playing);
 
@@ -3612,7 +3750,7 @@ void InitGame(void)
   game.lenses_time_left = 0;
   game.magnify_time_left = 0;
 
-  game.ball_state = level.ball_state_initial;
+  game.ball_active = level.ball_active_initial;
   game.ball_content_nr = 0;
 
   game.explosions_delayed = TRUE;
@@ -3746,12 +3884,13 @@ void InitGame(void)
   // use preferred player also in local single-player mode
   if (!network.enabled && !game.team_mode)
   {
-    int old_index_nr = local_player->index_nr;
     int new_index_nr = setup.network_player_nr;
 
     if (new_index_nr >= 0 && new_index_nr < MAX_PLAYERS)
     {
-      stored_player[old_index_nr].connected_locally = FALSE;
+      for (i = 0; i < MAX_PLAYERS; i++)
+       stored_player[i].connected_locally = FALSE;
+
       stored_player[new_index_nr].connected_locally = TRUE;
     }
   }
@@ -3933,7 +4072,7 @@ void InitGame(void)
   game.centered_player_nr = (network_playing ? local_player->index_nr : -1);
   game.centered_player_nr_next = game.centered_player_nr;
   game.set_centered_player = FALSE;
-  game.set_centered_player_fast = FALSE;
+  game.set_centered_player_wrap = FALSE;
 
   if (network_playing && tape.recording)
   {
@@ -4269,12 +4408,6 @@ void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y,
 {
   // this is used for non-R'n'D game engines to update certain engine values
 
-  if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-  {
-    actual_player_x = correctLevelPosX_EM(actual_player_x);
-    actual_player_y = correctLevelPosY_EM(actual_player_y);
-  }
-
   // needed to determine if sounds are played within the visible screen area
   scroll_x = actual_scroll_x;
   scroll_y = actual_scroll_y;
@@ -4367,6 +4500,12 @@ void InitMovDir(int x, int y)
       MovDir[x][y] = direction[2][element - EL_MOLE_LEFT];
       break;
 
+    case EL_SPRING_LEFT:
+    case EL_SPRING_RIGHT:
+      Feld[x][y] = EL_SPRING;
+      MovDir[x][y] = direction[2][element - EL_SPRING_LEFT];
+      break;
+
     default:
       if (IS_CUSTOM_ELEMENT(element))
       {
@@ -4492,7 +4631,7 @@ static void LevelSolved(void)
   game.GameOver = TRUE;
 
   game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                     level.native_em_level->lev->score :
+                     game_em.lev->score :
                      level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                      game_mm.score :
                      game.score);
@@ -9872,11 +10011,14 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
     // ---------- player actions  ---------------------------------------------
 
     case CA_MOVE_PLAYER:
+    case CA_MOVE_PLAYER_NEW:
     {
       // automatically move to the next field in specified direction
       for (i = 0; i < MAX_PLAYERS; i++)
        if (trigger_player_bits & (1 << i))
-         stored_player[i].programmed_action = action_arg_direction;
+         if (action_type == CA_MOVE_PLAYER ||
+             stored_player[i].MovPos == 0)
+           stored_player[i].programmed_action = action_arg_direction;
 
       break;
     }
@@ -10152,6 +10294,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
 
        if (CustomValue[x][y] == 0)
        {
+         // reset change counter (else CE_VALUE_GETS_ZERO would not work)
+         ChangeCount[x][y] = 0;        // allow at least one more change
+
          CheckElementChange(x, y, element, EL_UNDEFINED, CE_VALUE_GETS_ZERO);
          CheckTriggeredElementChange(x, y, element, CE_VALUE_GETS_ZERO_OF_X);
        }
@@ -10175,6 +10320,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page)
        {
          int xx, yy;
 
+         // reset change counter (else CE_SCORE_GETS_ZERO would not work)
+         ChangeCount[x][y] = 0;        // allow at least one more change
+
          CheckElementChange(x, y, element, EL_UNDEFINED, CE_SCORE_GETS_ZERO);
          CheckTriggeredElementChange(x, y, element, CE_SCORE_GETS_ZERO_OF_X);
 
@@ -11131,7 +11279,7 @@ static byte PlayerActions(struct PlayerInfo *player, byte player_action)
 static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
                                         byte *tape_action)
 {
-  if (!tape.use_mouse)
+  if (!tape.use_mouse_actions)
     return;
 
   mouse_action->lx     = tape_action[TAPE_ACTION_LX];
@@ -11142,7 +11290,7 @@ static void SetMouseActionFromTapeAction(struct MouseActionInfo *mouse_action,
 static void SetTapeActionFromMouseAction(byte *tape_action,
                                         struct MouseActionInfo *mouse_action)
 {
-  if (!tape.use_mouse)
+  if (!tape.use_mouse_actions)
     return;
 
   tape_action[TAPE_ACTION_LX]     = mouse_action->lx;
@@ -11240,7 +11388,7 @@ static void CheckLevelTime(void)
        if (!TimeLeft && setup.time_limit)
        {
          if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
-           level.native_em_level->lev->killed_out_of_time = TRUE;
+           game_em.lev->killed_out_of_time = TRUE;
          else
            for (i = 0; i < MAX_PLAYERS; i++)
              KillPlayer(&stored_player[i]);
@@ -11251,8 +11399,7 @@ static void CheckLevelTime(void)
        game_panel_controls[GAME_PANEL_TIME].value = TimePlayed;
       }
 
-      level.native_em_level->lev->time =
-       (game.no_time_limit ? TimePlayed : TimeLeft);
+      game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft);
     }
 
     if (tape.recording || tape.playing)
@@ -11341,7 +11488,7 @@ static void GameActionsExt(void)
   unsigned int game_frame_delay_value;
   byte *recorded_player_action;
   byte summarized_player_action = 0;
-  byte tape_action[MAX_PLAYERS];
+  byte tape_action[MAX_TAPE_ACTIONS] = { 0 };
   int i;
 
   // detect endless loops, caused by custom element programming
@@ -11674,6 +11821,8 @@ void GameActions_RND_Main(void)
 
 void GameActions_RND(void)
 {
+  static struct MouseActionInfo mouse_action_last = { 0 };
+  struct MouseActionInfo mouse_action = local_player->effective_mouse_action;
   int magic_wall_x = 0, magic_wall_y = 0;
   int i, x, y, element, graphic, last_gfx_frame;
 
@@ -11860,6 +12009,24 @@ void GameActions_RND(void)
 #endif
   }
 
+  if (mouse_action.button)
+  {
+    int new_button = (mouse_action.button && mouse_action_last.button == 0);
+
+    x = mouse_action.lx;
+    y = mouse_action.ly;
+    element = Feld[x][y];
+
+    if (new_button)
+    {
+      CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE);
+      CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X);
+    }
+
+    CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE);
+    CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X);
+  }
+
   SCAN_PLAYFIELD(x, y)
   {
     element = Feld[x][y];
@@ -12201,6 +12368,8 @@ void GameActions_RND(void)
   // use random number generator in every frame to make it less predictable
   if (game.engine_version >= VERSION_IDENT(3,1,1,0))
     RND(1);
+
+  mouse_action_last = mouse_action;
 }
 
 static boolean AllPlayersInSight(struct PlayerInfo *player, int x, int y)
@@ -14219,13 +14388,13 @@ static int DigField(struct PlayerInfo *player,
     {
       int xx, yy;
 
-      game.ball_state = !game.ball_state;
+      game.ball_active = !game.ball_active;
 
       SCAN_PLAYFIELD(xx, yy)
       {
        int e = Feld[xx][yy];
 
-       if (game.ball_state)
+       if (game.ball_active)
        {
          if (e == EL_EMC_MAGIC_BALL)
            CreateField(xx, yy, EL_EMC_MAGIC_BALL_ACTIVE);
@@ -14702,10 +14871,10 @@ static void PlayLevelMusic(void)
 
 void PlayLevelSound_EM(int xx, int yy, int element_em, int sample)
 {
-  int element = (element_em > -1 ? map_element_EM_to_RND(element_em) : 0);
-  int offset = (BorderElement == EL_STEELWALL ? 1 : 0);
-  int x = xx - 1 - offset;
-  int y = yy - 1 - offset;
+  int element = (element_em > -1 ? map_element_EM_to_RND_game(element_em) : 0);
+  int offset = 0;
+  int x = xx - offset;
+  int y = yy - offset;
 
   switch (sample)
   {