X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Fgame.c;h=88f19060e27be07027db1f802a268efe382fe76f;hb=ecd387d33963ae80311924143b2f49eb29642e7f;hp=4d67b93151c841f642ef47749dda96984093b649;hpb=68f0ce90185b6ea19cad62bc6328326bda538951;p=rocksndiamonds.git diff --git a/src/game.c b/src/game.c index 4d67b931..88f19060 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 @@ -49,13 +50,14 @@ #define SCROLL_INIT 0 #define SCROLL_GO_ON 1 -/* for Explode() */ +/* for Bang()/Explode() */ #define EX_PHASE_START 0 #define EX_TYPE_NONE 0 #define EX_TYPE_NORMAL (1 << 0) #define EX_TYPE_CENTER (1 << 1) #define EX_TYPE_BORDER (1 << 2) #define EX_TYPE_CROSS (1 << 3) +#define EX_TYPE_DYNA (1 << 4) #define EX_TYPE_SINGLE_TILE (EX_TYPE_CENTER | EX_TYPE_BORDER) /* special positions in the game control window (relative to control window) */ @@ -130,6 +132,8 @@ RND(element_info[e].ce_value_random_initial)) #define GET_CHANGE_DELAY(c) ( ((c)->delay_fixed * (c)->delay_frames) + \ RND((c)->delay_random * (c)->delay_frames)) +#define GET_CE_DELAY_VALUE(c) ( ((c)->delay_fixed) + \ + RND((c)->delay_random)) #define GET_TARGET_ELEMENT(e, ch) \ ((e) == EL_TRIGGER_ELEMENT ? (ch)->actual_trigger_element : \ @@ -1137,7 +1141,7 @@ static void resolve_group_element(int group_element, int recursion_depth) static void InitGameEngine() { - int i, j, k, l; + int i, j, k, l, x, y; /* set game engine from tape file when re-playing, else from level file */ game.engine_version = (tape.playing ? tape.engine_version : @@ -1209,6 +1213,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 */ @@ -1472,6 +1508,39 @@ static void InitGameEngine() for (i = 0; access_direction_list[i].element != EL_UNDEFINED; i++) element_info[access_direction_list[i].element].access_direction = access_direction_list[i].direction; + + /* ---------- initialize explosion content ------------------------------- */ + for (i = 0; i < MAX_NUM_ELEMENTS; i++) + { + if (IS_CUSTOM_ELEMENT(i)) + continue; + + for (y = 0; y < 3; y++) for (x = 0; x < 3; x++) + { + /* (content for EL_YAMYAM set at run-time with game.yamyam_content_nr) */ + + element_info[i].content.e[x][y] = + (i == EL_PLAYER_1 ? EL_EMERALD_YELLOW : + i == EL_PLAYER_2 ? EL_EMERALD_RED : + i == EL_PLAYER_3 ? EL_EMERALD : + i == EL_PLAYER_4 ? EL_EMERALD_PURPLE : + i == EL_MOLE ? EL_EMERALD_RED : + i == EL_PENGUIN ? EL_EMERALD_PURPLE : + i == EL_BUG ? (x == 1 && y == 1 ? EL_DIAMOND : EL_EMERALD) : + i == EL_BD_BUTTERFLY ? EL_BD_DIAMOND : + i == EL_SP_ELECTRON ? EL_SP_INFOTRON : + i == EL_AMOEBA_TO_DIAMOND ? level.amoeba_content : + i == EL_WALL_EMERALD ? EL_EMERALD : + i == EL_WALL_DIAMOND ? EL_DIAMOND : + i == EL_WALL_BD_DIAMOND ? EL_BD_DIAMOND : + i == EL_WALL_EMERALD_YELLOW ? EL_EMERALD_YELLOW : + i == EL_WALL_EMERALD_RED ? EL_EMERALD_RED : + i == EL_WALL_EMERALD_PURPLE ? EL_EMERALD_PURPLE : + i == EL_WALL_PEARL ? EL_PEARL : + i == EL_WALL_CRYSTAL ? EL_CRYSTAL : + EL_EMPTY); + } + } } int get_num_special_action(int element, int action_first, int action_last) @@ -1555,7 +1624,9 @@ void InitGame() player->StepFrame = 0; player->use_murphy = FALSE; - player->artwork_element = player->element_nr; + player->artwork_element = + (level.use_artwork_element[i] ? level.artwork_element[i] : + player->element_nr); player->block_last_field = FALSE; /* initialized in InitPlayerField() */ player->block_delay_adjustment = 0; /* initialized in InitPlayerField() */ @@ -1706,7 +1777,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; @@ -2993,6 +3064,21 @@ void Explode(int ex, int ey, int phase, int mode) if (phase == EX_PHASE_START) /* initialize 'Store[][]' field */ { int center_element = Feld[ex][ey]; + int artwork_element = center_element; /* for custom player artwork */ + int explosion_element = center_element; /* for custom player artwork */ + + if (IS_PLAYER(ex, ey)) + { + int player_nr = GET_PLAYER_NR(StorePlayer[ex][ey]); + + artwork_element = stored_player[player_nr].artwork_element; + + if (level.use_explosion_element[player_nr]) + { + explosion_element = level.explosion_element[player_nr]; + artwork_element = explosion_element; + } + } #if 0 /* --- This is only really needed (and now handled) in "Impact()". --- */ @@ -3006,7 +3092,7 @@ void Explode(int ex, int ey, int phase, int mode) if (mode == EX_TYPE_NORMAL || mode == EX_TYPE_CENTER || mode == EX_TYPE_CROSS) - PlayLevelSoundAction(ex, ey, ACTION_EXPLODING); + PlayLevelSoundElementAction(ex, ey, artwork_element, ACTION_EXPLODING); /* remove things displayed in background while burning dynamite */ if (Back[ex][ey] != EL_EMPTY && !IS_INDESTRUCTIBLE(Back[ex][ey])) @@ -3020,7 +3106,7 @@ void Explode(int ex, int ey, int phase, int mode) Feld[ex][ey] = center_element; } - last_phase = element_info[center_element].explosion_delay + 1; + last_phase = element_info[explosion_element].explosion_delay + 1; for (y = ey - 1; y <= ey + 1; y++) for (x = ex - 1; x <= ex + 1; x++) { @@ -3093,6 +3179,11 @@ void Explode(int ex, int ey, int phase, int mode) if (IS_PLAYER(ex, ey) && !PLAYER_EXPLOSION_PROTECTED(ex, ey)) { + int player_nr = StorePlayer[ex][ey] - EL_PLAYER_1; + + Store[x][y] = EL_PLAYER_IS_EXPLODING_1 + player_nr; + +#if 0 switch(StorePlayer[ex][ey]) { case EL_PLAYER_2: @@ -3109,10 +3200,29 @@ void Explode(int ex, int ey, int phase, int mode) Store[x][y] = EL_PLAYER_IS_EXPLODING_1; break; } +#endif if (PLAYERINFO(ex, ey)->use_murphy) Store[x][y] = EL_EMPTY; } +#if 1 + else if (center_element == EL_YAMYAM) + Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy]; + else if (element_info[center_element].content.e[xx][yy] != EL_EMPTY) + Store[x][y] = element_info[center_element].content.e[xx][yy]; +#if 1 + /* needed because EL_BD_BUTTERFLY is not defined as "CAN_EXPLODE" + (killing EL_BD_BUTTERFLY with dynamite would result in BD diamond + otherwise) -- FIX THIS !!! */ + else if (!CAN_EXPLODE(element) && element != EL_BD_BUTTERFLY) + Store[x][y] = element_info[element].content.e[1][1]; +#else + else if (!CAN_EXPLODE(element)) + Store[x][y] = element_info[element].content.e[1][1]; +#endif + else + Store[x][y] = EL_EMPTY; +#else else if (center_element == EL_MOLE) Store[x][y] = EL_EMERALD_RED; else if (center_element == EL_PENGUIN) @@ -3150,13 +3260,14 @@ void Explode(int ex, int ey, int phase, int mode) Store[x][y] = element_info[element].content.e[1][1]; else Store[x][y] = EL_EMPTY; +#endif if (x != ex || y != ey || mode == EX_TYPE_BORDER || center_element == EL_AMOEBA_TO_DIAMOND) Store2[x][y] = element; Feld[x][y] = EL_EXPLOSION; - GfxElement[x][y] = center_element; + GfxElement[x][y] = artwork_element; ExplodePhase[x][y] = 1; ExplodeDelay[x][y] = last_phase; @@ -3250,12 +3361,30 @@ void Explode(int ex, int ey, int phase, int mode) /* player can escape from explosions and might therefore be still alive */ if (element >= EL_PLAYER_IS_EXPLODING_1 && element <= EL_PLAYER_IS_EXPLODING_4) - Feld[x][y] = (stored_player[element - EL_PLAYER_IS_EXPLODING_1].active ? - EL_EMPTY : - element == EL_PLAYER_IS_EXPLODING_1 ? EL_EMERALD_YELLOW : - element == EL_PLAYER_IS_EXPLODING_2 ? EL_EMERALD_RED : - element == EL_PLAYER_IS_EXPLODING_3 ? EL_EMERALD : - EL_EMERALD_PURPLE); + { + static int player_death_elements[] = + { + EL_EMERALD_YELLOW, + EL_EMERALD_RED, + EL_EMERALD, + EL_EMERALD_PURPLE + }; + int player_nr = element - EL_PLAYER_IS_EXPLODING_1; + int player_death_element = player_death_elements[player_nr]; + + if (level.use_explosion_element[player_nr]) + { + int explosion_element = level.explosion_element[player_nr]; + int xx = MIN(MAX(0, x - stored_player[player_nr].jx + 1), 2); + int yy = MIN(MAX(0, y - stored_player[player_nr].jy + 1), 2); + + player_death_element = + element_info[explosion_element].content.e[xx][yy]; + } + + Feld[x][y] = (stored_player[player_nr].active ? EL_EMPTY : + player_death_element); + } /* restore probably existing indestructible background element */ if (Back[x][y] && IS_INDESTRUCTIBLE(Back[x][y])) @@ -3363,6 +3492,7 @@ void DynaExplode(int ex, int ey) void Bang(int x, int y) { int element = MovingOrBlocked2Element(x, y); + int explosion_type = EX_TYPE_NORMAL; if (IS_PLAYER(x, y) && !PLAYER_EXPLOSION_PROTECTED(x, y)) { @@ -3370,6 +3500,16 @@ void Bang(int x, int y) element = Feld[x][y] = (player->use_murphy ? EL_SP_MURPHY : player->element_nr); + + if (level.use_explosion_element[player->index_nr]) + { + int explosion_element = level.explosion_element[player->index_nr]; + + if (element_info[explosion_element].explosion_type == EXPLODES_CROSS) + explosion_type = EX_TYPE_CROSS; + else if (element_info[explosion_element].explosion_type == EXPLODES_1X1) + explosion_type = EX_TYPE_CENTER; + } } switch(element) @@ -3384,8 +3524,8 @@ void Bang(int x, int y) case EL_PACMAN: case EL_MOLE: RaiseScoreElement(element); - Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL); break; + case EL_DYNABOMB_PLAYER_1_ACTIVE: case EL_DYNABOMB_PLAYER_2_ACTIVE: case EL_DYNABOMB_PLAYER_3_ACTIVE: @@ -3393,27 +3533,30 @@ void Bang(int x, int y) case EL_DYNABOMB_INCREASE_NUMBER: case EL_DYNABOMB_INCREASE_SIZE: case EL_DYNABOMB_INCREASE_POWER: - DynaExplode(x, y); + explosion_type = EX_TYPE_DYNA; break; + case EL_PENGUIN: case EL_LAMP: case EL_LAMP_ACTIVE: case EL_AMOEBA_TO_DIAMOND: - if (IS_PLAYER(x, y)) - Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL); - else - Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER); + if (!IS_PLAYER(x, y)) /* penguin and player may be at same field */ + explosion_type = EX_TYPE_CENTER; break; + default: if (element_info[element].explosion_type == EXPLODES_CROSS) - Explode(x, y, EX_PHASE_START, EX_TYPE_CROSS); + explosion_type = EX_TYPE_CROSS; else if (element_info[element].explosion_type == EXPLODES_1X1) - Explode(x, y, EX_PHASE_START, EX_TYPE_CENTER); - else - Explode(x, y, EX_PHASE_START, EX_TYPE_NORMAL); + explosion_type = EX_TYPE_CENTER; break; } + if (explosion_type == EX_TYPE_DYNA) + DynaExplode(x, y); + else + Explode(x, y, EX_PHASE_START, explosion_type); + CheckTriggeredElementChange(x, y, element, CE_EXPLOSION_OF_X); } @@ -4081,7 +4224,7 @@ inline static void TurnRoundExt(int x, int y) { static struct { - int x, y; + int dx, dy; } move_xy[] = { { 0, 0 }, @@ -4116,10 +4259,10 @@ inline static void TurnRoundExt(int x, int y) int right_dir = turn[old_move_dir].right; int back_dir = turn[old_move_dir].back; - int left_dx = move_xy[left_dir].x, left_dy = move_xy[left_dir].y; - int right_dx = move_xy[right_dir].x, right_dy = move_xy[right_dir].y; - int move_dx = move_xy[old_move_dir].x, move_dy = move_xy[old_move_dir].y; - int back_dx = move_xy[back_dir].x, back_dy = move_xy[back_dir].y; + int left_dx = move_xy[left_dir].dx, left_dy = move_xy[left_dir].dy; + int right_dx = move_xy[right_dir].dx, right_dy = move_xy[right_dir].dy; + int move_dx = move_xy[old_move_dir].dx, move_dy = move_xy[old_move_dir].dy; + int back_dx = move_xy[back_dir].dx, back_dy = move_xy[back_dir].dy; int left_x = x + left_dx, left_y = y + left_dy; int right_x = x + right_dx, right_y = y + right_dy; @@ -4272,8 +4415,8 @@ inline static void TurnRoundExt(int x, int y) else MovDir[x][y] = back_dir; - xx = x + move_xy[MovDir[x][y]].x; - yy = y + move_xy[MovDir[x][y]].y; + xx = x + move_xy[MovDir[x][y]].dx; + yy = y + move_xy[MovDir[x][y]].dy; if (!IN_LEV_FIELD(xx, yy) || (!IS_FREE(xx, yy) && !IS_FOOD_PIG(Feld[xx][yy]))) @@ -4300,8 +4443,8 @@ inline static void TurnRoundExt(int x, int y) else MovDir[x][y] = back_dir; - xx = x + move_xy[MovDir[x][y]].x; - yy = y + move_xy[MovDir[x][y]].y; + xx = x + move_xy[MovDir[x][y]].dx; + yy = y + move_xy[MovDir[x][y]].dy; if (!IN_LEV_FIELD_AND_IS_FREE(xx, yy)) MovDir[x][y] = old_move_dir; @@ -5660,7 +5803,11 @@ void ContinueMoving(int x, int y) MovDelay[newx][newy] = 0; +#if 1 + if (CAN_CHANGE_OR_HAS_ACTION(element)) +#else if (CAN_CHANGE(element)) +#endif { /* copy element change control values to new field */ ChangeDelay[newx][newy] = ChangeDelay[x][y]; @@ -5675,7 +5822,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 @@ -6771,7 +6918,7 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) #else 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_CE_DELAY ? GET_CE_DELAY_VALUE(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 : @@ -7009,7 +7156,9 @@ static void ExecuteCustomElementAction(int x, int y, int element, int page) int artwork_element = action_arg_element; if (action_arg == CA_ARG_ELEMENT_RESET) - artwork_element = stored_player[i].element_nr; + artwork_element = + (level.use_artwork_element[i] ? level.artwork_element[i] : + stored_player[i].element_nr); stored_player[i].artwork_element = artwork_element; @@ -7118,11 +7267,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); @@ -7148,21 +7293,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]) - return FALSE; -#else - /* do not change already changed elements with same change event */ - if (Changed[x][y] & ChangeEvent[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; -#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) { @@ -7595,6 +7730,29 @@ static boolean CheckElementChangeExt(int x, int y, change->actual_trigger_side = trigger_side; change->actual_trigger_ce_value = CustomValue[x][y]; + /* special case: trigger element not at (x,y) position for some events */ + if (check_trigger_element) + { + static struct + { + int dx, dy; + } move_xy[] = + { + { 0, 0 }, + { -1, 0 }, + { +1, 0 }, + { 0, 0 }, + { 0, -1 }, + { 0, 0 }, { 0, 0 }, { 0, 0 }, + { 0, +1 } + }; + + int xx = x + move_xy[MV_DIR_OPPOSITE(trigger_side)].dx; + int yy = y + move_xy[MV_DIR_OPPOSITE(trigger_side)].dy; + + change->actual_trigger_ce_value = CustomValue[xx][yy]; + } + if (change->can_change && !change_done) { ChangeDelay[x][y] = 1; @@ -8044,7 +8202,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 */ @@ -8121,6 +8279,28 @@ void GameActions() if (graphic_info[graphic].anim_global_sync) GfxFrame[x][y] = FrameCounter; + else if (ANIM_MODE(graphic) == ANIM_CE_VALUE) + { + int old_gfx_frame = GfxFrame[x][y]; + + GfxFrame[x][y] = CustomValue[x][y]; + +#if 1 + if (GfxFrame[x][y] != old_gfx_frame) +#endif + DrawLevelGraphicAnimation(x, y, graphic); + } + else if (ANIM_MODE(graphic) == ANIM_CE_SCORE) + { + int old_gfx_frame = GfxFrame[x][y]; + + GfxFrame[x][y] = element_info[element].collect_score; + +#if 1 + if (GfxFrame[x][y] != old_gfx_frame) +#endif + DrawLevelGraphicAnimation(x, y, graphic); + } if (ANIM_MODE(graphic) == ANIM_RANDOM && IS_NEXT_FRAME(GfxFrame[x][y], graphic)) @@ -8151,6 +8331,11 @@ void GameActions() printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]); #endif +#if 0 + if (element == EL_CUSTOM_255) + printf("::: ChangeDelay == %d\n", ChangeDelay[x][y]); +#endif + #if 1 ChangeElement(x, y, page); #else @@ -8234,6 +8419,12 @@ void GameActions() else if (IS_ANIMATED(graphic) && !IS_CHANGING(x, y)) DrawLevelGraphicAnimationIfNeeded(x, y, graphic); +#if 0 + if (element == EL_CUSTOM_255 || + element == EL_CUSTOM_256) + DrawLevelGraphicAnimation(x, y, graphic); +#endif + if (IS_BELT_ACTIVE(element)) PlayLevelSoundAction(x, y, ACTION_ACTIVE); @@ -8652,8 +8843,19 @@ boolean MovePlayerOneStep(struct PlayerInfo *player, 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; } @@ -10468,7 +10670,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); @@ -10508,7 +10710,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; }