X-Git-Url: https://git.artsoft.org/?p=rocksndiamonds.git;a=blobdiff_plain;f=src%2Fgame.c;h=3d2f055f27c88415dccb499d83d65932ad8a5f1e;hp=cacf857b4dba76576bdf07575af52930baa29cbd;hb=567cfcb396e3fc031a25362b747dd0a5a096b4ec;hpb=f73fd0cea8ac4e9836ea9a4c12c89be0acef03ba diff --git a/src/game.c b/src/game.c index cacf857b..3d2f055f 100644 --- a/src/game.c +++ b/src/game.c @@ -15,9 +15,11 @@ #include "init.h" #include "tools.h" #include "screens.h" +#include "events.h" #include "files.h" #include "tape.h" #include "network.h" +#include "anim.h" /* DEBUG SETTINGS */ @@ -970,13 +972,16 @@ static struct GamePanelControlInfo game_panel_controls[] = #define GAME_CTRL_ID_STOP 0 #define GAME_CTRL_ID_PAUSE 1 #define GAME_CTRL_ID_PLAY 2 -#define SOUND_CTRL_ID_MUSIC 3 -#define SOUND_CTRL_ID_LOOPS 4 -#define SOUND_CTRL_ID_SIMPLE 5 -#define GAME_CTRL_ID_SAVE 6 +#define GAME_CTRL_ID_UNDO 3 +#define GAME_CTRL_ID_REDO 4 +#define GAME_CTRL_ID_SAVE 5 +#define GAME_CTRL_ID_PAUSE2 6 #define GAME_CTRL_ID_LOAD 7 +#define SOUND_CTRL_ID_MUSIC 8 +#define SOUND_CTRL_ID_LOOPS 9 +#define SOUND_CTRL_ID_SIMPLE 10 -#define NUM_GAME_BUTTONS 8 +#define NUM_GAME_BUTTONS 11 /* forward declaration for internal use */ @@ -1945,7 +1950,7 @@ static void InitField(int x, int y, boolean init_game) CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X); } -static inline void InitField_WithBug1(int x, int y, boolean init_game) +inline static void InitField_WithBug1(int x, int y, boolean init_game) { InitField(x, y, init_game); @@ -1955,7 +1960,7 @@ static inline void InitField_WithBug1(int x, int y, boolean init_game) InitMovDir(x, y); } -static inline void InitField_WithBug2(int x, int y, boolean init_game) +inline static void InitField_WithBug2(int x, int y, boolean init_game) { int old_element = Feld[x][y]; @@ -2442,7 +2447,7 @@ void DisplayGameControlValues() /* redraw game control buttons */ RedrawGameButtons(); - game_status = GAME_MODE_PSEUDO_PANEL; + SetGameStatus(GAME_MODE_PSEUDO_PANEL); for (i = 0; i < NUM_GAME_PANEL_CONTROLS; i++) { @@ -2570,12 +2575,12 @@ void DisplayGameControlValues() redraw_mask |= REDRAW_DOOR_1; } - game_status = GAME_MODE_PLAYING; + SetGameStatus(GAME_MODE_PLAYING); } void UpdateAndDisplayGameControlValues() { - if (tape.warp_forward) + if (tape.deactivate_display) return; UpdateGameControlValues(); @@ -3031,6 +3036,20 @@ static void InitGameEngine() setup.scroll_delay ? setup.scroll_delay_value : 0); 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++) + game.snapshot.last_action[i] = 0; + game.snapshot.changed_action = FALSE; + game.snapshot.collected_item = FALSE; + game.snapshot.mode = + (strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_STEP) ? + SNAPSHOT_MODE_EVERY_STEP : + strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_MOVE) ? + SNAPSHOT_MODE_EVERY_MOVE : + strEqual(setup.engine_snapshot_mode, STR_SNAPSHOT_MODE_EVERY_COLLECT) ? + SNAPSHOT_MODE_EVERY_COLLECT : SNAPSHOT_MODE_OFF); + game.snapshot.save_snapshot = FALSE; } int get_num_special_action(int element, int action_first, int action_last) @@ -3069,6 +3088,7 @@ void InitGame() { int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0); int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0); + int fade_mask = REDRAW_FIELD; boolean emulate_bd = TRUE; /* unless non-BOULDERDASH elements found */ boolean emulate_sb = TRUE; /* unless non-SOKOBAN elements found */ @@ -3076,23 +3096,35 @@ void InitGame() int initial_move_dir = MV_DOWN; int i, j, x, y; - game_status = GAME_MODE_PLAYING; - - StopAnimation(); + // required here to update video display before fading (FIX THIS) + DrawMaskedBorder(REDRAW_DOOR_2); if (!game.restart_level) CloseDoor(DOOR_CLOSE_1); + SetGameStatus(GAME_MODE_PLAYING); + if (level_editor_test_game) FadeSkipNextFadeIn(); else FadeSetEnterScreen(); - FadeOut(REDRAW_FIELD); + if (CheckIfGlobalBorderHasChanged()) + fade_mask = REDRAW_ALL; + + FadeSoundsAndMusic(); + + ExpireSoundLoops(TRUE); + + FadeOut(fade_mask); /* needed if different viewport properties defined for playing */ ChangeViewportPropertiesIfNeeded(); + ClearField(); + + OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW); + DrawCompleteVideoDisplay(); InitGameEngine(); @@ -3181,6 +3213,11 @@ void InitGame() player->is_bored = FALSE; player->is_sleeping = FALSE; + player->was_waiting = TRUE; + player->was_moving = FALSE; + player->was_snapping = FALSE; + player->was_dropping = FALSE; + player->frame_counter_bored = -1; player->frame_counter_sleeping = -1; @@ -3903,11 +3940,11 @@ void InitGame() /* blit playfield from scroll buffer to normal back buffer for fading in */ BlitScreenToBitmap(backbuffer); - - redraw_mask |= REDRAW_FROM_BACKBUFFER; /* !!! FIX THIS (END) !!! */ - FadeIn(REDRAW_FIELD); + DrawMaskedBorder(fade_mask); + + FadeIn(fade_mask); #if 1 // full screen redraw is required at this point in the following cases: @@ -3936,9 +3973,14 @@ void InitGame() { UnmapGameButtons(); UnmapTapeButtons(); + + FreeGameButtons(); + CreateGameButtons(); + game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music; game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops; game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple; + MapGameButtons(); MapTapeButtons(); @@ -3991,15 +4033,28 @@ void InitGame() } game.restart_level = FALSE; + + SaveEngineSnapshotToListInitial(); } -void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y) +void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y, + int actual_player_x, int actual_player_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; + + /* needed to get player position for "follow finger" playing input method */ + local_player->jx = actual_player_x; + local_player->jy = actual_player_y; } void InitMovDir(int x, int y) @@ -4377,29 +4432,30 @@ void GameEnd() local_player->LevelSolved_GameEnd = TRUE; - CloseDoor(DOOR_CLOSE_1); + if (!global.use_envelope_request) + CloseDoor(DOOR_CLOSE_1); if (local_player->LevelSolved_SaveTape) { SaveTapeChecked(tape.level_nr); /* ask to save tape */ } + CloseDoor(DOOR_CLOSE_ALL); + if (level_editor_test_game) { - game_status = GAME_MODE_MAIN; + SetGameStatus(GAME_MODE_MAIN); - DrawAndFadeInMainMenu(REDRAW_FIELD); + DrawMainMenu(); return; } if (!local_player->LevelSolved_SaveScore) { - FadeOut(REDRAW_FIELD); - - game_status = GAME_MODE_MAIN; + SetGameStatus(GAME_MODE_MAIN); - DrawAndFadeInMainMenu(REDRAW_FIELD); + DrawMainMenu(); return; } @@ -4416,7 +4472,7 @@ void GameEnd() if ((hi_pos = NewHiScore()) >= 0) { - game_status = GAME_MODE_SCORES; + SetGameStatus(GAME_MODE_SCORES); DrawHallOfFame(hi_pos); @@ -4428,9 +4484,7 @@ void GameEnd() } else { - FadeOut(REDRAW_FIELD); - - game_status = GAME_MODE_MAIN; + SetGameStatus(GAME_MODE_MAIN); if (raise_level) { @@ -4438,7 +4492,7 @@ void GameEnd() TapeErase(); } - DrawAndFadeInMainMenu(REDRAW_FIELD); + DrawMainMenu(); } } @@ -4858,168 +4912,107 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy) void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, boolean center_screen, boolean quick_relocation) { + unsigned int frame_delay_value_old = GetVideoFrameDelay(); boolean ffwd_delay = (tape.playing && tape.fast_forward); boolean no_delay = (tape.warp_forward); int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay); int wait_delay_value = (no_delay ? 0 : frame_delay_value); + int new_scroll_x, new_scroll_y; - if (quick_relocation) + if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y))) { - if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen) - { - if (!level.shifted_relocation || center_screen) - { - /* quick relocation (without scrolling), with centering of 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 */ - - 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); + /* case 1: quick relocation inside visible screen (without scrolling) */ - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + RedrawPlayfield(); - scroll_x = (offset_x < SBX_Left + MIDPOSX ? SBX_Left : - offset_x > SBX_Right + MIDPOSX ? SBX_Right : - offset_x - MIDPOSX); + return; + } - scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper : - offset_y > SBY_Lower + MIDPOSY ? SBY_Lower : - offset_y - MIDPOSY); - } - } - else - { - if (!level.shifted_relocation || center_screen) - { - /* quick relocation (without scrolling), with centering of screen */ + if (!level.shifted_relocation || center_screen) + { + /* relocation _with_ centering of screen */ - scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left : + new_scroll_x = (x < SBX_Left + MIDPOSX ? SBX_Left : x > SBX_Right + MIDPOSX ? SBX_Right : x - MIDPOSX); - scroll_y = (y < SBY_Upper + MIDPOSY ? SBY_Upper : + new_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 */ + } + else + { + /* relocation _without_ centering of screen */ - int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left : - old_x > SBX_Right + MIDPOSX ? SBX_Right : - old_x - MIDPOSX); + 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 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); + 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 : + new_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 : + new_scroll_y = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper : offset_y > SBY_Lower + MIDPOSY ? SBY_Lower : offset_y - MIDPOSY); - } - } - - RedrawPlayfield(TRUE, 0,0,0,0); } - else - { - int scroll_xx, scroll_yy; - - if (!level.shifted_relocation || center_screen) - { - /* visible relocation (with scrolling), with centering of screen */ - scroll_xx = (x < SBX_Left + MIDPOSX ? SBX_Left : - x > SBX_Right + MIDPOSX ? SBX_Right : - x - MIDPOSX); - - scroll_yy = (y < SBY_Upper + MIDPOSY ? SBY_Upper : - y > SBY_Lower + MIDPOSY ? SBY_Lower : - y - MIDPOSY); - } - else - { - /* visible relocation (with scrolling), but do not center screen */ + if (quick_relocation) + { + /* case 2: quick relocation (redraw without visible scrolling) */ - int center_scroll_x = (old_x < SBX_Left + MIDPOSX ? SBX_Left : - old_x > SBX_Right + MIDPOSX ? SBX_Right : - old_x - MIDPOSX); + scroll_x = new_scroll_x; + scroll_y = new_scroll_y; - int center_scroll_y = (old_y < SBY_Upper + MIDPOSY ? SBY_Upper : - old_y > SBY_Lower + MIDPOSY ? SBY_Lower : - old_y - MIDPOSY); + RedrawPlayfield(); - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + return; + } - scroll_xx = (offset_x < SBX_Left + MIDPOSX ? SBX_Left : - offset_x > SBX_Right + MIDPOSX ? SBX_Right : - offset_x - MIDPOSX); + /* case 3: visible relocation (with scrolling to new position) */ - scroll_yy = (offset_y < SBY_Upper + MIDPOSY ? SBY_Upper : - offset_y > SBY_Lower + MIDPOSY ? SBY_Lower : - offset_y - MIDPOSY); - } + ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */ + SetVideoFrameDelay(wait_delay_value); - ScrollScreen(NULL, SCROLL_GO_ON); /* scroll last frame to full tile */ + while (scroll_x != new_scroll_x || scroll_y != new_scroll_y) + { + int dx = 0, dy = 0; + int fx = FX, fy = FY; - while (scroll_x != scroll_xx || scroll_y != scroll_yy) - { - int dx = 0, dy = 0; - int fx = FX, fy = FY; + dx = (new_scroll_x < scroll_x ? +1 : new_scroll_x > scroll_x ? -1 : 0); + dy = (new_scroll_y < scroll_y ? +1 : new_scroll_y > scroll_y ? -1 : 0); - dx = (scroll_xx < scroll_x ? +1 : scroll_xx > scroll_x ? -1 : 0); - dy = (scroll_yy < scroll_y ? +1 : scroll_yy > scroll_y ? -1 : 0); + if (dx == 0 && dy == 0) /* no scrolling needed at all */ + break; - if (dx == 0 && dy == 0) /* no scrolling needed at all */ - break; + scroll_x -= dx; + scroll_y -= dy; - scroll_x -= dx; - scroll_y -= dy; + fx += dx * TILEX / 2; + fy += dy * TILEY / 2; - fx += dx * TILEX / 2; - fy += dy * TILEY / 2; + ScrollLevel(dx, dy); + DrawAllPlayers(); - ScrollLevel(dx, dy); - DrawAllPlayers(); + /* scroll in two steps of half tile size to make things smoother */ + BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY); - /* scroll in two steps of half tile size to make things smoother */ - BlitBitmap(drawto_field, window, fx, fy, SXSIZE, SYSIZE, SX, SY); - Delay(wait_delay_value); + /* scroll second step to align at full tile size */ + BlitScreenToBitmap(window); + } - /* scroll second step to align at full tile size */ - BackToFront(); - Delay(wait_delay_value); - } + DrawAllPlayers(); + BackToFront(); - DrawAllPlayers(); - BackToFront(); - Delay(wait_delay_value); - } + SetVideoFrameDelay(frame_delay_value_old); } void RelocatePlayer(int jx, int jy, int el_player_raw) @@ -5069,8 +5062,7 @@ void RelocatePlayer(int jx, int jy, int el_player_raw) DrawPlayer(player); - BackToFront(); - Delay(wait_delay_value); + BackToFront_WithFrameDelay(wait_delay_value); } DrawPlayer(player); /* needed here only to cleanup last field */ @@ -9528,6 +9520,8 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) { local_player->gems_still_needed = action_arg_number_new; + game.snapshot.collected_item = TRUE; + game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed; @@ -10216,9 +10210,50 @@ static void HandleElementChange(int x, int y, int page) { /* !!! not clear why graphic animation should be reset at all here !!! */ /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */ + /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */ + + /* + GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION: + + When using an animation frame delay of 1 (this only happens with + "sp_zonk.moving.left/right" in the classic graphics), the default + (non-moving) animation shows wrong animation frames (while the + moving animation, like "sp_zonk.moving.left/right", is correct, + so this graphical bug never shows up with the classic graphics). + For an animation with 4 frames, this causes wrong frames 0,0,1,2 + be drawn instead of the correct frames 0,1,2,3. This is caused by + "GfxFrame[][]" being reset *twice* (in two successive frames) after + an element change: First when the change delay ("ChangeDelay[][]") + counter has reached zero after decrementing, then a second time in + the next frame (after "GfxFrame[][]" was already incremented) when + "ChangeDelay[][]" is reset to the initial delay value again. + + This causes frame 0 to be drawn twice, while the last frame won't + be drawn anymore, resulting in the wrong frame sequence 0,0,1,2. + + As some animations may already be cleverly designed around this bug + (at least the "Snake Bite" snake tail animation does this), it cannot + simply be fixed here without breaking such existing animations. + Unfortunately, it cannot easily be detected if a graphics set was + designed "before" or "after" the bug was fixed. As a workaround, + a new graphics set option "game.graphics_engine_version" was added + to be able to specify the game's major release version for which the + graphics set was designed, which can then be used to decide if the + bugfix should be used (version 4 and above) or not (version 3 or + below, or if no version was specified at all, as with old sets). + + (The wrong/fixed animation frames can be tested with the test level set + "test_gfxframe" and level "000", which contains a specially prepared + custom element at level position (x/y) == (11/9) which uses the zonk + animation mentioned above. Using "game.graphics_engine_version: 4" + fixes the wrong animation frames, showing the correct frames 0,1,2,3. + This can also be seen from the debug output for this test element.) + */ + /* when a custom element is about to change (for example by change delay), do not reset graphic animation when the custom element is moving */ - if (!IS_MOVING(x, y)) + if (game.graphics_engine_version < 4 && + !IS_MOVING(x, y)) { ResetGfxAnimation(x, y); ResetRandomAnimationValue(x, y); @@ -10668,6 +10703,33 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) } } +static void CheckSaveEngineSnapshot(struct PlayerInfo *player) +{ + if ((!player->is_moving && player->was_moving) || + (player->MovPos == 0 && player->was_moving) || + (player->is_snapping && !player->was_snapping) || + (player->is_dropping && !player->was_dropping)) + { + if (!CheckSaveEngineSnapshotToList()) + return; + + player->was_moving = FALSE; + player->was_snapping = TRUE; + player->was_dropping = TRUE; + } + else + { + if (player->is_moving) + player->was_moving = TRUE; + + if (!player->is_snapping) + player->was_snapping = FALSE; + + if (!player->is_dropping) + player->was_dropping = FALSE; + } +} + static void CheckSingleStepMode(struct PlayerInfo *player) { if (tape.single_step && tape.recording && !tape.pausing) @@ -10680,6 +10742,8 @@ static void CheckSingleStepMode(struct PlayerInfo *player) SnapField(player, 0, 0); /* stop snapping */ } } + + CheckSaveEngineSnapshot(player); } static byte PlayerActions(struct PlayerInfo *player, byte player_action) @@ -10907,9 +10971,11 @@ void StartGameActions(boolean init_network_game, boolean record_tape, InitGame(); } -void GameActions() +void GameActionsExt() { +#if 0 static unsigned int game_frame_delay = 0; +#endif unsigned int game_frame_delay_value; byte *recorded_player_action; byte summarized_player_action = 0; @@ -10987,9 +11053,22 @@ void GameActions() if (tape.playing && tape.warp_forward && !tape.pausing) game_frame_delay_value = 0; + SetVideoFrameDelay(game_frame_delay_value); + +#if 0 +#if 0 + /* ---------- main game synchronization point ---------- */ + + int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value); + + printf("::: skip == %d\n", skip); + +#else /* ---------- main game synchronization point ---------- */ WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value); +#endif +#endif if (network_playing && !network_player_action_received) { @@ -11043,8 +11122,11 @@ void GameActions() SendToServer_MovePlayer(summarized_player_action); #endif + // summarize all actions at local players mapped input device position + // (this allows using different input devices in single player mode) if (!options.network && !game.team_mode) - local_player->effective_action = summarized_player_action; + stored_player[map_player_action[local_player->index_nr]].effective_action = + summarized_player_action; if (tape.recording && setup.team_mode && @@ -11080,6 +11162,7 @@ void GameActions() #if USE_NEW_PLAYER_ASSIGNMENTS // !!! also map player actions in single player mode !!! // if (game.team_mode) + if (1) { byte mapped_action[MAX_PLAYERS]; @@ -11113,6 +11196,21 @@ void GameActions() #endif #endif + for (i = 0; i < MAX_PLAYERS; i++) + { + // allow engine snapshot in case of changed movement attempt + if ((game.snapshot.last_action[i] & KEY_MOTION) != + (stored_player[i].effective_action & KEY_MOTION)) + game.snapshot.changed_action = TRUE; + + // allow engine snapshot in case of snapping/dropping attempt + if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 && + (stored_player[i].effective_action & KEY_BUTTON) != 0) + game.snapshot.changed_action = TRUE; + + game.snapshot.last_action[i] = stored_player[i].effective_action; + } + if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { GameActions_EM_Main(); @@ -11123,10 +11221,53 @@ void GameActions() } else { - GameActions_RND(); + GameActions_RND_Main(); + } + + BlitScreenToBitmap(backbuffer); + + CheckLevelTime(); + + AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */ + + if (options.debug) /* calculate frames per second */ + { + static unsigned int fps_counter = 0; + static int fps_frames = 0; + unsigned int fps_delay_ms = Counter() - fps_counter; + + fps_frames++; + + if (fps_delay_ms >= 500) /* calculate fps every 0.5 seconds */ + { + global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms; + + fps_frames = 0; + fps_counter = Counter(); + } + + redraw_mask |= REDRAW_FPS; } } +static void GameActions_CheckSaveEngineSnapshot() +{ + if (!game.snapshot.save_snapshot) + return; + + // clear flag for saving snapshot _before_ saving snapshot + game.snapshot.save_snapshot = FALSE; + + SaveEngineSnapshotToList(); +} + +void GameActions() +{ + GameActionsExt(); + + GameActions_CheckSaveEngineSnapshot(); +} + void GameActions_EM_Main() { byte effective_action[MAX_PLAYERS]; @@ -11137,10 +11278,6 @@ void GameActions_EM_Main() effective_action[i] = stored_player[i].effective_action; GameActions_EM(effective_action, warp_mode); - - CheckLevelTime(); - - AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */ } void GameActions_SP_Main() @@ -11153,10 +11290,11 @@ void GameActions_SP_Main() effective_action[i] = stored_player[i].effective_action; GameActions_SP(effective_action, warp_mode); +} - CheckLevelTime(); - - AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */ +void GameActions_RND_Main() +{ + GameActions_RND(); } void GameActions_RND() @@ -11658,32 +11796,9 @@ void GameActions_RND() } #endif - CheckLevelTime(); - DrawAllPlayers(); PlayAllPlayersSound(); - if (options.debug) /* calculate frames per second */ - { - static unsigned int fps_counter = 0; - static int fps_frames = 0; - unsigned int fps_delay_ms = Counter() - fps_counter; - - fps_frames++; - - if (fps_delay_ms >= 500) /* calculate fps every 0.5 seconds */ - { - global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms; - - fps_frames = 0; - fps_counter = Counter(); - } - - redraw_mask |= REDRAW_FPS; - } - - AdvanceFrameAndPlayerCounters(-1); /* advance counters for all players */ - if (local_player->show_envelope != 0 && local_player->MovPos == 0) { ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1); @@ -11737,16 +11852,16 @@ static boolean AllPlayersInVisibleScreen() void ScrollLevel(int dx, int dy) { - int softscroll_offset = (setup.soft_scrolling ? 2 * TILEX_VAR : 0); + int scroll_offset = 2 * TILEX_VAR; int x, y; BlitBitmap(drawto_field, drawto_field, - FX + TILEX_VAR * (dx == -1) - softscroll_offset, - FY + TILEY_VAR * (dy == -1) - softscroll_offset, - SXSIZE - TILEX_VAR * (dx != 0) + 2 * softscroll_offset, - SYSIZE - TILEY_VAR * (dy != 0) + 2 * softscroll_offset, - FX + TILEX_VAR * (dx == 1) - softscroll_offset, - FY + TILEY_VAR * (dy == 1) - softscroll_offset); + FX + TILEX_VAR * (dx == -1) - scroll_offset, + FY + TILEY_VAR * (dy == -1) - scroll_offset, + SXSIZE - TILEX_VAR * (dx != 0) + 2 * scroll_offset, + SYSIZE - TILEY_VAR * (dy != 0) + 2 * scroll_offset, + FX + TILEX_VAR * (dx == 1) - scroll_offset, + FY + TILEY_VAR * (dy == 1) - scroll_offset); if (dx != 0) { @@ -11983,7 +12098,7 @@ boolean MovePlayer(struct PlayerInfo *player, int dx, int dy) AdvanceFrameAndPlayerCounters(player->index_nr); DrawAllPlayers(); - BackToFront(); + BackToFront_WithFrameDelay(0); } player->move_delay_value = original_move_delay_value; @@ -12308,6 +12423,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (tape.single_step && tape.recording && !tape.pausing && !player->programmed_action) TapeTogglePause(TAPE_TOGGLE_AUTOMATIC); + + if (!player->programmed_action) + CheckSaveEngineSnapshot(player); } } @@ -13392,6 +13510,8 @@ static int DigField(struct PlayerInfo *player, if (local_player->gems_still_needed < 0) local_player->gems_still_needed = 0; + game.snapshot.collected_item = TRUE; + game_panel_controls[GAME_PANEL_GEMS].value = local_player->gems_still_needed; DisplayGameControlValues(); @@ -13590,9 +13710,16 @@ static int DigField(struct PlayerInfo *player, SCAN_PLAYFIELD(xx, yy) { if (Feld[xx][yy] == EL_SP_DISK_YELLOW) + { Bang(xx, yy); + } else if (Feld[xx][yy] == EL_SP_TERMINAL) + { Feld[xx][yy] = EL_SP_TERMINAL_ACTIVE; + + ResetGfxAnimation(xx, yy); + TEST_DrawLevelField(xx, yy); + } } } else if (IS_BELT_SWITCH(element)) @@ -14384,19 +14511,11 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) #endif { if (quick_quit) - { FadeSkipNextFadeIn(); - game_status = GAME_MODE_MAIN; + SetGameStatus(GAME_MODE_MAIN); - DrawAndFadeInMainMenu(REDRAW_FIELD); - } - else - { - game_status = GAME_MODE_MAIN; - - DrawAndFadeInMainMenu(REDRAW_FIELD); - } + DrawMainMenu(); } } else /* continue playing the game */ @@ -14551,22 +14670,22 @@ static void LoadEngineSnapshotValues_RND() } } -void FreeEngineSnapshot() +void FreeEngineSnapshotSingle() { - FreeEngineSnapshotBuffers(); + FreeSnapshotSingle(); setString(&snapshot_level_identifier, NULL); snapshot_level_nr = -1; } -void SaveEngineSnapshot() +void FreeEngineSnapshotList() { - /* do not save snapshots from editor */ - if (level_editor_test_game) - return; + FreeSnapshotList(); +} - /* free previous snapshot buffers, if needed */ - FreeEngineSnapshotBuffers(); +ListNode *SaveEngineSnapshotBuffers() +{ + ListNode *buffers = NULL; /* copy some special values to a structure better suited for the snapshot */ @@ -14575,87 +14694,82 @@ void SaveEngineSnapshot() if (level.game_engine_type == GAME_ENGINE_TYPE_EM) SaveEngineSnapshotValues_EM(); if (level.game_engine_type == GAME_ENGINE_TYPE_SP) - SaveEngineSnapshotValues_SP(); + SaveEngineSnapshotValues_SP(&buffers); /* save values stored in special snapshot structure */ if (level.game_engine_type == GAME_ENGINE_TYPE_RND) - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_rnd)); if (level.game_engine_type == GAME_ENGINE_TYPE_EM) - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_em)); if (level.game_engine_type == GAME_ENGINE_TYPE_SP) - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(engine_snapshot_sp)); /* save further RND engine values */ - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(stored_player)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(game)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(tape)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZX)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ZY)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitX)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExitY)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(FrameCounter)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeFrames)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimePlayed)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TimeLeft)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(TapeTime)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Feld)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovPos)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDir)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(MovDelay)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeDelay)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangePage)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CustomValue)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Store2)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(StorePlayer)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Back)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(AmoebaNr)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustMoving)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(WasJustFalling)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckCollision)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(CheckImpact)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Stop)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(Pushed)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeCount)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ChangeEvent)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodePhase)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(ExplodeField)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(RunnerVisit)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(PlayerVisit)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxElement)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxAction)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(GfxDir)); - - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_x)); - SaveEngineSnapshotBuffer(ARGS_ADDRESS_AND_SIZEOF(scroll_y)); - - /* save level identification information */ - - setString(&snapshot_level_identifier, leveldir_current->identifier); - snapshot_level_nr = level_nr; + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(stored_player)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(game)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(tape)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZX)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ZY)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitX)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExitY)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(FrameCounter)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeFrames)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimePlayed)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TimeLeft)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(TapeTime)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovDir)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenMovPos)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScreenGfxPos)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ScrollStepSize)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AllPlayersGone)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaCnt2)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Feld)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovPos)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDir)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(MovDelay)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeDelay)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangePage)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CustomValue)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Store2)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(StorePlayer)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Back)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(AmoebaNr)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustMoving)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(WasJustFalling)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckCollision)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(CheckImpact)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Stop)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(Pushed)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeCount)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ChangeEvent)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodePhase)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeDelay)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(ExplodeField)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(RunnerVisit)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(PlayerVisit)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir)); + + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_x)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(scroll_y)); #if 0 ListNode *node = engine_snapshot_list_rnd; @@ -14670,14 +14784,59 @@ void SaveEngineSnapshot() printf("::: size of engine snapshot: %d bytes\n", num_bytes); #endif + + return buffers; } -void LoadEngineSnapshot() +void SaveEngineSnapshotSingle() { - /* restore generically stored snapshot buffers */ + ListNode *buffers = SaveEngineSnapshotBuffers(); + + /* finally save all snapshot buffers to single snapshot */ + SaveSnapshotSingle(buffers); - LoadEngineSnapshotBuffers(); + /* save level identification information */ + setString(&snapshot_level_identifier, leveldir_current->identifier); + snapshot_level_nr = level_nr; +} + +boolean CheckSaveEngineSnapshotToList() +{ + boolean save_snapshot = + ((game.snapshot.mode == SNAPSHOT_MODE_EVERY_STEP) || + (game.snapshot.mode == SNAPSHOT_MODE_EVERY_MOVE && + game.snapshot.changed_action) || + (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT && + game.snapshot.collected_item)); + game.snapshot.changed_action = FALSE; + game.snapshot.collected_item = FALSE; + game.snapshot.save_snapshot = save_snapshot; + + return save_snapshot; +} + +void SaveEngineSnapshotToList() +{ + if (game.snapshot.mode == SNAPSHOT_MODE_OFF || + tape.quick_resume) + return; + + ListNode *buffers = SaveEngineSnapshotBuffers(); + + /* finally save all snapshot buffers to snapshot list */ + SaveSnapshotToList(buffers); +} + +void SaveEngineSnapshotToListInitial() +{ + FreeEngineSnapshotList(); + + SaveEngineSnapshotToList(); +} + +void LoadEngineSnapshotValues() +{ /* restore special values from snapshot structure */ if (level.game_engine_type == GAME_ENGINE_TYPE_RND) @@ -14688,12 +14847,38 @@ void LoadEngineSnapshot() LoadEngineSnapshotValues_SP(); } -boolean CheckEngineSnapshot() +void LoadEngineSnapshotSingle() +{ + LoadSnapshotSingle(); + + LoadEngineSnapshotValues(); +} + +void LoadEngineSnapshot_Undo(int steps) +{ + LoadSnapshotFromList_Older(steps); + + LoadEngineSnapshotValues(); +} + +void LoadEngineSnapshot_Redo(int steps) +{ + LoadSnapshotFromList_Newer(steps); + + LoadEngineSnapshotValues(); +} + +boolean CheckEngineSnapshotSingle() { return (strEqual(snapshot_level_identifier, leveldir_current->identifier) && snapshot_level_nr == level_nr); } +boolean CheckEngineSnapshotList() +{ + return CheckSnapshotList(); +} + /* ---------- new game button stuff ---------------------------------------- */ @@ -14706,36 +14891,48 @@ static struct } gamebutton_info[NUM_GAME_BUTTONS] = { { - IMG_GAME_BUTTON_GFX_STOP, &game.button.stop, + IMG_GFX_GAME_BUTTON_STOP, &game.button.stop, GAME_CTRL_ID_STOP, "stop game" }, { - IMG_GAME_BUTTON_GFX_PAUSE, &game.button.pause, + IMG_GFX_GAME_BUTTON_PAUSE, &game.button.pause, GAME_CTRL_ID_PAUSE, "pause game" }, { - IMG_GAME_BUTTON_GFX_PLAY, &game.button.play, + IMG_GFX_GAME_BUTTON_PLAY, &game.button.play, GAME_CTRL_ID_PLAY, "play game" }, { - IMG_GAME_BUTTON_GFX_SOUND_MUSIC, &game.button.sound_music, - SOUND_CTRL_ID_MUSIC, "background music on/off" + IMG_GFX_GAME_BUTTON_UNDO, &game.button.undo, + GAME_CTRL_ID_UNDO, "undo step" }, { - IMG_GAME_BUTTON_GFX_SOUND_LOOPS, &game.button.sound_loops, - SOUND_CTRL_ID_LOOPS, "sound loops on/off" + IMG_GFX_GAME_BUTTON_REDO, &game.button.redo, + GAME_CTRL_ID_REDO, "redo step" }, { - IMG_GAME_BUTTON_GFX_SOUND_SIMPLE, &game.button.sound_simple, - SOUND_CTRL_ID_SIMPLE, "normal sounds on/off" + IMG_GFX_GAME_BUTTON_SAVE, &game.button.save, + GAME_CTRL_ID_SAVE, "save game" }, { - IMG_GAME_BUTTON_GFX_SAVE, &game.button.save, - GAME_CTRL_ID_SAVE, "save game" + IMG_GFX_GAME_BUTTON_PAUSE2, &game.button.pause2, + GAME_CTRL_ID_PAUSE2, "pause game" }, { - IMG_GAME_BUTTON_GFX_LOAD, &game.button.load, + IMG_GFX_GAME_BUTTON_LOAD, &game.button.load, GAME_CTRL_ID_LOAD, "load game" + }, + { + IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music, + SOUND_CTRL_ID_MUSIC, "background music on/off" + }, + { + IMG_GFX_GAME_BUTTON_SOUND_LOOPS, &game.button.sound_loops, + SOUND_CTRL_ID_LOOPS, "sound loops on/off" + }, + { + IMG_GFX_GAME_BUTTON_SOUND_SIMPLE, &game.button.sound_simple, + SOUND_CTRL_ID_SIMPLE, "normal sounds on/off" } }; @@ -14771,7 +14968,6 @@ void CreateGameButtons() } if (id == GAME_CTRL_ID_STOP || - id == GAME_CTRL_ID_PAUSE || id == GAME_CTRL_ID_PLAY || id == GAME_CTRL_ID_SAVE || id == GAME_CTRL_ID_LOAD) @@ -14780,6 +14976,13 @@ void CreateGameButtons() checked = FALSE; event_mask = GD_EVENT_RELEASED; } + else if (id == GAME_CTRL_ID_UNDO || + id == GAME_CTRL_ID_REDO) + { + button_type = GD_TYPE_NORMAL_BUTTON; + checked = FALSE; + event_mask = GD_EVENT_PRESSED | GD_EVENT_REPEATED; + } else { button_type = GD_TYPE_CHECK_BUTTON; @@ -14823,12 +15026,80 @@ void FreeGameButtons() FreeGadget(game_gadget[i]); } +static void UnmapGameButtonsAtSamePosition(int id) +{ + int i; + + for (i = 0; i < NUM_GAME_BUTTONS; i++) + if (i != id && + gamebutton_info[i].pos->x == gamebutton_info[id].pos->x && + gamebutton_info[i].pos->y == gamebutton_info[id].pos->y) + UnmapGadget(game_gadget[i]); +} + +static void UnmapGameButtonsAtSamePosition_All() +{ + if (setup.show_snapshot_buttons) + { + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD); + } + else + { + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY); + } +} + +static void MapGameButtonsAtSamePosition(int id) +{ + int i; + + for (i = 0; i < NUM_GAME_BUTTONS; i++) + if (i != id && + gamebutton_info[i].pos->x == gamebutton_info[id].pos->x && + gamebutton_info[i].pos->y == gamebutton_info[id].pos->y) + MapGadget(game_gadget[i]); + + UnmapGameButtonsAtSamePosition_All(); +} + +void MapUndoRedoButtons() +{ + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); + + MapGadget(game_gadget[GAME_CTRL_ID_UNDO]); + MapGadget(game_gadget[GAME_CTRL_ID_REDO]); + + ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, TRUE, GDI_END); +} + +void UnmapUndoRedoButtons() +{ + UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]); + UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]); + + MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); + MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); + + ModifyGadget(game_gadget[GAME_CTRL_ID_PAUSE2], GDI_CHECKED, FALSE, GDI_END); +} + void MapGameButtons() { int i; for (i = 0; i < NUM_GAME_BUTTONS; i++) - MapGadget(game_gadget[i]); + if (i != GAME_CTRL_ID_UNDO && + i != GAME_CTRL_ID_REDO) + MapGadget(game_gadget[i]); + + UnmapGameButtonsAtSamePosition_All(); + + RedrawGameButtons(); } void UnmapGameButtons() @@ -14850,8 +15121,47 @@ void RedrawGameButtons() redraw_mask &= ~REDRAW_ALL; } -static void HandleGameButtonsExt(int id) +void GameUndoRedoExt() +{ + ClearPlayerAction(); + + tape.pausing = TRUE; + + RedrawPlayfield(); + UpdateAndDisplayGameControlValues(); + + DrawCompleteVideoDisplay(); + DrawVideoDisplay(VIDEO_STATE_TIME_ON, TapeTime); + DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter); + DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0); + + BackToFront(); +} + +void GameUndo(int steps) +{ + if (!CheckEngineSnapshotList()) + return; + + LoadEngineSnapshot_Undo(steps); + + GameUndoRedoExt(); +} + +void GameRedo(int steps) { + if (!CheckEngineSnapshotList()) + return; + + LoadEngineSnapshot_Redo(steps); + + GameUndoRedoExt(); +} + +static void HandleGameButtonsExt(int id, int button) +{ + static boolean game_undo_executed = FALSE; + int steps = BUTTON_STEPSIZE(button); boolean handle_game_buttons = (game_status == GAME_MODE_PLAYING || (game_status == GAME_MODE_MAIN && tape.show_game_buttons)); @@ -14873,6 +15183,7 @@ static void HandleGameButtonsExt(int id) break; case GAME_CTRL_ID_PAUSE: + case GAME_CTRL_ID_PAUSE2: if (options.network && game_status == GAME_MODE_PLAYING) { #if defined(NETWORK_AVALIABLE) @@ -14884,6 +15195,9 @@ static void HandleGameButtonsExt(int id) } else TapeTogglePause(TAPE_TOGGLE_MANUAL); + + game_undo_executed = FALSE; + break; case GAME_CTRL_ID_PLAY: @@ -14898,13 +15212,37 @@ static void HandleGameButtonsExt(int id) SendToServer_ContinuePlaying(); else #endif - { - tape.pausing = FALSE; - DrawVideoDisplay(VIDEO_STATE_PAUSE_OFF, 0); - } + TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE); } break; + case GAME_CTRL_ID_UNDO: + // Important: When using "save snapshot when collecting an item" mode, + // load last (current) snapshot for first "undo" after pressing "pause" + // (else the last-but-one snapshot would be loaded, because the snapshot + // pointer already points to the last snapshot when pressing "pause", + // which is fine for "every step/move" mode, but not for "every collect") + if (game.snapshot.mode == SNAPSHOT_MODE_EVERY_COLLECT && + !game_undo_executed) + steps--; + + game_undo_executed = TRUE; + + GameUndo(steps); + break; + + case GAME_CTRL_ID_REDO: + GameRedo(steps); + break; + + case GAME_CTRL_ID_SAVE: + TapeQuickSave(); + break; + + case GAME_CTRL_ID_LOAD: + TapeQuickLoad(); + break; + case SOUND_CTRL_ID_MUSIC: if (setup.sound_music) { @@ -14944,14 +15282,6 @@ static void HandleGameButtonsExt(int id) } break; - case GAME_CTRL_ID_SAVE: - TapeQuickSave(); - break; - - case GAME_CTRL_ID_LOAD: - TapeQuickLoad(); - break; - default: break; } @@ -14959,7 +15289,7 @@ static void HandleGameButtonsExt(int id) static void HandleGameButtons(struct GadgetInfo *gi) { - HandleGameButtonsExt(gi->custom_id); + HandleGameButtonsExt(gi->custom_id, gi->event.button); } void HandleSoundButtonKeys(Key key)