X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=c5e016f9f39e71801481f1d2ee3661e9327f6382;hb=000f4fbffe0d915d1ded9c981a5a2d521cdf7e5e;hp=3f3e6317d9629e25c395e863fc4ea8babdfe51bd;hpb=0c64a3cf001f3ef5ecdb80462ae1891d582379ee;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index 3f3e6317..c5e016f9 100644 --- a/src/game.c +++ b/src/game.c @@ -244,7 +244,8 @@ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, IS_FOOD_PIG(Feld[x][y])) #define PENGUIN_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN ||\ + ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, (Feld[x][y] == EL_EXIT_OPEN || \ + Feld[x][y] == EL_STEEL_EXIT_OPEN || \ IS_FOOD_PENGUIN(Feld[x][y]))) #define DRAGON_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_2(e, x, y, 0) @@ -379,6 +380,33 @@ static int getInvisibleFromInvisibleActiveElement(int); 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 */ @@ -447,6 +475,22 @@ static struct ChangingElementInfo change_delay_list[] = NULL, NULL }, + { + EL_STEEL_EXIT_OPENING, + EL_STEEL_EXIT_OPEN, + 29, + NULL, + NULL, + NULL + }, + { + EL_STEEL_EXIT_CLOSING, + EL_STEEL_EXIT_CLOSED, + 29, + NULL, + NULL, + NULL + }, { EL_SP_EXIT_OPENING, EL_SP_EXIT_OPEN, @@ -1786,6 +1830,11 @@ static void InitGameEngine() 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) @@ -2742,12 +2791,14 @@ void GameWon() /* close exit door after last player */ if (AllPlayersGone && (Feld[ExitX][ExitY] == EL_EXIT_OPEN || - Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN)) + Feld[ExitX][ExitY] == EL_SP_EXIT_OPEN || + Feld[ExitX][ExitY] == EL_STEEL_EXIT_OPEN)) { int element = Feld[ExitX][ExitY]; Feld[ExitX][ExitY] = (element == EL_EXIT_OPEN ? EL_EXIT_CLOSING : - EL_SP_EXIT_CLOSING); + element == EL_SP_EXIT_OPEN ? EL_SP_EXIT_CLOSING: + EL_STEEL_EXIT_CLOSING); PlayLevelSoundElementAction(ExitX, ExitY, element, ACTION_CLOSING); } @@ -5022,7 +5073,8 @@ inline static void TurnRoundExt(int x, int y) int ex = x + xy[i][0]; int ey = y + xy[i][1]; - if (IN_LEV_FIELD(ex, ey) && Feld[ex][ey] == EL_EXIT_OPEN) + if (IN_LEV_FIELD(ex, ey) && (Feld[ex][ey] == EL_EXIT_OPEN || + Feld[ex][ey] == EL_STEEL_EXIT_OPEN)) { attr_x = ex; attr_y = ey; @@ -6019,7 +6071,8 @@ void StartMoving(int x, int y) } else if (element == EL_PENGUIN && IN_LEV_FIELD(newx, newy)) { - if (Feld[newx][newy] == EL_EXIT_OPEN) + if (Feld[newx][newy] == EL_EXIT_OPEN || + Feld[newx][newy] == EL_STEEL_EXIT_OPEN) { RemoveField(x, y); DrawLevelField(x, y); @@ -7280,6 +7333,29 @@ void CheckExit(int x, int y) PlayLevelSoundNearest(x, y, SND_CLASS_EXIT_OPENING); } +void CheckExitSteel(int x, int y) +{ + if (local_player->gems_still_needed > 0 || + local_player->sokobanfields_still_needed > 0 || + local_player->lights_still_needed > 0) + { + int element = Feld[x][y]; + int graphic = el2img(element); + + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + + return; + } + + if (AllPlayersGone) /* do not re-open exit door closed after last player */ + return; + + Feld[x][y] = EL_STEEL_EXIT_OPENING; + + PlayLevelSoundNearest(x, y, SND_CLASS_STEEL_EXIT_OPENING); +} + void CheckExitSP(int x, int y) { if (local_player->gems_still_needed > 0) @@ -8578,6 +8654,14 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, 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; @@ -8646,6 +8730,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done_any; } @@ -8684,6 +8770,14 @@ static boolean CheckElementChangeExt(int x, int y, 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]; @@ -8763,6 +8857,8 @@ static boolean CheckElementChangeExt(int x, int y, } } + RECURSION_LOOP_DETECTION_END(); + return change_done; } @@ -9164,6 +9260,25 @@ void GameActions() 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); @@ -9564,6 +9679,7 @@ void GameActions_RND() else if ((element == EL_ACID || element == EL_EXIT_OPEN || element == EL_SP_EXIT_OPEN || + element == EL_STEEL_EXIT_OPEN || element == EL_SP_TERMINAL || element == EL_SP_TERMINAL_ACTIVE || element == EL_EXTRA_TIME || @@ -9589,6 +9705,8 @@ void GameActions_RND() Life(x, y); else if (element == EL_EXIT_CLOSED) CheckExit(x, y); + else if (element == EL_STEEL_EXIT_CLOSED) + CheckExitSteel(x, y); else if (element == EL_SP_EXIT_CLOSED) CheckExitSP(x, y); else if (element == EL_EXPANDABLE_WALL_GROWING) @@ -10411,6 +10529,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) player->last_jy = jy; if (Feld[jx][jy] == EL_EXIT_OPEN || + Feld[jx][jy] == EL_STEEL_EXIT_OPEN || Feld[jx][jy] == EL_SP_EXIT_OPEN || Feld[jx][jy] == EL_SP_EXIT_OPENING) /* <-- special case */ { @@ -11096,8 +11215,10 @@ void KillPlayer(struct PlayerInfo *player) "kill player X when explosion of "; 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; @@ -11374,6 +11495,7 @@ int DigField(struct PlayerInfo *player, return MP_NO_ACTION; } else if (element == EL_EXIT_OPEN || + element == EL_STEEL_EXIT_OPEN || element == EL_SP_EXIT_OPEN || element == EL_SP_EXIT_OPENING) { @@ -12536,13 +12658,9 @@ void RaiseScoreElement(int element) } } -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) @@ -12550,7 +12668,7 @@ void RequestQuitGame(boolean ask_if_really_quit) else #endif { - if (!ask_if_really_quit || level_editor_test_game) + if (quick_quit) { game_status = GAME_MODE_MAIN; @@ -12566,7 +12684,7 @@ void RequestQuitGame(boolean ask_if_really_quit) } } } - else + else /* continue playing the game */ { if (tape.playing && tape.deactivate_display) TapeDeactivateDisplayOff(TRUE); @@ -12578,6 +12696,15 @@ void RequestQuitGame(boolean ask_if_really_quit) } } +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 */