X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=3eff842eb44ece2ed7c4c5efd98b0db1f0c1fc88;hb=3bf51f147946d7080ed973f7d1fca2971e5009d2;hp=cc4dace14930446668c0b72ac43f2750b9f19be2;hpb=42ab1555a4ae65badec13e8ef32bf5ff71c34568;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index cc4dace1..3eff842e 100644 --- a/src/game.c +++ b/src/game.c @@ -34,6 +34,7 @@ #define USE_NEW_PLAYER_SPEED (USE_NEW_STUFF * 1) #define USE_NEW_DELAYED_ACTION (USE_NEW_STUFF * 1) #define USE_NEW_SNAP_DELAY (USE_NEW_STUFF * 1) +#define USE_ONLY_ONE_CHANGE_PER_FRAME (USE_NEW_STUFF * 0) /* for DigField() */ #define DF_NO_PUSH 0 @@ -135,9 +136,6 @@ ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \ (e) == EL_TRIGGER_PLAYER ? (ch)->actual_trigger_player : (e)) -#define GET_VALID_PLAYER_ELEMENT(e) \ - ((e) >= EL_PLAYER_1 && (e) <= EL_PLAYER_4 ? (e) : EL_PLAYER_1) - #define CAN_GROW_INTO(e) \ ((e) == EL_SAND || (IS_DIGGABLE(e) && level.grow_into_diggable)) @@ -235,6 +233,7 @@ /* forward declaration for internal use */ +static void SetPlayerWaiting(struct PlayerInfo *, boolean); static void AdvanceFrameAndPlayerCounters(int); static boolean MovePlayerOneStep(struct PlayerInfo *, int, int, int, int); @@ -683,7 +682,7 @@ static void InitPlayerField(int x, int y, int element, boolean init_game) } else { - stored_player[0].use_murphy_graphic = TRUE; + stored_player[0].use_murphy = TRUE; } Feld[x][y] = EL_PLAYER_1; @@ -900,7 +899,7 @@ static void InitField(int x, int y, boolean init_game) InitMovDir(x, y); #if USE_NEW_CUSTOM_VALUE - if (!element_info[element].use_last_ce_value) + if (!element_info[element].use_last_ce_value || init_game) CustomValue[x][y] = GET_NEW_CUSTOM_VALUE(Feld[x][y]); #endif } @@ -1211,6 +1210,38 @@ static void InitGameEngine() game.use_block_last_field_bug = (game.engine_version < VERSION_IDENT(3,1,1,0)); + /* + Summary of bugfix/change: + Changed behaviour of CE changes with multiple changes per single frame. + + Fixed/changed in version: + 3.2.0-6 + + Description: + Before 3.2.0-6, only one single CE change was allowed in each engine frame. + This resulted in race conditions where CEs seem to behave strange in some + situations (where triggered CE changes were just skipped because there was + already a CE change on that tile in the playfield in that engine frame). + Since 3.2.0-6, this was changed to allow up to MAX_NUM_CHANGES_PER_FRAME. + (The number of changes per frame must be limited in any case, because else + it is easily possible to define CE changes that would result in an infinite + loop, causing the whole game to freeze. The MAX_NUM_CHANGES_PER_FRAME value + should be set large enough so that it would only be reached in cases where + the corresponding CE change conditions run into a loop. Therefore, it seems + to be reasonable to set MAX_NUM_CHANGES_PER_FRAME to the same value as the + maximal number of change pages for custom elements.) + + Affected levels/tapes: + Probably many. + */ + +#if USE_ONLY_ONE_CHANGE_PER_FRAME + game.max_num_changes_per_frame = 1; +#else + game.max_num_changes_per_frame = + (game.engine_version < VERSION_IDENT(3,2,0,6) ? 1 : 32); +#endif + /* ---------------------------------------------------------------------- */ /* dynamically adjust element properties according to game engine version */ @@ -1476,6 +1507,28 @@ static void InitGameEngine() access_direction_list[i].direction; } +int get_num_special_action(int element, int action_first, int action_last) +{ + int num_special_action = 0; + int i, j; + + for (i = action_first; i <= action_last; i++) + { + boolean found = FALSE; + + for (j = 0; j < NUM_DIRECTIONS; j++) + if (el_act_dir2img(element, i, j) != + el_act_dir2img(element, ACTION_DEFAULT, j)) + found = TRUE; + + if (found) + num_special_action++; + else + break; + } + + return num_special_action; +} /* ============================================================================= @@ -1490,7 +1543,7 @@ void InitGame() 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 i, j, k, x, y; + int i, j, x, y; InitGameEngine(); @@ -1534,7 +1587,8 @@ void InitGame() player->Frame = 0; player->StepFrame = 0; - player->use_murphy_graphic = FALSE; + player->use_murphy = FALSE; + player->artwork_element = player->element_nr; player->block_last_field = FALSE; /* initialized in InitPlayerField() */ player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */ @@ -1560,6 +1614,8 @@ void InitGame() player->is_bored = FALSE; player->is_sleeping = FALSE; + player->cannot_move = FALSE; + player->frame_counter_bored = -1; player->frame_counter_sleeping = -1; @@ -1571,38 +1627,13 @@ void InitGame() player->special_action_bored = ACTION_DEFAULT; player->special_action_sleeping = ACTION_DEFAULT; - player->num_special_action_bored = 0; - player->num_special_action_sleeping = 0; - - /* determine number of special actions for bored and sleeping animation */ - for (j = ACTION_BORING_1; j <= ACTION_BORING_LAST; j++) - { - boolean found = FALSE; - - for (k = 0; k < NUM_DIRECTIONS; k++) - if (el_act_dir2img(player->element_nr, j, k) != - el_act_dir2img(player->element_nr, ACTION_DEFAULT, k)) - found = TRUE; - - if (found) - player->num_special_action_bored++; - else - break; - } - for (j = ACTION_SLEEPING_1; j <= ACTION_SLEEPING_LAST; j++) - { - boolean found = FALSE; - - for (k = 0; k < NUM_DIRECTIONS; k++) - if (el_act_dir2img(player->element_nr, j, k) != - el_act_dir2img(player->element_nr, ACTION_DEFAULT, k)) - found = TRUE; - - if (found) - player->num_special_action_sleeping++; - else - break; - } + /* set number of special actions for bored and sleeping animation */ + player->num_special_action_bored = + get_num_special_action(player->artwork_element, + ACTION_BORING_1, ACTION_BORING_LAST); + player->num_special_action_sleeping = + get_num_special_action(player->artwork_element, + ACTION_SLEEPING_1, ACTION_SLEEPING_LAST); player->switch_x = -1; player->switch_y = -1; @@ -1675,6 +1706,9 @@ void InitGame() game.gravity = level.initial_gravity; game.explosions_delayed = TRUE; + game.lenses_time_left = 0; + game.magnify_time_left = 0; + game.envelope_active = FALSE; for (i = 0; i < NUM_BELTS; i++) @@ -1705,7 +1739,7 @@ void InitGame() Stop[x][y] = FALSE; Pushed[x][y] = FALSE; - Changed[x][y] = FALSE; + Changed[x][y] = 0; ChangeEvent[x][y] = -1; ExplodePhase[x][y] = 0; @@ -1822,6 +1856,8 @@ void InitGame() player->element_nr = some_player->element_nr; #endif + player->artwork_element = some_player->artwork_element; + player->block_last_field = some_player->block_last_field; player->block_delay_adjustment = some_player->block_delay_adjustment; @@ -1930,6 +1966,7 @@ void InitGame() int start_x = 0, start_y = 0; int found_rating = 0; int found_element = EL_UNDEFINED; + int player_nr = local_player->index_nr; for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) { @@ -1938,6 +1975,17 @@ void InitGame() int xx, yy; boolean is_player; + if (level.use_start_element[player_nr] && + level.start_element[player_nr] == element && + found_rating < 4) + { + start_x = x; + start_y = y; + + found_rating = 4; + found_element = element; + } + if (!IS_CUSTOM_ELEMENT(element)) continue; @@ -1945,6 +1993,7 @@ void InitGame() { for (i = 0; i < element_info[element].num_change_pages; i++) { + /* 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); @@ -1961,6 +2010,7 @@ void InitGame() for (yy = 0; yy < 3; yy++) for (xx = 0; xx < 3; xx++) { + /* check for player created from custom element as explosion content */ content = element_info[element].content.e[xx][yy]; is_player = ELEM_IS_PLAYER(content); @@ -1978,6 +2028,7 @@ void InitGame() for (i = 0; i < element_info[element].num_change_pages; i++) { + /* check for player created from custom element as extended target */ content = element_info[element].change_page[i].target_content.e[xx][yy]; @@ -2306,12 +2357,17 @@ void GameWon() { if (!tape.playing && !setup.sound_loops) PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE); - if (TimeLeft > 0 && !(TimeLeft % 10)) - RaiseScore(level.score[SC_TIME_BONUS]); - if (TimeLeft > 100 && !(TimeLeft % 10)) + + if (TimeLeft > 100 && TimeLeft % 10 == 0) + { TimeLeft -= 10; + RaiseScore(level.score[SC_TIME_BONUS] * 10); + } else + { TimeLeft--; + RaiseScore(level.score[SC_TIME_BONUS]); + } DrawGameValue_Time(TimeLeft); @@ -2334,12 +2390,17 @@ void GameWon() { if (!tape.playing && !setup.sound_loops) PlaySoundStereo(SND_GAME_LEVELTIME_BONUS, SOUND_MIDDLE); - if (TimePlayed < 999 && !(TimePlayed % 10)) - RaiseScore(level.score[SC_TIME_BONUS]); - if (TimePlayed < 900 && !(TimePlayed % 10)) + + if (TimePlayed < 900 && TimePlayed % 10 == 0) + { TimePlayed += 10; + RaiseScore(level.score[SC_TIME_BONUS] * 10); + } else + { TimePlayed++; + RaiseScore(level.score[SC_TIME_BONUS]); + } DrawGameValue_Time(TimePlayed); @@ -2860,8 +2921,9 @@ void DrawRelocatePlayer(struct PlayerInfo *player) void RelocatePlayer(int jx, int jy, int el_player_raw) { - int el_player = GET_VALID_PLAYER_ELEMENT(el_player_raw); - struct PlayerInfo *player = &stored_player[el_player - EL_PLAYER_1]; + int el_player = GET_PLAYER_ELEMENT(el_player_raw); + int player_nr = GET_PLAYER_NR(el_player); + struct PlayerInfo *player = &stored_player[player_nr]; boolean ffwd_delay = (tape.playing && tape.fast_forward); boolean no_delay = (tape.warp_forward); int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay); @@ -3081,7 +3143,7 @@ void Explode(int ex, int ey, int phase, int mode) break; } - if (PLAYERINFO(ex, ey)->use_murphy_graphic) + if (PLAYERINFO(ex, ey)->use_murphy) Store[x][y] = EL_EMPTY; } else if (center_element == EL_MOLE) @@ -3339,7 +3401,7 @@ void Bang(int x, int y) { struct PlayerInfo *player = PLAYERINFO(x, y); - element = Feld[x][y] = (player->use_murphy_graphic ? EL_SP_MURPHY : + element = Feld[x][y] = (player->use_murphy ? EL_SP_MURPHY : player->element_nr); } @@ -3622,50 +3684,155 @@ static void RedrawAllLightSwitchesAndInvisibleElements() { int x, y; - for (y = 0; y < lev_fieldy; y++) + for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) { - for (x = 0; x < lev_fieldx; x++) + int element = Feld[x][y]; + + if (element == EL_LIGHT_SWITCH && + game.light_time_left > 0) { - int element = Feld[x][y]; + Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE; + DrawLevelField(x, y); + } + else if (element == EL_LIGHT_SWITCH_ACTIVE && + game.light_time_left == 0) + { + Feld[x][y] = EL_LIGHT_SWITCH; + DrawLevelField(x, y); + } + else if (element == EL_EMC_DRIPPER && + game.light_time_left > 0) + { + Feld[x][y] = EL_EMC_DRIPPER_ACTIVE; + DrawLevelField(x, y); + } + else if (element == EL_EMC_DRIPPER_ACTIVE && + game.light_time_left == 0) + { + Feld[x][y] = EL_EMC_DRIPPER; + DrawLevelField(x, y); + } + else if (element == EL_INVISIBLE_STEELWALL || + element == EL_INVISIBLE_WALL || + element == EL_INVISIBLE_SAND) + { + if (game.light_time_left > 0) + Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); - if (element == EL_LIGHT_SWITCH && - game.light_time_left > 0) - { - Feld[x][y] = EL_LIGHT_SWITCH_ACTIVE; - DrawLevelField(x, y); - } - else if (element == EL_LIGHT_SWITCH_ACTIVE && - game.light_time_left == 0) - { - Feld[x][y] = EL_LIGHT_SWITCH; - DrawLevelField(x, y); - } - else if (element == EL_INVISIBLE_STEELWALL || - element == EL_INVISIBLE_WALL || - element == EL_INVISIBLE_SAND) - { - if (game.light_time_left > 0) - Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + DrawLevelField(x, y); - DrawLevelField(x, y); + /* uncrumble neighbour fields, if needed */ + if (element == EL_INVISIBLE_SAND) + DrawLevelFieldCrumbledSandNeighbours(x, y); + } + else if (element == EL_INVISIBLE_STEELWALL_ACTIVE || + element == EL_INVISIBLE_WALL_ACTIVE || + element == EL_INVISIBLE_SAND_ACTIVE) + { + if (game.light_time_left == 0) + Feld[x][y] = getInvisibleFromInvisibleActiveElement(element); - /* uncrumble neighbour fields, if needed */ - if (element == EL_INVISIBLE_SAND) - DrawLevelFieldCrumbledSandNeighbours(x, y); - } - else if (element == EL_INVISIBLE_STEELWALL_ACTIVE || - element == EL_INVISIBLE_WALL_ACTIVE || - element == EL_INVISIBLE_SAND_ACTIVE) - { - if (game.light_time_left == 0) - Feld[x][y] = getInvisibleFromInvisibleActiveElement(element); + DrawLevelField(x, y); - DrawLevelField(x, y); + /* re-crumble neighbour fields, if needed */ + if (element == EL_INVISIBLE_SAND) + DrawLevelFieldCrumbledSandNeighbours(x, y); + } + } +} - /* re-crumble neighbour fields, if needed */ - if (element == EL_INVISIBLE_SAND) - DrawLevelFieldCrumbledSandNeighbours(x, y); - } +static void RedrawAllInvisibleElementsForLenses() +{ + int x, y; + + for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) + { + int element = Feld[x][y]; + + if (element == EL_EMC_DRIPPER && + game.lenses_time_left > 0) + { + Feld[x][y] = EL_EMC_DRIPPER_ACTIVE; + DrawLevelField(x, y); + } + else if (element == EL_EMC_DRIPPER_ACTIVE && + game.lenses_time_left == 0) + { + Feld[x][y] = EL_EMC_DRIPPER; + DrawLevelField(x, y); + } + else if (element == EL_INVISIBLE_STEELWALL || + element == EL_INVISIBLE_WALL || + element == EL_INVISIBLE_SAND) + { + if (game.lenses_time_left > 0) + Feld[x][y] = getInvisibleActiveFromInvisibleElement(element); + + DrawLevelField(x, y); + + /* uncrumble neighbour fields, if needed */ + if (element == EL_INVISIBLE_SAND) + DrawLevelFieldCrumbledSandNeighbours(x, y); + } + else if (element == EL_INVISIBLE_STEELWALL_ACTIVE || + element == EL_INVISIBLE_WALL_ACTIVE || + element == EL_INVISIBLE_SAND_ACTIVE) + { + if (game.lenses_time_left == 0) + Feld[x][y] = getInvisibleFromInvisibleActiveElement(element); + + DrawLevelField(x, y); + + /* re-crumble neighbour fields, if needed */ + if (element == EL_INVISIBLE_SAND) + DrawLevelFieldCrumbledSandNeighbours(x, y); + } + } +} + +static void RedrawAllInvisibleElementsForMagnifier() +{ + int x, y; + + for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) + { + int element = Feld[x][y]; + + if (element == EL_EMC_FAKE_GRASS && + game.magnify_time_left > 0) + { + Feld[x][y] = EL_EMC_FAKE_GRASS_ACTIVE; + DrawLevelField(x, y); + } + else if (element == EL_EMC_FAKE_GRASS_ACTIVE && + game.magnify_time_left == 0) + { + Feld[x][y] = EL_EMC_FAKE_GRASS; + DrawLevelField(x, y); + } + else if (IS_GATE_GRAY(element) && + game.magnify_time_left > 0) + { + Feld[x][y] = (IS_RND_GATE_GRAY(element) ? + element - EL_GATE_1_GRAY + EL_GATE_1_GRAY_ACTIVE : + IS_EM_GATE_GRAY(element) ? + element - EL_EM_GATE_1_GRAY + EL_EM_GATE_1_GRAY_ACTIVE : + IS_EMC_GATE_GRAY(element) ? + element - EL_EMC_GATE_5_GRAY + EL_EMC_GATE_5_GRAY_ACTIVE : + element); + DrawLevelField(x, y); + } + else if (IS_GATE_GRAY_ACTIVE(element) && + game.magnify_time_left == 0) + { + Feld[x][y] = (IS_RND_GATE_GRAY_ACTIVE(element) ? + element - EL_GATE_1_GRAY_ACTIVE + EL_GATE_1_GRAY : + IS_EM_GATE_GRAY_ACTIVE(element) ? + element - EL_EM_GATE_1_GRAY_ACTIVE + EL_EM_GATE_1_GRAY : + IS_EMC_GATE_GRAY_ACTIVE(element) ? + element - EL_EMC_GATE_5_GRAY_ACTIVE + EL_EMC_GATE_5_GRAY : + element); + DrawLevelField(x, y); } } } @@ -5541,7 +5708,7 @@ void ContinueMoving(int x, int y) ChangeDelay[x][y] = 0; ChangePage[x][y] = -1; - Changed[x][y] = FALSE; + Changed[x][y] = 0; ChangeEvent[x][y] = -1; #if USE_NEW_CUSTOM_VALUE @@ -5642,15 +5809,17 @@ void ContinueMoving(int x, int y) if (pushed_by_player && !game.use_change_when_pushing_bug) { - int dig_side = MV_DIR_OPPOSITE(direction); + int push_side = MV_DIR_OPPOSITE(direction); struct PlayerInfo *player = PLAYERINFO(x, y); CheckElementChangeByPlayer(newx, newy, element, CE_PUSHED_BY_PLAYER, - player->index_bit, dig_side); + player->index_bit, push_side); CheckTriggeredElementChangeByPlayer(newx,newy, element, CE_PLAYER_PUSHES_X, - player->index_bit, dig_side); + player->index_bit, push_side); } + CheckTriggeredElementChangeBySide(x, y, element, CE_MOVE_OF_X, direction); + TestIfElementTouchesCustomElement(x, y); /* empty or new element */ TestIfElementHitsCustomElement(newx, newy, direction); @@ -6574,7 +6743,11 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) if (!change->has_action) return; - /* ---------- determine action paramater values ---------- */ + /* ---------- determine action paramater values -------------------------- */ + + int level_time_value = + (level.time > 0 ? TimeLeft : + TimePlayed); int action_arg_element = (action_arg == CA_ARG_PLAYER_TRIGGER ? change->actual_trigger_player : @@ -6582,6 +6755,15 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_arg == CA_ARG_ELEMENT_TARGET ? change->target_element : EL_EMPTY); + int action_arg_direction = + (action_arg >= CA_ARG_DIRECTION_LEFT && + action_arg <= CA_ARG_DIRECTION_DOWN ? action_arg - CA_ARG_DIRECTION : + action_arg == CA_ARG_DIRECTION_TRIGGER ? + change->actual_trigger_side : + action_arg == CA_ARG_DIRECTION_TRIGGER_BACK ? + MV_DIR_OPPOSITE(change->actual_trigger_side) : + MV_NONE); + int action_arg_number_min = (action_type == CA_SET_PLAYER_SPEED ? MOVE_STEPSIZE_MIN : CA_ARG_MIN); @@ -6610,7 +6792,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) int action_arg_number = (action_arg <= CA_ARG_MAX ? action_arg : - action_arg >= CA_ARG_SPEED_VERY_SLOW && + action_arg >= CA_ARG_SPEED_NOT_MOVING && action_arg <= CA_ARG_SPEED_EVEN_FASTER ? (action_arg - CA_ARG_SPEED) : action_arg == CA_ARG_SPEED_RESET ? action_arg_number_reset : action_arg == CA_ARG_NUMBER_MIN ? action_arg_number_min : @@ -6623,6 +6805,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_arg == CA_ARG_NUMBER_CE_VALUE ? ei->custom_value_initial : #endif action_arg == CA_ARG_NUMBER_CE_DELAY ? GET_CHANGE_DELAY(change) : + action_arg == CA_ARG_NUMBER_LEVEL_TIME ? level_time_value : + action_arg == CA_ARG_NUMBER_LEVEL_GEMS ? local_player->gems_still_needed : + action_arg == CA_ARG_NUMBER_LEVEL_SCORE ? local_player->score : action_arg == CA_ARG_ELEMENT_TARGET ? GET_NEW_CUSTOM_VALUE(change->target_element) : action_arg == CA_ARG_ELEMENT_TRIGGER ? change->actual_trigger_ce_value : -1); @@ -6640,24 +6825,19 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) action_mode, action_arg_number, action_arg_number_min, action_arg_number_max); - int action_arg_player_bits = - (action_arg == CA_ARG_PLAYER_ANY ? PLAYER_BITS_ANY : - action_arg >= CA_ARG_PLAYER_1 && - action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER : - action_arg >= CA_ARG_1 && - action_arg <= CA_ARG_PLAYER_4 ? (1 << (action_arg - CA_ARG_1)) : - action_arg_element >= EL_PLAYER_1 && - action_arg_element <= EL_PLAYER_4 ? - (1 << (action_arg_element - EL_PLAYER_1)) : - PLAYER_BITS_ANY); - int trigger_player_bits = (change->actual_trigger_player >= EL_PLAYER_1 && change->actual_trigger_player <= EL_PLAYER_4 ? (1 << (change->actual_trigger_player - EL_PLAYER_1)) : PLAYER_BITS_ANY); - /* ---------- execute action ---------- */ + int action_arg_player_bits = + (action_arg >= CA_ARG_PLAYER_1 && + action_arg <= CA_ARG_PLAYER_4 ? action_arg - CA_ARG_PLAYER : + action_arg == CA_ARG_PLAYER_TRIGGER ? trigger_player_bits : + PLAYER_BITS_ANY); + + /* ---------- execute action -------------------------------------------- */ switch(action_type) { @@ -6666,67 +6846,109 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) return; } - case CA_EXIT_PLAYER: + /* ---------- level actions ------------------------------------------- */ + + case CA_RESTART_LEVEL: { - for (i = 0; i < MAX_PLAYERS; i++) - if (action_arg_player_bits & (1 << i)) - stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE; + game.restart_level = TRUE; break; } - case CA_KILL_PLAYER: + case CA_SHOW_ENVELOPE: { - for (i = 0; i < MAX_PLAYERS; i++) - if (action_arg_player_bits & (1 << i)) - KillPlayer(&stored_player[i]); + int element = getSpecialActionElement(action_arg_element, + action_arg_number, EL_ENVELOPE_1); + + if (IS_ENVELOPE(element)) + local_player->show_envelope = element; break; } - case CA_RESTART_LEVEL: + case CA_SET_LEVEL_TIME: { - game.restart_level = TRUE; + if (level.time > 0) /* only modify limited time value */ + { + TimeLeft = action_arg_number_new; + + DrawGameValue_Time(TimeLeft); + + if (!TimeLeft && setup.time_limit) + for (i = 0; i < MAX_PLAYERS; i++) + KillPlayer(&stored_player[i]); + } break; } - case CA_SHOW_ENVELOPE: + case CA_SET_LEVEL_SCORE: { - int element = getSpecialActionElement(action_arg_element, - action_arg_number, EL_ENVELOPE_1); + local_player->score = action_arg_number_new; - if (IS_ENVELOPE(element)) - local_player->show_envelope = element; + DrawGameValue_Score(local_player->score); break; } - case CA_ADD_KEY: + case CA_SET_LEVEL_GEMS: { - int element = getSpecialActionElement(action_arg_element, - action_arg_number, EL_KEY_1); + local_player->gems_still_needed = action_arg_number_new; - if (IS_KEY(element)) - { - for (i = 0; i < MAX_PLAYERS; i++) - { - if (trigger_player_bits & (1 << i)) - { - stored_player[i].key[KEY_NR(element)] = TRUE; + DrawGameValue_Emeralds(local_player->gems_still_needed); - DrawGameValue_Keys(stored_player[i].key); + break; + } - redraw_mask |= REDRAW_DOOR_1; - } - } - } + case CA_SET_LEVEL_GRAVITY: + { + game.gravity = (action_arg == CA_ARG_GRAVITY_OFF ? FALSE : + action_arg == CA_ARG_GRAVITY_ON ? TRUE : + action_arg == CA_ARG_GRAVITY_TOGGLE ? !game.gravity : + game.gravity); + break; + } + + case CA_SET_LEVEL_WIND: + { + game.wind_direction = action_arg_direction; break; } - case CA_REMOVE_KEY: + /* ---------- player actions ------------------------------------------ */ + + case CA_MOVE_PLAYER: { + /* automatically move to the next field in specified direction */ + for (i = 0; i < MAX_PLAYERS; i++) + if (trigger_player_bits & (1 << i)) + stored_player[i].programmed_action = action_arg_direction; + + break; + } + + case CA_EXIT_PLAYER: + { + for (i = 0; i < MAX_PLAYERS; i++) + if (action_arg_player_bits & (1 << i)) + stored_player[i].LevelSolved = stored_player[i].GameOver = TRUE; + + break; + } + + case CA_KILL_PLAYER: + { + for (i = 0; i < MAX_PLAYERS; i++) + if (action_arg_player_bits & (1 << i)) + KillPlayer(&stored_player[i]); + + break; + } + + case CA_SET_PLAYER_KEYS: + { + int key_state = (action_mode == CA_MODE_ADD ? TRUE : FALSE); int element = getSpecialActionElement(action_arg_element, action_arg_number, EL_KEY_1); @@ -6736,7 +6958,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) { if (trigger_player_bits & (1 << i)) { - stored_player[i].key[KEY_NR(element)] = FALSE; + stored_player[i].key[KEY_NR(element)] = key_state; DrawGameValue_Keys(stored_player[i].key); @@ -6748,7 +6970,6 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) break; } -#if 1 case CA_SET_PLAYER_SPEED: { for (i = 0; i < MAX_PLAYERS; i++) @@ -6778,110 +6999,69 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) /* do no immediately change -- the player might just be moving */ stored_player[i].move_delay_value_next = TILEX / move_stepsize; -#if 0 - printf("::: move_delay_value == %d [%d]\n", - stored_player[i].move_delay_value_next, action_arg_number); -#endif + stored_player[i].cannot_move = + (action_arg == CA_ARG_SPEED_NOT_MOVING ? TRUE : FALSE); } } break; } -#else - case CA_SET_PLAYER_SPEED: + + case CA_SET_PLAYER_SHIELD: { for (i = 0; i < MAX_PLAYERS; i++) { if (trigger_player_bits & (1 << i)) { - int move_stepsize = TILEX / stored_player[i].move_delay_value; - - if (action_mode == CA_MODE_ADD || action_mode == CA_MODE_SUBTRACT) + if (action_arg == CA_ARG_SHIELD_OFF) { - /* translate "+" and "-" to "*" and "/" with powers of two */ - action_arg_number = 1 << action_arg_number; - action_mode = (action_mode == CA_MODE_ADD ? CA_MODE_MULTIPLY : - CA_MODE_DIVIDE); + stored_player[i].shield_normal_time_left = 0; + stored_player[i].shield_deadly_time_left = 0; + } + else if (action_arg == CA_ARG_SHIELD_NORMAL) + { + stored_player[i].shield_normal_time_left = 999999; + } + else if (action_arg == CA_ARG_SHIELD_DEADLY) + { + stored_player[i].shield_normal_time_left = 999999; + stored_player[i].shield_deadly_time_left = 999999; } - - move_stepsize = - getModifiedActionNumber(move_stepsize, - action_mode, - action_arg_number, - action_arg_number_min, - action_arg_number_max); - - /* make sure that value is power of 2 */ - move_stepsize = (1 << log_2(move_stepsize)); - - /* do no immediately change -- the player might just be moving */ - stored_player[i].move_delay_value_next = TILEX / move_stepsize; - -#if 0 - printf("::: move_delay_value == %d [%d]\n", - stored_player[i].move_delay_value_next, action_arg_number); -#endif } } break; } -#endif - - case CA_SET_PLAYER_GRAVITY: - { - game.gravity = (action_arg == CA_ARG_GRAVITY_OFF ? FALSE : - action_arg == CA_ARG_GRAVITY_ON ? TRUE : - action_arg == CA_ARG_GRAVITY_TOGGLE ? !game.gravity : - game.gravity); - break; - } - - case CA_SET_WIND_DIRECTION: - { - game.wind_direction = (action_arg >= CA_ARG_DIRECTION_NONE && - action_arg <= CA_ARG_DIRECTION_DOWN ? - action_arg - CA_ARG_DIRECTION : - action_arg == CA_ARG_DIRECTION_TRIGGER ? - MV_DIR_OPPOSITE(change->actual_trigger_side) : - game.wind_direction); - - break; - } - case CA_SET_LEVEL_GEMS: + case CA_SET_PLAYER_ARTWORK: { - local_player->gems_still_needed = action_arg_number_new; - - DrawGameValue_Emeralds(local_player->gems_still_needed); + for (i = 0; i < MAX_PLAYERS; i++) + { + if (trigger_player_bits & (1 << i)) + { + int artwork_element = action_arg_element; - break; - } + if (action_arg == CA_ARG_ELEMENT_RESET) + artwork_element = stored_player[i].element_nr; - case CA_SET_LEVEL_TIME: - { - if (level.time > 0) /* only modify limited time value */ - { - TimeLeft = action_arg_number_new; + stored_player[i].artwork_element = artwork_element; - DrawGameValue_Time(TimeLeft); + SetPlayerWaiting(&stored_player[i], FALSE); - if (!TimeLeft && setup.time_limit) - for (i = 0; i < MAX_PLAYERS; i++) - KillPlayer(&stored_player[i]); + /* set number of special actions for bored and sleeping animation */ + stored_player[i].num_special_action_bored = + get_num_special_action(artwork_element, + ACTION_BORING_1, ACTION_BORING_LAST); + stored_player[i].num_special_action_sleeping = + get_num_special_action(artwork_element, + ACTION_SLEEPING_1, ACTION_SLEEPING_LAST); + } } break; } - case CA_SET_LEVEL_SCORE: - { - local_player->score = action_arg_number_new; - - DrawGameValue_Score(local_player->score); - - break; - } + /* ---------- CE actions ---------------------------------------------- */ case CA_SET_CE_SCORE: { @@ -6915,29 +7095,6 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) break; } -#if 0 - case CA_SET_DYNABOMB_NUMBER: - { - printf("::: CA_SET_DYNABOMB_NUMBER -- not yet implemented\n"); - - break; - } - - case CA_SET_DYNABOMB_SIZE: - { - printf("::: CA_SET_DYNABOMB_SIZE -- not yet implemented\n"); - - break; - } - - case CA_SET_DYNABOMB_POWER: - { - printf("::: CA_SET_DYNABOMB_POWER -- not yet implemented\n"); - - break; - } -#endif - default: break; } @@ -6994,11 +7151,7 @@ static void ChangeElementNowExt(struct ElementChangeInfo *change, if (ELEM_IS_PLAYER(target_element)) RelocatePlayer(x, y, target_element); -#if 1 - Changed[x][y] = TRUE; /* ignore all further changes in this frame */ -#else - Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */ -#endif + Changed[x][y]++; /* count number of changes in the same frame */ TestIfBadThingTouchesPlayer(x, y); TestIfPlayerTouchesCustomElement(x, y); @@ -7024,21 +7177,11 @@ static boolean ChangeElementNow(int x, int y, int element, int page) change->actual_trigger_ce_value = 0; } -#if 1 - /* do not change any elements that have already changed in this frame */ - if (Changed[x][y]) + /* do not change elements more than a specified maximum number of changes */ + if (Changed[x][y] >= game.max_num_changes_per_frame) return FALSE; -#else - /* do not change already changed elements with same change event */ - if (Changed[x][y] & ChangeEvent[x][y]) - return FALSE; -#endif -#if 1 - Changed[x][y] = TRUE; /* ignore all further changes in this frame */ -#else - Changed[x][y] |= ChangeEvent[x][y]; /* ignore same changes in this frame */ -#endif + Changed[x][y]++; /* count number of changes in the same frame */ if (change->explode) { @@ -7501,24 +7644,24 @@ static boolean CheckElementChangeExt(int x, int y, static void PlayPlayerSound(struct PlayerInfo *player) { int jx = player->jx, jy = player->jy; - int element = player->element_nr; + int sound_element = player->artwork_element; int last_action = player->last_action_waiting; int action = player->action_waiting; if (player->is_waiting) { if (action != last_action) - PlayLevelSoundElementAction(jx, jy, element, action); + PlayLevelSoundElementAction(jx, jy, sound_element, action); else - PlayLevelSoundElementActionIfLoop(jx, jy, element, action); + PlayLevelSoundElementActionIfLoop(jx, jy, sound_element, action); } else { if (action != last_action) - StopSound(element_info[element].sound[last_action]); + StopSound(element_info[sound_element].sound[last_action]); if (last_action == ACTION_SLEEPING) - PlayLevelSoundElementAction(jx, jy, element, ACTION_AWAKENING); + PlayLevelSoundElementAction(jx, jy, sound_element, ACTION_AWAKENING); } } @@ -7585,7 +7728,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) last_special_action < ACTION_SLEEPING_1 + num_special_action - 1 ? last_special_action + 1 : ACTION_SLEEPING); int special_graphic = - el_act_dir2img(player->element_nr, special_action, move_dir); + el_act_dir2img(player->artwork_element, special_action, move_dir); player->anim_delay_counter = graphic_info[special_graphic].anim_delay_fixed + @@ -7617,7 +7760,7 @@ static void SetPlayerWaiting(struct PlayerInfo *player, boolean is_waiting) int special_action = ACTION_BORING_1 + SimpleRND(player->num_special_action_bored); int special_graphic = - el_act_dir2img(player->element_nr, special_action, move_dir); + el_act_dir2img(player->artwork_element, special_action, move_dir); player->anim_delay_counter = graphic_info[special_graphic].anim_delay_fixed + @@ -7920,7 +8063,7 @@ void GameActions() for (y = 0; y < lev_fieldy; y++) for (x = 0; x < lev_fieldx; x++) { - Changed[x][y] = FALSE; + Changed[x][y] = 0; ChangeEvent[x][y] = -1; /* this must be handled before main playfield loop */ @@ -7939,6 +8082,8 @@ void GameActions() { RemoveField(x, y); DrawLevelField(x, y); + + TestIfElementTouchesCustomElement(x, y); /* for empty space */ } } #endif @@ -8240,6 +8385,22 @@ void GameActions() CloseAllOpenTimegates(); } + if (game.lenses_time_left > 0) + { + game.lenses_time_left--; + + if (game.lenses_time_left == 0) + RedrawAllInvisibleElementsForLenses(); + } + + if (game.magnify_time_left > 0) + { + game.magnify_time_left--; + + if (game.magnify_time_left == 0) + RedrawAllInvisibleElementsForMagnifier(); + } + for (i = 0; i < MAX_PLAYERS; i++) { struct PlayerInfo *player = &stored_player[i]; @@ -8508,6 +8669,25 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, if (!IN_LEV_FIELD(new_jx, new_jy)) return MF_NO_ACTION; + if (player->cannot_move) + { +#if 1 + if (player->MovPos == 0) + { + player->is_moving = FALSE; + player->is_digging = FALSE; + player->is_collecting = FALSE; + player->is_snapping = FALSE; + player->is_pushing = FALSE; + } +#else + DigField(player, 0, 0, 0, 0, 0, 0, DF_NO_PUSH); + SnapField(player, 0, 0); +#endif + + return MF_NO_ACTION; + } + if (!options.network && !AllPlayersInSight(player, new_jx, new_jy)) return MF_NO_ACTION; @@ -8897,6 +9077,9 @@ void ScrollPlayer(struct PlayerInfo *player, int mode) CheckTriggeredElementChangeByPlayer(jx, jy, new_element, CE_PLAYER_ENTERS_X, player->index_bit, enter_side); + + CheckTriggeredElementChangeBySide(jx, jy, player->element_nr, + CE_MOVE_OF_X, move_direction); } if (game.engine_version >= VERSION_IDENT(3,0,7,0)) @@ -9472,7 +9655,7 @@ void BuryPlayer(struct PlayerInfo *player) if (!player->active) return; - PlayLevelSoundElementAction(jx, jy, player->element_nr, ACTION_DYING); + PlayLevelSoundElementAction(jx, jy, player->artwork_element, ACTION_DYING); PlayLevelSound(jx, jy, SND_GAME_LOSING); player->GameOver = TRUE; @@ -9645,7 +9828,17 @@ int DigField(struct PlayerInfo *player, if (mode == DF_SNAP && !IS_SNAPPABLE(element) && game.engine_version >= VERSION_IDENT(2,2,0,0)) + { + CheckElementChangeByPlayer(x, y, element, CE_SNAPPED_BY_PLAYER, + player->index_bit, dig_side); + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, + player->index_bit, dig_side); + + if (Feld[x][y] != element) /* field changed by snapping */ + return MF_ACTION; + return MF_NO_ACTION; + } if (game.gravity && is_player && !player->is_auto_moving && canFallDown(player) && move_direction != MV_DOWN && @@ -9667,6 +9860,11 @@ int DigField(struct PlayerInfo *player, if (!player->key[RND_GATE_GRAY_NR(element)]) return MF_NO_ACTION; } + else if (IS_RND_GATE_GRAY_ACTIVE(element)) + { + if (!player->key[RND_GATE_GRAY_ACTIVE_NR(element)]) + return MF_NO_ACTION; + } else if (element == EL_EXIT_OPEN || element == EL_SP_EXIT_OPEN || element == EL_SP_EXIT_OPENING) @@ -9682,7 +9880,7 @@ int DigField(struct PlayerInfo *player, if (element_info[sound_element].sound[sound_action] != SND_UNDEFINED) PlayLevelSoundElementAction(x, y, sound_element, sound_action); else - PlayLevelSoundElementAction(x, y, player->element_nr, sound_action); + PlayLevelSoundElementAction(x, y, player->artwork_element, sound_action); } else if (IS_PASSABLE(element) && canPassField(x, y, move_direction)) { @@ -9702,6 +9900,11 @@ int DigField(struct PlayerInfo *player, if (!player->key[EM_GATE_GRAY_NR(element)]) return MF_NO_ACTION; } + else if (IS_EM_GATE_GRAY_ACTIVE(element)) + { + if (!player->key[EM_GATE_GRAY_ACTIVE_NR(element)]) + return MF_NO_ACTION; + } else if (IS_SP_PORT(element)) { if (element == EL_SP_GRAVITY_PORT_LEFT || @@ -9750,12 +9953,17 @@ int DigField(struct PlayerInfo *player, if (mode == DF_SNAP) { - TestIfElementTouchesCustomElement(x, y); /* for empty space */ - #if USE_NEW_SNAP_DELAY if (level.block_snap_field) setFieldForSnapping(x, y, element, move_direction); + else + TestIfElementTouchesCustomElement(x, y); /* for empty space */ +#else + TestIfElementTouchesCustomElement(x, y); /* for empty space */ #endif + + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, + player->index_bit, dig_side); } } else if (IS_COLLECTIBLE(element)) @@ -9815,6 +10023,18 @@ int DigField(struct PlayerInfo *player, { player->show_envelope = element; } + else if (element == EL_EMC_LENSES) + { + game.lenses_time_left = level.lenses_time * FRAMES_PER_SECOND; + + RedrawAllInvisibleElementsForLenses(); + } + else if (element == EL_EMC_MAGNIFIER) + { + game.magnify_time_left = level.magnify_time * FRAMES_PER_SECOND; + + RedrawAllInvisibleElementsForMagnifier(); + } else if (IS_DROPPABLE(element) || IS_THROWABLE(element)) /* can be collected and dropped */ { @@ -9847,12 +10067,17 @@ int DigField(struct PlayerInfo *player, if (mode == DF_SNAP) { - TestIfElementTouchesCustomElement(x, y); /* for empty space */ - #if USE_NEW_SNAP_DELAY if (level.block_snap_field) setFieldForSnapping(x, y, element, move_direction); + else + TestIfElementTouchesCustomElement(x, y); /* for empty space */ +#else + TestIfElementTouchesCustomElement(x, y); /* for empty space */ #endif + + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X, + player->index_bit, dig_side); } } else if (IS_PUSHABLE(element)) @@ -10079,6 +10304,9 @@ int DigField(struct PlayerInfo *player, CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X, player->index_bit, dig_side); + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X, + player->index_bit, dig_side); + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_PRESSES_X, player->index_bit, dig_side); @@ -10096,6 +10324,11 @@ int DigField(struct PlayerInfo *player, player->index_bit, dig_side); CheckTriggeredElementChangeByPlayer(x, y, element, CE_SWITCH_OF_X, player->index_bit, dig_side); + + CheckElementChangeByPlayer(x, y, element, CE_SWITCHED_BY_PLAYER, + player->index_bit, dig_side); + CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SWITCHES_X, + player->index_bit, dig_side); } CheckElementChangeByPlayer(x, y, element, CE_PRESSED_BY_PLAYER, @@ -10265,7 +10498,7 @@ boolean DropElement(struct PlayerInfo *player) PlayLevelSoundAction(dropx, dropy, ACTION_DROPPING); /* needed if previous element just changed to "empty" in the last frame */ - Changed[dropx][dropy] = FALSE; /* allow another change */ + Changed[dropx][dropy] = 0; /* allow at least one more change */ CheckElementChangeByPlayer(dropx, dropy, new_element, CE_DROPPED_BY_PLAYER, player->index_bit, drop_side); @@ -10305,7 +10538,7 @@ boolean DropElement(struct PlayerInfo *player) nextx = dropx + GET_DX_FROM_DIR(move_direction); nexty = dropy + GET_DY_FROM_DIR(move_direction); - Changed[dropx][dropy] = FALSE; /* allow another change */ + Changed[dropx][dropy] = 0; /* allow at least one more change */ CheckCollision[dropx][dropy] = 2; } @@ -10656,7 +10889,7 @@ void RaiseScoreElement(int element) RaiseScore(level.score[SC_SHIELD]); break; case EL_EXTRA_TIME: - RaiseScore(level.score[SC_TIME_BONUS]); + RaiseScore(level.extra_time_score); break; case EL_KEY_1: case EL_KEY_2: