X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=d4cc5ea8e0c5572bb737e62cb9202bdad128b344;hb=ef596d6fab46df4658603046b589b3e21bda9bca;hp=c4819c1190a382a0d52d8a7962cc4dd34415275e;hpb=dc6de008bb3338041279e0981536f3119fa4d6c3;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index c4819c11..d4cc5ea8 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)) @@ -1071,6 +1075,8 @@ static boolean CheckTriggeredElementChangeExt(int, int, int, int, int,int,int); 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 +1085,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 +1106,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 +1127,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 @@ -2326,24 +2336,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 { @@ -2354,7 +2384,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; } @@ -2372,7 +2402,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; @@ -2847,12 +2877,10 @@ void UpdateAndDisplayGameControlValues(void) DisplayGameControlValues(); } -#if 0 -static void UpdateGameDoorValues(void) +void UpdateGameDoorValues(void) { UpdateGameControlValues(); } -#endif void DrawGameDoorValues(void) { @@ -3514,7 +3542,6 @@ void InitGame(void) int fade_mask = REDRAW_FIELD; 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; @@ -3556,11 +3583,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 @@ -3693,6 +3724,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; @@ -3775,6 +3808,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; @@ -3849,8 +3885,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; @@ -3875,7 +3909,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 @@ -4273,7 +4306,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))) @@ -4291,7 +4324,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))) @@ -4312,7 +4345,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))) @@ -4427,7 +4460,9 @@ void InitGame(void) game.restart_level = FALSE; game.restart_game_message = NULL; + game.request_active = FALSE; + game.request_active_or_moving = FALSE; if (level.game_engine_type == GAME_ENGINE_TYPE_MM) InitGameActions_MM(); @@ -4661,41 +4696,64 @@ 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_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; + + // 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) { @@ -4705,6 +4763,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; @@ -4721,55 +4782,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_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) @@ -4822,72 +4886,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; @@ -4906,7 +4965,7 @@ 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; @@ -4916,7 +4975,11 @@ void GameEnd(void) 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 @@ -4947,6 +5010,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) @@ -4962,13 +5028,11 @@ void GameEnd(void) } } - 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 && @@ -4984,64 +5048,149 @@ 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++) - { - if (game.score_final > highscore[k].Score) + for (i = 0; i < MAX_SCORE_ENTRIES; i++) + { + 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)) + 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; + } } - if (position >= 0) - SaveScore(level_nr); + return -1; +} + +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); - return position; + 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 < 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(); + } + + 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; + + scores.entry[scores.last_added] = last_added_entry; + } } static int getElementMoveStepsizeExt(int x, int y, int direction) @@ -5315,13 +5464,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]); - 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); @@ -5562,7 +5713,7 @@ 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 } @@ -5732,7 +5883,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]; @@ -5872,7 +6023,7 @@ 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))) @@ -5894,7 +6045,7 @@ static void Explode(int ex, int ey, int phase, int mode) DrawLevelElementThruMask(x, y, Back[x][y]); } else if (!IS_WALKABLE_INSIDE(Back[x][y])) - DrawGraphic(SCREENX(x), SCREENY(y), graphic, frame); + DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame); } } @@ -8469,11 +8620,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); - MovPos[x][y] += getElementMoveStepsize(x, y); - - if (pushed_by_player) // special case: moving object pushed by player + 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); + + if (MovDelay[x][y]) + { + TEST_DrawLevelField(x, y); + + return; // element is still waiting + } + } + else // normal case: generically moving object + { + MovPos[x][y] += getElementMoveStepsize(x, y); + } if (ABS(MovPos[x][y]) < TILEX) { @@ -8636,7 +8809,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); } @@ -10446,7 +10619,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)); @@ -10622,7 +10795,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; @@ -10684,6 +10857,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); @@ -11260,13 +11437,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); @@ -11553,7 +11731,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 @@ -11929,6 +12107,10 @@ void GameActions_RND(void) 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; @@ -11953,6 +12135,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 @@ -12005,10 +12191,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; } } @@ -12063,6 +12259,7 @@ void GameActions_RND(void) 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); x = mouse_action.lx; y = mouse_action.ly; @@ -12070,12 +12267,14 @@ void GameActions_RND(void) if (new_button) { - CheckElementChange(x, y, element, EL_UNDEFINED, CE_CLICKED_BY_MOUSE); - CheckTriggeredElementChange(x, y, element, CE_MOUSE_CLICKED_ON_X); + CheckElementChangeByMouse(x, y, element, CE_CLICKED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_CLICKED_ON_X, + ch_button); } - CheckElementChange(x, y, element, EL_UNDEFINED, CE_PRESSED_BY_MOUSE); - CheckTriggeredElementChange(x, y, element, CE_MOUSE_PRESSED_ON_X); + CheckElementChangeByMouse(x, y, element, CE_PRESSED_BY_MOUSE, ch_button); + CheckTriggeredElementChangeByMouse(x, y, element, CE_MOUSE_PRESSED_ON_X, + ch_button); } SCAN_PLAYFIELD(x, y) @@ -12426,6 +12625,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++) @@ -12441,7 +12642,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) @@ -13007,11 +13208,26 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) if (!player->is_pushing) TestIfElementTouchesCustomElement(jx, jy); // for empty space + if (level.finish_dig_collect && + (player->is_digging || player->is_collecting)) + { + 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); + + CheckTriggeredElementChangeByPlayer(jx, jy, last_element, change_event, + player->index_bit, enter_side); + + player->last_removed_element = EL_UNDEFINED; + } + if (!player->active) RemovePlayer(player); } - if (!game.LevelSolved && level.use_step_counter) + if (level.use_step_counter) { int i; @@ -13021,14 +13237,14 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) { TimeLeft--; - if (TimeLeft <= 10 && setup.time_limit) + if (TimeLeft <= 10 && setup.time_limit && !game.LevelSolved) PlaySound(SND_GAME_RUNNING_OUT_OF_TIME); game_panel_controls[GAME_PANEL_TIME].value = TimeLeft; DisplayGameControlValues(); - if (!TimeLeft && setup.time_limit) + if (!TimeLeft && setup.time_limit && !game.LevelSolved) for (i = 0; i < MAX_PLAYERS; i++) KillPlayer(&stored_player[i]); } @@ -13741,7 +13957,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); @@ -13751,6 +13968,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); @@ -13760,6 +13980,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() @@ -14039,22 +14277,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)) @@ -14122,7 +14366,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) { @@ -14166,25 +14413,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)) @@ -14306,7 +14556,7 @@ 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; @@ -14522,6 +14772,8 @@ static int DigField(struct PlayerInfo *player, { player->is_collecting = !player->is_digging; player->is_active = TRUE; + + player->last_removed_element = element; } } @@ -15242,12 +15494,12 @@ 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); + // door may still be open due to skipped or envelope style request CloseDoor(DOOR_CLOSE_1); } @@ -15275,10 +15527,13 @@ 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); RequestQuitGameExt(skip_request, quick_quit, "Do you really want to quit the game?"); @@ -15931,12 +16186,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); @@ -15949,17 +16210,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) @@ -15971,15 +16228,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[] = @@ -16001,9 +16249,7 @@ 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 (!on_tape || gamebutton_info[i].allowed_on_tape) MapGadget(game_gadget[i]); UnmapGameButtonsAtSamePosition_All(); @@ -16095,6 +16341,8 @@ static void GameUndoRedoExt(void) DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter); DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0); + ModifyPauseButtons(); + BackToFront(); } @@ -16103,8 +16351,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(); } @@ -16113,8 +16365,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(); } @@ -16140,7 +16396,7 @@ static void HandleGameButtonsExt(int id, int button) if (tape.playing) TapeStop(); else - RequestQuitGame(TRUE); + RequestQuitGame(FALSE); break;