static struct GadgetInfo *game_gadget[NUM_GAME_BUTTONS];
+/* for detection of endless loops, caused by custom element programming */
+/* (using "MAX_PLAYFIELD_WIDTH" here is just a rough approximation...) */
+#define MAX_ELEMENT_CHANGE_RECURSION_DEPTH (MAX_PLAYFIELD_WIDTH)
+
+#define RECURSION_LOOP_DETECTION_START(e, rc) \
+{ \
+ if (recursion_loop_detected) \
+ return (rc); \
+ \
+ if (recursion_loop_depth > MAX_ELEMENT_CHANGE_RECURSION_DEPTH) \
+ { \
+ recursion_loop_detected = TRUE; \
+ recursion_loop_element = (e); \
+ } \
+ \
+ recursion_loop_depth++; \
+}
+
+#define RECURSION_LOOP_DETECTION_END() \
+{ \
+ recursion_loop_depth--; \
+}
+
+static int recursion_loop_depth;
+static boolean recursion_loop_detected;
+static boolean recursion_loop_element;
+
/* ------------------------------------------------------------------------- */
/* definition of elements that automatically change to other elements after */
EL_EMPTY);
}
}
+
+ /* ---------- initialize recursion detection ------------------------------ */
+ recursion_loop_depth = 0;
+ recursion_loop_detected = FALSE;
+ recursion_loop_element = EL_UNDEFINED;
}
int get_num_special_action(int element, int action_first, int action_last)
player->present = FALSE;
player->active = FALSE;
+ player->killed = FALSE;
player->action = 0;
player->effective_action = 0;
*sy = (sy1 + sy2) / 2;
}
-void DrawRelocateScreen(int x, int y, int move_dir, boolean center_screen,
- boolean quick_relocation)
+void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
+ boolean center_screen, boolean quick_relocation)
{
boolean ffwd_delay = (tape.playing && tape.fast_forward);
boolean no_delay = (tape.warp_forward);
if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
{
- scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left :
- x > SBX_Right + MIDPOSX ? SBX_Right :
- x - MIDPOSX);
+ if (center_screen)
+ {
+ scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left :
+ x > SBX_Right + MIDPOSX ? SBX_Right :
+ x - MIDPOSX);
+
+ scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ y - MIDPOSY);
+ }
+ else
+ {
+ /* quick relocation (without scrolling), but do not center screen */
- scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper :
- y > SBY_Lower + MIDPOSY ? SBY_Lower :
- y - MIDPOSY);
+ int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left :
+ old_x > SBX_Right + MIDPOSX ? SBX_Right :
+ old_x - MIDPOSX);
+
+ int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ old_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ old_y - MIDPOSY);
+
+ int offset_x = x + (scroll_x - center_scroll_x);
+ int offset_y = y + (scroll_y - center_scroll_y);
+
+ scroll_x = (offset_x < SBX_Left + MIDPOSX ? SBX_Left :
+ offset_x > SBX_Right + MIDPOSX ? SBX_Right :
+ offset_x - MIDPOSX);
+
+ scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper :
+ offset_y > SBY_Lower + MIDPOSY ? SBY_Lower :
+ offset_y - MIDPOSY);
+ }
}
else
{
}
/* only visually relocate centered player */
- DrawRelocateScreen(player->jx, player->jy, player->MovDir, FALSE,
- level.instant_relocation);
+ DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir,
+ FALSE, level.instant_relocation);
TestIfPlayerTouchesBadThing(jx, jy);
TestIfPlayerTouchesCustomElement(jx, jy);
if (!(trigger_events[trigger_element][trigger_event]))
return FALSE;
+#if 0
+ printf("::: CheckTriggeredElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+ trigger_event, recursion_loop_depth, recursion_loop_detected,
+ recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+ RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++)
{
int element = EL_CUSTOM_START + i;
}
}
+ RECURSION_LOOP_DETECTION_END();
+
return change_done_any;
}
return FALSE;
#endif
+#if 0
+ printf("::: CheckElementChangeExt %d ... [%d, %d, %d, '%s']\n",
+ trigger_event, recursion_loop_depth, recursion_loop_detected,
+ recursion_loop_element, EL_NAME(recursion_loop_element));
+#endif
+
+ RECURSION_LOOP_DETECTION_START(trigger_element, FALSE);
+
for (p = 0; p < element_info[element].num_change_pages; p++)
{
struct ElementChangeInfo *change = &element_info[element].change_page[p];
}
}
+ RECURSION_LOOP_DETECTION_END();
+
return change_done;
}
byte tape_action[MAX_PLAYERS];
int i;
+ /* detect endless loops, caused by custom element programming */
+ if (recursion_loop_detected && recursion_loop_depth == 0)
+ {
+ char *message = getStringCat3("Internal Error ! Element ",
+ EL_NAME(recursion_loop_element),
+ " caused endless loop ! Quit the game ?");
+
+ Error(ERR_WARN, "element '%s' caused endless loop in game engine",
+ EL_NAME(recursion_loop_element));
+
+ RequestQuitGameExt(FALSE, level_editor_test_game, message);
+
+ recursion_loop_detected = FALSE; /* if game should be continued */
+
+ free(message);
+
+ return;
+ }
+
if (game.restart_level)
StartGameActions(options.network, setup.autorecord, NEW_RANDOMIZE);
game.centered_player_nr = game.centered_player_nr_next;
game.set_centered_player = FALSE;
- DrawRelocateScreen(sx, sy, MV_NONE, TRUE, setup.quick_switch);
+ DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch);
DrawGameDoorValues();
}
if (!player->active)
return;
+ /* the following code was introduced to prevent an infinite loop when calling
+ -> Bang()
+ -> CheckTriggeredElementChangeExt()
+ -> ExecuteCustomElementAction()
+ -> KillPlayer()
+ -> (infinitely repeating the above sequence of function calls)
+ which occurs when killing the player while having a CE with the setting
+ "kill player X when explosion of <player X>"; the solution using a new
+ field "player->killed" was chosen for backwards compatibility, although
+ clever use of the fields "player->active" etc. would probably also work */
+#if 1
+ if (player->killed)
+ return;
+#endif
+
+ player->killed = TRUE;
+
/* remove accessible field at the player's position */
Feld[jx][jy] = EL_EMPTY;
}
}
-void RequestQuitGame(boolean ask_if_really_quit)
+void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
{
- if (AllPlayersGone ||
- !ask_if_really_quit ||
- level_editor_test_game ||
- Request("Do you really want to quit the game ?",
- REQ_ASK | REQ_STAY_CLOSED))
+ if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED))
{
#if defined(NETWORK_AVALIABLE)
if (options.network)
else
#endif
{
- if (!ask_if_really_quit || level_editor_test_game)
+ if (quick_quit)
{
game_status = GAME_MODE_MAIN;
}
}
}
- else
+ else /* continue playing the game */
{
if (tape.playing && tape.deactivate_display)
TapeDeactivateDisplayOff(TRUE);
}
}
+void RequestQuitGame(boolean ask_if_really_quit)
+{
+ boolean quick_quit = (!ask_if_really_quit || level_editor_test_game);
+ boolean skip_request = AllPlayersGone || quick_quit;
+
+ RequestQuitGameExt(skip_request, quick_quit,
+ "Do you really want to quit the game ?");
+}
+
/* ------------------------------------------------------------------------- */
/* random generator functions */