X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=abf350ce93311d668bc344cc1301affb90fa0a1e;hb=HEAD;hp=dafe7cbfc56f3c67706a06989e63ba845da56c94;hpb=47c4aca280552f23a943a701fafeac3e9ebd0e98;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index dafe7cbf..fd943b99 100644 --- a/src/game.c +++ b/src/game.c @@ -879,6 +879,10 @@ static struct GamePanelControlInfo game_panel_controls[] = RND(element_info[e].move_delay_random)) #define GET_MAX_MOVE_DELAY(e) ( (element_info[e].move_delay_fixed) + \ (element_info[e].move_delay_random)) +#define GET_NEW_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \ + RND(element_info[e].step_delay_random)) +#define GET_MAX_STEP_DELAY(e) ( (element_info[e].step_delay_fixed) + \ + (element_info[e].step_delay_random)) #define GET_NEW_CE_VALUE(e) ( (element_info[e].ce_value_fixed_initial) +\ RND(element_info[e].ce_value_random_initial)) #define GET_CE_SCORE(e) ( (element_info[e].collect_score)) @@ -958,7 +962,7 @@ static struct GamePanelControlInfo game_panel_controls[] = ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, Tile[x][y] == EL_DIAMOND) #define DARK_YAMYAM_CAN_ENTER_FIELD(e, x, y) \ - ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x,y, IS_FOOD_DARK_YAMYAM(Tile[x][y])) + ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_FOOD_DARK_YAMYAM(Tile[x][y])) #define PACMAN_CAN_ENTER_FIELD(e, x, y) \ ELEMENT_CAN_ENTER_FIELD_BASE_3(e, x, y, IS_AMOEBOID(Tile[x][y])) @@ -1013,19 +1017,22 @@ static struct GamePanelControlInfo game_panel_controls[] = #define GAME_CTRL_ID_SAVE 5 #define GAME_CTRL_ID_PAUSE2 6 #define GAME_CTRL_ID_LOAD 7 -#define GAME_CTRL_ID_PANEL_STOP 8 -#define GAME_CTRL_ID_PANEL_PAUSE 9 -#define GAME_CTRL_ID_PANEL_PLAY 10 -#define GAME_CTRL_ID_TOUCH_STOP 11 -#define GAME_CTRL_ID_TOUCH_PAUSE 12 -#define SOUND_CTRL_ID_MUSIC 13 -#define SOUND_CTRL_ID_LOOPS 14 -#define SOUND_CTRL_ID_SIMPLE 15 -#define SOUND_CTRL_ID_PANEL_MUSIC 16 -#define SOUND_CTRL_ID_PANEL_LOOPS 17 -#define SOUND_CTRL_ID_PANEL_SIMPLE 18 - -#define NUM_GAME_BUTTONS 19 +#define GAME_CTRL_ID_RESTART 8 +#define GAME_CTRL_ID_PANEL_STOP 9 +#define GAME_CTRL_ID_PANEL_PAUSE 10 +#define GAME_CTRL_ID_PANEL_PLAY 11 +#define GAME_CTRL_ID_PANEL_RESTART 12 +#define GAME_CTRL_ID_TOUCH_STOP 13 +#define GAME_CTRL_ID_TOUCH_PAUSE 14 +#define GAME_CTRL_ID_TOUCH_RESTART 15 +#define SOUND_CTRL_ID_MUSIC 16 +#define SOUND_CTRL_ID_LOOPS 17 +#define SOUND_CTRL_ID_SIMPLE 18 +#define SOUND_CTRL_ID_PANEL_MUSIC 19 +#define SOUND_CTRL_ID_PANEL_LOOPS 20 +#define SOUND_CTRL_ID_PANEL_SIMPLE 21 + +#define NUM_GAME_BUTTONS 22 // forward declaration for internal use @@ -1054,7 +1061,10 @@ static void CheckGravityMovementWhenNotMoving(struct PlayerInfo *); static void KillPlayerUnlessEnemyProtected(int, int); static void KillPlayerUnlessExplosionProtected(int, int); +static void CheckNextToConditions(int, int); +static void TestIfPlayerNextToCustomElement(int, int); static void TestIfPlayerTouchesCustomElement(int, int); +static void TestIfElementNextToCustomElement(int, int); static void TestIfElementTouchesCustomElement(int, int); static void TestIfElementHitsCustomElement(int, int, int); @@ -1062,15 +1072,17 @@ static void HandleElementChange(int, int, int); static void ExecuteCustomElementAction(int, int, int, int); static boolean ChangeElement(int, int, int, int); -static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int); +static boolean CheckTriggeredElementChangeExt(int, int, int, int, int, int, int); #define CheckTriggeredElementChange(x, y, e, ev) \ - CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY,CH_SIDE_ANY, -1) + CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, -1) #define CheckTriggeredElementChangeByPlayer(x, y, e, ev, p, s) \ CheckTriggeredElementChangeExt(x, y, e, ev, p, s, -1) #define CheckTriggeredElementChangeBySide(x, y, e, ev, s) \ CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1) #define CheckTriggeredElementChangeByPage(x, y, e, ev, p) \ CheckTriggeredElementChangeExt(x,y,e,ev, CH_PLAYER_ANY, CH_SIDE_ANY, p) +#define CheckTriggeredElementChangeByMouse(x, y, e, ev, s) \ + CheckTriggeredElementChangeExt(x, y, e, ev, CH_PLAYER_ANY, s, -1) static boolean CheckElementChangeExt(int, int, int, int, int, int, int); #define CheckElementChange(x, y, e, te, ev) \ @@ -1079,6 +1091,8 @@ static boolean CheckElementChangeExt(int, int, int, int, int, int, int); CheckElementChangeExt(x, y, e, EL_EMPTY, ev, p, s) #define CheckElementChangeBySide(x, y, e, te, ev, s) \ CheckElementChangeExt(x, y, e, te, ev, CH_PLAYER_ANY, s) +#define CheckElementChangeByMouse(x, y, e, ev, s) \ + CheckElementChangeExt(x, y, e, EL_UNDEFINED, ev, CH_PLAYER_ANY, s) static void PlayLevelSound(int, int, int); static void PlayLevelSoundNearest(int, int, int); @@ -1098,7 +1112,7 @@ void ContinueMoving(int, int); void Bang(int, int); void InitMovDir(int, int); void InitAmoebaNr(int, int); -int NewHiScore(int); +void NewHighScore(int, boolean); void TestIfGoodThingHitsBadThing(int, int, int); void TestIfBadThingHitsGoodThing(int, int, int); @@ -1119,6 +1133,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 @@ -1541,6 +1557,14 @@ access_direction_list[] = { EL_UNDEFINED, MV_NONE } }; +static struct XY xy_topdown[] = +{ + { 0, -1 }, + { -1, 0 }, + { +1, 0 }, + { 0, +1 } +}; + static boolean trigger_events[MAX_NUM_ELEMENTS][NUM_CHANGE_EVENTS]; #define IS_AUTO_CHANGING(e) (element_info[e].has_change_event[CE_DELAY]) @@ -1763,7 +1787,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) player->active = TRUE; // remove potentially duplicate players - if (StorePlayer[jx][jy] == Tile[x][y]) + if (IN_LEV_FIELD(jx, jy) && StorePlayer[jx][jy] == Tile[x][y]) StorePlayer[jx][jy] = 0; StorePlayer[x][y] = Tile[x][y]; @@ -1824,15 +1848,15 @@ static void InitField(int x, int y, boolean init_game) break; case EL_STONEBLOCK: - if (x < lev_fieldx-1 && Tile[x+1][y] == EL_ACID) + if (x < lev_fieldx - 1 && Tile[x + 1][y] == EL_ACID) Tile[x][y] = EL_ACID_POOL_TOPLEFT; - else if (x > 0 && Tile[x-1][y] == EL_ACID) + else if (x > 0 && Tile[x - 1][y] == EL_ACID) Tile[x][y] = EL_ACID_POOL_TOPRIGHT; - else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPLEFT) + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPLEFT) Tile[x][y] = EL_ACID_POOL_BOTTOMLEFT; - else if (y > 0 && Tile[x][y-1] == EL_ACID) + else if (y > 0 && Tile[x][y - 1] == EL_ACID) Tile[x][y] = EL_ACID_POOL_BOTTOM; - else if (y > 0 && Tile[x][y-1] == EL_ACID_POOL_TOPRIGHT) + else if (y > 0 && Tile[x][y - 1] == EL_ACID_POOL_TOPRIGHT) Tile[x][y] = EL_ACID_POOL_BOTTOMRIGHT; break; @@ -2015,6 +2039,14 @@ static void InitField(int x, int y, boolean init_game) InitField(x, y, init_game); } + else if (IS_EMPTY_ELEMENT(element)) + { + GfxElementEmpty[x][y] = element; + Tile[x][y] = EL_EMPTY; + + if (element_info[element].use_gfx_element) + game.use_masked_elements = TRUE; + } break; } @@ -2218,7 +2250,7 @@ static void UpdateGameControlValues(void) game_sp.time_played : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.energy_left : - game.no_time_limit ? TimePlayed : TimeLeft); + game.no_level_time_limit ? TimePlayed : TimeLeft); int score = (game.LevelSolved ? game.LevelSolved_CountingScore : level.game_engine_type == GAME_ENGINE_TYPE_EM ? @@ -2251,6 +2283,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,24 +2358,44 @@ static void UpdateGameControlValues(void) stored_player[player_nr].num_white_keys; } - // try to display as many collected keys as possible in the default game panel - for (i = STD_NUM_KEYS; i < MAX_NUM_KEYS + 1; i++) // EMC keys + white key + // 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; - int emc_key = get_key_element_from_nr(i); - int element = (i < MAX_NUM_KEYS ? emc_key : EL_DC_KEY_WHITE); struct GamePanelControlInfo *gpc = &game_panel_controls[nr]; struct TextPosInfo *pos = gpc->pos; - // check if panel position is undefined for a certain EMC key or white key - if (gpc->value != EL_EMPTY && pos->x == -1 && pos->y == -1) + // 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 = element; + game_panel_controls[nr_new].value = gpc->value; } else { @@ -2353,7 +2406,7 @@ static void UpdateGameControlValues(void) if (game_panel_controls[nr_new].value == EL_EMPTY) { - game_panel_controls[nr_new].value = element; + game_panel_controls[nr_new].value = gpc->value; break; } @@ -2371,7 +2424,7 @@ static void UpdateGameControlValues(void) } game_panel_controls[GAME_PANEL_SCORE].value = score; - game_panel_controls[GAME_PANEL_HIGHSCORE].value = highscore[0].Score; + game_panel_controls[GAME_PANEL_HIGHSCORE].value = scores.entry[0].score; game_panel_controls[GAME_PANEL_TIME].value = time; @@ -2515,11 +2568,15 @@ 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 : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : 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 { @@ -2527,7 +2584,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) @@ -2536,8 +2593,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; @@ -2549,11 +2605,15 @@ 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 : + graphic_info[graphic].anim_global_anim_sync ? + getGlobalAnimSyncFrame() : 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 { @@ -2561,7 +2621,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) @@ -2636,15 +2696,25 @@ static void DisplayGameControlValues(void) if (type == TYPE_INTEGER) { if (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_SCORE || + nr == GAME_PANEL_HIGHSCORE || nr == GAME_PANEL_TIME) { boolean use_dynamic_size = (size == -1 ? TRUE : FALSE); if (use_dynamic_size) // use dynamic number of digits { - int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : 1000); - int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : 3); - int size2 = size1 + 1; + int value_change = (nr == GAME_PANEL_LEVEL_NUMBER ? 100 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1000 : 100000); + int size_add = (nr == GAME_PANEL_LEVEL_NUMBER || + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 1 : 2); + int size1 = (nr == GAME_PANEL_LEVEL_NUMBER ? 2 : + nr == GAME_PANEL_INVENTORY_COUNT || + nr == GAME_PANEL_TIME ? 3 : 5); + int size2 = size1 + size_add; int font1 = pos->font; int font2 = pos->font_alt; @@ -2843,12 +2913,10 @@ void UpdateAndDisplayGameControlValues(void) DisplayGameControlValues(); } -#if 0 -static void UpdateGameDoorValues(void) +void UpdateGameDoorValues(void) { UpdateGameControlValues(); } -#endif void DrawGameDoorValues(void) { @@ -3030,6 +3098,9 @@ static void InitGameEngine(void) game_em.use_single_button = (game.engine_version > VERSION_IDENT(4,0,0,2)); + game_em.use_push_delay = + (game.engine_version > VERSION_IDENT(4,3,7,1)); + game_em.use_snap_key_bug = (game.engine_version < VERSION_IDENT(4,0,1,0)); @@ -3130,6 +3201,17 @@ static void InitGameEngine(void) SET_PROPERTY(ch_delay->element, EP_CAN_CHANGE_OR_HAS_ACTION, TRUE); } + // ---------- initialize if element can trigger global animations ----------- + + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + struct ElementInfo *ei = &element_info[i]; + + ei->has_anim_event = FALSE; + } + + InitGlobalAnimEventsForCustomElements(); + // ---------- initialize internal run-time variables ------------------------ for (i = 0; i < NUM_CUSTOM_ELEMENTS; i++) @@ -3195,12 +3277,16 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - ei->change_page[j].actual_trigger_element = EL_EMPTY; - ei->change_page[j].actual_trigger_player = EL_EMPTY; - ei->change_page[j].actual_trigger_player_bits = CH_PLAYER_NONE; - ei->change_page[j].actual_trigger_side = CH_SIDE_NONE; - ei->change_page[j].actual_trigger_ce_value = 0; - ei->change_page[j].actual_trigger_ce_score = 0; + struct ElementChangeInfo *change = &ei->change_page[j]; + + change->actual_trigger_element = EL_EMPTY; + change->actual_trigger_player = EL_EMPTY; + change->actual_trigger_player_bits = CH_PLAYER_NONE; + change->actual_trigger_side = CH_SIDE_NONE; + change->actual_trigger_ce_value = 0; + change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } } @@ -3218,16 +3304,18 @@ static void InitGameEngine(void) for (j = 0; j < ei->num_change_pages; j++) { - if (!ei->change_page[j].can_change_or_has_action) + struct ElementChangeInfo *change = &ei->change_page[j]; + + if (!change->can_change_or_has_action) continue; - if (ei->change_page[j].has_event[CE_BY_OTHER_ACTION]) + if (change->has_event[CE_BY_OTHER_ACTION]) { - int trigger_element = ei->change_page[j].trigger_element; + int trigger_element = change->trigger_element; for (k = 0; k < NUM_CHANGE_EVENTS; k++) { - if (ei->change_page[j].has_event[k]) + if (change->has_event[k]) { if (IS_GROUP_ELEMENT(trigger_element)) { @@ -3394,8 +3482,9 @@ static void InitGameEngine(void) 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); + if (game.forced_scroll_delay_value == -1) + 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++) @@ -3436,10 +3525,10 @@ static void InitGameEngine(void) { 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)) + if (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_CLICKED_ON_X) || + HAS_ANY_CHANGE_EVENT(element, CE_MOUSE_PRESSED_ON_X)) game.use_mouse_actions = TRUE; } } @@ -3508,9 +3597,8 @@ void InitGame(void) 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 restarting = (game_status == GAME_MODE_PLAYING); boolean emulate_bd = TRUE; // unless non-BOULDERDASH elements found - boolean emulate_sb = TRUE; // unless non-SOKOBAN elements found boolean emulate_sp = TRUE; // unless non-SUPAPLEX elements found int initial_move_dir = MV_DOWN; int i, j, x, y; @@ -3521,7 +3609,15 @@ void InitGame(void) if (!game.restart_level) CloseDoor(DOOR_CLOSE_1); - SetGameStatus(GAME_MODE_PLAYING); + if (restarting) + { + // force fading out global animations displayed during game play + SetGameStatus(GAME_MODE_PSEUDO_RESTARTING); + } + else + { + SetGameStatus(GAME_MODE_PLAYING); + } if (level_editor_test_game) FadeSkipNextFadeOut(); @@ -3537,6 +3633,18 @@ void InitGame(void) FadeOut(fade_mask); + if (restarting) + { + // force restarting global animations displayed during game play + RestartGlobalAnimsByStatus(GAME_MODE_PSEUDO_RESTARTING); + + // this is required for "transforming" fade modes like cross-fading + // (else global animations will be stopped, but not restarted here) + SetAnimStatusBeforeFading(GAME_MODE_PSEUDO_RESTARTING); + + SetGameStatus(GAME_MODE_PLAYING); + } + if (level_editor_test_game) FadeSkipNextFadeIn(); @@ -3552,11 +3660,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 @@ -3624,7 +3736,8 @@ void InitGame(void) player->can_fall_into_acid = CAN_MOVE_INTO_ACID(player->element_nr); - player->actual_frame_counter = 0; + player->actual_frame_counter.count = 0; + player->actual_frame_counter.value = 1; player->step_counter = 0; @@ -3689,6 +3802,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; @@ -3758,9 +3873,12 @@ void InitGame(void) game.LevelSolved_CountingScore = 0; game.LevelSolved_CountingHealth = 0; + game.RestartGameRequested = FALSE; + game.panel.active = TRUE; - game.no_time_limit = (level.time == 0); + game.no_level_time_limit = (level.time == 0); + game.time_limit = (leveldir_current->time_limit && setup.time_limit); game.yamyam_content_nr = 0; game.robot_wheel_active = FALSE; @@ -3771,6 +3889,9 @@ void InitGame(void) game.switchgate_pos = 0; game.wind_direction = level.wind_direction_initial; + game.time_final = 0; + game.score_time_final = 0; + game.score = 0; game.score_final = 0; @@ -3794,6 +3915,9 @@ void InitGame(void) game.envelope_active = FALSE; + // special case: set custom artwork setting to initial value + game.use_masked_elements = game.use_masked_elements_initial; + for (i = 0; i < NUM_BELTS; i++) { game.belt_dir[i] = MV_NONE; @@ -3835,7 +3959,9 @@ void InitGame(void) GfxFrame[x][y] = 0; GfxRandom[x][y] = INIT_GFX_RANDOM(); + GfxRandomStatic[x][y] = INIT_GFX_RANDOM(); GfxElement[x][y] = EL_UNDEFINED; + GfxElementEmpty[x][y] = EL_EMPTY; GfxAction[x][y] = ACTION_DEFAULT; GfxDir[x][y] = MV_NONE; GfxRedraw[x][y] = GFX_REDRAW_NONE; @@ -3845,8 +3971,6 @@ void InitGame(void) { if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y])) emulate_bd = FALSE; - if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y])) - emulate_sb = FALSE; if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y])) emulate_sp = FALSE; @@ -3857,6 +3981,10 @@ void InitGame(void) InitBeltMovement(); + // required if level does not contain any "empty space" element + if (element_info[EL_EMPTY].use_gfx_element) + game.use_masked_elements = TRUE; + for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; @@ -3871,7 +3999,6 @@ void InitGame(void) } game.emulation = (emulate_bd ? EMU_BOULDERDASH : - emulate_sb ? EMU_SOKOBAN : emulate_sp ? EMU_SUPAPLEX : EMU_NONE); // initialize type of slippery elements @@ -4269,7 +4396,7 @@ void InitGame(void) { // check for player created from custom element as single target content = element_info[element].change_page[i].target_element; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 3 || (found_rating == 3 && element < found_element))) @@ -4287,7 +4414,7 @@ void InitGame(void) { // check for player created from custom element as explosion content content = element_info[element].content.e[xx][yy]; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 2 || (found_rating == 2 && element < found_element))) @@ -4308,7 +4435,7 @@ void InitGame(void) content = element_info[element].change_page[i].target_content.e[xx][yy]; - is_player = ELEM_IS_PLAYER(content); + is_player = IS_PLAYER_ELEMENT(content); if (is_player && (found_rating < 1 || (found_rating == 1 && element < found_element))) @@ -4332,6 +4459,11 @@ void InitGame(void) scroll_y = SCROLL_POSITION_Y(local_player->jy); } + if (game.forced_scroll_x != ARG_UNDEFINED_VALUE) + scroll_x = game.forced_scroll_x; + if (game.forced_scroll_y != ARG_UNDEFINED_VALUE) + scroll_y = game.forced_scroll_y; + // !!! FIX THIS (START) !!! if (level.game_engine_type == GAME_ENGINE_TYPE_EM) { @@ -4422,7 +4554,6 @@ void InitGame(void) } game.restart_level = FALSE; - game.restart_game_message = NULL; game.request_active = FALSE; if (level.game_engine_type == GAME_ENGINE_TYPE_MM) @@ -4437,6 +4568,8 @@ void InitGame(void) if (setup.sound_music) PlayLevelMusic(); } + + SetPlayfieldMouseCursorEnabled(!game.use_mouse_actions); } void UpdateEngineValues(int actual_scroll_x, int actual_scroll_y, @@ -4657,41 +4790,66 @@ void InitAmoebaNr(int x, int y) AmoebaCnt2[group_nr]++; } -static void LevelSolved(void) +static void LevelSolved_SetFinalGameValues(void) { - if (level.game_engine_type == GAME_ENGINE_TYPE_RND && - game.players_still_needed > 0) - return; - - game.LevelSolved = TRUE; - game.GameOver = TRUE; + game.time_final = (game.no_level_time_limit ? TimePlayed : TimeLeft); + game.score_time_final = (level.use_step_counter ? TimePlayed : + TimePlayed * FRAMES_PER_SECOND + TimeFrames); game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ? game_em.lev->score : level.game_engine_type == GAME_ENGINE_TYPE_MM ? game_mm.score : game.score); + game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ? MM_HEALTH(game_mm.laser_overload_value) : game.health); - game.LevelSolved_CountingTime = (game.no_time_limit ? TimePlayed : TimeLeft); + game.LevelSolved_CountingTime = game.time_final; game.LevelSolved_CountingScore = game.score_final; game.LevelSolved_CountingHealth = game.health_final; } +static void LevelSolved_DisplayFinalGameValues(int time, int score, int health) +{ + game.LevelSolved_CountingTime = time; + game.LevelSolved_CountingScore = score; + game.LevelSolved_CountingHealth = health; + + game_panel_controls[GAME_PANEL_TIME].value = time; + game_panel_controls[GAME_PANEL_SCORE].value = score; + game_panel_controls[GAME_PANEL_HEALTH].value = health; + + DisplayGameControlValues(); +} + +static void LevelSolved(void) +{ + if (level.game_engine_type == GAME_ENGINE_TYPE_RND && + game.players_still_needed > 0) + return; + + game.LevelSolved = TRUE; + game.GameOver = TRUE; + + tape.solved = TRUE; + + // needed here to display correct panel values while player walks into exit + LevelSolved_SetFinalGameValues(); +} + 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; static int game_over_delay_3 = 0; - 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) { @@ -4701,6 +4859,9 @@ void GameWon(void) if (local_player->active && local_player->MovPos) return; + // calculate final game values after player finished walking into exit + LevelSolved_SetFinalGameValues(); + game.LevelSolved_GameWon = TRUE; game.LevelSolved_SaveTape = tape.recording; game.LevelSolved_SaveScore = !tape.playing; @@ -4717,55 +4878,58 @@ void GameWon(void) TapeStop(); - game_over_delay_1 = 0; - game_over_delay_2 = 0; - game_over_delay_3 = game_over_delay_value_3; + game_over_delay_1 = FRAMES_PER_SECOND; // delay before counting time + game_over_delay_2 = FRAMES_PER_SECOND / 2; // delay before counting health + game_over_delay_3 = FRAMES_PER_SECOND; // delay before ending the game - time = time_final = (game.no_time_limit ? TimePlayed : TimeLeft); + time = time_final = game.time_final; score = score_final = game.score_final; health = health_final = game.health_final; - if (level.score[SC_TIME_BONUS] > 0) + // update game panel values before (delayed) counting of score (if any) + LevelSolved_DisplayFinalGameValues(time, score, health); + + // if level has time score defined, calculate new final game values + if (time_score > 0) { + int time_final_max = 999; + int time_frames_final_max = time_final_max * FRAMES_PER_SECOND; + int time_frames = 0; + int time_frames_left = TimeLeft * FRAMES_PER_SECOND - TimeFrames; + int time_frames_played = TimePlayed * FRAMES_PER_SECOND + TimeFrames; + if (TimeLeft > 0) { time_final = 0; - score_final += TimeLeft * level.score[SC_TIME_BONUS]; + time_frames = time_frames_left; } - else if (game.no_time_limit && TimePlayed < 999) + else if (game.no_level_time_limit && TimePlayed < time_final_max) { - time_final = 999; - score_final += (999 - TimePlayed) * level.score[SC_TIME_BONUS]; + time_final = time_final_max; + time_frames = time_frames_final_max - time_frames_played; } - time_count_steps = MAX(1, ABS(time_final - time) / 100); + score_final += time_score * time_frames / FRAMES_PER_SECOND + 0.5; - game_over_delay_1 = game_over_delay_value_1; + time_count_steps = MAX(1, ABS(time_final - time) / 100); if (level.game_engine_type == GAME_ENGINE_TYPE_MM) { health_final = 0; - score_final += health * level.score[SC_TIME_BONUS]; - - game_over_delay_2 = game_over_delay_value_2; + score_final += health * time_score; } game.score_final = score_final; game.health_final = health_final; } - if (level_editor_test_game) + // if not counting score after game, immediately update game panel values + if (level_editor_test_game || !setup.count_score_after_game) { time = time_final; score = score_final; - game.LevelSolved_CountingTime = time; - game.LevelSolved_CountingScore = score; - - game_panel_controls[GAME_PANEL_TIME].value = time; - game_panel_controls[GAME_PANEL_SCORE].value = score; - - DisplayGameControlValues(); + LevelSolved_DisplayFinalGameValues(time, score, health); } if (level.game_engine_type == GAME_ENGINE_TYPE_RND) @@ -4818,72 +4982,67 @@ void GameWon(void) PlaySound(SND_GAME_WINNING); } - if (game_over_delay_1 > 0) + if (setup.count_score_after_game) { - game_over_delay_1--; - - return; - } - - if (time != time_final) - { - int time_to_go = ABS(time_final - time); - int time_count_dir = (time < time_final ? +1 : -1); - - if (time_to_go < time_count_steps) - time_count_steps = 1; + if (time != time_final) + { + if (game_over_delay_1 > 0) + { + game_over_delay_1--; - time += time_count_steps * time_count_dir; - score += time_count_steps * level.score[SC_TIME_BONUS]; + return; + } - game.LevelSolved_CountingTime = time; - game.LevelSolved_CountingScore = score; + int time_to_go = ABS(time_final - time); + int time_count_dir = (time < time_final ? +1 : -1); - game_panel_controls[GAME_PANEL_TIME].value = time; - game_panel_controls[GAME_PANEL_SCORE].value = score; + if (time_to_go < time_count_steps) + time_count_steps = 1; - DisplayGameControlValues(); + time += time_count_steps * time_count_dir; + score += time_count_steps * time_score; - if (time == time_final) - StopSound(SND_GAME_LEVELTIME_BONUS); - else if (setup.sound_loops) - PlaySoundLoop(SND_GAME_LEVELTIME_BONUS); - else - PlaySound(SND_GAME_LEVELTIME_BONUS); + // set final score to correct rounding differences after counting score + if (time == time_final) + score = score_final; - return; - } + LevelSolved_DisplayFinalGameValues(time, score, health); - if (game_over_delay_2 > 0) - { - game_over_delay_2--; + if (time == time_final) + StopSound(SND_GAME_LEVELTIME_BONUS); + else if (setup.sound_loops) + PlaySoundLoop(SND_GAME_LEVELTIME_BONUS); + else + PlaySound(SND_GAME_LEVELTIME_BONUS); - return; - } + return; + } - if (health != health_final) - { - int health_count_dir = (health < health_final ? +1 : -1); + if (health != health_final) + { + if (game_over_delay_2 > 0) + { + game_over_delay_2--; - health += health_count_dir; - score += level.score[SC_TIME_BONUS]; + return; + } - game.LevelSolved_CountingHealth = health; - game.LevelSolved_CountingScore = score; + int health_count_dir = (health < health_final ? +1 : -1); - game_panel_controls[GAME_PANEL_HEALTH].value = health; - game_panel_controls[GAME_PANEL_SCORE].value = score; + health += health_count_dir; + score += time_score; - DisplayGameControlValues(); + LevelSolved_DisplayFinalGameValues(time, score, health); - if (health == health_final) - StopSound(SND_GAME_LEVELTIME_BONUS); - else if (setup.sound_loops) - PlaySoundLoop(SND_GAME_LEVELTIME_BONUS); - else - PlaySound(SND_GAME_LEVELTIME_BONUS); + if (health == health_final) + StopSound(SND_GAME_LEVELTIME_BONUS); + else if (setup.sound_loops) + PlaySoundLoop(SND_GAME_LEVELTIME_BONUS); + else + PlaySound(SND_GAME_LEVELTIME_BONUS); - return; + return; + } } game.panel.active = FALSE; @@ -4902,23 +5061,27 @@ void GameEnd(void) { // used instead of "level_nr" (needed for network games) int last_level_nr = levelset.level_nr; - int hi_pos; + boolean tape_saved = FALSE; game.LevelSolved_GameEnd = TRUE; - if (game.LevelSolved_SaveTape) + if (game.LevelSolved_SaveTape && !score_info_tape_play) { // make sure that request dialog to save tape does not open door again if (!global.use_envelope_request) CloseDoor(DOOR_CLOSE_1); - SaveTapeChecked_LevelSolved(tape.level_nr); // ask to save tape + // ask to save tape + tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr); + + // set unique basename for score tape (also saved in high score table) + strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name)); } // if no tape is to be saved, close both doors simultaneously CloseDoor(DOOR_CLOSE_ALL); - if (level_editor_test_game) + if (level_editor_test_game || score_info_tape_play) { SetGameStatus(GAME_MODE_MAIN); @@ -4943,6 +5106,9 @@ void GameEnd(void) SaveLevelSetup_SeriesInfo(); } + // save score and score tape before potentially erasing tape below + NewHighScore(last_level_nr, tape_saved); + if (setup.increment_levels && level_nr < leveldir_current->last_level && !network_playing) @@ -4952,23 +5118,22 @@ void GameEnd(void) if (setup.auto_play_next_level) { + scores.continue_playing = TRUE; + scores.next_level_nr = level_nr; + LoadLevel(level_nr); SaveLevelSetup_SeriesInfo(); } } - hi_pos = NewHiScore(last_level_nr); - - if (hi_pos >= 0 && !setup.skip_scores_after_game) + if (scores.last_added >= 0 && setup.show_scores_after_game) { SetGameStatus(GAME_MODE_SCORES); - DrawHallOfFame(last_level_nr, hi_pos); + DrawHallOfFame(last_level_nr); } - else if (setup.auto_play_next_level && setup.increment_levels && - last_level_nr < leveldir_current->last_level && - !network_playing) + else if (scores.continue_playing) { StartGameActions(network.enabled, setup.autorecord, level.random_seed); } @@ -4980,64 +5145,171 @@ void GameEnd(void) } } -int NewHiScore(int level_nr) +static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry, + boolean one_score_entry_per_name) { - int k, l; - int position = -1; - boolean one_score_entry_per_name = !program.many_scores_per_name; - - LoadScore(level_nr); + int i; - if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) || - game.score_final < highscore[MAX_SCORE_ENTRIES - 1].Score) + if (strEqual(new_entry->name, EMPTY_PLAYER_NAME)) return -1; - for (k = 0; k < MAX_SCORE_ENTRIES; k++) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) { - if (game.score_final > highscore[k].Score) + struct ScoreEntry *entry = &list->entry[i]; + boolean score_is_better = (new_entry->score > entry->score); + boolean score_is_equal = (new_entry->score == entry->score); + boolean time_is_better = (new_entry->time < entry->time); + boolean time_is_equal = (new_entry->time == entry->time); + boolean better_by_score = (score_is_better || + (score_is_equal && time_is_better)); + boolean better_by_time = (time_is_better || + (time_is_equal && score_is_better)); + boolean is_better = (level.rate_time_over_score ? better_by_time : + better_by_score); + boolean entry_is_empty = (entry->score == 0 && + entry->time == 0); + + // prevent adding server score entries if also existing in local score file + // (special case: historic score entries have an empty tape basename entry) + if (strEqual(new_entry->tape_basename, entry->tape_basename) && + !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME)) + { + // add fields from server score entry not stored in local score entry + // (currently, this means setting platform, version and country fields; + // in rare cases, this may also correct an invalid score value, as + // historic scores might have been truncated to 16-bit values locally) + *entry = *new_entry; + + return -1; + } + + if (is_better || entry_is_empty) { // player has made it to the hall of fame - if (k < MAX_SCORE_ENTRIES - 1) + if (i < MAX_SCORE_ENTRIES - 1) { int m = MAX_SCORE_ENTRIES - 1; + int l; if (one_score_entry_per_name) { - for (l = k; l < MAX_SCORE_ENTRIES; l++) - if (strEqual(setup.player_name, highscore[l].Name)) + for (l = i; l < MAX_SCORE_ENTRIES; l++) + if (strEqual(list->entry[l].name, new_entry->name)) m = l; - if (m == k) // player's new highscore overwrites his old one + if (m == i) // player's new highscore overwrites his old one goto put_into_list; } - for (l = m; l > k; l--) - { - strcpy(highscore[l].Name, highscore[l - 1].Name); - highscore[l].Score = highscore[l - 1].Score; - } + for (l = m; l > i; l--) + list->entry[l] = list->entry[l - 1]; } put_into_list: - strncpy(highscore[k].Name, setup.player_name, MAX_PLAYER_NAME_LEN); - highscore[k].Name[MAX_PLAYER_NAME_LEN] = '\0'; - highscore[k].Score = game.score_final; - position = k; + *entry = *new_entry; - break; + return i; } else if (one_score_entry_per_name && - !strncmp(setup.player_name, highscore[k].Name, - MAX_PLAYER_NAME_LEN)) - break; // player already there with a higher score + strEqual(entry->name, new_entry->name)) + { + // player already in high score list with better score or time + + return -1; + } + } + + // special case: new score is beyond the last high score list position + return MAX_SCORE_ENTRIES; +} + +void NewHighScore(int level_nr, boolean tape_saved) +{ + struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119) + boolean one_per_name = FALSE; + + strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN); + strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN); + + new_entry.score = game.score_final; + new_entry.time = game.score_time_final; + + LoadScore(level_nr); + + scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name); + + if (scores.last_added >= MAX_SCORE_ENTRIES) + { + scores.last_added = MAX_SCORE_ENTRIES - 1; + scores.force_last_added = TRUE; + + scores.entry[scores.last_added] = new_entry; + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + return; + } + + if (scores.last_added < 0) + return; + + SaveScore(level_nr); + + // store last added local score entry (before merging server scores) + scores.last_added_local = scores.last_added; + + if (!game.LevelSolved_SaveTape) + return; + + SaveScoreTape(level_nr); + + if (setup.ask_for_using_api_server) + { + setup.use_api_server = + Request("Upload your score and tape to the high score server?", REQ_ASK); + + if (!setup.use_api_server) + Request("Not using high score server! Use setup menu to enable again!", + REQ_CONFIRM); + + runtime.use_api_server = setup.use_api_server; + + // after asking for using API server once, do not ask again + setup.ask_for_using_api_server = FALSE; + + SaveSetup_ServerSetup(); } - if (position >= 0) - SaveScore(level_nr); + SaveServerScore(level_nr, tape_saved); +} + +void MergeServerScore(void) +{ + struct ScoreEntry last_added_entry; + boolean one_per_name = FALSE; + int i; + + if (scores.last_added >= 0) + last_added_entry = scores.entry[scores.last_added]; + + for (i = 0; i < server_scores.num_entries; i++) + { + int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name); + + if (pos >= 0 && pos <= scores.last_added) + scores.last_added++; + } + + if (scores.last_added >= MAX_SCORE_ENTRIES) + { + scores.last_added = MAX_SCORE_ENTRIES - 1; + scores.force_last_added = TRUE; - return position; + scores.entry[scores.last_added] = last_added_entry; + } } static int getElementMoveStepsizeExt(int x, int y, int direction) @@ -5089,6 +5361,8 @@ static void ResetGfxFrame(int x, int y) if (graphic_info[graphic].anim_global_sync) GfxFrame[x][y] = FrameCounter; + else if (graphic_info[graphic].anim_global_anim_sync) + GfxFrame[x][y] = getGlobalAnimSyncFrame(); else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) GfxFrame[x][y] = CustomValue[x][y]; else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) @@ -5168,17 +5442,9 @@ void Moving2Blocked(int x, int y, int *goes_to_x, int *goes_to_y) void Blocked2Moving(int x, int y, int *comes_from_x, int *comes_from_y) { - int oldx = x, oldy = y; int direction = MovDir[x][y]; - - if (direction == MV_LEFT) - oldx++; - else if (direction == MV_RIGHT) - oldx--; - else if (direction == MV_UP) - oldy++; - else if (direction == MV_DOWN) - oldy--; + int oldx = x + (direction & MV_LEFT ? +1 : direction & MV_RIGHT ? -1 : 0); + int oldy = y + (direction & MV_UP ? +1 : direction & MV_DOWN ? -1 : 0); *comes_from_x = oldx; *comes_from_y = oldy; @@ -5193,16 +5459,17 @@ static int MovingOrBlocked2Element(int x, int y) int oldx, oldy; Blocked2Moving(x, y, &oldx, &oldy); + return Tile[oldx][oldy]; } - else - return element; + + return element; } static int MovingOrBlocked2ElementIfNotLeaving(int x, int y) { // like MovingOrBlocked2Element(), but if element is moving - // and (x,y) is the field the moving element is just leaving, + // and (x, y) is the field the moving element is just leaving, // return EL_BLOCKED instead of the element value int element = Tile[x][y]; @@ -5311,13 +5578,15 @@ void DrawDynamite(int x, int y) return; if (Back[x][y]) - DrawGraphic(sx, sy, el2img(Back[x][y]), 0); + DrawLevelElement(x, y, Back[x][y]); else if (Store[x][y]) - DrawGraphic(sx, sy, el2img(Store[x][y]), 0); + DrawLevelElement(x, y, Store[x][y]); + else if (game.use_masked_elements) + DrawLevelElement(x, y, EL_EMPTY); - frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + frame = getGraphicAnimationFrameXY(graphic, x, y); - if (Back[x][y] || Store[x][y]) + if (Back[x][y] || Store[x][y] || game.use_masked_elements) DrawGraphicThruMask(sx, sy, graphic, frame); else DrawGraphic(sx, sy, graphic, frame); @@ -5393,7 +5662,7 @@ static void setScreenCenteredToAllPlayers(int *sx, int *sy) *sy = (sy1 + sy2) / 2; } -static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, +static void DrawRelocateScreen(int old_x, int old_y, int x, int y, boolean center_screen, boolean quick_relocation) { unsigned int frame_delay_value_old = GetVideoFrameDelay(); @@ -5423,14 +5692,47 @@ static void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir, { // relocation _without_ centering of screen - int center_scroll_x = SCROLL_POSITION_X(old_x); - int center_scroll_y = SCROLL_POSITION_Y(old_y); - int offset_x = x + (scroll_x - center_scroll_x); - int offset_y = y + (scroll_y - center_scroll_y); + // apply distance between old and new player position to scroll position + int shifted_scroll_x = scroll_x + (x - old_x); + int shifted_scroll_y = scroll_y + (y - old_y); + + // make sure that shifted scroll position does not scroll beyond screen + new_scroll_x = SCROLL_POSITION_X(shifted_scroll_x + MIDPOSX); + new_scroll_y = SCROLL_POSITION_Y(shifted_scroll_y + MIDPOSY); + + // special case for teleporting from one end of the playfield to the other + // (this kludge prevents the destination area to be shifted by half a tile + // against the source destination for even screen width or screen height; + // probably most useful when used with high "game.forced_scroll_delay_value" + // in combination with "game.forced_scroll_x" and "game.forced_scroll_y") + if (quick_relocation) + { + if (EVEN(SCR_FIELDX)) + { + // relocate (teleport) between left and right border (half or full) + if (scroll_x == SBX_Left && new_scroll_x == SBX_Right - 1) + new_scroll_x = SBX_Right; + else if (scroll_x == SBX_Left + 1 && new_scroll_x == SBX_Right) + new_scroll_x = SBX_Right - 1; + else if (scroll_x == SBX_Right && new_scroll_x == SBX_Left + 1) + new_scroll_x = SBX_Left; + else if (scroll_x == SBX_Right - 1 && new_scroll_x == SBX_Left) + new_scroll_x = SBX_Left + 1; + } - // for new screen position, apply previous offset to center position - new_scroll_x = SCROLL_POSITION_X(offset_x); - new_scroll_y = SCROLL_POSITION_Y(offset_y); + if (EVEN(SCR_FIELDY)) + { + // relocate (teleport) between top and bottom border (half or full) + if (scroll_y == SBY_Upper && new_scroll_y == SBY_Lower - 1) + new_scroll_y = SBY_Lower; + else if (scroll_y == SBY_Upper + 1 && new_scroll_y == SBY_Lower) + new_scroll_y = SBY_Lower - 1; + else if (scroll_y == SBY_Lower && new_scroll_y == SBY_Upper + 1) + new_scroll_y = SBY_Upper; + else if (scroll_y == SBY_Lower - 1 && new_scroll_y == SBY_Upper) + new_scroll_y = SBY_Upper + 1; + } + } } if (quick_relocation) @@ -5558,13 +5860,13 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw) possible that the relocation target field did not contain a player element, but a walkable element, to which the new player was relocated -- in this case, restore that (already initialized!) element on the player field */ - if (!ELEM_IS_PLAYER(element)) // player may be set on walkable element + if (!IS_PLAYER_ELEMENT(element)) // player may be set on walkable element { Tile[jx][jy] = element; // restore previously existing element } // only visually relocate centered player - DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, player->MovDir, + DrawRelocateScreen(old_jx, old_jy, player->jx, player->jy, FALSE, level.instant_relocation); TestIfPlayerTouchesBadThing(jx, jy); @@ -5597,9 +5899,6 @@ static void Explode(int ex, int ey, int phase, int mode) int last_phase; int border_element; - // !!! eliminate this variable !!! - int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2); - if (game.explosions_delayed) { ExplodeField[ex][ey] = mode; @@ -5609,6 +5908,8 @@ static void Explode(int ex, int ey, int phase, int mode) if (phase == EX_PHASE_START) // initialize 'Store[][]' field { int center_element = Tile[ex][ey]; + int ce_value = CustomValue[ex][ey]; + int ce_score = element_info[center_element].collect_score; int artwork_element, explosion_element; // set these values later // remove things displayed in background while burning dynamite @@ -5728,7 +6029,7 @@ static void Explode(int ex, int ey, int phase, int mode) // !!! check this case -- currently needed for rnd_rado_negundo_v, // !!! levels 015 018 019 020 021 022 023 026 027 028 !!! - else if (ELEM_IS_PLAYER(center_element)) + else if (IS_PLAYER_ELEMENT(center_element)) Store[x][y] = EL_EMPTY; else if (center_element == EL_YAMYAM) Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy]; @@ -5747,6 +6048,14 @@ static void Explode(int ex, int ey, int phase, int mode) else Store[x][y] = EL_EMPTY; + if (IS_CUSTOM_ELEMENT(center_element)) + Store[x][y] = (Store[x][y] == EL_CURRENT_CE_VALUE ? ce_value : + Store[x][y] == EL_CURRENT_CE_SCORE ? ce_score : + Store[x][y] >= EL_PREV_CE_8 && + Store[x][y] <= EL_NEXT_CE_8 ? + RESOLVED_REFERENCE_ELEMENT(center_element, Store[x][y]) : + Store[x][y]); + if (x != ex || y != ey || mode == EX_TYPE_BORDER || center_element == EL_AMOEBA_TO_DIAMOND) Store2[x][y] = element; @@ -5820,6 +6129,10 @@ static void Explode(int ex, int ey, int phase, int mode) return; } + // this can happen if the player was just killed by an explosion + if (GfxElement[x][y] == EL_UNDEFINED) + GfxElement[x][y] = EL_EMPTY; + if (phase == last_phase) { int element; @@ -5868,15 +6181,15 @@ static void Explode(int ex, int ey, int phase, int mode) if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) StorePlayer[x][y] = 0; - if (ELEM_IS_PLAYER(element)) + if (IS_PLAYER_ELEMENT(element)) RelocatePlayer(x, y, element); } else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y))) { int graphic = el_act2img(GfxElement[x][y], ACTION_EXPLODING); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); - if (phase == delay) + if (phase == 1) TEST_DrawLevelFieldCrumbled(x, y); if (IS_WALKABLE_OVER(Back[x][y]) && Back[x][y] != EL_EMPTY) @@ -5886,11 +6199,11 @@ static void Explode(int ex, int ey, int phase, int mode) } else if (IS_WALKABLE_UNDER(Back[x][y])) { - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); DrawLevelElementThruMask(x, y, Back[x][y]); } else if (!IS_WALKABLE_INSIDE(Back[x][y])) - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } } @@ -5901,13 +6214,7 @@ static void DynaExplode(int ex, int ey) int dynabomb_size = 1; boolean dynabomb_xl = FALSE; struct PlayerInfo *player; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (IS_ACTIVE_BOMB(dynabomb_element)) { @@ -5923,8 +6230,8 @@ static void DynaExplode(int ex, int ey) { for (j = 1; j <= dynabomb_size; j++) { - int x = ex + j * xy[i][0]; - int y = ey + j * xy[i][1]; + int x = ex + j * xy[i].x; + int y = ey + j * xy[i].y; int element; if (!IN_LEV_FIELD(x, y) || IS_INDESTRUCTIBLE(Tile[x][y])) @@ -6205,7 +6512,7 @@ static void ToggleBeltSwitch(int x, int y) } } -static void ToggleSwitchgateSwitch(int x, int y) +static void ToggleSwitchgateSwitch(void) { int xx, yy; @@ -6685,7 +6992,7 @@ static void Impact(int x, int y) smashed == EL_DC_SWITCHGATE_SWITCH_UP || smashed == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y + 1); + ToggleSwitchgateSwitch(); } else if (smashed == EL_LIGHT_SWITCH || smashed == EL_LIGHT_SWITCH_ACTIVE) @@ -7059,18 +7366,12 @@ static void TurnRoundExt(int x, int y) if (element == EL_PENGUIN) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int ex = x + xy[i][0]; - int ey = y + xy[i][1]; + int ex = x + xy[i].x; + int ey = y + xy[i].y; if (IN_LEV_FIELD(ex, ey) && (Tile[ex][ey] == EL_EXIT_OPEN || Tile[ex][ey] == EL_EM_EXIT_OPEN || @@ -7314,7 +7615,7 @@ static void TurnRoundExt(int x, int y) boolean can_turn_left = CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, left_x, left_y); boolean can_turn_right = - CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x,right_y); + CUSTOM_ELEMENT_CAN_ENTER_FIELD(element, right_x, right_y); if (element_info[element].move_stepsize == 0) // "not moving" return; @@ -7469,25 +7770,13 @@ static void TurnRoundExt(int x, int y) } else if (move_pattern & MV_MAZE_RUNNER_STYLE) { - static int test_xy[7][2] = + struct XY *test_xy = xy_topdown; + static int test_dir[4] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 }, - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - }; - static int test_dir[7] = - { - MV_UP, - MV_LEFT, - MV_RIGHT, - MV_DOWN, MV_UP, MV_LEFT, MV_RIGHT, + MV_DOWN }; boolean hunter_mode = (move_pattern == MV_MAZE_HUNTER); int move_preference = -1000000; // start with very low preference @@ -7497,11 +7786,12 @@ static void TurnRoundExt(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int move_dir = test_dir[start_test + i]; + int j = (start_test + i) % 4; + int move_dir = test_dir[j]; int move_dir_preference; - xx = x + test_xy[start_test + i][0]; - yy = y + test_xy[start_test + i][1]; + xx = x + test_xy[j].x; + yy = y + test_xy[j].y; if (hunter_mode && IN_LEV_FIELD(xx, yy) && (IS_PLAYER(xx, yy) || Tile[xx][yy] == EL_PLAYER_IS_LEAVING)) @@ -8063,7 +8353,7 @@ static void StartMoving(int x, int y) dir == MV_RIGHT ? IMG_FLAMES_1_RIGHT : dir == MV_UP ? IMG_FLAMES_1_UP : dir == MV_DOWN ? IMG_FLAMES_1_DOWN : IMG_EMPTY); - int frame = getGraphicAnimationFrame(graphic, GfxFrame[x][y]); + int frame = getGraphicAnimationFrameXY(graphic, x, y); GfxAction[x][y] = ACTION_ATTACKING; @@ -8101,7 +8391,7 @@ static void StartMoving(int x, int y) if (IN_SCR_FIELD(sx, sy)) { TEST_DrawLevelFieldCrumbled(xx, yy); - DrawGraphic(sx, sy, flame_graphic, frame); + DrawScreenGraphic(sx, sy, flame_graphic, frame); } } else @@ -8155,7 +8445,7 @@ static void StartMoving(int x, int y) PlayLevelSound(newx, newy, SND_PENGUIN_PASSING); if (IN_SCR_FIELD(SCREENX(newx), SCREENY(newy))) - DrawGraphicThruMask(SCREENX(newx),SCREENY(newy), el2img(element), 0); + DrawGraphicThruMask(SCREENX(newx), SCREENY(newy), el2img(element), 0); game.friends_still_needed--; if (!game.friends_still_needed && @@ -8167,7 +8457,7 @@ static void StartMoving(int x, int y) } else if (IS_FOOD_PENGUIN(Tile[newx][newy])) { - if (DigField(local_player, x, y, newx, newy, 0,0, DF_DIG) == MP_MOVING) + if (DigField(local_player, x, y, newx, newy, 0, 0, DF_DIG) == MP_MOVING) TEST_DrawLevelField(newx, newy); else GfxDir[x][y] = MovDir[x][y] = MV_NONE; @@ -8250,6 +8540,9 @@ static void StartMoving(int x, int y) GfxDir[x][y] = diagonal_move_dir; ChangeDelay[x][y] = change_delay; + if (Store[x][y] == EL_EMPTY) + Store[x][y] = GfxElementEmpty[x][y]; + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); @@ -8465,11 +8758,33 @@ void ContinueMoving(int x, int y) boolean pushed_by_player = (Pushed[x][y] && IS_PLAYER(x, y)); boolean pushed_by_conveyor = (Pushed[x][y] && !IS_PLAYER(x, y)); boolean last_line = (newy == lev_fieldy - 1); + boolean use_step_delay = (GET_MAX_STEP_DELAY(element) != 0); + + if (pushed_by_player) // special case: moving object pushed by player + { + MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x, y)->MovPos)); + } + else if (use_step_delay) // special case: moving object has step delay + { + if (!MovDelay[x][y]) + MovPos[x][y] += getElementMoveStepsize(x, y); + + if (MovDelay[x][y]) + MovDelay[x][y]--; + else + MovDelay[x][y] = GET_NEW_STEP_DELAY(element); - MovPos[x][y] += getElementMoveStepsize(x, y); + if (MovDelay[x][y]) + { + TEST_DrawLevelField(x, y); - if (pushed_by_player) // special case: moving object pushed by player - MovPos[x][y] = SIGN(MovPos[x][y]) * (TILEX - ABS(PLAYERINFO(x,y)->MovPos)); + return; // element is still waiting + } + } + else // normal case: generically moving object + { + MovPos[x][y] += getElementMoveStepsize(x, y); + } if (ABS(MovPos[x][y]) < TILEX) { @@ -8632,7 +8947,7 @@ void ContinueMoving(int x, int y) if (GFX_CRUMBLED(Tile[x][y])) TEST_DrawLevelFieldCrumbledNeighbours(x, y); - if (ELEM_IS_PLAYER(move_leave_element)) + if (IS_PLAYER_ELEMENT(move_leave_element)) RelocatePlayer(x, y, move_leave_element); } @@ -8709,7 +9024,7 @@ void ContinueMoving(int x, int y) CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER, player->index_bit, push_side); - CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X, + CheckTriggeredElementChangeByPlayer(newx, newy, element, CE_PLAYER_PUSHES_X, player->index_bit, push_side); } @@ -8734,18 +9049,12 @@ int AmoebaNeighbourNr(int ax, int ay) int i; int element = Tile[ax][ay]; int group_nr = 0; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int x = ax + xy[i][0]; - int y = ay + xy[i][1]; + int x = ax + xy[i].x; + int y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8761,21 +9070,15 @@ static void AmoebaMerge(int ax, int ay) { int i, x, y, xx, yy; int new_group_nr = AmoebaNr[ax][ay]; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (new_group_nr == 0) return; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8838,18 +9141,12 @@ void AmoebaToDiamond(int ax, int ay) } else { - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - x = ax + xy[i][0]; - y = ay + xy[i][1]; + x = ax + xy[i].x; + y = ay + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -8904,17 +9201,16 @@ static void AmoebaToDiamondBD(int ax, int ay, int new_element) static void AmoebaGrowing(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new growing cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) + if (DelayReached(&sound_delay)) { PlayLevelSoundElementAction(x, y, Store[x][y], ACTION_GROWING); - sound_delay_value = 30; + sound_delay.value = 30; } } @@ -8926,7 +9222,7 @@ static void AmoebaGrowing(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_GROWING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_GROWING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_GROWING, frame); } if (!MovDelay[x][y]) @@ -8940,15 +9236,14 @@ static void AmoebaGrowing(int x, int y) static void AmoebaShrinking(int x, int y) { - static unsigned int sound_delay = 0; - static unsigned int sound_delay_value = 0; + static DelayCounter sound_delay = { 0 }; if (!MovDelay[x][y]) // start new shrinking cycle { MovDelay[x][y] = 7; - if (DelayReached(&sound_delay, sound_delay_value)) - sound_delay_value = 30; + if (DelayReached(&sound_delay)) + sound_delay.value = 30; } if (MovDelay[x][y]) // wait some time before shrinking @@ -8959,7 +9254,7 @@ static void AmoebaShrinking(int x, int y) int frame = getGraphicAnimationFrame(IMG_AMOEBA_SHRINKING, 6 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), IMG_AMOEBA_SHRINKING, frame); + DrawLevelGraphic(x, y, IMG_AMOEBA_SHRINKING, frame); } if (!MovDelay[x][y]) @@ -8981,13 +9276,7 @@ static void AmoebaReproduce(int ax, int ay) int graphic = el2img(element); int newax = ax, neway = ay; boolean can_drop = (element == EL_AMOEBA_WET || element == EL_EMC_DRIPPER); - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; if (!level.amoeba_speed && element != EL_EMC_DRIPPER) { @@ -9012,8 +9301,8 @@ static void AmoebaReproduce(int ax, int ay) if (can_drop) // EL_AMOEBA_WET or EL_EMC_DRIPPER { int start = RND(4); - int x = ax + xy[start][0]; - int y = ay + xy[start][1]; + int x = ax + xy[start].x; + int y = ay + xy[start].y; if (!IN_LEV_FIELD(x, y)) return; @@ -9038,8 +9327,8 @@ static void AmoebaReproduce(int ax, int ay) for (i = 0; i < NUM_DIRECTIONS; i++) { int j = (start + i) % 4; - int x = ax + xy[j][0]; - int y = ay + xy[j][1]; + int x = ax + xy[j].x; + int y = ay + xy[j].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -9213,7 +9502,7 @@ static void Life(int ax, int ay) num_neighbours <= life_parameter[3]) { Tile[xx][yy] = element; - MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time-1); + MovDelay[xx][yy] = (element == EL_GAME_OF_LIFE ? 0 : life_time - 1); if (Tile[xx][yy] != old_element) TEST_DrawLevelField(xx, yy); Stop[xx][yy] = TRUE; @@ -9461,7 +9750,7 @@ static void DrawTwinkleOnField(int x, int y) } } -static void MauerWaechst(int x, int y) +static void WallGrowing(int x, int y) { int delay = 6; @@ -9477,7 +9766,7 @@ static void MauerWaechst(int x, int y) int graphic = el_dir2img(Tile[x][y], GfxDir[x][y]); int frame = getGraphicAnimationFrame(graphic, 17 - MovDelay[x][y]); - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawLevelGraphic(x, y, graphic, frame); } if (!MovDelay[x][y]) @@ -9511,15 +9800,59 @@ static void MauerWaechst(int x, int y) } } -static void MauerAbleger(int ax, int ay) +static void CheckWallGrowing(int ax, int ay) { int element = Tile[ax][ay]; int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; + boolean free_top = FALSE; + boolean free_bottom = FALSE; + boolean free_left = FALSE; + boolean free_right = FALSE; + boolean stop_top = FALSE; + boolean stop_bottom = FALSE; + boolean stop_left = FALSE; + boolean stop_right = FALSE; + boolean new_wall = FALSE; + + boolean is_steelwall = (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_STEELWALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean grow_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL_ANY || + element == EL_EXPANDABLE_WALL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_EXPANDABLE_STEELWALL_ANY); + + boolean stop_vertical = (element == EL_EXPANDABLE_WALL_VERTICAL || + element == EL_EXPANDABLE_STEELWALL_VERTICAL); + + boolean stop_horizontal = (element == EL_EXPANDABLE_WALL_HORIZONTAL || + element == EL_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL); + + int wall_growing = (is_steelwall ? + EL_EXPANDABLE_STEELWALL_GROWING : + EL_EXPANDABLE_WALL_GROWING); + + int gfx_wall_growing_up = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_UP : + IMG_EXPANDABLE_WALL_GROWING_UP); + int gfx_wall_growing_down = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_DOWN : + IMG_EXPANDABLE_WALL_GROWING_DOWN); + int gfx_wall_growing_left = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_LEFT : + IMG_EXPANDABLE_WALL_GROWING_LEFT); + int gfx_wall_growing_right = (is_steelwall ? + IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT : + IMG_EXPANDABLE_WALL_GROWING_RIGHT); if (IS_ANIMATED(graphic)) DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); @@ -9534,210 +9867,101 @@ static void MauerAbleger(int ax, int ay) return; } - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; + if (IN_LEV_FIELD(ax, ay - 1) && IS_FREE(ax, ay - 1)) + free_top = TRUE; + if (IN_LEV_FIELD(ax, ay + 1) && IS_FREE(ax, ay + 1)) + free_bottom = TRUE; + if (IN_LEV_FIELD(ax - 1, ay) && IS_FREE(ax - 1, ay)) + free_left = TRUE; + if (IN_LEV_FIELD(ax + 1, ay) && IS_FREE(ax + 1, ay)) + free_right = TRUE; - if (element == EL_EXPANDABLE_WALL_VERTICAL || - element == EL_EXPANDABLE_WALL_ANY) + if (grow_vertical) { - if (oben_frei) + if (free_top) { - Tile[ax][ay-1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_WALL_GROWING_UP, 0); + Tile[ax][ay - 1] = wall_growing; + Store[ax][ay - 1] = element; + GfxDir[ax][ay - 1] = MovDir[ax][ay - 1] = MV_UP; + + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay - 1))) + DrawLevelGraphic(ax, ay - 1, gfx_wall_growing_up, 0); + new_wall = TRUE; } - if (unten_frei) + + if (free_bottom) { - Tile[ax][ay+1] = EL_EXPANDABLE_WALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_WALL_GROWING_DOWN, 0); + Tile[ax][ay + 1] = wall_growing; + Store[ax][ay + 1] = element; + GfxDir[ax][ay + 1] = MovDir[ax][ay + 1] = MV_DOWN; + + if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay + 1))) + DrawLevelGraphic(ax, ay + 1, gfx_wall_growing_down, 0); + new_wall = TRUE; } } - if (element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL_ANY || - element == EL_EXPANDABLE_WALL || - element == EL_BD_EXPANDABLE_WALL) + if (grow_horizontal) { - if (links_frei) + if (free_left) { - Tile[ax-1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_LEFT, 0); + Tile[ax - 1][ay] = wall_growing; + Store[ax - 1][ay] = element; + GfxDir[ax - 1][ay] = MovDir[ax - 1][ay] = MV_LEFT; + + if (IN_SCR_FIELD(SCREENX(ax - 1), SCREENY(ay))) + DrawLevelGraphic(ax - 1, ay, gfx_wall_growing_left, 0); + new_wall = TRUE; } - if (rechts_frei) + if (free_right) { - Tile[ax+1][ay] = EL_EXPANDABLE_WALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_WALL_GROWING_RIGHT, 0); + Tile[ax + 1][ay] = wall_growing; + Store[ax + 1][ay] = element; + GfxDir[ax + 1][ay] = MovDir[ax + 1][ay] = MV_RIGHT; + + if (IN_SCR_FIELD(SCREENX(ax + 1), SCREENY(ay))) + DrawLevelGraphic(ax + 1, ay, gfx_wall_growing_right, 0); + new_wall = TRUE; } } - if (element == EL_EXPANDABLE_WALL && (links_frei || rechts_frei)) + if (element == EL_EXPANDABLE_WALL && (free_left || free_right)) TEST_DrawLevelField(ax, ay); - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay])) - rechts_massiv = TRUE; - - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_WALL_HORIZONTAL || - element == EL_EXPANDABLE_WALL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_WALL_VERTICAL)) - Tile[ax][ay] = EL_WALL; + if (!IN_LEV_FIELD(ax, ay - 1) || IS_WALL(Tile[ax][ay - 1])) + stop_top = TRUE; + if (!IN_LEV_FIELD(ax, ay + 1) || IS_WALL(Tile[ax][ay + 1])) + stop_bottom = TRUE; + if (!IN_LEV_FIELD(ax - 1, ay) || IS_WALL(Tile[ax - 1][ay])) + stop_left = TRUE; + if (!IN_LEV_FIELD(ax + 1, ay) || IS_WALL(Tile[ax + 1][ay])) + stop_right = TRUE; + + if (((stop_top && stop_bottom) || stop_horizontal) && + ((stop_left && stop_right) || stop_vertical)) + Tile[ax][ay] = (is_steelwall ? EL_STEELWALL : EL_WALL); if (new_wall) PlayLevelSoundAction(ax, ay, ACTION_GROWING); } -static void MauerAblegerStahl(int ax, int ay) -{ - int element = Tile[ax][ay]; - int graphic = el2img(element); - boolean oben_frei = FALSE, unten_frei = FALSE; - boolean links_frei = FALSE, rechts_frei = FALSE; - boolean oben_massiv = FALSE, unten_massiv = FALSE; - boolean links_massiv = FALSE, rechts_massiv = FALSE; - boolean new_wall = FALSE; - - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(ax, ay, graphic); - - if (!MovDelay[ax][ay]) // start building new wall - MovDelay[ax][ay] = 6; - - if (MovDelay[ax][ay]) // wait some time before building new wall - { - MovDelay[ax][ay]--; - if (MovDelay[ax][ay]) - return; - } - - if (IN_LEV_FIELD(ax, ay-1) && IS_FREE(ax, ay-1)) - oben_frei = TRUE; - if (IN_LEV_FIELD(ax, ay+1) && IS_FREE(ax, ay+1)) - unten_frei = TRUE; - if (IN_LEV_FIELD(ax-1, ay) && IS_FREE(ax-1, ay)) - links_frei = TRUE; - if (IN_LEV_FIELD(ax+1, ay) && IS_FREE(ax+1, ay)) - rechts_frei = TRUE; - - if (element == EL_EXPANDABLE_STEELWALL_VERTICAL || - element == EL_EXPANDABLE_STEELWALL_ANY) - { - if (oben_frei) - { - Tile[ax][ay-1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay-1] = element; - GfxDir[ax][ay-1] = MovDir[ax][ay-1] = MV_UP; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay-1))) - DrawGraphic(SCREENX(ax), SCREENY(ay - 1), - IMG_EXPANDABLE_STEELWALL_GROWING_UP, 0); - new_wall = TRUE; - } - if (unten_frei) - { - Tile[ax][ay+1] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax][ay+1] = element; - GfxDir[ax][ay+1] = MovDir[ax][ay+1] = MV_DOWN; - if (IN_SCR_FIELD(SCREENX(ax), SCREENY(ay+1))) - DrawGraphic(SCREENX(ax), SCREENY(ay + 1), - IMG_EXPANDABLE_STEELWALL_GROWING_DOWN, 0); - new_wall = TRUE; - } - } - - if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || - element == EL_EXPANDABLE_STEELWALL_ANY) - { - if (links_frei) - { - Tile[ax-1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax-1][ay] = element; - GfxDir[ax-1][ay] = MovDir[ax-1][ay] = MV_LEFT; - if (IN_SCR_FIELD(SCREENX(ax-1), SCREENY(ay))) - DrawGraphic(SCREENX(ax - 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_LEFT, 0); - new_wall = TRUE; - } - - if (rechts_frei) - { - Tile[ax+1][ay] = EL_EXPANDABLE_STEELWALL_GROWING; - Store[ax+1][ay] = element; - GfxDir[ax+1][ay] = MovDir[ax+1][ay] = MV_RIGHT; - if (IN_SCR_FIELD(SCREENX(ax+1), SCREENY(ay))) - DrawGraphic(SCREENX(ax + 1), SCREENY(ay), - IMG_EXPANDABLE_STEELWALL_GROWING_RIGHT, 0); - new_wall = TRUE; - } - } - - if (!IN_LEV_FIELD(ax, ay-1) || IS_WALL(Tile[ax][ay-1])) - oben_massiv = TRUE; - if (!IN_LEV_FIELD(ax, ay+1) || IS_WALL(Tile[ax][ay+1])) - unten_massiv = TRUE; - if (!IN_LEV_FIELD(ax-1, ay) || IS_WALL(Tile[ax-1][ay])) - links_massiv = TRUE; - if (!IN_LEV_FIELD(ax+1, ay) || IS_WALL(Tile[ax+1][ay])) - rechts_massiv = TRUE; - - if (((oben_massiv && unten_massiv) || - element == EL_EXPANDABLE_STEELWALL_HORIZONTAL) && - ((links_massiv && rechts_massiv) || - element == EL_EXPANDABLE_STEELWALL_VERTICAL)) - Tile[ax][ay] = EL_STEELWALL; - - if (new_wall) - PlayLevelSoundAction(ax, ay, ACTION_GROWING); -} - -static void CheckForDragon(int x, int y) +static void CheckForDragon(int x, int y) { int i, j; boolean dragon_found = FALSE; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { for (j = 0; j < 4; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; if (IN_LEV_FIELD(xx, yy) && (Tile[xx][yy] == EL_FLAMES || Tile[xx][yy] == EL_DRAGON)) @@ -9756,8 +9980,9 @@ static void CheckForDragon(int x, int y) { for (j = 0; j < 3; j++) { - int xx = x + j * xy[i][0], yy = y + j * xy[i][1]; - + int xx = x + j * xy[i].x; + int yy = y + j * xy[i].y; + if (IN_LEV_FIELD(xx, yy) && Tile[xx][yy] == EL_FLAMES) { Tile[xx][yy] = EL_EMPTY; @@ -9787,18 +10012,12 @@ static void InitBuggyBase(int x, int y) static void WarnBuggyBase(int x, int y) { int i; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; if (IN_LEV_FIELD(xx, yy) && IS_PLAYER(xx, yy)) { @@ -10001,7 +10220,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) DisplayGameControlValues(); - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) for (i = 0; i < MAX_PLAYERS; i++) KillPlayer(&stored_player[i]); } @@ -10442,7 +10661,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) int previous_move_direction = MovDir[x][y]; int last_ce_value = CustomValue[x][y]; boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y); - boolean new_element_is_player = ELEM_IS_PLAYER(new_element); + boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element); boolean add_player_onto_element = (new_element_is_player && new_element != EL_SOKOBAN_FIELD_PLAYER && IS_WALKABLE(old_element)); @@ -10473,17 +10692,26 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change) if (GFX_CRUMBLED(new_element)) TEST_DrawLevelFieldCrumbledNeighbours(x, y); - } - // check if element under the player changes from accessible to unaccessible - // (needed for special case of dropping element which then changes) - // (must be checked after creating new element for walkable group elements) - if (IS_PLAYER(x, y) && !player_explosion_protected && - IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) - { - Bang(x, y); + if (old_element == EL_EXPLOSION) + { + Store[x][y] = Store2[x][y] = 0; - return; + // check if new element replaces an exploding player, requiring cleanup + if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present) + StorePlayer[x][y] = 0; + } + + // check if element under the player changes from accessible to unaccessible + // (needed for special case of dropping element which then changes) + // (must be checked after creating new element for walkable group elements) + if (IS_PLAYER(x, y) && !player_explosion_protected && + IS_ACCESSIBLE(old_element) && !IS_ACCESSIBLE(new_element)) + { + KillPlayer(PLAYERINFO(x, y)); + + return; + } } // "ChangeCount" not set yet to allow "entered by player" change one time @@ -10543,6 +10771,8 @@ static boolean ChangeElement(int x, int y, int element, int page) change->actual_trigger_side = CH_SIDE_NONE; change->actual_trigger_ce_value = 0; change->actual_trigger_ce_score = 0; + change->actual_trigger_x = -1; + change->actual_trigger_y = -1; } // do not change elements more than a specified maximum number of changes @@ -10551,6 +10781,11 @@ static boolean ChangeElement(int x, int y, int element, int page) ChangeCount[x][y]++; // count number of changes in the same frame + if (ei->has_anim_event) + HandleGlobalAnimEventByElementChange(element, page, x, y, + change->actual_trigger_x, + change->actual_trigger_y); + if (change->explode) { Bang(x, y); @@ -10618,7 +10853,7 @@ static boolean ChangeElement(int x, int y, int element, int page) (change->replace_when == CP_WHEN_COLLECTIBLE && is_collectible) || (change->replace_when == CP_WHEN_REMOVABLE && is_removable) || (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) && - !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element))); + !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element))); if (!can_replace[xx][yy]) complete_replace = FALSE; @@ -10680,6 +10915,10 @@ static boolean ChangeElement(int x, int y, int element, int page) Store[x][y] = EL_EMPTY; } + // special case: element changes to player (and may be kept if walkable) + if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce) + CreateElementFromChange(x, y, EL_EMPTY); + CreateElementFromChange(x, y, target_element); PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING); @@ -10781,13 +11020,14 @@ static void HandleElementChange(int x, int y, int page) if (ChangeDelay[x][y] != 0) // continue element change { - if (change->can_change) - { - int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); + int graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); - if (IS_ANIMATED(graphic)) - DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + // also needed if CE can not change, but has CE delay with CE action + if (IS_ANIMATED(graphic)) + DrawLevelGraphicAnimationIfNeeded(x, y, graphic); + if (change->can_change) + { if (change->change_function) change->change_function(x, y); } @@ -10874,6 +11114,8 @@ static boolean CheckTriggeredElementChangeExt(int trigger_x, int trigger_y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[trigger_x][trigger_y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = trigger_x; + change->actual_trigger_y = trigger_y; if ((change->can_change && !change_done) || change->has_action) { @@ -10969,7 +11211,8 @@ static boolean CheckElementChangeExt(int x, int y, different to element changes that affect other elements to change on the whole playfield (which is handeld by CheckTriggeredElementChangeExt()) */ boolean check_trigger_element = - (trigger_event == CE_TOUCHING_X || + (trigger_event == CE_NEXT_TO_X || + trigger_event == CE_TOUCHING_X || trigger_event == CE_HITTING_X || trigger_event == CE_HIT_BY_X || trigger_event == CE_DIGGING_X); // this one was forgotten until 3.2.3 @@ -10987,6 +11230,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = x; + change->actual_trigger_y = y; // special case: trigger element not at (x,y) position for some events if (check_trigger_element) @@ -11010,6 +11255,8 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_ce_value = CustomValue[xx][yy]; change->actual_trigger_ce_score = GET_CE_SCORE(trigger_element); + change->actual_trigger_x = xx; + change->actual_trigger_y = yy; } if (change->can_change && !change_done) @@ -11256,13 +11503,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); @@ -11395,6 +11643,35 @@ static void CheckLevelSolved(void) } } +static void CheckLevelTime_StepCounter(void) +{ + int i; + + TimePlayed++; + + if (TimeLeft > 0) + { + TimeLeft--; + + if (TimeLeft <= 10 && game.time_limit && !game.LevelSolved) + PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + + game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; + + DisplayGameControlValues(); + + if (!TimeLeft && game.time_limit && !game.LevelSolved) + for (i = 0; i < MAX_PLAYERS; i++) + KillPlayer(&stored_player[i]); + } + else if (game.no_level_time_limit && !game.all_players_gone) + { + game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; + + DisplayGameControlValues(); + } +} + static void CheckLevelTime(void) { int i; @@ -11425,7 +11702,7 @@ static void CheckLevelTime(void) { TimeLeft--; - if (TimeLeft <= 10 && setup.time_limit) + if (TimeLeft <= 10 && game.time_limit) PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); /* this does not make sense: game_panel_controls[GAME_PANEL_TIME].value @@ -11433,7 +11710,7 @@ static void CheckLevelTime(void) game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && game.time_limit) { if (level.game_engine_type == GAME_ENGINE_TYPE_EM) game_em.lev->killed_out_of_time = TRUE; @@ -11442,12 +11719,12 @@ static void CheckLevelTime(void) KillPlayer(&stored_player[i]); } } - else if (game.no_time_limit && !game.all_players_gone) + else if (game.no_level_time_limit && !game.all_players_gone) { game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; } - game_em.lev->time = (game.no_time_limit ? TimePlayed : TimeLeft); + game_em.lev->time = (game.no_level_time_limit ? TimePlayed : TimeLeft); } if (tape.recording || tape.playing) @@ -11509,6 +11786,64 @@ void AdvanceFrameAndPlayerCounters(int player_nr) } } +void AdvanceFrameCounter(void) +{ + FrameCounter++; +} + +void AdvanceGfxFrame(void) +{ + int x, y; + + SCAN_PLAYFIELD(x, y) + { + GfxFrame[x][y]++; + } +} + +static void HandleMouseAction(struct MouseActionInfo *mouse_action, + struct MouseActionInfo *mouse_action_last) +{ + if (mouse_action->button) + { + int new_button = (mouse_action->button && mouse_action_last->button == 0); + int ch_button = CH_SIDE_FROM_BUTTON(mouse_action->button); + int x = mouse_action->lx; + int y = mouse_action->ly; + int element = Tile[x][y]; + + if (new_button) + { + CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X, + ch_button); + } + + CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X, + ch_button); + + if (level.use_step_counter) + { + boolean counted_click = FALSE; + + // element clicked that can change when clicked/pressed + if (CAN_CHANGE_OR_HAS_ACTION(element) && + (HAS_ANY_CHANGE_EVENT(element, CE_CLICKED_BY_MOUSE) || + HAS_ANY_CHANGE_EVENT(element, CE_PRESSED_BY_MOUSE))) + counted_click = TRUE; + + // element clicked that can trigger change when clicked/pressed + if (trigger_events[element][CE_MOUSE_CLICKED_ON_X] || + trigger_events[element][CE_MOUSE_PRESSED_ON_X]) + counted_click = TRUE; + + if (new_button && counted_click) + CheckLevelTime_StepCounter(); + } + } +} + void StartGameActions(boolean init_network_game, boolean record_tape, int random_seed) { @@ -11517,6 +11852,9 @@ void StartGameActions(boolean init_network_game, boolean record_tape, if (record_tape) TapeStartRecording(new_random_seed); + if (setup.auto_pause_on_start && !tape.pausing) + TapeTogglePause(TAPE_TOGGLE_MANUAL); + if (init_network_game) { SendToServer_LevelFile(); @@ -11549,7 +11887,7 @@ static void GameActionsExt(void) Warn("element '%s' caused endless loop in game engine", EL_NAME(recursion_loop_element)); - RequestQuitGameExt(FALSE, level_editor_test_game, message); + RequestQuitGameExt(program.headless, level_editor_test_game, message); recursion_loop_detected = FALSE; // if game should be continued @@ -11701,7 +12039,7 @@ static void GameActionsExt(void) TapeRecordAction(tape_action); // remember if game was played (especially after tape stopped playing) - if (!tape.playing && summarized_player_action) + if (!tape.playing && summarized_player_action && !checkGameFailed()) game.GamePlayed = TRUE; #if USE_NEW_PLAYER_ASSIGNMENTS @@ -11824,25 +12162,23 @@ void GameActions(void) void GameActions_EM_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_EM(effective_action, warp_mode); + GameActions_EM(effective_action); } void GameActions_SP_Main(void) { byte effective_action[MAX_PLAYERS]; - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); int i; for (i = 0; i < MAX_PLAYERS; i++) effective_action[i] = stored_player[i].effective_action; - GameActions_SP(effective_action, warp_mode); + GameActions_SP(effective_action); for (i = 0; i < MAX_PLAYERS; i++) { @@ -11855,9 +12191,9 @@ void GameActions_SP_Main(void) void GameActions_MM_Main(void) { - boolean warp_mode = (tape.playing && tape.warp_forward && !tape.pausing); + AdvanceGfxFrame(); - GameActions_MM(local_player->effective_mouse_action, warp_mode); + GameActions_MM(local_player->effective_mouse_action); } void GameActions_RND_Main(void) @@ -11921,10 +12257,14 @@ void GameActions_RND(void) game.centered_player_nr = game.centered_player_nr_next; game.set_centered_player = FALSE; - DrawRelocateScreen(0, 0, sx, sy, MV_NONE, TRUE, setup.quick_switch); + DrawRelocateScreen(0, 0, sx, sy, TRUE, setup.quick_switch); 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; @@ -11949,6 +12289,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 @@ -12001,10 +12345,20 @@ 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); + + if (IS_ENVELOPE(element)) + local_player->show_envelope = element; } } @@ -12056,23 +12410,7 @@ 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 = Tile[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); - } + HandleMouseAction(&mouse_action, &mouse_action_last); SCAN_PLAYFIELD(x, y) { @@ -12080,6 +12418,9 @@ void GameActions_RND(void) graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); last_gfx_frame = GfxFrame[x][y]; + if (element == EL_EMPTY) + graphic = el2img(GfxElementEmpty[x][y]); + ResetGfxFrame(x, y); if (GfxFrame[x][y] != last_gfx_frame && !Stop[x][y]) @@ -12113,6 +12454,8 @@ void GameActions_RND(void) graphic = el_act_dir2img(element, GfxAction[x][y], GfxDir[x][y]); } + CheckNextToConditions(x, y); + if (!IS_MOVING(x, y) && (CAN_FALL(element) || CAN_MOVE(element))) { StartMoving(x, y); @@ -12173,17 +12516,16 @@ void GameActions_RND(void) CheckExitSP(x, y); else if (element == EL_EXPANDABLE_WALL_GROWING || element == EL_EXPANDABLE_STEELWALL_GROWING) - MauerWaechst(x, y); + WallGrowing(x, y); else if (element == EL_EXPANDABLE_WALL || element == EL_EXPANDABLE_WALL_HORIZONTAL || element == EL_EXPANDABLE_WALL_VERTICAL || element == EL_EXPANDABLE_WALL_ANY || - element == EL_BD_EXPANDABLE_WALL) - MauerAbleger(x, y); - else if (element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || + element == EL_BD_EXPANDABLE_WALL || + element == EL_EXPANDABLE_STEELWALL_HORIZONTAL || element == EL_EXPANDABLE_STEELWALL_VERTICAL || element == EL_EXPANDABLE_STEELWALL_ANY) - MauerAblegerStahl(x, y); + CheckWallGrowing(x, y); else if (element == EL_FLAMES) CheckForDragon(x, y); else if (element == EL_EXPLOSION) @@ -12192,7 +12534,7 @@ void GameActions_RND(void) element == EL_DIAGONAL_SHRINKING || element == EL_DIAGONAL_GROWING) { - graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y],GfxDir[x][y]); + graphic = el_act_dir2img(GfxElement[x][y], GfxAction[x][y], GfxDir[x][y]); DrawLevelGraphicAnimationIfNeeded(x, y, graphic); } @@ -12237,7 +12579,7 @@ void GameActions_RND(void) y = RND(lev_fieldy); element = Tile[x][y]; - if (!IS_PLAYER(x,y) && + if (!IS_PLAYER(x, y) && (element == EL_EMPTY || CAN_GROW_INTO(element) || element == EL_QUICKSAND_EMPTY || @@ -12245,10 +12587,10 @@ void GameActions_RND(void) element == EL_ACID_SPLASH_LEFT || element == EL_ACID_SPLASH_RIGHT)) { - if ((IN_LEV_FIELD(x, y-1) && Tile[x][y-1] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x-1, y) && Tile[x-1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x+1, y) && Tile[x+1][y] == EL_AMOEBA_WET) || - (IN_LEV_FIELD(x, y+1) && Tile[x][y+1] == EL_AMOEBA_WET)) + if ((IN_LEV_FIELD(x, y - 1) && Tile[x][y - 1] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x - 1, y) && Tile[x - 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x + 1, y) && Tile[x + 1][y] == EL_AMOEBA_WET) || + (IN_LEV_FIELD(x, y + 1) && Tile[x][y + 1] == EL_AMOEBA_WET)) Tile[x][y] = EL_AMOEBA_DROP; } @@ -12422,6 +12764,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++) @@ -12437,7 +12781,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) @@ -12611,7 +12955,7 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, !AllPlayersInSight(player, new_jx, new_jy)) return MP_NO_ACTION; - can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx,real_dy, DF_DIG); + can_move = DigField(player, jx, jy, new_jx, new_jy, real_dx, real_dy, DF_DIG); if (can_move != MP_MOVING) return can_move; @@ -12884,7 +13228,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (mode == SCROLL_INIT) { - player->actual_frame_counter = FrameCounter; + player->actual_frame_counter.count = FrameCounter; player->GfxPos = move_stepsize * (player->MovPos / move_stepsize); if ((player->block_last_field || player->block_delay_adjustment > 0) && @@ -12913,7 +13257,7 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (player->MovPos != 0) // player has not yet reached destination return; } - else if (!FrameReached(&player->actual_frame_counter, 1)) + else if (!FrameReached(&player->actual_frame_counter)) return; if (player->MovPos != 0) @@ -12942,9 +13286,6 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) } } - player->last_jx = jx; - player->last_jy = jy; - if (Tile[jx][jy] == EL_EXIT_OPEN || Tile[jx][jy] == EL_EM_EXIT_OPEN || Tile[jx][jy] == EL_EM_EXIT_OPENING || @@ -12962,6 +13303,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) LevelSolved(); } + player->last_jx = jx; + player->last_jy = jy; + // this breaks one level: "machine", level 000 { int move_direction = player->MovDir; @@ -12981,13 +13325,18 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) CE_PLAYER_LEAVES_X, player->index_bit, leave_side); - if (IS_CUSTOM_ELEMENT(new_element)) - CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, - player->index_bit, enter_side); + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location + if (!player->is_pushing) + { + if (IS_CUSTOM_ELEMENT(new_element)) + CheckElementChangeByPlayer(jx, jy, new_element, CE_ENTERED_BY_PLAYER, + player->index_bit, enter_side); - CheckTriggeredElementChangeByPlayer(jx, jy, new_element, - CE_PLAYER_ENTERS_X, - player->index_bit, enter_side); + CheckTriggeredElementChangeByPlayer(jx, jy, new_element, + CE_PLAYER_ENTERS_X, + player->index_bit, enter_side); + } CheckTriggeredElementChangeBySide(jx, jy, player->initial_element, CE_MOVE_OF_X, move_direction); @@ -12998,44 +13347,33 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) TestIfPlayerTouchesBadThing(jx, jy); TestIfPlayerTouchesCustomElement(jx, jy); - /* needed because pushed element has not yet reached its destination, - so it would trigger a change event at its previous field location */ + // needed because pushed element has not yet reached its destination, + // so it would trigger a change event at its previous field location if (!player->is_pushing) TestIfElementTouchesCustomElement(jx, jy); // for empty space - if (!player->active) - RemovePlayer(player); - } - - if (!game.LevelSolved && level.use_step_counter) - { - int i; - - TimePlayed++; - - if (TimeLeft > 0) + if (level.finish_dig_collect && + (player->is_digging || player->is_collecting)) { - TimeLeft--; + 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); - if (TimeLeft <= 10 && setup.time_limit) - PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); + CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event, + player->index_bit, enter_side); - game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; - - DisplayGameControlValues(); - - if (!TimeLeft && setup.time_limit) - for (i = 0; i < MAX_PLAYERS; i++) - KillPlayer(&stored_player[i]); + player->last_removed_element = EL_UNDEFINED; } - else if (game.no_time_limit && !game.all_players_gone) - { - game_panel_controls[GAME_PANEL_TIME].value = TimePlayed; - DisplayGameControlValues(); - } + if (!player->active) + RemovePlayer(player); } + if (level.use_step_counter) + CheckLevelTime_StepCounter(); + if (tape.single_step && tape.recording && !tape.pausing && !player->programmed_action) TapeTogglePause(TAPE_TOGGLE_AUTOMATIC); @@ -13047,20 +13385,22 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) void ScrollScreen(struct PlayerInfo *player, int mode) { - static unsigned int screen_frame_counter = 0; + static DelayCounter screen_frame_counter = { 0 }; if (mode == SCROLL_INIT) { // set scrolling step size according to actual player's moving speed ScrollStepSize = TILEX / player->move_delay_value; - screen_frame_counter = FrameCounter; + screen_frame_counter.count = FrameCounter; + screen_frame_counter.value = 1; + ScreenMovDir = player->MovDir; ScreenMovPos = player->MovPos; ScreenGfxPos = ScrollStepSize * (ScreenMovPos / ScrollStepSize); return; } - else if (!FrameReached(&screen_frame_counter, 1)) + else if (!FrameReached(&screen_frame_counter)) return; if (ScreenMovPos) @@ -13073,15 +13413,73 @@ void ScrollScreen(struct PlayerInfo *player, int mode) ScreenMovDir = MV_NONE; } -void TestIfPlayerTouchesCustomElement(int x, int y) +void CheckNextToConditions(int x, int y) { - static int xy[4][2] = + int element = Tile[x][y]; + + if (IS_PLAYER(x, y)) + TestIfPlayerNextToCustomElement(x, y); + + if (CAN_CHANGE_OR_HAS_ACTION(element) && + HAS_ANY_CHANGE_EVENT(element, CE_NEXT_TO_X)) + TestIfElementNextToCustomElement(x, y); +} + +void TestIfPlayerNextToCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int i; + + if (!IS_PLAYER(x, y)) + return; + + struct PlayerInfo *player = PLAYERINFO(x, y); + + if (player->is_moving) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + CheckElementChangeByPlayer(xx, yy, border_element, CE_NEXT_TO_PLAYER, + player->index_bit, border_side); + CheckTriggeredElementChangeByPlayer(xx, yy, border_element, + CE_PLAYER_NEXT_TO_X, + player->index_bit, border_side); + + /* use player element that is initially defined in the level playfield, + not the player element that corresponds to the runtime player number + (example: a level that contains EL_PLAYER_3 as the only player would + incorrectly give EL_PLAYER_1 for "player->element_nr") */ + + CheckElementChangeBySide(xx, yy, border_element, player->initial_element, + CE_NEXT_TO_X, border_side); + } +} + +void TestIfPlayerTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13102,8 +13500,8 @@ void TestIfPlayerTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_side = trigger_sides[i][1]; int border_element; @@ -13137,8 +13535,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(x, y)->initial_element; + // as element "X" is the player here, check opposite (center) side CheckElementChangeBySide(xx, yy, border_element, player_element, - CE_TOUCHING_X, border_side); + CE_TOUCHING_X, center_side); } } else if (IS_PLAYER(xx, yy)) // player found at border element @@ -13164,8 +13563,9 @@ void TestIfPlayerTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, - CE_TOUCHING_X, center_side); + CE_TOUCHING_X, border_side); } break; @@ -13173,15 +13573,48 @@ void TestIfPlayerTouchesCustomElement(int x, int y) } } -void TestIfElementTouchesCustomElement(int x, int y) +void TestIfElementNextToCustomElement(int x, int y) { - static int xy[4][2] = + struct XY *xy = xy_topdown; + static int trigger_sides[4][2] = { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } + // center side border side + { CH_SIDE_TOP, CH_SIDE_BOTTOM }, // check top + { CH_SIDE_LEFT, CH_SIDE_RIGHT }, // check left + { CH_SIDE_RIGHT, CH_SIDE_LEFT }, // check right + { CH_SIDE_BOTTOM, CH_SIDE_TOP } // check bottom }; + int center_element = Tile[x][y]; // should always be non-moving! + int i; + + if (IS_MOVING(x, y) || IS_BLOCKED(x, y)) + return; + + for (i = 0; i < NUM_DIRECTIONS; i++) + { + int xx = x + xy[i].x; + int yy = y + xy[i].y; + int border_side = trigger_sides[i][1]; + int border_element; + + if (!IN_LEV_FIELD(xx, yy)) + continue; + + if (IS_MOVING(xx, yy) || IS_BLOCKED(xx, yy)) + continue; // center and border element not connected + + border_element = Tile[xx][yy]; + + // check for change of center element (but change it only once) + if (CheckElementChangeBySide(x, y, center_element, border_element, + CE_NEXT_TO_X, border_side)) + break; + } +} + +void TestIfElementTouchesCustomElement(int x, int y) +{ + struct XY *xy = xy_topdown; static int trigger_sides[4][2] = { // center side border side @@ -13204,8 +13637,8 @@ void TestIfElementTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_element; border_element_old[i] = -1; @@ -13227,8 +13660,8 @@ void TestIfElementTouchesCustomElement(int x, int y) for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int center_side = trigger_sides[i][0]; int border_element = border_element_old[i]; @@ -13239,13 +13672,13 @@ void TestIfElementTouchesCustomElement(int x, int y) CheckElementChangeBySide(xx, yy, border_element, center_element, CE_TOUCHING_X, center_side); - // (center element cannot be player, so we dont have to check this here) + // (center element cannot be player, so we don't have to check this here) } for (i = 0; i < NUM_DIRECTIONS; i++) { - int xx = x + xy[i][0]; - int yy = y + xy[i][1]; + int xx = x + xy[i].x; + int yy = y + xy[i].y; int border_side = trigger_sides[i][1]; int border_element = border_element_old[i]; @@ -13266,6 +13699,7 @@ void TestIfElementTouchesCustomElement(int x, int y) incorrectly give EL_PLAYER_1 for "player->element_nr") */ int player_element = PLAYERINFO(xx, yy)->initial_element; + // as element "X" is the player here, check opposite (border) side CheckElementChangeBySide(x, y, center_element, player_element, CE_TOUCHING_X, border_side); } @@ -13332,13 +13766,7 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) int i, kill_x = -1, kill_y = -1; int bad_element = -1; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *test_xy = xy_topdown; static int test_dir[4] = { MV_UP, @@ -13351,8 +13779,8 @@ void TestIfGoodThingHitsBadThing(int good_x, int good_y, int good_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = good_x + test_xy[i][0]; - test_y = good_y + test_xy[i][1]; + test_x = good_x + test_xy[i].x; + test_y = good_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13397,13 +13825,7 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int i, kill_x = -1, kill_y = -1; int bad_element = Tile[bad_x][bad_y]; - static int test_xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *test_xy = xy_topdown; static int touch_dir[4] = { MV_LEFT | MV_RIGHT, @@ -13426,8 +13848,8 @@ void TestIfBadThingHitsGoodThing(int bad_x, int bad_y, int bad_move_dir) { int test_x, test_y, test_move_dir, test_element; - test_x = bad_x + test_xy[i][0]; - test_y = bad_y + test_xy[i][1]; + test_x = bad_x + test_xy[i].x; + test_y = bad_y + test_xy[i].y; if (!IN_LEV_FIELD(test_x, test_y)) continue; @@ -13579,20 +14001,14 @@ void TestIfBadThingTouchesFriend(int x, int y) void TestIfBadThingTouchesOtherBadThing(int bad_x, int bad_y) { int i, kill_x = bad_x, kill_y = bad_y; - static int xy[4][2] = - { - { 0, -1 }, - { -1, 0 }, - { +1, 0 }, - { 0, +1 } - }; + struct XY *xy = xy_topdown; for (i = 0; i < NUM_DIRECTIONS; i++) { int x, y, element; - x = bad_x + xy[i][0]; - y = bad_y + xy[i][1]; + x = bad_x + xy[i].x; + y = bad_y + xy[i].y; if (!IN_LEV_FIELD(x, y)) continue; @@ -13641,7 +14057,7 @@ void KillPlayer(struct PlayerInfo *player) player->killed = TRUE; // remove accessible field at the player's position - Tile[jx][jy] = EL_EMPTY; + RemoveField(jx, jy); // deactivate shield (else Bang()/Explode() would not work right) player->shield_normal_time_left = 0; @@ -13687,7 +14103,6 @@ void BuryPlayer(struct PlayerInfo *player) return; PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING); - PlayLevelSound(jx, jy, SND_GAME_LOSING); RemovePlayer(player); @@ -13737,7 +14152,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); @@ -13747,6 +14163,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); @@ -13756,6 +14175,24 @@ 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); + int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X : + CE_PLAYER_COLLECTS_X); + + CheckTriggeredElementChangeByPlayer(x, y, element, change_event, + player_index_bit, dig_side); + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, + player_index_bit, dig_side); + } +} + /* ============================================================================= checkDiagonalPushing() @@ -13833,7 +14270,6 @@ static int DigField(struct PlayerInfo *player, return MP_NO_ACTION; } } - if (IS_TUBE(Back[jx][jy]) && game.engine_version >= VERSION_IDENT(2,2,0,0)) old_element = Back[jx][jy]; @@ -13845,7 +14281,7 @@ static int DigField(struct PlayerInfo *player, if (IS_WALKABLE(old_element) && !ACCESS_FROM(old_element, move_direction)) return MP_NO_ACTION; // field has no opening in this direction - if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element,opposite_direction)) + if (IS_PASSABLE(old_element) && !ACCESS_FROM(old_element, opposite_direction)) return MP_NO_ACTION; // field has no opening in this direction if (player_can_move && element == EL_ACID && move_direction == MV_DOWN) @@ -14035,22 +14471,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); + // 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)) @@ -14077,9 +14519,13 @@ static int DigField(struct PlayerInfo *player, } else if (element == EL_SHIELD_NORMAL || element == EL_SHIELD_DEADLY) { - player->shield_normal_time_left += level.shield_normal_time; + int shield_time = (element == EL_SHIELD_DEADLY ? + level.shield_deadly_time : + level.shield_normal_time); + + player->shield_normal_time_left += shield_time; if (element == EL_SHIELD_DEADLY) - player->shield_deadly_time_left += level.shield_deadly_time; + player->shield_deadly_time_left += shield_time; } else if (element == EL_DYNAMITE || element == EL_EM_DYNAMITE || @@ -14118,7 +14564,10 @@ static int DigField(struct PlayerInfo *player, } else if (IS_ENVELOPE(element)) { - player->show_envelope = element; + boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field); + + if (!wait_for_snapping) + player->show_envelope = element; } else if (element == EL_EMC_LENSES) { @@ -14162,25 +14611,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); + 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)) @@ -14302,13 +14754,13 @@ static int DigField(struct PlayerInfo *player, if (sokoban_task_solved && game.sokoban_fields_still_needed == 0 && game.sokoban_objects_still_needed == 0 && - (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban)) + level.auto_exit_sokoban) { game.players_still_needed = 0; LevelSolved(); - PlayLevelSound(x, y, SND_GAME_SOKOBAN_SOLVING); + PlaySound(SND_GAME_SOKOBAN_SOLVING); } } else @@ -14393,7 +14845,7 @@ static int DigField(struct PlayerInfo *player, element == EL_DC_SWITCHGATE_SWITCH_UP || element == EL_DC_SWITCHGATE_SWITCH_DOWN) { - ToggleSwitchgateSwitch(x, y); + ToggleSwitchgateSwitch(); } else if (element == EL_LIGHT_SWITCH || element == EL_LIGHT_SWITCH_ACTIVE) @@ -14434,7 +14886,7 @@ static int DigField(struct PlayerInfo *player, if (level.time > 0 || level.use_time_orb_bug) { TimeLeft += level.time_orb_time; - game.no_time_limit = FALSE; + game.no_level_time_limit = FALSE; game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; @@ -14518,6 +14970,8 @@ static int DigField(struct PlayerInfo *player, { player->is_collecting = !player->is_digging; player->is_active = TRUE; + + player->last_removed_element = element; } } @@ -14893,10 +15347,12 @@ static void StopLevelSoundActionIfLoop(int x, int y, int action) static int getLevelMusicNr(void) { + int level_pos = level_nr - leveldir_current->first_level; + if (levelset.music[level_nr] != MUS_UNDEFINED) return levelset.music[level_nr]; // from config file else - return MAP_NOCONF_MUSIC(level_nr); // from music dir + return MAP_NOCONF_MUSIC(level_pos); // from music dir } static void FadeLevelSounds(void) @@ -15238,13 +15694,14 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) { if (skip_request || Request(message, REQ_ASK | REQ_STAY_CLOSED)) { - // closing door required in case of envelope style request dialogs - if (!skip_request) + if (!quick_quit) { // prevent short reactivation of overlay buttons while closing door SetOverlayActive(FALSE); + UnmapGameButtons(); - CloseDoor(DOOR_CLOSE_1); + // door may still be open due to skipped or envelope style request + CloseDoor(score_info_tape_play ? DOOR_CLOSE_ALL : DOOR_CLOSE_1); } if (network.enabled) @@ -15271,30 +15728,55 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message) } } -void RequestQuitGame(boolean ask_if_really_quit) +void RequestQuitGame(boolean escape_key_pressed) { - boolean quick_quit = (!ask_if_really_quit || level_editor_test_game); - boolean skip_request = game.all_players_gone || quick_quit; + boolean ask_on_escape = (setup.ask_on_escape && setup.ask_on_quit_game); + boolean quick_quit = ((escape_key_pressed && !ask_on_escape) || + level_editor_test_game); + boolean skip_request = (game.all_players_gone || !setup.ask_on_quit_game || + quick_quit || score_info_tape_play); RequestQuitGameExt(skip_request, quick_quit, "Do you really want to quit the game?"); } -void RequestRestartGame(char *message) +static char *getRestartGameMessage(void) { - game.restart_game_message = NULL; + boolean play_again = hasStartedNetworkGame(); + static char message[MAX_OUTPUT_LINESIZE]; + char *game_over_text = "Game over!"; + char *play_again_text = " Play it again?"; + if (level.game_engine_type == GAME_ENGINE_TYPE_MM && + game_mm.game_over_message != NULL) + game_over_text = game_mm.game_over_message; + + snprintf(message, MAX_OUTPUT_LINESIZE, "%s%s", game_over_text, + (play_again ? play_again_text : "")); + + return message; +} + +static void RequestRestartGame(void) +{ + char *message = getRestartGameMessage(); boolean has_started_game = hasStartedNetworkGame(); int request_mode = (has_started_game ? REQ_ASK : REQ_CONFIRM); + int door_state = DOOR_CLOSE_1; - if (Request(message, request_mode | REQ_STAY_CLOSED) && has_started_game) + if (Request(message, request_mode | REQ_STAY_OPEN) && has_started_game) { + CloseDoor(door_state); + StartGameActions(network.enabled, setup.autorecord, level.random_seed); } else { - // needed in case of envelope request to close game panel - CloseDoor(DOOR_CLOSE_1); + // if game was invoked from level editor, also close tape recorder door + if (level_editor_test_game) + door_state = DOOR_CLOSE_ALL; + + CloseDoor(door_state); SetGameStatus(GAME_MODE_MAIN); @@ -15302,42 +15784,50 @@ void RequestRestartGame(char *message) } } -void CheckGameOver(void) +boolean CheckRestartGame(void) { - static boolean last_game_over = FALSE; static int game_over_delay = 0; int game_over_delay_value = 50; boolean game_over = checkGameFailed(); - // do not handle game over if request dialog is already active - if (game.request_active) - return; - - // do not ask to play again if game was never actually played - if (!game.GamePlayed) - return; - if (!game_over) { - last_game_over = FALSE; game_over_delay = game_over_delay_value; - return; + return FALSE; } if (game_over_delay > 0) { + if (game_over_delay == game_over_delay_value / 2) + PlaySound(SND_GAME_LOSING); + game_over_delay--; - return; + return FALSE; } - if (last_game_over != game_over) - game.restart_game_message = (hasStartedNetworkGame() ? - "Game over! Play it again?" : - "Game over!"); + // do not ask to play again if request dialog is already active + if (game.request_active) + return FALSE; + + // do not ask to play again if request dialog already handled + if (game.RestartGameRequested) + return FALSE; + + // do not ask to play again if game was never actually played + if (!game.GamePlayed) + return FALSE; + + // do not ask to play again if this was disabled in setup menu + if (!setup.ask_on_game_over) + return FALSE; + + game.RestartGameRequested = TRUE; + + RequestRestartGame(); - last_game_over = game_over; + return TRUE; } boolean checkGameSolved(void) @@ -15521,7 +16011,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) if (level.game_engine_type == GAME_ENGINE_TYPE_SP) SaveEngineSnapshotValues_SP(&buffers); if (level.game_engine_type == GAME_ENGINE_TYPE_MM) - SaveEngineSnapshotValues_MM(&buffers); + SaveEngineSnapshotValues_MM(); // save values stored in special snapshot structure @@ -15586,6 +16076,7 @@ static ListNode *SaveEngineSnapshotBuffers(void) SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxFrame)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandom)); + SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxRandomStatic)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxElement)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxAction)); SaveSnapshotBuffer(&buffers, ARGS_ADDRESS_AND_SIZEOF(GfxDir)); @@ -15758,6 +16249,11 @@ static struct GAME_CTRL_ID_LOAD, NULL, TRUE, FALSE, "load game" }, + { + IMG_GFX_GAME_BUTTON_RESTART, &game.button.restart, + GAME_CTRL_ID_RESTART, NULL, + TRUE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_PANEL_STOP, &game.button.panel_stop, GAME_CTRL_ID_PANEL_STOP, NULL, @@ -15773,6 +16269,11 @@ static struct GAME_CTRL_ID_PANEL_PLAY, NULL, FALSE, FALSE, "play game" }, + { + IMG_GFX_GAME_BUTTON_PANEL_RESTART, &game.button.panel_restart, + GAME_CTRL_ID_PANEL_RESTART, NULL, + FALSE, FALSE, "restart game" + }, { IMG_GFX_GAME_BUTTON_TOUCH_STOP, &game.button.touch_stop, GAME_CTRL_ID_TOUCH_STOP, NULL, @@ -15783,6 +16284,11 @@ static struct GAME_CTRL_ID_TOUCH_PAUSE, NULL, FALSE, TRUE, "pause game" }, + { + IMG_GFX_GAME_BUTTON_TOUCH_RESTART, &game.button.touch_restart, + GAME_CTRL_ID_TOUCH_RESTART, NULL, + FALSE, TRUE, "restart game" + }, { IMG_GFX_GAME_BUTTON_SOUND_MUSIC, &game.button.sound_music, SOUND_CTRL_ID_MUSIC, &setup.sound_music, @@ -15845,6 +16351,10 @@ void CreateGameButtons(void) int y = (is_touch_button ? pos->y : GDI_ACTIVE_POS(pos->y)); int id = i; + // do not use touch buttons if overlay touch buttons are disabled + if (is_touch_button && !setup.touch.overlay_buttons) + continue; + if (gfx->bitmap == NULL) { game_gadget[id] = NULL; @@ -15858,7 +16368,10 @@ void CreateGameButtons(void) id == GAME_CTRL_ID_PLAY || id == GAME_CTRL_ID_PANEL_PLAY || id == GAME_CTRL_ID_SAVE || - id == GAME_CTRL_ID_LOAD) + id == GAME_CTRL_ID_LOAD || + id == GAME_CTRL_ID_RESTART || + id == GAME_CTRL_ID_PANEL_RESTART || + id == GAME_CTRL_ID_TOUCH_RESTART) { button_type = GD_TYPE_NORMAL_BUTTON; checked = FALSE; @@ -15927,12 +16440,18 @@ static void UnmapGameButtonsAtSamePosition(int id) static void UnmapGameButtonsAtSamePosition_All(void) { - if (setup.show_snapshot_buttons) + if (setup.show_load_save_buttons) { UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE); UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2); UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD); } + else if (setup.show_undo_redo_buttons) + { + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); + } else { UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP); @@ -15945,17 +16464,13 @@ static void UnmapGameButtonsAtSamePosition_All(void) } } -static void MapGameButtonsAtSamePosition(int id) +void MapLoadSaveButtons(void) { - int i; + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD); + UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE); - 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(); + MapGadget(game_gadget[GAME_CTRL_ID_LOAD]); + MapGadget(game_gadget[GAME_CTRL_ID_SAVE]); } void MapUndoRedoButtons(void) @@ -15967,15 +16482,6 @@ void MapUndoRedoButtons(void) MapGadget(game_gadget[GAME_CTRL_ID_REDO]); } -void UnmapUndoRedoButtons(void) -{ - UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]); - UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]); - - MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO); - MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO); -} - void ModifyPauseButtons(void) { static int ids[] = @@ -15988,6 +16494,10 @@ void ModifyPauseButtons(void) }; int i; + // do not redraw pause button on closed door (may happen when restarting game) + if (!(GetDoorState() & DOOR_OPEN_1)) + return; + for (i = 0; ids[i] > -1; i++) ModifyGadget(game_gadget[ids[i]], GDI_CHECKED, tape.pausing, GDI_END); } @@ -15997,10 +16507,15 @@ static void MapGameButtonsExt(boolean on_tape) int i; for (i = 0; i < NUM_GAME_BUTTONS; i++) - if ((!on_tape || gamebutton_info[i].allowed_on_tape) && - i != GAME_CTRL_ID_UNDO && - i != GAME_CTRL_ID_REDO) + { + if ((i == GAME_CTRL_ID_UNDO || + i == GAME_CTRL_ID_REDO) && + game_status != GAME_MODE_PLAYING) + continue; + + if (!on_tape || gamebutton_info[i].allowed_on_tape) MapGadget(game_gadget[i]); + } UnmapGameButtonsAtSamePosition_All(); @@ -16091,6 +16606,8 @@ static void GameUndoRedoExt(void) DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter); DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0); + ModifyPauseButtons(); + BackToFront(); } @@ -16099,8 +16616,12 @@ static void GameUndo(int steps) if (!CheckEngineSnapshotList()) return; + int tape_property_bits = tape.property_bits; + LoadEngineSnapshot_Undo(steps); + tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT; + GameUndoRedoExt(); } @@ -16109,8 +16630,12 @@ static void GameRedo(int steps) if (!CheckEngineSnapshotList()) return; + int tape_property_bits = tape.property_bits; + LoadEngineSnapshot_Redo(steps); + tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT; + GameUndoRedoExt(); } @@ -16130,13 +16655,7 @@ static void HandleGameButtonsExt(int id, int button) case GAME_CTRL_ID_STOP: case GAME_CTRL_ID_PANEL_STOP: case GAME_CTRL_ID_TOUCH_STOP: - if (game_status == GAME_MODE_MAIN) - break; - - if (tape.playing) - TapeStop(); - else - RequestQuitGame(TRUE); + TapeStopGame(); break; @@ -16200,6 +16719,13 @@ static void HandleGameButtonsExt(int id, int button) TapeQuickLoad(); break; + case GAME_CTRL_ID_RESTART: + case GAME_CTRL_ID_PANEL_RESTART: + case GAME_CTRL_ID_TOUCH_RESTART: + TapeRestartGame(); + + break; + case SOUND_CTRL_ID_MUSIC: case SOUND_CTRL_ID_PANEL_MUSIC: if (setup.sound_music)